pypa/pip · error · InvalidLicenseExpression
Unknown license exception
Error message
Unknown license exception: {token!r} What it means
Raised during the final token pass of canonicalize_license_expression (licenses/__init__.py:167-168). When the previous normalized token is WITH, the current token is looked up in the SPDX EXCEPTIONS table; if it is not a registered SPDX license-exception identifier, InvalidLicenseExpression is raised. The input is lowercased first, so casing of the identifier does not matter, but the spelling must match an SPDX-listed exception.
Solutions
- Use the exact SPDX exception identifier, e.g. 'Classpath-exception-2.0', 'LLVM-exception', 'Linux-syscall-note'.
- Remove the WITH clause if you are unsure which exception applies.
Example fix
# before
canonicalize_license_expression('mit with linux-syscall')
# after
canonicalize_license_expression('apache-2.0 with linux-syscall-note') Defensive patterns
Strategy: try-catch
Validate before calling
from pip._vendor.packaging.licenses._spdx import EXCEPTIONS
def is_known_exception(token: str) -> bool:
return token.lower() in EXCEPTIONS
Try / catch
from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
try:
canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
# surface to user / config validation
...
Prevention
- Keep a reference to the SPDX exceptions list when building license UIs.
- Treat WITH's right operand as an exception id, not a license id.
When it happens
Trigger: Passing 'mit with foo' (foo not an exception), 'apache-2.0 with linux-syscall' (correct id is 'Linux-syscall-note'), or 'mit with apache-2.0' (a license id used where an exception id belongs).
Common situations: Guessing or abbreviating exception identifiers; using a license id after WITH; an older packaging vendoring an older SPDX list that lacks a newer exception id.
Related errors
- Invalid license expression
- Invalid licenseref
- Unknown license
- Compressed tag set would generate
- Invalid sdist filename
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/551fb2a19df735d2.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/licenses/__init__.py:168
normalized_tokens = []
last_license_start = False
for index, token in enumerate(tokens):
if token in {"or", "and", "with", "(", ")"}:
if token == "with" and (
not last_license_start
or index + 1 == len(tokens)
or tokens[index + 1] in {"or", "and", "with", "(", ")"}
):
message = f"Invalid license expression: {raw_license_expression!r}"
raise InvalidLicenseExpression(message)
normalized_tokens.append(token.upper())
last_license_start = False
continue
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"])
last_license_start = False
else:
if token.endswith("+"):
final_token = token[:-1]
suffix = "+"
else:
final_token = token
suffix = ""
if final_token.startswith("licenseref-"):
license_ref_id = final_token[len("licenseref-") :]
if suffix or not license_ref_allowed.match(license_ref_id):
message = f"Invalid licenseref: {token!r}"
raise InvalidLicenseExpression(message)
normalized_tokens.append(license_refs[final_token])
else:View on GitHub (pinned to f399c37189)