PyPI repositories work with the pip, uv, poetry, and twine clients. The examples use the conventional names pypi-proxy and pypi-hosted.

Connecting to the proxy

Point the client at the repository index — the path ends with /simple/:

pip install --index-url https://<USERNAME>:<PASSWORD>@<GALLEON_HOST>/repository/pypi-proxy/simple/ six==1.16.0

For uv, add the index to pyproject.toml (credentials go in the UV_INDEX_GALLEON_USERNAME and UV_INDEX_GALLEON_PASSWORD variables):

[[tool.uv.index]]
name = "galleon"
url = "https://<GALLEON_HOST>/repository/pypi-proxy/simple/"
default = true

For poetry (credentials — POETRY_HTTP_BASIC_GALLEON_USERNAME and POETRY_HTTP_BASIC_GALLEON_PASSWORD):

poetry source add --priority=primary galleon https://<GALLEON_HOST>/repository/pypi-proxy/simple/

Private packages

Packages from the hosted repository are wired as an additional index next to the proxy.

For pip — an extra index:

pip install --index-url https://<GALLEON_HOST>/repository/pypi-proxy/simple/ \
  --extra-index-url https://<GALLEON_HOST>/repository/pypi-hosted/simple/ mypkg

With several indexes, pip may pick a package from any of them — private package names must be unique relative to the public PyPI (dependency confusion protection).

For uv — a second [[tool.uv.index]] block with the pypi-hosted address and pinning the private package to it via [tool.uv.sources]:

[[tool.uv.index]]
name = "galleon-private"
url = "https://<GALLEON_HOST>/repository/pypi-hosted/simple/"

[tool.uv.sources]
mypkg = { index = "galleon-private" }

Publishing

twine, uv publish, and poetry publish are supported. Authentication is username and password only (or the “Name code” and “Pass code” of a token); the single-secret __token__ format is not supported.

Publishing with twine (~/.pypirc):

[distutils]
index-servers = galleon

[galleon]
repository = https://<GALLEON_HOST>/repository/pypi-hosted/
username = <USERNAME>
password = <PASSWORD>
python -m build
twine upload --repository galleon dist/*

Publishing with uv:

UV_PUBLISH_USERNAME=<USERNAME> UV_PUBLISH_PASSWORD=<PASSWORD> \
  uv publish --publish-url https://<GALLEON_HOST>/repository/pypi-hosted/ dist/*

Specifics

Format specifics:

  • Published versions are immutable: re-uploading a file is rejected with “File already exists”. For idempotent CI publishing use uv publish --check-url or poetry publish --skip-existing — they succeed on byte-identical already-uploaded files.
  • Project names are normalized per the PyPI rules: any spelling (Foo_Bar, foo.bar) leads to the single canonical page foo-bar.
  • Wheel metadata is served as separate files (PEP 658) — uv lock gathers dependency information without downloading the packages themselves.