Registries

PyPI

PyPI's JSON API for metadata and the Simple API for versions, with PEP 503 name normalization and extras turned into dependency scopes.
IDpypi03 / 6keyless · pypi.org
Registry

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

Access

Createcreate("pypi")
Import@agntn/registries/registries/pypi
PURLpkg:pypi/name
Trypkg:pypi/flask in the explorer

Address it

ts
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

CallEndpoint
fetchPackageGET /pypi/{name}/json
fetchVersionsGET /simple/{name}/ with Accept: application/vnd.pypi.simple.v1+json (PEP 691)
fetchDependenciesGET /pypi/{name}/{version}/json, requires_dist
fetchMaintainersGET /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:

Extrascopeoptional
noneruntimefalse
dev, developmentdevelopmenttrue
test, tests, testingtesttrue
anything elseruntimetrue

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

ts
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 after fetchVersions ran on the same adapter instance; before that it points at the project page.
  • requires_dist is what the sdist or wheel declared, not what pip resolved. Conditional dependencies show up unconditionally.
  • licenses is the PEP 639 license_expression when the project ships one. Older projects only have the free text license field, which goes through normalizeLicense, and a long license body is returned as is.

Where it lives

src/registries/pypi.ts.