Repositories & Package Sources

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

Poetry separates two things that pip mixes into its --index-url options: package sources, which the project declares in pyproject.toml to install from, and publishable repositories, which each user configures with poetry config to upload to. This page covers both for Poetry 2.5.1. The examples ran on 2026-10-22 on Linux with CPython 3.14.6, using the birdtally-report project and two local pypiserver indexes: birdwatch on 127.0.0.1:8082, which requires HTTP basic authentication and holds birdtally, and internal on 127.0.0.1:8083, without authentication. Credentials come from environment variables; scratch paths are shortened to ….

A dependency with a source constraint is looked up only in that source; otherwise primary sources (or the implicit PyPI) are searched, then supplemental sources; explicit sources are never searched unless named

The implicit PyPI source

With no [[tool.poetry.source]] entries, every dependency comes from PyPI, which Poetry reads through its JSON API (falling back to downloading a distribution when the published metadata is invalid). PyPI has the reserved name PyPI (any case) and a fixed URL:

poetry source add pypi https://pypi.org/simple/
The URL of PyPI is fixed and cannot be set.

Adding a source

poetry source add [--priority=primary|supplemental|explicit] <name> [<url>] writes a [[tool.poetry.source]] table:

poetry source add --priority=explicit birdwatch http://127.0.0.1:8082/simple/
Adding source with name birdwatch.
[[tool.poetry.source]]
name = "birdwatch"
url = "http://127.0.0.1:8082/simple/"
priority = "explicit"

Adding a name that exists replaces it (Source with name internal already exists. Updating.). Leaving out --priority makes a primary source and, since Poetry 2.1, prints a deprecation warning:

The default priority will change to supplemental in a future release.
Adding source with name internal.

Always pass --priority. A Simple API URL needs its trailing /simple/.

poetry source show [<name>…] lists the sources from pyproject.toml (not the implicit PyPI) and poetry source remove <name> deletes one:

poetry source remove internal
poetry source show
Removing source with name internal.
 name      : birdwatch
 url       : http://127.0.0.1:8082/simple/
 priority  : explicit

 name      : PyPI
 priority  : supplemental

Sources are a Poetry feature only: they are not written into the built package’s metadata. Installing the project with pip (including pip install -e .) ignores them and fetches every dependency from pip’s own index.

Priorities

Priority Behaviour

primary

Searched for every dependency without a source constraint; all primary sources are queried. Configuring any primary source turns the implicit PyPI off.

supplemental (1.5.0)

Searched only when no higher-priority source has a compatible distribution, in order, until one has it. Good for slow sources and for few packages.

explicit (1.5.0)

Searched only for dependencies that name it with source = "…". The documentation’s example is PyTorch’s GPU wheel index: poetry source add --priority=explicit pytorch-gpu-src https://download.pytorch.org/whl/cu118, then poetry add --source pytorch-gpu-src torch.

Within each class the order in pyproject.toml counts. The older default = true, secondary = true and priority = "default" / "secondary" forms (deprecated in 1.5.0 and 1.8.0) are gone in Poetry 2:

The Poetry configuration is invalid:
  - tool.poetry.source[0].priority must be one of ['primary', 'supplemental', 'explicit']

A primary source turns PyPI off

With the internal index added as primary, a package that only exists on PyPI cannot be found any more:

poetry source add internal http://127.0.0.1:8083/simple/
poetry add attrs
Could not find a matching version of package attrs

Adding PyPI back with a priority of its own (no URL) fixes it; primary would put it on a par with the internal index, supplemental asks it only when the internal index has nothing:

poetry source add --priority=supplemental PyPI
poetry add attrs
Adding source with name PyPI.
Using version ^26.1.0 for attrs

Updating dependencies
Resolving dependencies...
…
Writing lock file
[[tool.poetry.source]]
name = "PyPI"
priority = "supplemental"

A pypiserver started without --disable-fallback redirects requests for packages it does not have to PyPI, so a "primary only" setup that points at it still pulls from PyPI; the first run of this example did exactly that.

Source constraints

poetry add --source <name> ties a dependency to one source. That source is the only one asked for it, whatever its priority:

export POETRY_HTTP_BASIC_BIRDWATCH_USERNAME=birdwatch
export POETRY_HTTP_BASIC_BIRDWATCH_PASSWORD=…   # from your secret store
poetry add --source birdwatch birdtally
Using version ^0.1.0 for birdtally

Updating dependencies
Resolving dependencies...

Package operations: 1 install, 0 updates, 0 removals

  - Installing birdtally (0.1.0)

Writing lock file

The constraint goes into [tool.poetry.dependencies], next to the PEP 621 entry, and the lock file records where the package came from:

[project]
dependencies = [
    "birdtally (>=0.1.0,<0.2.0)"
]

[tool.poetry.dependencies]
birdtally = {source = "birdwatch"}
[package.source]
type = "legacy"
url = "http://127.0.0.1:8082/simple"
reference = "birdwatch"

