Skip to content

Contributing and Development

bacetiner edited this page Oct 6, 2026 · 2 revisions

rAPIdtools is developed in the open under the BSD-3-Clause license; this page covers setting up a development environment, the style and type gates, how the offline test suite works, what CI runs, and how releases are cut.

Repository: RAPID-Facility/rAPIdtools. Package layout is summarised in Core Concepts.

Setting up

The package requires Python 3.11 or newer. Clone the repository and install it editable with the development extras:

git clone https://github.com/RAPID-Facility/rAPIdtools.git
cd rAPIdtools
make install          # python -m pip install -e ".[dev]"

pyproject.toml defines three extras:

Extra Contents
test pytest, pytest-cov, requests-mock
docs sphinx>=8, sphinx-book-theme, sphinx-design, sphinx-copybutton
dev test and docs plus ruff, black, mypy

PYTHON can be overridden for any target, for example make test PYTHON=python3.12. On a machine without a GPU, install CPU-only PyTorch first so the install stays small, and install torch and torchvision from the same index (mixing indexes breaks every model module; see Troubleshooting and FAQ):

pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

Make targets

Target What it does
make install Editable install with the test and docs extras.
make lint ruff check and ruff format --check on rapidtools and tests.
make format Apply Ruff fixes and formatting.
make typecheck mypy rapidtools.
make test pytest with the options from pyproject.toml.
make coverage Test suite plus an HTML coverage report in htmlcov/.
make check lint, typecheck and test: what a pull request must pass.
make docs Build the Sphinx site into docs/build/html.
make docs-serve Build the docs and serve them at http://localhost:8000.
make clean Remove build, cache and coverage artifacts.

Style and typing

Ruff is the style gate, for linting and formatting alike. Its configuration in pyproject.toml ([tool.ruff]) sets a line length of 88, target py311, single quotes ([tool.ruff.lint.flake8-quotes] and [tool.ruff.format]), and the rule sets E, W, F, I, UP, B and Q. Run make format before committing; make lint must pass cleanly.

mypy runs over the package with the [tool.mypy] settings (python_version = "3.11", ignore_missing_imports = true, untyped definitions allowed). The package ships a py.typed marker, so keep public signatures annotated. The whole package type-checks clean; new code should too.

Source files carry the BSD-3-Clause header, a Contributors: list and a Last updated: date (see any module, or tests/conftest.py). Keep that convention in new files.

Tests

make test             # or: python -m pytest

[tool.pytest.ini_options] adds -ra -q --cov=rapidtools --cov-report=term-missing, so every run ends with a coverage table that lists the uncovered lines of each module. The suite is 44 modules and about 1,200 tests; coverage sits at 99% of the package's statements, and the badge in the README is updated from the Ubuntu / Python 3.11 cell of the CI matrix.

Most modules test one component and are named after it (test_image_analyzers.py, test_gui_server.py, test_mapillary_client.py, ...). A few cut across components:

Module What it holds
test_edge_cases.py Error branches and fallbacks of the street-level modules: the duplicate resolver's unreadable crops, failed model calls and cancellation; the verifier's odd confidence and range values; the re-identification loader for both the CLIP and the DINOv2 branches; localisation and tracking helpers fed parallel rays, sightings without a ground position, rays that meet beyond the range limit, wandering and phantom tracks, singular merge gates.
test_coverage_gaps.py Small fallbacks of otherwise well-covered modules: outline parsing, GUI server helpers, workflow model listing, prompt builder helpers, image asset parsing, provider rejections in the analyzer.
test_api_harmonization.py Public names, lazy imports and deprecation shims of rapidtools.
test_cancellation.py Every long-running component honours its cancel_event.

Put a new test next to the component it exercises; use the cross-cutting modules only for a branch that needs a fixture the component's own module does not have.

