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.1
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.1.tar.gz | 14.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|