Thank you for your interest in contributing to CNVkit! This page is the detailed developer guide: environment setup, code style, testing, the pull request process, and the maintainer workflows for building documentation and Docker images.
CNVkit is scientific software used in medical research, so we value:
- Correctness over cleverness
- Clear code over compact code
- Thorough testing for critical functionality
For day-to-day architecture and coding conventions, see CLAUDE.md in the
repository root.
Prerequisites:
- Python 3.11 or later
- R with the DNAcopy package (for the default CBS segmentation)
- Git
Set up a development environment using one of the following options.
Option A: Conda (recommended). This brings in R and DNAcopy as well:
conda env create -f environment-dev.yml conda activate cnvkit pip install -e '.[test]'
Option B: pip. You will need to install R and DNAcopy separately:
pip install -e '.[test]'
Option C: VS Code DevContainer. Open the project in VS Code and choose "Reopen in Container"; everything is pre-configured.
Finally, install the pre-commit hooks so quality checks run on every commit:
pre-commit install
The project uses pre-commit to run code-quality
checks automatically before each commit. The configuration lives in
.pre-commit-config.yaml, and the tools it runs are configured in
pyproject.toml (the [tool.ruff] and [tool.bandit] sections).
On every git commit:
- Ruff linting -- style (PEP 8), common bugs, unused imports/variables, and modern Python idioms (pyupgrade)
- Ruff formatting -- Black-compatible auto-formatting
- Bandit -- scans for common security issues
- File hygiene -- trailing whitespace, end-of-file newline, YAML/TOML syntax, merge-conflict markers, and large files (>1 MB)
When a hook fails, the commit is blocked. Formatting and whitespace issues are
auto-fixed in place -- review the changes with git diff, git add them,
and commit again.
pre-commit run # On staged files pre-commit run --all-files # On the whole tree pre-commit run ruff --all-files # A single hook pre-commit autoupdate # Update hooks to their latest versions
The root Makefile also wraps the underlying tools:
make pre-commit # Install hooks and run on all files make lint # Run ruff make format # Auto-format code make security # Run pip-audit + bandit
git commit --no-verify # Skip all hooks (not recommended) SKIP=bandit git commit # Skip a specific hook
If a hook environment misbehaves, clear the pre-commit cache:
pre-commit clean pre-commit gc
CNVkit uses Ruff for both linting and formatting:
- Format: Black-compatible, 88-character line length
- Style: PEP 8 with project-specific exceptions (see
pyproject.toml) - Type hints: encouraged; checked with mypy (
mypycheckscnvlibandskgenome) - Docstrings: use for public APIs, in RST format for Sphinx
Key conventions:
- Code must work with Python 3.11+
- Use pytest, not unittest
- Subprocess calls are allowed for bioinformatics tools (samtools, R, etc.)
- Use descriptive variable names (e.g.
bam_fnamefor file paths) - Each command has a
_cmd_*()function for CLI parsing and ado_*()function for the core logic / Python API
See CLAUDE.md for the full architecture notes and modern-Python style rules.
Run the test suite directly with pytest:
pytest test/ # All tests pytest test/test_cnvlib.py # A specific file pytest -v -k "batch" # Tests matching a pattern
Or use the Makefile and tox for broader coverage:
make test # pytest -n auto make test-all # tox: full matrix across Python versions cd test && make mini # Integration tests with real genomic data
When writing tests:
- Add unit tests for new functionality under
test/ - Write a failing test first, then implement the fix or feature
- Use descriptive names, e.g.
test_batch_with_bedgraph_input() - Cover edge cases: empty inputs, NaN/missing values, single-element arrays
- Integration tests go in
test/Makefile
Check coverage locally:
pytest --cov=cnvlib --cov=skgenome test/ tox -e coverage
User-facing documentation lives in this doc/ directory and is built with
Sphinx, then published to ReadTheDocs:
make docs # Build the HTML docs (via tox -e doc) make docs-serve # Build and serve locally at http://localhost:8000
When you add or change a user-facing feature, update the relevant doc/*.rst
page in the same pull request.
- Create a feature branch (
git checkout -b feature/your-feature-name). - Make your changes following the conventions above.
- Ensure the tests pass and the code is formatted.
- Update documentation for any user-facing change.
- Push your branch and open a pull request on GitHub with a clear description.
Before opening a PR, confirm:
- Tests pass locally (
pytest test/) - Code is formatted (
make format) - Pre-commit hooks pass
- Documentation is updated (if needed)
- A changelog entry is added (for user-facing changes)
Build a wheel and source distribution into dist/:
make build
The full, narrative release procedure lives in the Release procedure wiki page. The
repository enforces one invariant mechanically: the ``master`` branch always
carries a development version (X.Y.Z.devN), and a plain X.Y.Z version
exists only transiently on the tagged release commit. The Version Guard
GitHub Actions workflow fails any master build or pull request whose
cnvlib/_version.py is left on a plain release version, which prevents the
post-release .dev0 bump from being silently skipped. Check it locally with:
make check-version
To cut a release, once the notes are written:
Set
cnvlib/_version.pyto the plain release versionX.Y.Z(drop the.devNsuffix) and commit.Tag the commit with the summarized changelog as its message and push:
git tag vX.Y.Z -aF release-notes.md git push --tags
GitHub Actions builds and publishes the Docker image for the tag.
Build and upload the Python distributions to PyPI:
make build make release_version=X.Y.Z upload-pypi
Bump
cnvlib/_version.pyto the next development versionX.Y.(Z+1).dev0, commit, and push. (Version Guardenforces that this step is not forgotten.)
End-user instructions for running the published images are in :doc:`docker`. The workflows below are for maintainers.
make docker # Build the production image (etal/cnvkit:devel) from source make docker-test # Run cnvkit.py version inside the image make docker-dev # Build the DevContainer image
make release_version=0.9.14 docker-release # Build a tagged image from PyPI make release_version=0.9.14 docker-push # Push to Docker Hub (needs auth)
Docker images are built and pushed automatically:
- On every commit to
master:etal/cnvkit:devel - On every tag
vX.Y.Z:etal/cnvkit:X.Y.Z,etal/cnvkit:X.Y, andetal/cnvkit:latest
To trigger a release build, push a version tag:
git tag v0.9.14 git push origin v0.9.14
For maintainers who push images or set up CI:
- Create a Docker Hub access token at https://hub.docker.com/settings/security
- Add these secrets to the GitHub repository settings:
DOCKER_USERNAME-- your Docker Hub usernameDOCKER_PASSWORD-- your Docker Hub access token
Docker Hub is the primary registry, but images can also be pushed to
ghcr.io as a backup. To enable it, uncomment the GHCR sections in
.github/workflows/docker-build.yml and set DOCKER_IMAGE to
ghcr.io/${{ github.repository_owner }}/cnvkit.
- Questions about contributing: open a GitHub Discussion
- Questions about CNVkit usage: ask on Biostars
- Bug reports: GitHub Issues
Be respectful and constructive, focus on what is best for the community, and show empathy toward other contributors.
By contributing to CNVkit, you agree that your contributions will be licensed under the Apache License 2.0.