(target-contributing)= # Contributing (target-before-you-start)= ## Before you start Before starting work on a contribution, please check the [issue tracker](movement-github:issues) to see if there's already an issue describing what you have in mind. - If there is, add a comment to let others know you're willing to work on it. - If there isn't, please create a new issue to describe your idea. We strongly encourage discussing your plans before you start coding—either in the issue itself or on our [Zulip chat](movement-zulip:). This helps avoid duplicated effort and ensures your work aligns with the project's [scope](target-mission) and [roadmap](target-roadmaps). Keep in mind that we use issues liberally to track development. Some may be vague or aspirational, serving as reminders for future work rather than tasks ready to be tackled. There are a few reasons an issue might not be actionable yet: - It depends on other issues being resolved first. - It hasn't been clearly scoped. In such cases, helping to clarify the scope or breaking the issue into smaller parts can be a valuable contribution. Maintainers typically lead this process, but you're welcome to participate in the discussion. - It doesn't currently fit into the roadmap or the maintainers' priorities, meaning we may be unable to commit to timely guidance and prompt code reviews. If you're unsure whether an issue is ready to work on, just ask! Some issues are labelled as ``good first issue``. These are especially suitable if you're new to the project, and we recommend starting there. (target-contribution-workflow)= ## Contribution workflow If you want to contribute to `movement` and don't have permission to make changes directly, you can create your own copy of the project, make updates, and then suggest those updates for inclusion in the main project. This process is often called a "fork and pull request" workflow. When you create your own copy (or "fork") of a project, it's like making a new workspace that shares code with the original project. Once you've made your changes in your copy, you can submit them as a pull request, which is a way to propose changes back to the main project. If you are not familiar with `git`, we recommend reading up on [this guide](https://docs.github.com/en/get-started/using-git/about-git#basic-git-commands). ### Forking the repository 1. Fork the [repository](movement-github:) on GitHub. You can read more about [forking in the GitHub docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo). 2. Clone your fork to your local machine and navigate to the repository folder: ```sh git clone [https://github.com/](https://github.com/)/movement.git cd movement ``` 3. Set the upstream remote to the base `movement` repository: This links your local copy to the original project so you can pull the latest changes. ```sh git remote add upstream [https://github.com/neuroinformatics-unit/movement.git](https://github.com/neuroinformatics-unit/movement.git) ``` :::{note} Your repository now has two remotes: `origin` (your fork, where you push changes) and `upstream` (the main repository, where you pull updates from) ### Creating a development environment Now that you have the repository locally, you need to set up a Python environment and install the project dependencies. 1. Create an environment using [conda](conda:) or [uv](uv:getting-started/installation/) and install `movement` in editable mode, including development dependencies. ::::{tab-set} :::{tab-item} conda First, create and activate a `conda` environment: ```sh conda create -n movement-dev -c conda-forge python=3.13 conda activate movement-dev ``` Then, install the package in editable mode with development dependencies: ```sh pip install -e ".[dev]" ``` ::: :::{tab-item} uv First, create and activate a [virtual environment](uv:pip/environments/): ```sh uv venv --python=3.13 source .venv/bin/activate # On macOS and Linux .venv\Scripts\activate # On Windows PowerShell ``` Then, install the package in editable mode with development dependencies: ```sh uv pip install -e ".[dev]" ``` ::: :::: If you also want to edit the documentation and preview the changes locally, you will additionally need the `docs` extra dependencies. See [Editing the documentation](#editing-the-documentation) for more details. 2. Finally, initialise the [pre-commit hooks](#formatting-and-pre-commit-hooks): ```sh pre-commit install ``` ### Pull requests In all cases, please submit code to the main repository via a pull request (PR). We recommend, and adhere, to the following conventions: - Please submit _draft_ PRs as early as possible to allow for discussion. - The PR title should be descriptive e.g. "Add new function to do X" or "Fix bug in Y". - The PR description should be used to provide context and motivation for the changes. - If the PR is solving an issue, please add the issue number to the PR description, e.g. "Fixes #123" or "Closes #123". - Make sure to include cross-links to other relevant issues, PRs and Zulip threads, for context. - The maintainers triage PRs and assign suitable reviewers using the GitHub review system. - One approval of a PR (by a maintainer) is enough for it to be merged. - Unless someone approves the PR with optional comments, the PR is immediately merged by the approving reviewer. - PRs are preferably merged via the ["squash and merge"](github-docs:pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits) option, to keep a clean commit history on the _main_ branch. A typical PR workflow would be: * Create a new branch, make your changes, and stage them. * When you try to commit, the [pre-commit hooks](#formatting-and-pre-commit-hooks) will be triggered. * Stage any changes made by the hooks, and commit. * You may also run the pre-commit hooks manually, at any time, with `pre-commit run -a`. * Make sure to write tests for any new features or bug fixes. See [testing](#testing) below. * Don't forget to update the documentation, if necessary. See [contributing documentation](#contributing-documentation) below. * Push your changes to your fork on GitHub(`git push origin `). * Open a draft pull request from your fork to the upstream `movement` repository, with a meaningful title and a thorough description of the changes. :::{note} When creating the PR, ensure the base repository is `neuroinformatics-unit/movement` (the `upstream`) and the head repository is your fork. GitHub sometimes defaults to comparing against your own fork. Also make sure to tick the "Allow edits by maintainers" checkbox, so that maintainers can make small fixes directly to your branch. ::: * If all checks (e.g. linting, type checking, testing) run successfully, you may mark the pull request as ready for review. * Respond to review comments and implement any requested changes. * One of the maintainers will approve the PR and add it to the [merge queue](https://github.blog/changelog/2023-02-08-pull-request-merge-queue-public-beta/). * Success 🎉 !! Your PR will be (squash-)merged into the _main_ branch. (target-dev-guidelines)= ## Development guidelines ### Formatting and pre-commit hooks Running `pre-commit install` will set up [pre-commit hooks](https://pre-commit.com/) to ensure a consistent formatting style. Currently, these include: * [ruff](https://github.com/astral-sh/ruff) does a number of jobs, including code linting and auto-formatting. * [mypy](https://mypy.readthedocs.io/en/stable/index.html) as a static type checker. * [check-manifest](https://github.com/mgedmin/check-manifest) to ensure that the right files are included in the pip package. * [codespell](https://github.com/codespell-project/codespell) to check for common misspellings. These will prevent code from being committed if any of these hooks fail. To run all the hooks before committing: ```sh pre-commit run # for staged files pre-commit run -a # for all files in the repository ``` Some problems will be automatically fixed by the hooks. In this case, you should stage the auto-fixed changes and run the hooks again: ```sh git add . pre-commit run ``` If a problem cannot be auto-fixed, the corresponding tool will provide information on what the issue is and how to fix it. For example, `ruff` might output something like: ```sh movement/io/load_poses.py:551:80: E501 Line too long (90 > 79) ``` This pinpoints the problem to a single code line and a specific [ruff rule](https://docs.astral.sh/ruff/rules/) violation. Sometimes you may have good reasons to ignore a particular rule for a specific line of code. You can do this by adding an inline comment, e.g. `# noqa: E501`. Replace `E501` with the code of the rule you want to ignore. For docstrings, we adhere to the [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) style. Make sure to provide docstrings for all public functions, classes, and methods. This is important as it allows for [automatic generation of the API reference](#updating-the-api-reference). ### Testing We use [pytest](https://docs.pytest.org/en/latest/) for testing, aiming for ~100% test coverage where feasible. All new features should be accompanied by tests. Tests are stored in the `tests` directory, structured as follows: - `test_unit/`: Contains unit tests that closely follow the `movement` package structure. - `test_integration/`: Includes tests for interactions between different modules. - `fixtures/`: Holds reusable test data fixtures, automatically imported via `conftest.py`. Check for existing fixtures before adding new ones, to avoid duplication. For tests requiring experimental data, you can use [sample data](#sample-data) from our external data repository. These datasets are accessible through the `pytest.DATA_PATHS` dictionary, populated in `conftest.py`. Avoid including large data files directly in the GitHub repository. #### Running benchmark tests Some tests are marked as `benchmark` because we use them along with [pytest-benchmark](pytest-benchmark:) to measure the performance of a section of the code. These tests are excluded from the default test run to keep CI and local test running fast. This applies to all ways of running `pytest` (via command line, IDE, tox or CI). To run only the benchmark tests locally: ```sh pytest -m benchmark ``` To run all tests, including those marked as `benchmark`: ```sh pytest -m "" ``` #### Comparing benchmark runs across branches To compare performance between branches (e.g., `main` and a PR branch), we use [pytest-benchmark](pytest-benchmark:)'s save and compare functionality: 1. Run benchmarks on the `main` branch and save the results: ```sh git checkout main pytest -m benchmark --benchmark-save=main ``` By default the results are saved to `.benchmarks/` (a directory ignored by git) as JSON files with the format `/0001_main.json`, where `` is a directory whose name relates to the machine specifications, `0001` is a counter for the benchmark run, and `main` corresponds to the string passed in the `--benchmark-save` option. 2. Switch to your PR branch and run the benchmarks again: ```sh git checkout pr-branch pytest -m benchmark --benchmark-save=pr ``` 3. Show the results from both runs together: ```sh pytest-benchmark compare --group-by=name ``` Instead of providing the paths to the results, you can also provide the identifiers of the runs (e.g. `0001_main` and `0002_pr`), or use glob patterns to match the results (e.g. `*main*` and `*pr*`). You can sort the results by the name of the run using the `--sort='name'`, or group them with the `--group-by=