Skip to content

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:

mkdir -p /hpc/mydata/first.last/.cache/uv && ln -s /hpc/mydata/first.last/.cache/uv ~/.cache/uv

2. Get the code:

git clone https://github.com/mehta-lab/VisCy.git

Fork the repo, then clone your fork. See forking a repo.

3. Create the environment and sync:

cd VisCy/
uv venv -p 3.13                 # (1)!
uv sync --all-packages          # (2)!
  1. VisCy targets Python ≥ 3.12.
  2. Installs every workspace package plus the default dev dependency 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:

uvx prek install
uvx prek run --all-files   # run manually

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.

uv sync --all-packages --group doc    # (1)!
uv run python docs/_gen_versions.py   # (2)!
uv run zensical serve                 # http://localhost:8000
  1. --all-packages matters: mkdocstrings imports the packages to read docstrings.
  2. Rewrites the package version table in docs/packages/index.md.
uv run zensical build --clean   # output in site/ (git-ignored)

Authoring notes:

  • Markdown lives in docs/; the nav table in zensical.toml sets page order.
  • ::: viscy_data renders 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.toml enables 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:

# standard release
gh release create viscy-data-v0.2.1 --generate-notes --title "viscy-data v0.2.1"

# pre-release (rc / alpha / beta)
gh release create viscy-data-v0.2.1rc1 --prerelease --generate-notes
  1. Releases → Draft a new release.
  2. Choose or create the tag, e.g. viscy-data-v0.2.1.
  3. Tick Set as a pre-release for an rc / a / b tag.
  4. 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 pytest passes
  • uvx prek run --all-files is clean
  • Docs build (uv run zensical build --clean) with no warnings
  • New doc pages are listed in the nav table
  • The PR references an issue or describes the change

  1. This mirrors the repo's CONTRIBUTING.md; edit both if you change the process.