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

  1. Provide a valid SPDX license identifier (e.g. MIT, Apache-2.0)
  2. Handle empty or None input before calling the function
  3. 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

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


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 an

View on GitHub (pinned to f399c37189)