PyPI
PyPI
- PyPIRegistry
- pkg:pypi
JSON API for metadata, Simple API for versions. Names normalized per PEP 503, extras become scopes.
- API
- pypi.org
- Marks
- yanked
- Maintainers
- author
Lookups
- PackagefetchPackage
- VersionsfetchVersions
- DependenciesfetchDependencies
- MaintainersfetchMaintainers
Access
- Create
create("pypi") - Import
@agntn/registries/registries/pypi - PURL
pkg:pypi/name - Trypkg:pypi/flask in the explorer
Address it
await fetchPackageFromPURL("pkg:pypi/flask");
await fetchPackageFromPURL("pkg:pypi/Flask"); // the same package
await fetchPackageFromPURL("pkg:pypi/typing_extensions"); // normalized to typing-extensions
Names are normalized the way PEP 503 says: lowercase, runs of -, _ and . collapsed to -. parsePURL does it for PyPI types, and the adapter does it again on any name it receives, so PyPIRegistry called directly behaves the same.
What it reads
| Call | Endpoint |
|---|---|
fetchPackage | GET /pypi/{name}/json |
fetchVersions | GET /simple/{name}/ with Accept: application/vnd.pypi.simple.v1+json (PEP 691) |
fetchDependencies | GET /pypi/{name}/{version}/json, requires_dist |
fetchMaintainers | GET /pypi/{name}/json, author, author_email, maintainer and maintainer_email |
latestVersion is info.version. repository is the first of the Repository, Source, Source Code, GitHub and Homepage project URLs, normalized. documentation is the Documentation project URL. Keywords are split from the keywords string on commas or spaces.
Versions
The Simple API lists every version and every file. The adapter groups files by version, matching wheel and sdist filenames against the version list, and takes the sdist when there is one. publishedAt is that file's upload-time, integrity its sha256-, status is yanked when the file is yanked. A version with no files has no date and no hash.
Dependencies
requires_dist entries are parsed for the name, the version specifier and the environment marker. An extra == "…" marker sets the scope:
| Extra | scope | optional |
|---|---|---|
| none | runtime | false |
dev, development | development | true |
test, tests, testing | test | true |
| anything else | runtime | true |
Other markers, such as python_version, are dropped; the dependency stays.
Maintainers
Authors first with role: "author", then maintainers with role: "maintainer", deduplicated by email. PEP 621 projects leave author null and list everyone in author_email as Name <email>, Name <email>, so pydantic comes back as twelve entries and requests as one author plus two maintainers. A plain author name pairs with a bare author_email; a name that has no address of its own stays one entry beside the emailed people. RFC 822 comments and quoted display names are read too. The project's owners on pypi.org are not read.
URLs
urls.registry("flask"); // https://pypi.org/project/flask/
urls.download("flask", "3.1.3"); // the sdist URL seen during fetchVersions, else the project page
urls.documentation("flask"); // https://pypi.org/project/flask
urls.readme("flask", "3.1.3"); // https://pypi.org/project/flask/3.1.3/
Gotchas
download()only knows a file URL afterfetchVersionsran on the same adapter instance; before that it points at the project page.requires_distis what the sdist or wheel declared, not what pip resolved. Conditional dependencies show up unconditionally.licensesis the PEP 639license_expressionwhen the project ships one. Older projects only have the free textlicensefield, which goes throughnormalizeLicense, and a long license body is returned as is.
Where it lives
src/registries/pypi.ts.