pypa/pip · error · InvalidLicenseExpression

Unknown license: {final_token!r}

Error message

Unknown license: {final_token!r}

What it means

Thrown by canonicalize_license_expression in packaging.licenses when a token in the SPDX expression does not match any identifier in the LICENSES table (nor a 'licenseref-' prefix). The library validates against the canonical SPDX license list, so any non-SPDX or misspelled identifier is rejected. It is raised as InvalidLicenseExpression after the expression's boolean structure has already been validated.

Source

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

            normalized_tokens.append(EXCEPTIONS[token]["id"])
        else:
            if token.endswith("+"):
                final_token = token[:-1]
                suffix = "+"
            else:
                final_token = token
                suffix = ""

            if final_token.startswith("licenseref-"):
                if not license_ref_allowed.match(final_token):
                    message = f"Invalid licenseref: {final_token!r}"
                    raise InvalidLicenseExpression(message)
                normalized_tokens.append(license_refs[final_token] + suffix)
            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)

    normalized_expression = " ".join(normalized_tokens)

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

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Replace the offending token with its canonical SPDX identifier (look it up at https://spdx.org/licenses/).
  2. For a non-SPDX license, prefix the token with 'licenseref-' (e.g. 'licenseref-mycorp-1.0') and ensure it matches the licenseref_allowed regex.
  3. If the expression was user-supplied, catch InvalidLicenseExpression and report the unknown token back to the user rather than crashing.
  4. Validate the expression with a license lint tool (e.g. spdx-tools) before passing it to packaging.

Example fix

// before
canonicalize_license_expression("MIT OR BSD-3")
// after
canonicalize_license_expression("MIT OR BSD-3-Clause")
Defensive patterns

Strategy: validation

Validate before calling

from packaging.licenses import _LICENSES as LICENSES  # or canonicalize in try/except
import re
license_ref_re = re.compile(r'^licenseref-.+$')

def is_valid_license_token(tok: str) -> bool:
    if tok.endswith('+'):
        tok = tok[:-1]
    if tok.startswith('licenseref-'):
        return bool(license_ref_re.match(tok))
    return tok in LICENSES

Try / catch

from packaging.licenses import InvalidLicenseExpression
try:
    canon = canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
    # report unknown token to user, keep input unchanged
    log.warning('bad license expression %r: %s', expr, e)

Prevention

When it happens

Trigger: Calling canonicalize_license_expression('MIT OR BSD-3') (BSD-3 is not SPDX; the correct id is 'BSD-3-Clause'), or canonicalize_license_expression('Apache2') (correct is 'Apache-2.0'), or passing a custom/proprietary license id without the 'licenseref-' prefix. Also triggered by typos like 'GPL-v3' instead of 'GPL-3.0-only'.

Common situations: License classifiers copied from a README that uses informal names; project metadata generated by tooling that does not enforce SPDX; upgrading to a packaging version that added license normalization (newer metadata 2.4+ License-Expression field). Mirrors the 'Unknown license exception' sibling raised at line 158 for the RHS of WITH.

Related errors


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