pypa/pip · error · InvalidLicenseExpression

Unknown license

Error message

Unknown license: {final_token!r}

What it means

Raised during the final pass of canonicalize_license_expression (licenses/__init__.py:187-189). Any token that is not a boolean op, paren, WITH, or a LicenseRef-* must be a recognized SPDX license identifier present in the LICENSES table; otherwise InvalidLicenseExpression is raised. The token is lowercased before lookup, so only the exact SPDX spelling (case-insensitive) is accepted.

Solutions

  1. Use the exact SPDX license identifier (e.g. 'MIT', 'Apache-2.0', 'BSD-3-Clause', 'GPL-3.0-or-later').
  2. For non-SPDX licenses, use a 'LicenseRef-*' token instead.

Example fix

# before
canonicalize_license_expression('GPLv3')
# after
canonicalize_license_expression('GPL-3.0-or-later')
Defensive patterns

Strategy: try-catch

Validate before calling

from pip._vendor.packaging.licenses._spdx import LICENSES

def is_known_license(token: str) -> bool:
    t = token.lower().removesuffix("+")
    return t.startswith("licenseref-") or t in LICENSES

Try / catch

from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression

try:
    canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
    ...

Prevention

When it happens

Trigger: Passing 'Use-it-after-midnight', 'proprietary', 'BSD' (too vague — use 'BSD-3-Clause'), 'GPLv3' (use 'GPL-3.0-only'/'GPL-3.0-or-later'), or 'Apache' (use 'Apache-2.0').

Common situations: Using colloquial or display license names; forgetting the -only/-or-later suffix on GPL family licenses; a newer SPDX id absent from an older vendored packaging; custom licenses not written as LicenseRef-*.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/7db903a5760f592e. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/licenses/__init__.py:189

            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:
                if final_token not in LICENSES:
                    message = f"Unknown license: {final_token!r}"
                    raise InvalidLicenseExpression(message)
                normalized_tokens.append(LICENSES[final_token]["id"] + suffix)
            last_license_start = True

    normalized_expression = " ".join(normalized_tokens)

    return cast(
        "NormalizedLicenseExpression",
        normalized_expression.replace("( ", "(").replace(" )", ")"),
    )

View on GitHub (pinned to f399c37189)