Publish packages
velodex accepts the legacy upload API, the wire protocol both
twine and uv publish speak. Uploads need
a hosted index with an upload_token; the default topology's hosted index has none, so uploads are off until you set
one:
[[index]]
name = "pypi"
cached = "https://pypi.org/simple/"
[[index]]
name = "hosted"
upload_token = "<secret>"
[[index]]
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"
Then publish to the virtual index's route. velodex accepts any username; the token is the password, matching the
pypi.org __token__ convention:
twine upload --repository-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p <secret> dist/*
# or
uv publish --publish-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p <secret> dist/*
velodex accepts wheels and modern .tar.gz source distributions. It rejects .egg, .zip, and ambiguous legacy
archives on upload; those files can still be mirrored if an upstream index lists them. During upload, velodex checks the
declared sha256 and blake2b-256 digests while streaming the artifact into a staged blob. A lone md5 digest is rejected.
Before publishing the staged blob, velodex validates the project name, PEP 440 version, safe filename shape, filetype,
archive readability, and metadata identity. Wheel uploads must contain one normalized {name}-{version}.dist-info/
directory with METADATA, WHEEL, and RECORD. The WHEEL tags and optional build field must match the filename, and
RECORD must cover each archive file except RECORD and deprecated RECORD signatures with sha256-or-better hashes.
When RECORD includes a size, the size must match the archive member.
Modern sdists must be .tar.gz files whose filename follows PEP 625. The archive must contain one top-level
{name}-{version}/ directory with pyproject.toml and PKG-INFO. velodex rejects tar entries with absolute paths,
traversal, unsafe links, special files, or device entries. For Metadata 2.4 and newer, every License-File header must
name a file inside the sdist.
The filename, form fields, and METADATA or PKG-INFO Name and Version must agree. Metadata-Version,
Requires-Python, license fields, extras, and project URLs are compared when the upload form supplies them and the
metadata model can represent them. Requires-Python, when present in the form or metadata, must parse as Python version
specifiers.
Accepted files are stored content-addressed and served from /root/pypi/simple/<project>/ alongside the cached index's
packages. Your file shadows an upstream file of the same name. For wheels, velodex extracts METADATA; for sdists, it
extracts the verified PKG-INFO. Both are served as PEP 658/714 .metadata siblings, so resolvers get the fast path
for your uploads and the web UI can show the full package page.
In .pypirc
The .pypirc file holds the repository and
credentials:
[distutils]
index-servers = velodex
[velodex]
repository = http://127.0.0.1:4433/root/pypi/
username = __token__
password = <secret>
twine upload -r velodex dist/* then works without flags.
GET /root/pypi/+api returns the same .pypirc shape when the request reaches Velodex with the public Host header.
The discovery document keeps the password as <upload-token>; replace it with the hosted index token before publishing.
For offline setup, print the same snippet from the config file:
velodex config-snippet --base-url http://127.0.0.1:4433 --index root/pypi .pypircUpload failures
Validation failures return 400 with the field or archive check that failed. Common causes:
- The filename is not a wheel or
.tar.gzsdist. - The filename's normalized project name or version does not match the form fields.
- The archive is corrupt, lacks required wheel or sdist files, has unsafe tar entries, or has a bad
RECORD. - Core metadata names a different project or version.
- A Metadata 2.4+ sdist lists a
License-Filethat is missing from the archive. - A declared sha256 or blake2b-256 digest does not match the received bytes.
- The same filename was already uploaded with different bytes.
Related
- What shadowing an upstream name buys you: the index model
- Undo a bad release: yank and delete
- The upload protocol itself: HTTP endpoints