{"id":"4087aa46035d2581","repo":"python-poetry/poetry","slug":"the-name-pypi-is-reserved-for-repositories","errorCode":null,"errorMessage":"The name [pypi] is reserved for repositories","messagePattern":"The name \\[pypi\\] is reserved for repositories","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/poetry/repositories/legacy_repository.py","lineNumber":39,"sourceCode":"    from packaging.utils import NormalizedName\n    from poetry.core.constraints.version import Version\n    from poetry.core.packages.utils.link import Link\n\n    from poetry.config.config import Config\n\n\nclass LegacyRepository(HTTPRepository):\n    def __init__(\n        self,\n        name: str,\n        url: str,\n        *,\n        config: Config | None = None,\n        disable_cache: bool = False,\n        pool_size: int = requests.adapters.DEFAULT_POOLSIZE,\n    ) -> None:\n        if name == \"pypi\":\n            raise ValueError(\"The name [pypi] is reserved for repositories\")\n\n        super().__init__(\n            name,\n            url.rstrip(\"/\"),\n            config=config,\n            disable_cache=disable_cache,\n            pool_size=pool_size,\n        )\n\n    def package(self, name: str, version: Version) -> Package:\n        \"\"\"\n        Retrieve the release information.\n\n        This is a heavy task which takes time.\n        We have to download a package to get the dependencies.\n        We also need to download every file matching this release\n        to get the various hashes.\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/python-poetry/poetry/blob/92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54/src/poetry/repositories/legacy_repository.py#L21-L57","documentation":"LegacyRepository.__init__ rejects the repository name 'pypi' because it collides with the built-in PyPIRepository that Poetry registers under that exact name (legacy_repository.py:38-39). Using it would shadow the default PyPI source and break resolution, so construction fails fast.","triggerScenarios":"Instantiating LegacyRepository(name=\"pypi\", url=...) directly, or running `poetry source add pypi <url>` which constructs a LegacyRepository with that name.","commonSituations":"A user adding a custom/legacy source and carelessly naming it 'pypi'; copy-pasting a source config that reused the reserved name.","solutions":["Choose a distinct source name, e.g. `poetry source add my-pypi <url>`.","If you genuinely want to override the default PyPI URL, configure the PyPI repository's URL instead of adding a new 'pypi' source.","Rename the existing offending entry in pyproject.toml [[tool.poetry.source]] to a non-reserved name."],"exampleFix":"// before\n[[tool.poetry.source]]\nname = \"pypi\"\nurl = \"https://mymirror.example.com/simple/\"\n// after\n[[tool.poetry.source]]\nname = \"mymirror\"\nurl = \"https://mymirror.example.com/simple/\"","handlingStrategy":"validation","validationCode":"RESERVED = {\"pypi\"}\nif source_name.lower() in RESERVED:\n    raise ValueError(f\"'{source_name}' is reserved; choose another source name\")\nrepo = LegacyRepository(name=source_name, url=url)","typeGuard":null,"tryCatchPattern":"try:\n    repo = LegacyRepository(name=name, url=url)\nexcept ValueError as e:\n    if \"reserved\" in str(e):\n        name = f\"{name}-custom\"\n        repo = LegacyRepository(name=name, url=url)\n    raise","preventionTips":["Never name a custom source 'pypi'.","Use descriptive, unique source names.","Validate source names in config-loading code before constructing repositories."],"tags":["repository","config","validation","reserved-name"],"analyzedSha":"92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54","analyzedAt":"2026-08-04T20:33:34.072Z","schemaVersion":2}