The suite is offline and never downloads model weights:

  • tests/conftest.py has an autouse fixture, _no_huggingface_login, that replaces rapidtools.auth.get_token with a stub returning a test token and rapidtools.models.local_base.ensure_huggingface_login with lambda: True. Because a token is always "cached", the real ensure_huggingface_login (which the re-identification embedder imports from rapidtools.auth) also returns True without contacting the Hugging Face Hub or the token registry.
  • Local model tests (tests/test_models_local.py) monkeypatch the processor and model loaders with fakes (FakeProcessor.from_pretrained and friends), so transformers never loads a checkpoint. tests/test_street_objects.py passes a FakeSAM3 that returns fixed masks. The re-identification test in tests/test_edge_cases.py goes one step further and injects fake torch and transformers modules into sys.modules that expose only what the loader touches (AutoConfig, AutoModel, CLIPVisionModelWithProjection, AutoImageProcessor, inference_mode), so the loader runs end to end in milliseconds.
  • HTTP calls to providers and tile servers are answered by requests-mock fixtures, or by small fake clients such as the FakeClient in tests/test_street_objects.py, which serves canned images, detections and pixels for a synthetic survey.
  • Vision-language models are stand-ins with a run_inference method that answer from the image file name or a script; a judge callable does the same for the duplicate resolver.
  • A second autouse fixture, _restore_package_logger, undoes any configure_logging() call a test or the GUI CLI performed, so logger state does not leak between tests.
  • Rasters, GeoJSON, shapefiles and crops are built in tmp_path; the only committed fixtures are the small files under tests/mapillary_images.

Follow the same pattern for new tests: patch the network boundary, never call out to a real service, and keep fixtures deterministic. Do not let a change lower the coverage total; when a branch is hard to reach through the public API, test the helper directly rather than leaving it uncovered. make coverage writes an HTML report to htmlcov/ for finding what a change left out. The documentation's Development and testing page has the same material.

Continuous integration

Three workflows live in .github/workflows/:

  • ci.yml (Tests) runs on pushes and pull requests to main. A lint job on Python 3.11 installs CPU-only PyTorch and runs ruff check, ruff format --check and mypy rapidtools. A test job runs pytest --cov=rapidtools on a matrix of ubuntu-latest, windows-latest and macos-latest with Python 3.11, 3.12 and 3.13 (fail-fast: false), and the Ubuntu / 3.11 cell updates the coverage badge.
  • docs.yml (Docs) builds the Sphinx site on every push to main and deploys it to GitHub Pages at https://rapid-facility.github.io/rAPIdtools/.
  • release.yml (Release) runs when a v* tag is pushed. It checks that the tag matches project.version in pyproject.toml, extracts that version's section from CHANGELOG.md and creates the GitHub Release from it. Zenodo watches GitHub Releases to mint a DOI.

A fourth workflow, container.yml, builds the GUI server image from deploy/Dockerfile on the same tags and publishes it to the GitHub Container Registry in a CUDA and a -cpu variant (see Deployment). It lands with the release after 0.2.0.

Commits and pull requests

  • Branch from main and open a pull request against it. CI must be green; locally that means make check passes.
  • Commit subjects in this repository follow the pattern bc - <Sentence describing the change>, for example bc - Making the ego-vehicle filter linear in the sequence length.
  • Add a line to the Unreleased section of CHANGELOG.md under Added, Changed, Deprecated, Removed or Fixed. The file follows Keep a Changelog and the project uses Semantic Versioning with the usual 0.x caveat that minor releases may change public APIs.
  • When renaming a constructor parameter, keep the old name working with a DeprecationWarning, as the harmonised names in 0.2.0 did.
  • Public classes and functions carry Google-style docstrings with an Example: block; the API reference is generated from them.

Documentation

The docs are Sphinx with the Book theme; docs/source/ holds the getting_started, user_guide, examples and api sections. Build them with make docs (or cd docs && make html) and open docs/build/html/index.html. Autodoc imports the package, so the docs extra plus a CPU build of PyTorch is enough. Longer worked examples belong in examples/ and are referenced from Examples.

Releasing

  1. Move the Unreleased entries in CHANGELOG.md under a new ## [x.y.z] - YYYY-MM-DD heading.
  2. Set version in pyproject.toml, and version and date-released in CITATION.cff, to the same value.
  3. Commit, tag vx.y.z and push the tag. release.yml creates the GitHub Release and container.yml publishes the images; both refuse a tag that does not match pyproject.toml.

License and citation

rAPIdtools is released under the BSD-3-Clause license (LICENSE, copyright The University of Washington). CITATION.cff holds the citation metadata GitHub shows under Cite this repository and Zenodo reads when archiving a release; keep it in step with the version.

Clone this wiki locally