Share a mypy HTML report

Adopting typing in an existing Python codebase is a long grind with one recurring question: what do we annotate next? The answer is in a report almost nobody generates, because the report has nowhere to go.

pip install 'mypy[reports]'
mypy --html-report mypyreport/ src/

That writes a ranked table of every module by imprecision — the share of lines mypy could not check precisely — and a page per module with the offending lines highlighted. It is the closest thing to a prioritised worklist the type migration will ever have, and it lands in a directory that gets .gitignored.

Precision, not annotation count

The number is worth understanding before anyone argues about it. A module can be fully annotated and still score badly, because a dependency returns Any and that Any spreads through everything it touches. That is the useful signal: it points at the one stub package worth installing rather than at the hundred functions worth annotating.

Which is also why this should be a link and not a percentage in a CI log. The percentage starts an argument. The page ends it.

Publish the folder

Drag mypyreport/ (or a zip of it) into Comma:

  • index.html becomes the report body — the ranked table.
  • The per-module pages and stylesheet upload alongside it, with relative references rewritten to the uploaded copies, so clicking a module row still opens its annotated source.
  • Scripts run inside a sandboxed iframe (allow-scripts, no allow-same-origin).

From CI

mypy --html-report mypyreport/ src/ || true   # report even when the gate fails

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html mypyreport/index.html \
        --arg t "Type coverage — $(git rev-parse --short HEAD)" \
        '{title: $t, html: $html}')"

The || true matters. A type-coverage report is most interesting on the run that failed, and a job that exits before publishing throws away the artifact that explains why. See publishing from CI and the API reference.

Supporting pages go up through POST /api/v1/reports/$REPORT_ID/assets as base64.

A revision per run is the migration record

PATCHing one saved report id keeps a permanent URL, and every publish appends a revision. Six months of those is the only honest answer to "is the typing effort actually working?" — better than a graph, because you can open any two points and see which modules moved.

Comments, so a decision stays decided

Half the red rows on a type-coverage report are red on purpose: a legacy module nobody will annotate, a dynamic dispatch layer that resists typing, a dependency without stubs. Anchoring that reasoning to the row means the next engineer reads it instead of rediscovering it. See commenting on HTML.

Limits

  • Entry HTML: 5 MB. Assets: 25 MB per file, 250 MB and 500 files total. A large codebase generates one page per module — over 500, publish the index alone, or report per package.
  • Scripts run, sandboxed — no same-origin access.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a mypy report →

Related