python-poetry/poetry · error · InvalidSourceError

The PyPI repository cannot be configured with a custom url.

Error message

The PyPI repository cannot be configured with a custom url.

What it means

Raised by Factory.create_package_source when a source entry is named 'pypi' (case-insensitive) AND also provides a 'url' key. Poetry treats PyPI as a built-in repository with a fixed URL and refuses any override. The guard is at src/poetry/factory.py:221-225 and throws InvalidSourceError.

Source

Thrown at src/poetry/factory.py:223

    @classmethod
    def create_package_source(
        cls, source: dict[str, str], config: Config, disable_cache: bool = False
    ) -> HTTPRepository:
        from poetry.repositories.exceptions import InvalidSourceError
        from poetry.repositories.legacy_repository import LegacyRepository
        from poetry.repositories.pypi_repository import PyPiRepository
        from poetry.repositories.single_page_repository import SinglePageRepository

        try:
            name = source["name"]
        except KeyError:
            raise InvalidSourceError("Missing [name] in source.")

        pool_size = config.installer_max_workers

        if name.lower() == "pypi":
            if "url" in source:
                raise InvalidSourceError(
                    "The PyPI repository cannot be configured with a custom url."
                )
            return PyPiRepository(
                config=config,
                disable_cache=disable_cache,
                pool_size=pool_size,
            )

        try:
            url = source["url"]
        except KeyError:
            raise InvalidSourceError(f"Missing [url] in source {name!r}.")

        repository_class = LegacyRepository

        if re.match(r".*\.(htm|html)$", url):
            repository_class = SinglePageRepository

View on GitHub (pinned to 92b74dcfe3)

Solutions

  1. Rename the source to something other than 'pypi' (e.g. 'my-mirror') and keep its url — custom sources are allowed a url.
  2. If you truly want to override PyPI's URL, configure it via Poetry's repositories.pypi.url setting or environment POETRY_PYPI_URL, not a source table named 'pypi'.
  3. Remove the url line from the 'pypi' source entry if you only wanted to disable/enable default PyPI.

Example fix

# before
[[tool.poetry.source]]
name = "pypi"
url = "https://pypi.org/simple"

# after (private mirror)
[[tool.poetry.source]]
name = "my-mirror"
url = "https://mirrors.example.com/pypi/simple"
Defensive patterns

Strategy: validation

Validate before calling

def validate_source(source: dict) -> None:
    name = source.get('name', '')
    if name.lower() == 'pypi' and 'url' in source:
        raise ValueError(
            "Source named 'pypi' must not set 'url'; rename the source or drop the url."
        )

Try / catch

from poetry.repositories.exceptions import InvalidSourceError

try:
    Factory.create_package_source(source, config)
except InvalidSourceError as e:
    if 'cannot be configured with a custom url' in str(e):
        source = {k: v for k, v in source.items() if k != 'url'} or {
            **source, 'name': source['name'] + '-mirror'
        }
        Factory.create_package_source(source, config)

Prevention

When it happens

Trigger: Calling Factory.create_package_source({'name': 'pypi', 'url': '...'}) or having a [[tool.poetry.source]] table in pyproject.toml with name = "pypi" plus a url = "..." line. Any case variant like 'PyPI' triggers it because the check uses name.lower() == 'pypi'.

Common situations: A developer tries to point Poetry at a private mirror by reusing the name 'pypi', or migrates from pip's index-url config and copies the name verbatim. Also occurs when redirecting PyPI through a corporate proxy.

Related errors


AI-assisted analysis of python-poetry/poetry@92b74dcfe3 (2026-08-04). Data as JSON: /data/errors/c3e0a8b339f979ff.json. Report an issue: GitHub.