Contributing to iiisight

Thanks for your interest in iiisight. This guide covers the development setup and the conventions CI enforces. For the architecture and deeper design rationale, see CLAUDE.md and PLAN.md.

Development setup

Requires Python 3.10+.

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"          # installs iiisight + the test/lint/build toolchain

The version is derived from the git tag by setuptools-scm; there is no hand-maintained version string.

Everyday commands

pytest                                            # run the suite + coverage gate
pytest tests/test_normalize_v3.py                 # a single file
pytest tests/test_image.py::test_tile_grid_scale_factor_8   # a single test
ruff check .                                      # lint
ruff format .                                     # auto-format
ruff format --check .                             # verify formatting (as CI does)

CI (.gitlab-ci.yml) runs ruff check, ruff format --check, and pytest across Python 3.10–3.13. All three must pass.

Conventions

  • Coverage gate: 90% (--cov-fail-under=90 in pyproject.toml). New behavior needs tests; if a line is genuinely defensive/unreachable, mark it # pragma: no cover with a reason rather than lowering the gate.

  • Tolerance is a core invariant. normalize() must never raise on spec-imperfect input — it repairs and records a Diagnostic. Every normalization path should have a malformed-input test asserting “does not raise, emits the expected diagnostic, yields a usable model.”

  • The model is pure and I/O-free. All network access is explicit on the client (fetch, expand, load_info, …); never fetch behind attribute access.

  • Style: ruff for lint + format (line length 100, double quotes). Precise type hints on public functions; Google-style docstrings; f-strings. Prefer native X | None / list[str] over typing/from __future__ import.

  • Public API is a contract once released. Keep import paths and the normalized model’s attribute names stable; a break is a MAJOR-version, CHANGELOG-documented event.

Changelog

Every user-affecting change gets an entry under ## [Unreleased] in CHANGELOG.md (Keep a Changelog format), in the same commit as the change. Internal-only churn (refactors, test/CI/formatting) does not need an entry.

Real-world check (optional)

scripts/validate_realworld.py runs iiisight against live IIIF servers (not part of CI — it needs network). Run it when touching normalization, the upgrade, or the tile math:

python scripts/validate_realworld.py

Documentation

The user documentation is a Sphinx site under docs/ (MyST Markdown, Furo theme), published on Read the Docs. Build it locally with:

pip install -e ".[docs]"
python -m sphinx -b html --keep-going docs docs/_build/html
open docs/_build/html/index.html

The build must complete with zero warnings (broken cross-references show up as warnings). When you change the public API or behavior, update the relevant guide page under docs/guide/, keep every public symbol’s docstring current (the API reference is generated from them), and show both async and sync styles for any example that does I/O. See the “Keeping docs in sync” section of CLAUDE.md for the full policy.

Releases

Releases are automated via GitLab CI and OIDC trusted publishing — see the Build & release section of CLAUDE.md. In short: push a vX.Y tag to publish to PyPI (or a vX.Yrc1 tag to rehearse on TestPyPI); do not twine upload from a workstation.