Development Guide¶
Quick reference for day-to-day canVODpy development.
Initial Setup¶
git clone https://github.com/nfb2021/canvodpy.git
cd canvodpy
git submodule update --init --recursive # test data + demo data
uv sync # install all packages (editable)
just hooks # install pre-commit hooks
just test # verify everything works
Submodules
Two data repositories are pulled as submodules:
packages/canvod-readers/tests/test_data— falsified/corrupted RINEX files for validation testsdemo— clean real-world data for demos and documentation
Tests that depend on these datasets are automatically skipped if submodules are not initialised.
Prerequisites¶
Two tools must be installed outside uv:
| Tool | Install | Purpose |
|---|---|---|
| uv | brew install uv |
Python + dependency management |
| just | brew install just |
Task runner |
just check-dev-tools # verify both are present
Configuration Management¶
canVODpy uses three YAML files in config/:
| File | Purpose |
|---|---|
sites.yaml |
Research sites, receiver definitions, data root paths, VOD analysis pairs |
processing.yaml |
Processing parameters, NASA Earthdata credentials, storage strategies, Icechunk config |
sids.yaml |
Signal ID filtering — all, a named preset, or custom list |
Each file has a .example template in the same directory.
just config-init # copy .example templates → YAML files
# edit config/sites.yaml and config/processing.yaml
just config-validate # check for errors
just config-show # print resolved config
just config-validate # after editing
just config-show # inspect current values
Testing¶
just test # all tests
just test-package canvod-readers # specific package
just test-coverage # with HTML coverage report
Tests live in packages/<pkg>/tests/. Integration tests are marked
@pytest.mark.integration and excluded from the default run.
Code Quality¶
just check # lint + format + type-check (run before committing)
just check-lint # ruff linting only
just check-format # ruff formatting only
| Tool | Purpose |
|---|---|
| ruff | Linting + formatting (replaces flake8, black, isort) |
| ty | Type checking (replaces mypy) |
| pytest | Testing with coverage |
Keeping Your Fork in Sync¶
If you are working from a fork (rather than a direct clone), periodically pull updates from the upstream repository:
git checkout main
git fetch upstream
git merge upstream/main
git push origin main
# Then update your feature branch:
git checkout feature/my-feature
git rebase main
If upstream is not configured, add it once:
git remote add upstream git@github.com:nfb2021/canvodpy.git
Pre-Commit Hooks¶
just hooks installs Git hooks that run automatically on every commit. If any hook fails, the commit is rejected — your changes stay staged but no commit is created.
What runs¶
| Hook | Stage | Effect |
|---|---|---|
| ruff check --fix | pre-commit |
Lints and auto-fixes Python code |
| ruff format | pre-commit |
Formats Python code |
| uv-lock | pre-commit |
Verifies uv.lock matches pyproject.toml |
| trailing-whitespace | pre-commit |
Strips trailing whitespace |
| check-added-large-files | pre-commit |
Blocks large files from being committed |
| detect-private-key | pre-commit |
Prevents accidental secret commits |
| end-of-file-fixer | pre-commit |
Ensures files end with one newline |
| commitizen | commit-msg |
Validates Conventional Commits format |
When your commit is rejected¶
# Most common: ruff auto-fixed your code. Stage the fixes and retry:
git add -u && git commit -m "feat(readers): your message"
# If commitizen rejects your message, use the correct format:
git commit -m "type(scope): description"
# types: feat, fix, docs, refactor, test, chore, perf, ci
# scopes: readers, aux, grids, vod, store, viz, utils, naming, ops, docs, ci, deps
# If uv-lock is out of date:
uv sync && git add uv.lock && git commit -m "feat(readers): your message"
Contributing Workflow¶
git checkout -b feature/my-feature
# … make changes in packages/<pkg>/src/ …
# … add tests in packages/<pkg>/tests/ …
just check && just test
git add packages/<pkg>/src/... packages/<pkg>/tests/...
git commit -m "feat(readers): add RINEX 4.0 support"
git push origin feature/my-feature
# → open pull request on GitHub
Conventional Commit Scopes¶
readers · aux · grids · vod · store · viz · utils · naming · ops · docs · ci · deps
Continuous Integration¶
Every push and PR triggers GitHub Actions workflows:
| Workflow | Checks |
|---|---|
| Code Quality | ruff lint, ruff format, ty type-check, lockfile consistency |
| Test with Coverage | pytest → coverage.lcov → Coveralls |
| Platform Tests | Multi-OS, multi-Python-version test matrix |
Coverage reports are posted as PR comments and tracked on Coveralls over time. To run coverage locally:
just test-coverage # generates HTML report
Private or Local Add-on Packages¶
You may want to develop an add-on package alongside canVODpy that is not part of the public repository — for example, a private algorithm, a site-specific extension, or an experimental module you are not yet ready to publish.
The recommended pattern keeps the public workspace and lock file clean while letting you use the package locally without friction.
The pattern¶
1. Place the package inside packages/
packages/
canvod-myprivatepackage/
pyproject.toml
src/
canvod/myprivatepackage/
2. Add it to .gitignore (so it is never accidentally committed)
packages/canvod-myprivatepackage
3. Exclude it from the uv workspace in the root pyproject.toml
[tool.uv.workspace]
members = ["packages/*", "canvodpy"]
exclude = ["packages/canvod-myprivatepackage"]
This keeps uv.lock identical for everyone else.
CI will never see the directory, so it resolves the same 240 packages with or
without your local copy.
4. Install locally with uv pip install
uv pip install -e packages/canvod-myprivatepackage/
uv pip install is a direct venv install — it does not modify
pyproject.toml or uv.lock.
After a fresh uv sync, re-run the command to restore your local install.
Upgrade path¶
When the package is ready to become public:
- Remove it from
.gitignoreand from theexcludelist. - Commit the package directory.
- Run
uv lockand commit the updateduv.lock.
It becomes a first-class workspace member and appears in the lock for everyone.
Why not git submodules?¶
You could also host the private package in its own repository and add it as a
git submodule. actions/checkout@v4 does not initialise submodules by
default, so CI would still see an empty directory. The trade-off is that the
uv.lock would diverge between users who have initialised the submodule and
those who have not, requiring everyone to keep the exclude in place anyway.
For a single-developer private add-on the simpler approach above is preferred.
All Just Commands¶
just # list all commands
just check # lint + format + type-check
just test # run all tests
just sync # install/update dependencies
just clean # remove build artifacts
just hooks # install pre-commit hooks
just config-init # initialize config files
just config-validate # validate configuration
just config-show # view resolved configuration
just docs # preview documentation (localhost:3000)
just build-all # build all packages
just deps-report # dependency metrics report
just deps-graph # mermaid dependency graph
Troubleshooting¶
No module named 'canvod.X'
Run uv sync to install/reinstall packages in editable mode.
Command not found: just
Install just: brew install just (macOS) or see getting-started.
Tests fail after dependency changes
uv sync --all-extras
uv.lock needs to be updated — CI fails but local works
An untracked directory inside packages/ is being picked up by the
packages/* workspace glob and included in the lock. CI does not have
the directory and therefore resolves fewer packages, causing a mismatch.
Identify the culprit:
git ls-tree HEAD packages/ # shows only committed packages
ls packages/ # shows all local packages (including untracked)
Any directory present locally but absent from the git ls-tree output is
the source of the divergence. Follow the
Private or Local Add-on Packages
pattern to exclude it and regenerate the lock.