{"id":"596c20b27299fdce","repo":"pypa/pip","slug":"duplicate-labels-in-project-urls","errorCode":null,"errorMessage":"duplicate labels in project urls","messagePattern":"duplicate labels in project urls","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/metadata.py","lineNumber":216,"sourceCode":"        #\n        # The other potential issue is that it's possible to have the\n        # same label multiple times in the metadata, with no solid \"right\"\n        # answer with what to do in that case. As such, we'll do the only\n        # thing we can, which is treat the field as unparsable and add it\n        # to our list of unparsed fields.\n        #\n        # TODO: The spec doesn't say anything about if the keys should be\n        #       considered case sensitive or not... logically they should\n        #       be case-preserving and case-insensitive, but doing that\n        #       would open up more cases where we might have duplicate\n        #       entries.\n        label, _, url = (s.strip() for s in pair.partition(\",\"))\n\n        if label in urls:\n            # The label already exists in our set of urls, so this field\n            # is unparsable, and we can just add the whole thing to our\n            # unparsable data and stop processing it.\n            raise KeyError(\"duplicate labels in project urls\")\n        urls[label] = url\n\n    return urls\n\n\ndef _get_payload(msg: email.message.Message, source: bytes | str) -> str:\n    \"\"\"Get the body of the message.\"\"\"\n    # If our source is a str, then our caller has managed encodings for us,\n    # and we don't need to deal with it.\n    if isinstance(source, str):\n        payload = msg.get_payload()\n        assert isinstance(payload, str)\n        return payload\n    # If our source is a bytes, then we're managing the encoding and we need\n    # to deal with it.\n    else:\n        bpayload = msg.get_payload(decode=True)\n        assert isinstance(bpayload, bytes)","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/metadata.py#L198-L234","documentation":"Raised as KeyError('duplicate labels in project urls') by _parse_project_urls in packaging.metadata when the comma-partitioned Project-URL field contains the same label more than once. Per the spec there is no defined merge behavior, so the field is treated as unparsable.","triggerScenarios":"Metadata with 'Project-URL: Homepage, https://x' and a second 'Project-URL: Homepage, https://y'. Calling parse_email over METADATA containing duplicate labels. Calling _parse_project_urls directly on a raw field string with repeated labels.","commonSituations":"Generated metadata merging URLs from multiple sources (setup.cfg + pyproject); misconfigured pyproject where 'Homepage' is listed twice; tooling that appends rather than replaces labels.","solutions":["Dedupe Project-URL entries by label before writing metadata, keeping the intended one.","Validate metadata locally with 'packaging' or twine check before publishing.","When generating, use a dict keyed by label so duplicates cannot occur.","Catch the error during parsing and report which label is duplicated."],"exampleFix":"# before\n[Project-URL]\nHomepage = \\\"https://a\\\"\nHomepage = \\\"https://b\\\"\n# after\n[Project-URL]\nHomepage = \\\"https://a\\\"\nSource = \\\"https://b\\\"","handlingStrategy":"validation","validationCode":"def dedupe_project_urls(pairs):\n    seen = set()\n    out = []\n    for label, url in pairs:\n        if label in seen:\n            raise ValueError(f'duplicate Project-URL label {label!r}')\n        seen.add(label)\n        out.append((label, url))\n    return out","typeGuard":null,"tryCatchPattern":"try:\n    md = Metadata.from_email(raw)\nexcept KeyError as e:\n    if 'duplicate labels' in str(e):\n        # the whole field is unparsable; surface to user\n        report_duplicate_url_label()","preventionTips":["Build Project-URL from a dict keyed by label so duplicates cannot occur.","Run twine check before publishing.","Merge URL sources with last-write-wins or fail-loud dedupe.","Validate metadata locally with packaging before release."],"tags":["metadata","pep621","project-urls","validation"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}