Skip to main content

readmeta

Check that your README will actually render on PyPI — by inspecting the built artifact, not the repo.

The problem

PyPI renders the long_description from your built artifact (the body of METADATA in a wheel / PKG-INFO in an sdist). It does not render your repo's README.md. Things that look perfect on GitHub silently break on PyPI:

  • Relative images (![shot](docs/shot.png)) → 404, PyPI has no docs/ folder
  • Relative links ([usage](docs/usage.md)) → resolve against pypi.org, broken
  • Raw SVG references (assets/logo.svg) → 404; PyPI doesn't serve repo files
  • In-page anchors ([setup](#instalation)) → go nowhere when the heading id doesn't exist

twine check validates that the description renders — it does not check that any link or image actually resolves.

Real cases this catches:

  • armsmith v1.2.1 shipped a relative SVG badge that 404'd on its PyPI page while rendering fine on GitHub.
  • aicertify maintains a separate README-pypi.md with rewritten absolute URLs, precisely because the GitHub README breaks on PyPI.
  • An audit of the modern-python GitHub org found 23 of 25 packages shipping relative asset references that can't resolve on PyPI.

Install

pip install readmeta

Requires Python 3.9+. No dependencies — stdlib only.

Usage

Check your built artifacts (build first, then check what PyPI will actually see):

python -m build
readmeta check dist/*

Or check what's currently hosted on PyPI:

readmeta check --pypi requests

Example output:

fakepkg 0.1.0  [text/markdown]  <- dist/fakepkg-0.1.0-py3-none-any.whl
Found 2 issue(s):

  [relative-image] dist/fakepkg-0.1.0-py3-none-any.whl:12
      docs/shot.png
      -> PyPI renders the description standalone; relative image paths 404. Use an absolute https:// URL (e.g. raw.githubusercontent.com).

  [broken-anchor] dist/fakepkg-0.1.0-py3-none-any.whl:20
      #instalation
      -> No heading with a matching id was found; the link goes nowhere on PyPI.

Exit codes are CI-friendly: 0 = clean, 1 = issues found, 2 = error (unreadable artifact, PyPI unreachable, bad usage).

CI example

- name: Build
  run: python -m build

- name: Check PyPI rendering
  run: |
    pip install readmeta
    readmeta check dist/*

How it works

  1. Reads long_description from .whl (zipfile → .dist-info/METADATA) or .tar.gz (tarfile → PKG-INFO), plus the Description-Content-Type header.
  2. For text/html, parses with html.parser and validates every img[src], a[href], and #anchor against collected element ids (explicit ids plus GitHub-style heading slugs).
  3. For Markdown/RST (what artifacts actually carry — the raw source, not rendered HTML), scans with regexes: inline and reference-style images/links, embedded <img> tags, RST image::/figure:: directives, and #anchor links against # Heading slugs.
  4. --pypi mode fetches https://pypi.org/pypi/<name>/json and checks the hosted description.

Limitations (v0.1)

  • Check only — no --fix. A build-time rewrite mode (convert relative refs to absolute URLs at build time) is planned.
  • Anchor validation for RST is limited (Markdown headings and HTML ids are covered; RST .. _target: definitions are not yet resolved).
  • Code spans and fenced code blocks are ignored (documenting a bad pattern doesn't flag it); indented code blocks are still scanned.
  • Heading-slug generation approximates GitHub/PyPI's algorithm; exotic headings could produce false positives — explicit id attributes always win.
  • --pypi uses the raw description from the JSON API (the API doesn't expose rendered HTML); findings are identical to checking a fresh local build.

Metadata

Release files for readmeta 0.1.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 readmeta 0.1.1
File Size Uploaded
readmeta-0.1.1.tar.gz 14.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for readmeta 0.1.1
File Interpreter ABI Platform
readmeta-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 25.8 kB

Release files / readmeta-0.1.1.tar.gz

Download URL readmeta-0.1.1.tar.gz
Size 14.8 kB
Tags Source
SHA-256 checksum
How to use checksums
ad253f6b5fbdafa4e94cfacbcbbba11f5bc6f8ccfe25f68ebc9be68b942793da
BLAKE2b-256 checksum
How to use checksums
2c1cc941ef1191f82683a453416352e441ea624b9240ed122842a7013fa8c67b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / readmeta-0.1.1-py3-none-any.whl

Download URL readmeta-0.1.1-py3-none-any.whl
Size 11.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5d237c7ea0803ba0b8ebc8b78cf3cbf898689b21bff9ba4efbdfa58b2c71790
BLAKE2b-256 checksum
How to use checksums
ae2165ee7663c4384f531a5403a14432655e15384c97f66ba04175174dafb620
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.1.1 This release

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