PyPI
PyPI defines the wheel and sdist formats and the HTTP protocol that installers use to find and download them. A
wheel (.whl) is a built
package that an installer can install; an
sdist (source distribution, a
.tar.gz or a .zip) contains the source used to build a wheel. Both are artifacts, the files an installer
fetches.
PyPI-to-peryx mapping
peryx uses Python's index, project, version, and file terms for PyPI. See
the index model and glossary.
The binary includes the PyPI implementation. An index with ecosystem = "pypi" activates it. Omitting ecosystem
selects PyPI. Without a PyPI index, peryx installs no PyPI routes, jobs, or services. [availability] selects
coordination for the process.
| Python term | peryx concept | Meaning |
|---|---|---|
index (--index-url) | index | Client endpoint; a cached index fronts one upstream |
| project or package | project | One distribution name, such as requests |
| release or version | version | One released project version |
| distribution | artifact | A wheel or source-distribution file |
| file | file | One content-addressed distribution file |
| publish or upload | upload | Add a distribution to a hosted index |
| install or download | download | Fetch a distribution through peryx |
| pull-through mirror | cached index | Read-through proxy for one upstream index |
peryx uses the role names cached, hosted, and virtual, plus shadowing, across ecosystems.
PyPI index roles
The three index roles map to PyPI as follows:
- cached: a read-through cache of an upstream Python index such as pypi.org. On a miss, peryx fetches, stores, and serves the project page or artifact. Later requests use the stored copy. A cached index can front pypi.org, TestPyPI, Artifactory, or a GitLab registry.
- hosted: a store for wheels and sdists published through the standard upload API. Twine or
uv publishwrites the files without using an upstream. - virtual: an ordered stack of cached and hosted indexes under one URL. The default mode unions versions and keeps
the first occurrence of each filename. Clients use one
index-urlwithout--extra-index-url. Configure project isolation when a hosted project must exclude cached candidates with other filenames.
A cached route retries upstream server errors, timeouts, and 429 responses with bounded backoff. A valid Retry-After
delay or HTTP date takes precedence, capped at 30 seconds.
Protocol
Python installers speak the Simple API. An index exposes one page per project with links to its files. peryx supports these forms:
- PEP 503: the original HTML page of download links. peryx parses it from upstreams that only speak HTML.
- PEP 691: the modern JSON form of the same data. peryx canonicalizes every upstream to this once, at fetch time, and serves JSON (with HTML on request) downstream.
- PEP 658/714: a
.metadatasibling next to each file lets a resolver read a few kilobytes of dependency metadata without downloading a wheel. peryx serves it and synthesizes it with byte-range reads when an upstream lacks it. - Legacy upload API: the POST endpoint twine and
uv publishuse to publish into a hosted index.
For the full standards map, see standards.
Configure clients
Assume peryx is running at http://127.0.0.1:4433 with the default virtual route root/pypi. Installers read from
.../simple/; publishers post to the route root.
Install
pip
# one-off
pip install --index-url http://127.0.0.1:4433/root/pypi/simple/ requests
# persistent: environment
export PIP_INDEX_URL=http://127.0.0.1:4433/root/pypi/simple/
# persistent: pip.conf (~/.config/pip/pip.conf or venv pip.conf)
# [global]
# index-url = http://127.0.0.1:4433/root/pypi/simple/uv
# one-off
uv pip install --index-url http://127.0.0.1:4433/root/pypi/simple/ requests
# persistent: environment
export UV_INDEX_URL=http://127.0.0.1:4433/root/pypi/simple/Poetry
poetry source add --priority=primary peryx http://127.0.0.1:4433/root/pypi/simple/Publish
Publishing can use a hosted layer with an upload token or exchange a GitHub Actions or GitLab CI identity for a short-lived token. The CI flow scopes that token to one repository route and the configured project globs.
Inject the token as TWINE_PASSWORD with TWINE_USERNAME=__token__, or as UV_PUBLISH_TOKEN, through your secret
environment before publishing.
Twine
twine upload --repository-url http://127.0.0.1:4433/root/pypi/ dist/*uv
uv publish --publish-url http://127.0.0.1:4433/root/pypi/ dist/*.pypirc
# ~/.pypirc
[distutils]
index-servers = peryx
[peryx]
repository = http://127.0.0.1:4433/root/pypi/
username = __token__
password = <token>
GET /root/pypi/+api returns a ready-made .pypirc snippet for any configured route.
Generate configuration files
peryx config-snippet prints pip.conf, uv.toml, or .pypirc without starting the server:
peryx config-snippet --base-url https://packages.example --index root/pypi pip.conf
peryx config-snippet --base-url https://packages.example --index root/pypi uv.toml
peryx config-snippet --base-url https://packages.example --index root/pypi .pypirc
--base-url is the public origin, including any proxy path prefix and excluding the index route. pip.conf and
uv.toml work for read-only and writable indexes. .pypirc requires a hosted upload target that accepts writes, and
the generated file contains <upload-token> instead of the configured secret.
PyPI metadata pages use the transformed-page memory cache controlled by the top-level hot_cache_bytes setting. Setting
it to 0 disables that cache without removing stored pages or distributions.
Web UI
The PyPI implementation labels searchable entities as packages and opens an index card on its project list. A project page shows the long description, summary, install command, versions, dependencies, project links, classifiers, and files. Release groups follow PEP 440 order. Files that do not map to one declared release remain visible under Legacy or unassociated files.
Each file row shows size, upload time, sha256, yank state, metadata availability, source, and byte availability. A file with PEP 740 provenance has a disclosure for its predicate types and subject binding. The disclosure reports claims and binding checks; it does not claim that peryx verified a Sigstore signature, certificate, or transparency log.
Wheels, zip files, zipped eggs, .tar, .tar.gz, and .tgz files expose archive contents. The browser lists members
and previews bounded text chunks. Other compressed tar formats remain download-only.
An upload-enabled route adds an Upload page for one wheel or .tar.gz source distribution. A project page adds
Manage uploads actions for yank, un-yank, delete, and restore. These controls call the same endpoints documented in
yank and delete packages.
Related
- How peryx compares to devpi, proxpi, pypiserver, and pypicloud: PyPI performance
- Front an index that is not pypi.org: front another index
- Add credentials for a private upstream: proxy a private upstream
- Publish your own packages: publish
- Block compromised distributions without deleting evidence: digest revocations
-
Performance
Cold and warm installs, file throughput, parallel CI, and request throughput for peryx, devpi, proxpi, pypiserver, and pypicloud.
-
Upload compatibility
Wheel names, declared digests, equivalent versions, mutation paths, provenance, and durability rules.
-
Simple API response guarantees
Upstream-derived API versions, reachable signature markers, provenance objects, and canonical URL redirects.
-
Block revoked PyPI content
How digest revocations govern PyPI discovery and downloads.
-
Tutorials
Learning-oriented lessons for serving and publishing Python packages through peryx.
-
How-to guides
Recipes for publishing, CI caches, virtual indexes, yanking, and deletion in the PyPI ecosystem.
-
Reference
PyPI protocol, policy, upload, mirroring, and availability reference.
-
Migration
Move to peryx from another Python index server: devpi, proxpi, pypiserver, pypicloud, bandersnatch, and more.