Without the credentials the same add fails with exit code 1:

Source (birdwatch): Authorization error accessing http://127.0.0.1:8082/simple/birdtally/

Could not find a matching version of package birdtally

Two rules matter in practice:

  • Constraints are not inherited. If package-a comes from internal and depends on package-b, which is also only on internal, package-b is looked up by the normal priorities. Pin it as well, with a wildcard so the version still comes from package-a: poetry add --source internal "package-b@*" (in a separate group if you do not want it as a main dependency).

  • Dependency confusion. Someone can publish a package with your private name on PyPI. If PyPI is primary (or the implicit source) alongside your index, the resolver may pick the public one. Give every private package a source constraint, keep private-only sources explicit, and keep PyPI out of the primary class when you can; see Supply-Chain Security.

Kinds of source

  • Simple API repositories (PEP 503), such as devpi, pypiserver, Artifactory, Nexus, GitLab, AWS CodeArtifact or Azure Artifacts. When the index also serves PEP 658 metadata (supported since 1.2.0), Poetry reads it instead of downloading each candidate. When it serves no hashes in its links, Poetry downloads every candidate wheel to hash it, because poetry.lock is platform-independent and needs the metadata of every platform’s files: resolution against such an index can be slow.

  • Single page link sources (1.2.0): one HTML page of links, as some projects publish their binary wheels, for example poetry source add --priority=explicit jax https://storage.googleapis.com/jax-releases/jax_releases.html.

Credentials

A source and a publishable repository with the same name share credentials, looked up in this order: POETRY_HTTP_BASIC_<NAME>_* or POETRY_PYPI_TOKEN_<NAME> environment variables, then the system keyring (service name poetry-repository-<name>), then auth.toml in the config directory.

poetry config http-basic.birdwatch birdwatch            # prompts for the password
poetry config pypi-token.pypi "$PYPI_TOKEN"            # a pypi-AgEI… token
poetry config -- http-basic.ci ci-user "-starts-with-dash"
poetry config keyring.enabled false                     # store in auth.toml instead
  • Use the environment variables in CI; a prompt for the password makes a non-interactive job hang.

  • A password typed on the command line ends up in shell history (many shells skip lines that start with a space).

  • -- stops a password that starts with - from being read as an option.

  • An empty password ("") is allowed, as some token-based indexes need; with an empty username Poetry stores the keyring entry under the literal name __poetry_source_empty_username__.

  • If credentials are set and authorisation still fails, look for a ~/.netrc: it is known to conflict.

  • Poetry also asks keyring the pip way, so keyring back ends such as artifacts-keyring work when they are installed into Poetry’s own environment (best as a plugin; see Plugins).

Certificates

poetry config certificates.birdwatch.cert /path/to/ca.pem
poetry config certificates.birdwatch.client-cert /path/to/client.pem

cert sets the CA bundle used to verify that source, client-cert a certificate for mutual TLS; cert false skips verification, which the documentation discourages. Unlike pip since 24.2, Poetry does not use the operating system’s trust store. Behind the TLS-inspecting proxy used for these examples, poetry add six --dry-run failed with a network error until REQUESTS_CA_BUNDLE named the proxy’s CA bundle; set it, or cert for one source (see pip: Authentication, Proxies & Certificates for the pip side).

Publishable repositories

Upload targets are user configuration, never part of pyproject.toml, and use the upload ("legacy") API URL, which differs from the Simple API URL of the same service (TestPyPI: https://test.pypi.org/legacy/ versus https://test.pypi.org/simple/). A missing trailing slash is added automatically.

poetry config repositories.birdwatch-pub http://127.0.0.1:8082/
export POETRY_HTTP_BASIC_BIRDWATCH_PUB_USERNAME=birdwatch
export POETRY_HTTP_BASIC_BIRDWATCH_PUB_PASSWORD=…
poetry publish --build --repository birdwatch-pub

Use a different name for the upload target when it needs different credentials from the source. pypi is predefined; the publishing workflow, tokens and the round trip against a local index are on Publishing with Poetry.

Caches and debugging

Poetry caches HTTP responses (honouring Cache-Control), package metadata per source and downloaded distributions. When a source behaves oddly, rerun with --no-cache and -vvv to see every request, then clear the cache if that helped:

poetry --no-cache -vvv add --source birdwatch birdtally
poetry cache list
poetry cache clear --all birdwatch

Compared with pip

Concern Poetry pip

Declared where

[[tool.poetry.source]] in pyproject.toml, shared with the team

--index-url/--extra-index-url on the command line, in pip.conf, PIP_INDEX_URL or a requirements file

Ordering

priorities; one source per package with a constraint

none: every index is merged and the best version wins, which makes --extra-index-url prone to dependency confusion

Restrict one package to one index

source = "name"

no equivalent; use a single index that proxies PyPI instead

Credentials

POETRY_HTTP_BASIC_*, keyring, auth.toml

URL, .netrc, keyring