Skip to main content

flask-coverage

Live code coverage for a running Flask application.

flask-coverage wraps coverage.py as a Flask extension and exposes a small debug blueprint at /debug/coverage. You can introspect what's been executed so far, take snapshots, view a per-file HTML report, and export the raw .coverage data — all from a running process, without restarting it.

It is designed for two scenarios:

  • Browser tests. Run your full Flask app under Playwright/Selenium/Cypress, drive it however you like, then read the live coverage report to see which paths your end-to-end tests actually reach.
  • Production / canary. Measure what code your live traffic exercises. Coverage measurement carries some overhead (typically <15% on Python 3.12+ with sys.monitoring), but for low-to-mid QPS services that's a reasonable trade for ground-truth dead-code detection.

Install

pip install flask-coverage

Requires Python ≥ 3.12, Flask ≥ 2.3, coverage ≥ 7.4.

Quickstart

from flask import Flask
from flask_coverage import FlaskCoverage

app = Flask(__name__)
FlaskCoverage(app)   # mounts /debug/coverage

Run your app and visit http://127.0.0.1:5000/debug/coverage/.

A runnable demo with a step-by-step walkthrough lives in examples/.

Endpoints

Mounted under /debug/coverage by default (override with FlaskCoverage(app, url_prefix="…")).

Method Path Purpose
GET / Dashboard: cache timestamp, snapshots panel, embedded text report
GET /report Cached text report (same format as coverage report)
GET /html/ Cached HTML report (coverage.py's native, per-file source views)
GET /files Cached JSON list: {file, statements, missing, covered, percent}
GET /export Cached merged .coverage data file
POST /refresh Regenerate the cache. Browser refresh does not.
POST /snapshot?label=… Take a labelled, timestamped copy into snapshot_dir
GET /snapshots JSON list of saved snapshots
GET /snapshots/<id> Download a saved snapshot's .coverage file
POST /reset Erase all collected data

POST endpoints content-negotiate: Accept: application/json returns JSON, anything else gets a 303 redirect to the dashboard with a flash message. Snapshots and refresh are designed to be operable from the dashboard without leaving it.

Configuration

Coverage settings are read from [tool.coverage.*] in pyproject.toml automatically (via coverage.py's native config support), or from .coveragerc / setup.cfg / tox.ini if present.

[tool.coverage.run]
source = ["myapp"]
parallel = true        # recommended for gunicorn/uwsgi (see Multi-worker)

[tool.coverage.report]
omit = ["*/migrations/*", "*/tests/*"]

Starting coverage early

For accurate measurement of module-level code, coverage must start before your application modules are imported. Three options, in order of preference:

  1. COVERAGE_PROCESS_START env var (best for gunicorn/uwsgi):

    export COVERAGE_PROCESS_START=$(pwd)/pyproject.toml
    gunicorn myapp:app
    
  2. flask-coverage CLI shim:

    flask-coverage --app myapp run --debug
    
  3. Manual start_early() as the very first line in wsgi.py:

    from flask_coverage import start_early
    start_early()                    # before any of your app modules
    from myapp import create_app
    app = create_app()
    

If a Coverage instance is already running (any of the above, or pytest-cov in tests), FlaskCoverage(app) adopts it instead of creating a duplicate tracer.

Security

The /debug/coverage blueprint exposes filesystem paths for every measured source file — treat it as sensitive. Registration is fail-closed: it requires one of the following, or it raises RuntimeError:

  • app.debug is True, or

  • FLASK_COVERAGE_PASSWORD env var is set (HTTP Basic auth — user admin, override with FLASK_COVERAGE_USERNAME), or

  • a custom auth= callback is passed to FlaskCoverage(...):

    FlaskCoverage(app, auth=lambda: current_user.is_authenticated and current_user.is_admin)
    

The basic-auth check uses hmac.compare_digest for constant-time comparison.

Operations

Disabling without redeploy

Set FLASK_COVERAGE_DISABLED to a truthy value (1, true, yes, on) before the process starts, and FlaskCoverage(app) becomes a no-op: no tracer, no blueprint, no auth check.

FLASK_COVERAGE_DISABLED=1 gunicorn myapp:app

Multi-worker (gunicorn / uwsgi)

Each worker traces independently. Set parallel = true under [tool.coverage.run] so each worker writes .coverage.<host>.<pid>.<rand>. Two things happen automatically:

  1. Auto-save thread. Each worker runs a daemon thread that calls cov.save() every autosave_interval seconds (default 30, configurable via FlaskCoverage(autosave_interval=…); set to 0 to disable). This bounds inter-worker staleness — when worker A serves /refresh, the on-disk data from workers B, C, D is at most 30s old.

  2. Non-destructive combine. Refreshing the cache merges the running worker's data with all sibling parallel files in a temp dir, runs coverage combine there, and uses the merged file. The original per-worker files on disk are never deleted, so workers keep accumulating data normally.

The dashboard surfaces both pieces of information: it shows the last-refresh timestamp and the autosave interval, so you can reason about freshness from the UI alone.

Caching and explicit refresh

/report, /files, /html/, and /export all serve from a file-based cache (.flask-coverage-cache/ next to the data file, configurable via cache_dir=…). Refreshing the browser never regenerates the cache — only POST /refresh does. This makes the cost of hitting the dashboard predictable, and prevents accidental thundering herds in production. Cache regeneration is file-locked across workers.

Performance

On Python 3.12+, coverage.py uses sys.monitoring (PEP 669), which is significantly faster than the legacy sys.settrace path — typically under 15% overhead. Acceptable for staging and canary; profile before turning on for high-QPS production traffic.

Development

git clone https://github.com/abilian/flask-coverage
cd flask-coverage
uv sync
uv run pytest          # 45 tests
make check             # lint + format + type check

Tests are organised by the test pyramid:

  • tests/a_unit/ — fast, isolated, mock-based
  • tests/b_integration/ — Flask test-client + mocked Coverage
  • tests/c_e2e/ — real coverage.Coverage running, including multi-worker simulation

CI runs ruff (lint + format), ty (type check), and pytest across Python 3.12 / 3.13 / 3.14.

License

MIT — see LICENSE.

Metadata

Release files for flask-coverage 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flask-coverage 0.2.1
File Size Uploaded
flask_coverage-0.2.1.tar.gz 18.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flask-coverage 0.2.1
File Interpreter ABI Platform
flask_coverage-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 38.9 kB

Release files / flask_coverage-0.2.1.tar.gz

Download URL flask_coverage-0.2.1.tar.gz
Size 18.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5c65d3c8bf9d3c5f374449235276b29d749fc3bc1c044bdbbc31f61a7bd0db99
BLAKE2b-256 checksum
How to use checksums
02abac419b1469f1e1f8e6d3196f2c43b4dca7d84dfd62b4e4c01710f89bdc3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / flask_coverage-0.2.1-py3-none-any.whl

Download URL flask_coverage-0.2.1-py3-none-any.whl
Size 20.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
37087f7798dcba7e905ab87725db320c5d44f8ff558835328dcd2dacf3833e48
BLAKE2b-256 checksum
How to use checksums
243b39b46c8efe7670b0a04a24a102302551cab86e275ab0b79f1734721eac40
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page