Developer guide#
- Release:
6.1.2
- Date:
Oct 28, 2026
How to set up a development environment, run the checks that continuous integration runs, and contribute to FLPQ_Data. If you only want to use the package, start with Getting started instead.
CI as source of truth#
The CI workflows under .github/workflows/ are the source of truth
for every command they run: this page and the agent skills reference the
workflow file and step name instead of restating such a command, so a
command changes in exactly one place. Commands with non-trivial arguments
(flags, paths, multi-part pipelines) always take that reference form; bare
tool invocations (uv run ty check, uv run pyright) may stay inline
where they name a gate step, because they carry no drift risk. What this
page still documents is what CI does not cover: local-only commands
(environment setup, installing the git hook, running a single test module)
and the policies behind the checks — the no-warnings build, the 95/95
coverage gate, doctests as tests.
Development setup#
FLPQ_Data requires Python 3.11–3.13 (pyproject.toml declares
>=3.11,<3.14). The canonical development environment is uv — all CI
workflows install their dependencies through it. Install uv once (see the
uv installation guide),
then from the repository root:
uv sync --all-groups
--all-groupspulls the three PEP 735 dependency groups frompyproject.toml: developer tools (ruff,ty,pyright,pre-commit,boto3), test extras (pytest,pytest-cov), and the Sphinx docs stack. The full environment is needed because every check must resolve imports from any dependency group — ty/pyright type-checktests/,docs/conf.pyandutils/, which import pytest, sphinx and boto3.uv syncalso installsflpq_dataitself (editable) into the project environment, so no separate install step is needed;uv run ...executes commands inside that environment.The committed
uv.lockpins every dependency; CI passes--frozento fail on a lockfile that is out of sync withpyproject.toml.
The math-snippet guard (see “Test pipeline”) validates docs math with the MathJax engine itself, which runs on Node.js (>= 20). Install Node.js once, then from the repository root:
npm ci
The committed package-lock.json pins the engine version; it must
match the mathjax_js URL in docs/conf.py, so the guard and the
browser render with the same MathJax version.
Quality checks#
Linting, formatting, and type checking are managed by uv and run through
pre-commit. The hook list — hygiene checks, the official uv-lock and
ruff hooks (check with autofix plus format), a version-sync check that fails
when flpq_data/config.py and pyproject.toml declare different
versions, a local ty type check, and a math-snippet check that validates the
docs and docstring math with the MathJax engine (see “Test pipeline”) — is
defined in .pre-commit-config.yaml, which is the source of truth; do
not maintain a copy of it elsewhere.
The ruff-pre-commit hook revision must match the ruff version locked in
uv.lock; bump both together when upgrading ruff.
Install the git hook once so the checks run on every commit:
uv run pre-commit install
Run the full pass manually — it is exactly the “Run pre-commit” step of
.github/workflows/lint.yml.
The individual tools can also be run directly:
uv run ruff check .
uv run ruff format .
uv run ty check
CI additionally runs Pyright, the stricter of the two type checkers; ty is
the fast local check. The full pass runs on every push and pull request
(.github/workflows/lint.yml).
The package ships a PEP 561 py.typed marker, so type checkers in
downstream projects use the inline annotations of flpq_data instead of
treating it as untyped.
Test pipeline#
The test suite is pytest with doctests enabled: the Examples
sections of public docstrings are executed as tests, so the documented
behavior and the tested behavior are the same code. The canonical command
is exactly what CI runs — the “Test FLPQ_Data with coverage (line and
branch >= 95%)” step of .github/workflows/coverage.yml.
Coverage is part of the pipeline: branch coverage is always on
([tool.coverage.run] in pyproject.toml), and the check fails unless
both line and branch coverage are at least 95%. The threshold lives in
utils/check_coverage.py because the combined --cov-fail-under cannot
enforce each metric separately. A single module or function runs without the
coverage gate:
uv run pytest tests/graphs/utils/test_add_reverse_edges.py
The suite also validates the math snippets of the docs pages and of the
package docstrings (rendered on the reference pages): Sphinx passes math
verbatim to MathJax, which renders it in the browser, so a broken snippet
sails through the Sphinx build and shows up only as an error on the
deployed site. The guard therefore validates every snippet with the MathJax
engine itself — utils/check_math_snippets.py extracts the :math:
roles and .. math:: bodies and compiles them with
utils/check_math_snippets_node.cjs (the same engine version the browser
loads, see “Development setup”) — and fails the suite on any snippet the
engine rejects, reporting the engine’s own error message.
Doctest discovery and test paths are configured in
pyproject.toml([tool.pytest.ini_options]).tests/mirrors the package layout (e.g.tests/graphs/generators/forflpq_data/graphs/generators/).
Continuous integration:
.github/workflows/tests.yml— runs the suite on every push and pull request across a matrix of Linux, macOS, and Windows with Python 3.11..github/workflows/coverage.yml— runs the same suite with coverage, enforces the 95/95 line+branch gate withutils/check_coverage.py, and uploads the report to Codecov.
Docs build and deploy#
The documentation is built with Sphinx from the docs/ directory.
The build and link-check commands are exactly what CI runs — the “Build”
and “Check links” steps of .github/workflows/docs.yml; the local
setup command lives in docs/README.md. Two policies matter:
No-warnings policy. The build runs with
-W --keep-going(set indocs/Makefile), so any Sphinx warning — including an unresolved cross-reference undernitpicky = True— fails the build. Fix warnings; do not suppress them.Full link check.
sphinx-build -b linkcheckverifies every local target and external URL (two documented exceptions indocs/conf.py); it exits non-zero on broken or timed-out links. It is network-bound and slow locally (rate-limited retries), so it runs in CI only — the “Check links” step of.github/workflows/docs.yml— and is not part of the local quality gate (see “Quality gate”).Resilient inventory fetches. Intersphinx inventories are fetched with retries on transient connection errors (
docs/conf.py); a persistent failure still warns and fails the build.Fresh-state verification. CI builds from a fresh checkout; locally two artifact classes survive a plain rebuild and can mask failures — cached doctrees (references in unchanged documents are not re-resolved) and the git-ignored autosummary stubs under
docs/*/generated/(parsed as ordinary documents, registering objects of an older build state). After adding or removing reference targets, autosummary entries, or automodule directives, verify with the fresh build (thefreshtarget ofdocs/Makefile; the local command indocs/README.md).
Deployment. Pushing to master deploys the site:
.github/workflows/deploy_docs.yml builds the HTML and publishes
docs/_build/html to the gh-pages branch, which GitHub serves as
the project website (https://formallanguageconstrainedpathquerying.github.io/FLPQ_Data).
The workflow skips forks — it runs only for the owning repository.
Package deploy#
Cutting a release — version bump, changelog, vX.Y.Z tag, and the
automated PyPI publish (Trusted Publishing) plus dataset publishing — is
documented in Release process.
Contribution guidelines#
Branching model#
dev— the stable development branch; all work merges here first.master— the protected release branch. A release mergesdevintomastervia a pull request and then pushes avX.Y.Ztag matching the package version (see Release process).feature/XXX-short-description— one feature branch per task; never combine multiple tasks in a single branch.
Commits#
Conventional Commits with exactly one subtask identifier, one commit per atomic subtask:
feat(XXX-SN): description
fix(XXX-SN): description
docs(XXX-SN): description
XXX is the task’s GitHub issue number and SN a single subtask
identifier (ranges or lists are not allowed). Commit messages must explain
why the change was made, not only what it does.
Issue references#
Every task is itself a GitHub issue (label task; the working loop lives
in the workflow-management skill). GitHub closes an issue automatically
once a commit containing a closing keyword (close, closes, closed,
fixes, fixed) reaches the repository’s default branch (mechanism).
This project relies on that mechanism:
A completed task carries the closing keyword for its own issue — and for every linked issue it fully resolves — in exactly one commit: by convention, the last subtask’s commit. Each keyword is a standalone line in the message body:
Fixes #Nwhen the work fixes a reported defect,Closes #Notherwise. No other commit of the task repeats a keyword:docs(XXX-S3): final subtask — verify and clean up Closes #XXX Fixes #127
A task that only partially addresses a linked issue references it without a closing keyword (bare
#N); GitHub links the issue but leaves it open.
Because the keywords live in the last subtask’s commit, an incomplete or blocked task — whose branch never merges — carries no keyword and can never close its issue by accident.
Since feature branches merge to dev and only releases merge dev into
master, the default branch, a task’s issue closes when the release
containing the task lands on master — not when the task merges to dev.
Quality gate#
Before a feature branch merges to dev, all of the following must pass —
no exceptions:
The full test suite: 0 failures, 0 skipped.
The full pre-commit pass: no errors.
Type checking:
uv run ty checkand Pyright both report no errors.The docs build: exit 0 under the no-warnings policy.
The link check (CI only): the “Check links” step of
.github/workflows/docs.ymlreports no broken or timed-out links. It is network-bound and slow locally (rate-limited retries), so it is not part of the local gate — the branch’s CI run must be green before merge.
Merge strategy#
Rebase the feature branch onto dev and fast-forward dev to it —
linear history, no squash, so every subtask commit keeps its own message.
Adding graphs and grammars#
New dataset entries — graphs, queries, grammars — enter through a single CONTRIBUTE NEW DATA issue; the full flow is documented in Contributing data.