Documentation

PyPI

PyPI is the Python packaging ecosystem: the format of wheels and sdists, and the HTTP protocol installers use to find and download them. A wheel (.whl) is a pre-built, ready-to-install package; an sdist (source distribution, .tar.gz) is the source a wheel is built from. Both are artifacts — the actual files an installer fetches. This is the one ecosystem velodex ships today.

The roles for PyPI

The three index roles map onto PyPI like this:

  • cached — a read-through cache of an upstream Python index such as pypi.org. On a miss velodex fetches the project page or artifact from upstream, stores it, and serves it; later requests come from disk. Point one at pypi.org, a TestPyPI, an Artifactory, or a GitLab registry.
  • hosted — a store you publish your own wheels and sdists to over the standard upload API. Nothing upstream; the files live here because twine or uv publish put them there.
  • virtual — an ordered stack of the two, served under one URL, where your hosted uploads shadow same-named upstream files. This is what clients point at: one index-url, private packages winning over public ones, no --extra-index-url.

The wire protocol

Python installers speak the Simple API: an index exposes a page per project listing that project's files, and the installer downloads what it resolves. velodex serves and understands every current form:

  • PEP 503 — the original HTML page of download links. velodex parses it from upstreams that only speak HTML.
  • PEP 691 — the modern JSON form of the same data. velodex canonicalizes every upstream to this once, at fetch time, and serves JSON (with HTML on request) downstream.
  • PEP 658/714 — a .metadata sibling next to each file, so a resolver reads a few kilobytes of dependency metadata instead of downloading a whole wheel. velodex serves it, and synthesizes it with byte-range reads when an upstream lacks it.
  • Legacy upload API — the POST endpoint twine and uv publish use to publish into a hosted index.

For the full standards map, see standards.

Set me up

Assume velodex 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

# 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/
# 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 source add --priority=primary velodex http://127.0.0.1:4433/root/pypi/simple/

Publish

Publishing needs a hosted layer with an upload_token. velodex accepts any username; the token is the password, matching pypi.org's __token__ convention.

twine upload --repository-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p <token> dist/*
uv publish --publish-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p <token> dist/*
# ~/.pypirc
[distutils]
index-servers = velodex

[velodex]
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.

Other ecosystems

OCI (container images) and npm (JavaScript) are planned: the same cached/hosted/virtual roles apply, driven by each format's own protocol. The capability matrix tracks where each stands.

In practice

On this page