Skip to main content

Live code coverage for a running Flask application.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flask_coverage-0.2.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flask_coverage-0.2.0-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file flask_coverage-0.2.0.tar.gz.

File metadata

  • Download URL: flask_coverage-0.2.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for flask_coverage-0.2.0.tar.gz
Algorithm Hash digest
SHA256 93aeb5025bf72ece3eb7b35c65cab94c4e2e901dbd2783757419577a979afc59
MD5 04fcbb30831c86f574a22f3d8fe08531
BLAKE2b-256 a18e16e878f4d1d25f217f64416d9e5fc1f9af2ef10ba640b2a67c740207f8d3

See more details on using hashes here.

File details

Details for the file flask_coverage-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: flask_coverage-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for flask_coverage-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 33ceafebb710950f8cfabe0406decf89a385acc62dc371a90ed2c0383954ba31
MD5 2d11519dad1e24982957b62da7c16c79
BLAKE2b-256 b2e70de48204c97ede5766e6d3ae73c351b62889a4d9c7dceadd25f930917792

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page