pypa/pip · error · InvalidLicenseExpression

Invalid licenseref: {token!r}

Error message

Invalid licenseref: {token!r}

What it means

Raised by canonicalize_license_expression (licenses/__init__.py:182-184) for a LicenseRef-* token whose suffix (after 'LicenseRef-') fails the regex ^[A-Za-z0-9.-]+$ (license_ref_allowed) or which carries a '+' OR-later suffix. Custom LicenseRef identifiers are allowed beyond the SPDX built-ins, but they must be alphanumeric with dots/hyphens only and cannot be versioned with '+'.

Source

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

            if token not in EXCEPTIONS:
                message = f"Unknown license exception: {token!r}"
                raise InvalidLicenseExpression(message)

            normalized_tokens.append(EXCEPTIONS[token]["id"])
            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)

Solutions

  1. Use only [A-Za-z0-9.-] after 'LicenseRef-', replacing underscores with hyphens.
  2. Remove any trailing '+' from a LicenseRef token.

Example fix

# before
canonicalize_license_expression('LicenseRef-My_License+')
# after
canonicalize_license_expression('LicenseRef-My-License')
Defensive patterns

Strategy: validation

Validate before calling

import re

_LICENSE_REF = re.compile(r"^[A-Za-z0-9.-]+$")

def valid_license_ref(token: str) -> bool:
    t = token.lower()
    if not t.startswith("licenseref-"):
        return True  # not a ref, not our concern here
    if t.endswith("+"):
        return False
    suffix = t[len("licenseref-"):]
    return bool(_LICENSE_REF.match(suffix))

Try / catch

from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression

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

Prevention

When it happens

Trigger: Passing 'LicenseRef-' (empty suffix), 'LicenseRef-My_License' (underscore), 'LicenseRef-foo/bar' (slash), 'LicenseRef-foo bar' (space), or 'LicenseRef-Custom+' (the '+' suffix is rejected for refs).

Common situations: Reusing Python-style identifiers (underscores) in a custom license ref; appending '+' thinking OR-later applies to custom licenses; paths/slashes leaking into the ref.

Related errors


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