pypa/pip · error · InvalidLicenseExpression
Invalid licenseref: {final_token!r}
Error message
Invalid licenseref: {final_token!r} What it means
Raised by `canonicalize_license_expression` when a `LicenseRef-*` token contains characters outside the allowed set `[A-Za-z0-9.-]`. packaging extends SPDX to allow arbitrary LicenseRef identifiers (not just the standard Public-Domain/Proprietary), but still enforces a character whitelist on the part after `LicenseRef-`.
Source
Thrown at src/pip/_vendor/packaging/licenses/__init__.py:173
if normalized_tokens and normalized_tokens[-1] == "WITH":
if token not in EXCEPTIONS:
message = f"Unknown license exception: {token!r}"
raise InvalidLicenseExpression(message)
normalized_tokens.append(EXCEPTIONS[token]["id"])
else:
if token.endswith("+"):
final_token = token[:-1]
suffix = "+"
else:
final_token = token
suffix = ""
if final_token.startswith("licenseref-"):
if not license_ref_allowed.match(final_token):
message = f"Invalid licenseref: {final_token!r}"
raise InvalidLicenseExpression(message)
normalized_tokens.append(license_refs[final_token] + suffix)
else:
if final_token not in LICENSES:
message = f"Unknown license: {final_token!r}"
raise InvalidLicenseExpression(message)
normalized_tokens.append(LICENSES[final_token]["id"] + suffix)
normalized_expression = " ".join(normalized_tokens)
return cast(
"NormalizedLicenseExpression",
normalized_expression.replace("( ", "(").replace(" )", ")"),
)
View on GitHub (pinned to d7d0d0a394)
Solutions
- Restrict LicenseRef identifiers to letters, digits, dots, and hyphens only
- Replace disallowed characters (e.g. `_` → `-`, space → `-`)
- Validate with the regex `^[A-Za-z0-9.-]*$` on the part after `LicenseRef-` before canonicalizing
Example fix
// before license = "LicenseRef-My_Custom_License" // after license = "LicenseRef-My-Custom-License"
Defensive patterns
Strategy: validation
Validate before calling
import re
LICENSEREF_ALLOWED = re.compile(r'^[A-Za-z0-9.-]*$')
def validate_licenseref(token: str) -> None:
if token.lower().startswith('licenseref-'):
suffix = token[len('LicenseRef-'):]
if not LICENSEREF_ALLOWED.match(suffix):
raise ValueError(f'invalid chars in LicenseRef: {token!r}') Type guard
import re
def is_valid_licenseref(token: str) -> bool:
suffix = token[len('LicenseRef-'):] if token.lower().startswith('licenseref-') else token
return bool(re.match(r'^[A-Za-z0-9.-]*$', suffix)) Try / catch
from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
import re
try:
canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
if 'Invalid licenseref' in str(e):
expr = re.sub(r'[^A-Za-z0-9.-]', '-', expr) # sanitize
canonicalize_license_expression(expr)
raise Prevention
- Restrict LicenseRef suffixes to [A-Za-z0-9.-]
- Replace underscores/spaces with hyphens when generating LicenseRef identifiers
- Validate identifiers against the regex before adding them to pyproject.toml
When it happens
Trigger: Expressions like `LicenseRef-My Org` (space), `LicenseRef-foo_bar` (underscore not allowed), `LicenseRef-äö` (non-ASCII), or `LicenseRef-a/b` (slash). The regex `^[A-Za-z0-9.-]*$` rejects these.
Common situations: Using underscores or spaces in custom license identifiers; Unicode characters in proprietary license names;slashes or colons from file paths accidentally embedded in the ref.
Related errors
- Unknown license exception: {token!r}
- Invalid license expression: {raw_license_expression!r}
- Unknown license: {final_token!r}
- Invalid requirement: {req_as_string!r}: {exc}
- Invalid requirement: {req_string!r}: {exc}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/4bdb8ef21e08b0e8.json.
Report an issue: GitHub.