pypa/pip · error · InvalidLicenseExpression
Unknown license: {final_token!r}
Error message
Unknown license: {final_token!r} What it means
Thrown by canonicalize_license_expression in packaging.licenses when a token in the SPDX expression does not match any identifier in the LICENSES table (nor a 'licenseref-' prefix). The library validates against the canonical SPDX license list, so any non-SPDX or misspelled identifier is rejected. It is raised as InvalidLicenseExpression after the expression's boolean structure has already been validated.
Source
Thrown at src/pip/_vendor/packaging/licenses/__init__.py:178
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
- Replace the offending token with its canonical SPDX identifier (look it up at https://spdx.org/licenses/).
- For a non-SPDX license, prefix the token with 'licenseref-' (e.g. 'licenseref-mycorp-1.0') and ensure it matches the licenseref_allowed regex.
- If the expression was user-supplied, catch InvalidLicenseExpression and report the unknown token back to the user rather than crashing.
- Validate the expression with a license lint tool (e.g. spdx-tools) before passing it to packaging.
Example fix
// before
canonicalize_license_expression("MIT OR BSD-3")
// after
canonicalize_license_expression("MIT OR BSD-3-Clause") Defensive patterns
Strategy: validation
Validate before calling
from packaging.licenses import _LICENSES as LICENSES # or canonicalize in try/except
import re
license_ref_re = re.compile(r'^licenseref-.+$')
def is_valid_license_token(tok: str) -> bool:
if tok.endswith('+'):
tok = tok[:-1]
if tok.startswith('licenseref-'):
return bool(license_ref_re.match(tok))
return tok in LICENSES Try / catch
from packaging.licenses import InvalidLicenseExpression
try:
canon = canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
# report unknown token to user, keep input unchanged
log.warning('bad license expression %r: %s', expr, e) Prevention
- Use a license lint tool (spdx-tools) before persisting license strings.
- Source identifiers from https://spdx.org/licenses/ IDs only.
- For proprietary licenses use 'licenseref-<name>' form.
- Catch InvalidLicenseExpression on user input and surface a helpful message.
When it happens
Trigger: Calling canonicalize_license_expression('MIT OR BSD-3') (BSD-3 is not SPDX; the correct id is 'BSD-3-Clause'), or canonicalize_license_expression('Apache2') (correct is 'Apache-2.0'), or passing a custom/proprietary license id without the 'licenseref-' prefix. Also triggered by typos like 'GPL-v3' instead of 'GPL-3.0-only'.
Common situations: License classifiers copied from a README that uses informal names; project metadata generated by tooling that does not enforce SPDX; upgrading to a packaging version that added license normalization (newer metadata 2.4+ License-Expression field). Mirrors the 'Unknown license exception' sibling raised at line 158 for the RHS of WITH.
Related errors
- Unknown license exception: {token!r}
- Invalid licenseref: {final_token!r}
- {name} has an invalid wheel, {e}
- multiple .dist-info directories found: {}
- .dist-info directory {info_dir!r} does not start with {canon
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/7db903a5760f592e.json.
Report an issue: GitHub.