pypa/pip · error · InvalidLicenseExpression
Invalid license expression: {raw_license_expression!r}
Error message
Invalid license expression: {raw_license_expression!r} What it means
Raised by `canonicalize_license_expression` when the input is empty/falsy. An empty license-expression is invalid because it carries no license information; the SPDX/PEP 639 grammar requires at least one license token. This is the earliest validation check in the canonicalizer.
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 d7d0d0a394)
Solutions
- Provide a real SPDX expression such as `license = "MIT"` in pyproject.toml
- If you genuinely have no license to declare, omit the field rather than passing empty
- Validate `if not raw_license_expression: raise` upstream of canonicalize
Example fix
// before [project] license = "" // after [project] license = "MIT"
Defensive patterns
Strategy: validation
Validate before calling
def validate_license_expression(expr: str) -> str:
if not expr:
raise ValueError('license expression must not be empty')
return expr Type guard
def is_nonempty_license_expression(s: object) -> bool:
return isinstance(s, str) and bool(s.strip()) Try / catch
from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
try:
norm = canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
if 'Invalid license expression' in str(e) and not expr:
raise ValueError('project license is required') from e
raise Prevention
- Set a concrete SPDX license in pyproject.toml, e.g. license = "MIT"
- Omit the license field rather than passing ''
- Run `python -m build` locally to catch license errors before publishing
When it happens
Trigger: Calling `canonicalize_license_expression('')`, `canonicalize_license_expression(None)` (after coercion), or passing a whitespace-only string that evaluates falsy. Hit by build backends (hatchling, setuptools) reading an empty `license = ""` from `pyproject.toml`.
Common situations: A `pyproject.toml` with `license = ""` or a missing license field that defaulted to empty; a tool that builds the license string from optional config and produces `''`; migration from the deprecated `license-file` table leaving an empty value.
Related errors
- Unknown license exception: {token!r}
- Invalid licenseref: {final_token!r}
- unable to parse identification
- Unexpected type {type(value).__name__} (expected {expected_t
- Unknown license: {final_token!r}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/3bf24b0167703c6a.json.
Report an issue: GitHub.