Python
nolapse supports Python projects via a subprocess runner that invokes pytest and reads the resulting coverage data. The runner understands both the JSON report format (coverage.json) and the SQLite binary format (.coverage).
Prerequisites
Section titled “Prerequisites”- Python 3.8 or later must be available as
python3onPATH. - pytest and pytest-cov must be installed in the Python environment used by the project:
pip install pytest pytest-cov- A baseline must exist before enforcement can run. See baseline init if you have not created one yet.
How the runner works
Section titled “How the runner works”When you run nolapse run --repo . --lang python, nolapse:
- Locates the runner script (
coverage_runner.py). By default the script ships alongside the nolapse binary. You can override this withNOLAPSE_RUNNER_PATH— see Overriding the runner path. - Invokes it as a subprocess:
python3 <script> <repo_path>. - The subprocess runs
pytest --cov=. --cov-report=json(or reads an existing.coveragefile ifcoverage.jsonis not present). - The runner emits a structured summary line that nolapse parses.
- nolapse compares the result against the stored baseline and applies threshold logic.
The Python runner enforces a 5-minute timeout on the entire pytest invocation.
Generating coverage data
Section titled “Generating coverage data”nolapse expects one of two coverage outputs in the repo root:
Option 1 — JSON report (recommended)
Section titled “Option 1 — JSON report (recommended)”pytest --cov=. --cov-report=jsonThis produces coverage.json. The runner reads the totals.percent_covered field.
Option 2 — SQLite binary
Section titled “Option 2 — SQLite binary”If coverage.json is not present, the runner falls back to reading the .coverage SQLite file that pytest-cov writes by default. This works without extra flags but is slightly slower to parse.
Setting the language in nolapse.yaml
Section titled “Setting the language in nolapse.yaml”lang: pythonwarn_threshold: -1.0fail_threshold: -5.0strict_mode: falsePlace nolapse.yaml in the root of the directory you pass to --repo.
| Field | Type | Default | Description |
|---|---|---|---|
lang | string | go | Set to python to activate the Python runner. |
warn_threshold | float | -1.0 | Warn if coverage drops by more than this many percentage points. |
fail_threshold | float | -5.0 | Fail (exit 1) if coverage drops by more than this many percentage points. |
strict_mode | bool | false | When true, any negative delta is treated as a failure. |
Initialising a baseline
Section titled “Initialising a baseline”nolapse baseline init --repo . --lang pythonnolapse runs the Python runner, records the current coverage percentage, and writes nolapse-baseline.json. Commit this file so CI has a reference point.
Running nolapse
Section titled “Running nolapse”nolapse run --repo . --lang pythonExample output
Section titled “Example output”nolapse: invoking python3 /usr/local/lib/nolapse/coverage_runner.py /home/ci/myprojectnolapse: coverage report found: coverage.jsonnolapse: total coverage: 73.8%nolapse: baseline coverage: 74.5%nolapse: delta: -0.7%nolapse: warn threshold: -1.0% fail threshold: -5.0%nolapse: result: PASS (delta within warn threshold)Overriding the runner path
Section titled “Overriding the runner path”If your environment cannot access the default runner location (for example, a restricted container image or an air-gapped environment), point nolapse at your own copy of coverage_runner.py:
export NOLAPSE_RUNNER_PATH=/opt/myorg/nolapse/coverage_runner.pynolapse run --repo . --lang pythonThe NOLAPSE_RUNNER_PATH environment variable must be set before invoking nolapse. The script at that path is called with a single positional argument — the absolute path to the repo — and must emit the summary line on stdout.
Example CI workflow
Section titled “Example CI workflow”The following is a complete GitHub Actions example. See the GitHub Actions guide for the full action reference.
name: Coverageon: [pull_request]
jobs: coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0
- uses: actions/setup-python@v5 with: python-version: '3.12'
- name: Install dependencies run: pip install pytest pytest-cov
- name: Install nolapse run: go install github.com/nolapse/nolapse-cli/nolapse-cli/cmd/nolapse@latest
- name: Coverage check uses: ./nolapse-gh-action with: repo-token: ${{ secrets.GITHUB_TOKEN }} warn-threshold: '1.0' fail-threshold: '5.0' pr-number: ${{ github.event.pull_request.number }}Tips for accurate coverage
Section titled “Tips for accurate coverage”- Run pytest from the repo root so relative import paths in
coverage.jsonresolve correctly. - Exclude virtual environments. Add
omit = .venv/*,venv/*to your.coveragercorpyproject.toml[tool.coverage.run]section so installed packages do not inflate the denominator. - Exclude migrations and generated files. Django migration files and auto-generated gRPC stubs should be omitted from measurement. Use
omitpatterns in.coveragerc. - Use
--cov-sourceto scope coverage.pytest --cov=srcrestricts measurement to yoursrc/tree, avoiding test files themselves from appearing as covered.