pypa/pip · error · KeyError
duplicate labels in project urls
Error message
duplicate labels in project urls
What it means
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.
Source
Thrown at src/pip/_vendor/packaging/metadata.py:216
#
# The other potential issue is that it's possible to have the
# same label multiple times in the metadata, with no solid "right"
# answer with what to do in that case. As such, we'll do the only
# thing we can, which is treat the field as unparsable and add it
# to our list of unparsed fields.
#
# TODO: The spec doesn't say anything about if the keys should be
# considered case sensitive or not... logically they should
# be case-preserving and case-insensitive, but doing that
# would open up more cases where we might have duplicate
# entries.
label, _, url = (s.strip() for s in pair.partition(","))
if label in urls:
# The label already exists in our set of urls, so this field
# is unparsable, and we can just add the whole thing to our
# unparsable data and stop processing it.
raise KeyError("duplicate labels in project urls")
urls[label] = url
return urls
def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
"""Get the body of the message."""
# If our source is a str, then our caller has managed encodings for us,
# and we don't need to deal with it.
if isinstance(source, str):
payload = msg.get_payload()
assert isinstance(payload, str)
return payload
# If our source is a bytes, then we're managing the encoding and we need
# to deal with it.
else:
bpayload = msg.get_payload(decode=True)
assert isinstance(bpayload, bytes)View on GitHub (pinned to d7d0d0a394)
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.
Example fix
# before [Project-URL] Homepage = \"https://a\" Homepage = \"https://b\" # after [Project-URL] Homepage = \"https://a\" Source = \"https://b\"
Defensive patterns
Strategy: validation
Validate before calling
def dedupe_project_urls(pairs):
seen = set()
out = []
for label, url in pairs:
if label in seen:
raise ValueError(f'duplicate Project-URL label {label!r}')
seen.add(label)
out.append((label, url))
return out Try / catch
try:
md = Metadata.from_email(raw)
except KeyError as e:
if 'duplicate labels' in str(e):
# the whole field is unparsable; surface to user
report_duplicate_url_label() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Generated metadata merging URLs from multiple sources (setup.cfg + pyproject); misconfigured pyproject where 'Homepage' is listed twice; tooling that appends rather than replaces labels.
Related errors
- {name} has an invalid wheel, {e}
- multiple .dist-info directories found: {}
- .dist-info directory {info_dir!r} does not start with {canon
- error decoding {path!r}: {e!r}
- WHEEL is missing Wheel-Version
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/596c20b27299fdce.json.
Report an issue: GitHub.