Skip to content

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 tests
  • demo — 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

Full installation guide


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

Detailed fork sync guide


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"

Full troubleshooting guide


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:

  1. Remove it from .gitignore and from the exclude list.
  2. Commit the package directory.
  3. Run uv lock and commit the updated uv.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.