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 (
) → 404, PyPI has nodocs/folder - Relative links (
[usage](docs/usage.md)) → resolve againstpypi.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:
armsmithv1.2.1 shipped a relative SVG badge that 404'd on its PyPI page while rendering fine on GitHub.aicertifymaintains a separateREADME-pypi.mdwith rewritten absolute URLs, precisely because the GitHub README breaks on PyPI.- An audit of the
modern-pythonGitHub 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
- Reads
long_descriptionfrom.whl(zipfile→.dist-info/METADATA) or.tar.gz(tarfile→PKG-INFO), plus theDescription-Content-Typeheader. - For
text/html, parses withhtml.parserand validates everyimg[src],a[href], and#anchoragainst collected element ids (explicit ids plus GitHub-style heading slugs). - 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, RSTimage::/figure::directives, and#anchorlinks against# Headingslugs. --pypimode fetcheshttps://pypi.org/pypi/<name>/jsonand 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
idattributes always win. --pypiuses the rawdescriptionfrom 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.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| readmeta-0.1.0.tar.gz | 11.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| readmeta-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.9 kB
Release files / readmeta-0.1.0.tar.gz
| Download URL | readmeta-0.1.0.tar.gz |
|---|---|
| Size | 11.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
179fac55a8488fba491e49405ab5e4f0ca6f6838cbd8e9216a02ce78e9a5f184
|
|
BLAKE2b-256 checksum How to use checksums |
8f56358fa7d86aeef602b20a9d2de3195fefcd0ba1616f01a8556ea404b36d21
|
| 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.0-py3-none-any.whl
| Download URL | readmeta-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e4755cd2c14b8fcb0ea1c461f096cc6e109d53546c9b8b1f983be951177144ea
|
|
BLAKE2b-256 checksum How to use checksums |
49095b4a551e47fd55aaca05b000c060014a3b46ced487fe7b535be8289f77ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|