pypa/pip · error · InvalidLicenseExpression
Invalid license expression
Error message
Invalid license expression: {raw_license_expression!r} What it means
canonicalize_license_expression() validates and normalizes SPDX license expressions per PEP 639. The first check is for a falsy input (empty string or None). An empty license expression is invalid because PEP 639 requires a non-empty SPDX expression. This raises InvalidLicenseExpression (a ValueError subclass) at line 104.
Solutions
- Provide a valid SPDX license identifier (e.g. MIT, Apache-2.0)
- Handle empty or None input before calling the function
- Set a default license in your build configuration
Example fix
# before
from packaging.licenses import canonicalize_license_expression
normalized = canonicalize_license_expression(project_license) # empty string raises
# after
if not project_license:
raise ValueError("License expression is required")
normalized = canonicalize_license_expression(project_license) Defensive patterns
Strategy: validation
Validate before calling
def safe_canonicalize_license(expr: str) -> str:
if not expr or not expr.strip():
raise ValueError("License expression must not be empty")
from packaging.licenses import canonicalize_license_expression
return canonicalize_license_expression(expr) Type guard
def is_non_empty_license(expr) -> bool:
return isinstance(expr, str) and bool(expr.strip()) Try / catch
from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
try:
normalized = canonicalize_license_expression(license_expr)
except InvalidLicenseExpression:
# prompt user for a valid SPDX license identifier
pass Prevention
- Require a license field in pyproject.toml
- Validate non-empty before calling canonicalize
- Use SPDX identifiers consistently (MIT, Apache-2.0, etc.)
When it happens
Trigger: Calling canonicalize_license_expression(''), canonicalize_license_expression(None), or passing a whitespace-only string.
Common situations: Missing or empty license/license-expression field in pyproject.toml. Programmatic license validation where the value comes from user input or config that may be unset.
Related errors
- Invalid licenseref
- Unknown license exception
- Unknown license
- Algorithm used in hash field has different value in hashes…
- Algorithm used in hash field is not present in hashes field
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/3bf24b0167703c6a.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/licenses/__init__.py:104
.. doctest::
>>> from packaging.licenses import canonicalize_license_expression
>>> canonicalize_license_expression("mit")
'MIT'
>>> canonicalize_license_expression("mit and (apache-2.0 or bsd-2-clause)")
'MIT AND (Apache-2.0 OR BSD-2-Clause)'
>>> canonicalize_license_expression("(mit")
Traceback (most recent call last):
...
InvalidLicenseExpression: Invalid license expression: '(mit'
>>> canonicalize_license_expression("Use-it-after-midnight")
Traceback (most recent call last):
...
InvalidLicenseExpression: Unknown license: 'Use-it-after-midnight'
"""
if not raw_license_expression:
message = f"Invalid license expression: {raw_license_expression!r}"
raise InvalidLicenseExpression(message)
# Pad any parentheses so tokenization can be achieved by merely splitting on
# whitespace.
license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ")
licenseref_prefix = "LicenseRef-"
license_refs = {
ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :]
for ref in license_expression.split()
if ref.lower().startswith(licenseref_prefix.lower())
}
# Normalize to lower case so we can look up licenses/exceptions
# and so boolean operators are Python-compatible.
license_expression = license_expression.lower()
tokens = license_expression.split()
# Rather than implementing a parenthesis/boolean logic parser, create anView on GitHub (pinned to f399c37189)