Contributing¶
Thanks for your interest in contributing to VisCy! This page covers the whole workflow — setup, code, tests, docs, and releases.1
Set up your environment¶
VisCy uses uv and is a uv workspace monorepo.
1. Install uv — see the installation docs.
On HPC
Your home directory fills up fast. Symlink the uv cache to scratch storage before the first sync:
2. Get the code:
Fork the repo, then clone your fork. See forking a repo.
3. Create the environment and sync:
- VisCy targets Python ≥ 3.12.
- Installs every workspace package plus the default
devdependency group.
Report an issue¶
We track bugs, feature requests, and support in issues. Search existing issues (including closed ones) before opening a new one.
Make changes¶
Every change to main goes through a
pull request. Reference the issue it
addresses, or describe the fix or feature in the PR.
Packages vs. applications
Shared code belongs in packages/. Applications consume packages and never
import each other — the dependency graph flows applications/ → packages/.
Repository structure¶
viscy/
├─ pyproject.toml # root workspace config (ruff, pytest, uv)
├─ packages/ # independently published packages
│ ├─ viscy-data/
│ ├─ viscy-models/
│ ├─ viscy-transforms/
│ └─ viscy-utils/
├─ applications/ # self-contained research apps
├─ docs/ # this documentation site
└─ src/viscy/ # umbrella package (re-exports)
Each packages/ member is developed in isolation, published to PyPI separately,
and installed on its own.
Test¶
Add tests for every code change.
uv run pytest # everything
uv run pytest packages/viscy-data/ # one package
uv run pytest --cov=viscy_data # with coverage
Code style¶
We use prek to format and lint on commit. Install the hooks once:
ruff handles linting and formatting; docstrings follow the numpy style.
Ruff config lives only in the root
All [tool.ruff.*] settings belong in the root pyproject.toml. Ruff does not
inherit config — a [tool.ruff.*] section in a subpackage silently overrides
the entire root config.
Documentation¶
Zensical builds one site for the whole monorepo, from
zensical.toml and docs/ at the repo root. Doc tools live in the root doc
group.
Authoring notes:
- Markdown lives in
docs/; thenavtable inzensical.tomlsets page order. ::: viscy_datarenders a package's API from its docstrings. A template override (docs/_templates/python/material/module.html.jinja) hides the package's top-level docstring — write overview prose in the Markdown page instead.zensical.tomlenables only the Markdown extensions in use. Adding new syntax (math, keyboard keys, …)? Enable its extension there, or it renders as text.
Release a package¶
Publishing a GitHub Release ships that package to PyPI — no tokens, no manual upload,
no version bump in code. Both the workspace libraries and the applications release this
way. Versions come from the tag via
uv-dynamic-versioning; the tag
prefix picks the package, and each package versions independently.
| Package | Tag prefix | Example |
|---|---|---|
viscy (umbrella) |
none | v0.6.0 |
viscy-data |
viscy-data- |
viscy-data-v0.2.1 |
viscy-models |
viscy-models- |
viscy-models-v0.4.0 |
viscy-transforms |
viscy-transforms- |
viscy-transforms-v0.1.3 |
viscy-utils |
viscy-utils- |
viscy-utils-v0.3.0 |
airtable-utils |
airtable-utils- |
airtable-utils-v0.1.0 |
cytoland |
cytoland- |
cytoland-v0.3.0 |
dynacell |
dynacell- |
dynacell-v0.2.0 |
dynaclr |
dynaclr- |
dynaclr-v0.1.0 |
viscy-qc |
viscy-qc- |
viscy-qc-v0.1.0 |
Create the Release on the tag:
- Releases → Draft a new release.
- Choose or create the tag, e.g.
viscy-data-v0.2.1. - Tick Set as a pre-release for an
rc/a/btag. - Publish release.
Spell versions the PEP 440 way: v0.2.1, v0.2.1rc1. A
misspelled or unprefixed tag fails the run before anything builds.
What CI does
Publishing the Release runs .github/workflows/release.yml: parse the tag, build
the sdist and wheel, verify the version matches the tag, publish to PyPI over OIDC
(PEP 740 attestations, no stored secrets), and attach the artifacts to the Release.
How publishing is authorized
Each package publishes through PyPI
trusted publishing (OIDC): GitHub mints
a short-lived credential per run, scoped to release.yml and that package's
environment (pypi-viscy, pypi-viscy-data, …).
Docs deploy automatically
CI publishes the site with mike: merging
to main updates the dev build; a vX.Y.Z umbrella tag publishes a release
and moves stable.
Before you open a PR check that:¶
- Tests cover the change and
uv run pytestpasses -
uvx prek run --all-filesis clean - Docs build (
uv run zensical build --clean) with no warnings - New doc pages are listed in the
navtable - The PR references an issue or describes the change
-
This mirrors the repo's
CONTRIBUTING.md; edit both if you change the process. ↩