pypa/pip · error · InvalidLicenseExpression

Invalid licenseref: {final_token!r}

Error message

Invalid licenseref: {final_token!r}

What it means

Raised by `canonicalize_license_expression` when a `LicenseRef-*` token contains characters outside the allowed set `[A-Za-z0-9.-]`. packaging extends SPDX to allow arbitrary LicenseRef identifiers (not just the standard Public-Domain/Proprietary), but still enforces a character whitelist on the part after `LicenseRef-`.

Source

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

        if normalized_tokens and normalized_tokens[-1] == "WITH":
            if token not in EXCEPTIONS:
                message = f"Unknown license exception: {token!r}"
                raise InvalidLicenseExpression(message)

            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. Restrict LicenseRef identifiers to letters, digits, dots, and hyphens only
  2. Replace disallowed characters (e.g. `_` → `-`, space → `-`)
  3. Validate with the regex `^[A-Za-z0-9.-]*$` on the part after `LicenseRef-` before canonicalizing

Example fix

// before
license = "LicenseRef-My_Custom_License"
// after
license = "LicenseRef-My-Custom-License"
Defensive patterns

Strategy: validation

Validate before calling

import re
LICENSEREF_ALLOWED = re.compile(r'^[A-Za-z0-9.-]*$')

def validate_licenseref(token: str) -> None:
    if token.lower().startswith('licenseref-'):
        suffix = token[len('LicenseRef-'):]
        if not LICENSEREF_ALLOWED.match(suffix):
            raise ValueError(f'invalid chars in LicenseRef: {token!r}')

Type guard

import re
def is_valid_licenseref(token: str) -> bool:
    suffix = token[len('LicenseRef-'):] if token.lower().startswith('licenseref-') else token
    return bool(re.match(r'^[A-Za-z0-9.-]*$', suffix))

Try / catch

from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
import re
try:
    canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
    if 'Invalid licenseref' in str(e):
        expr = re.sub(r'[^A-Za-z0-9.-]', '-', expr)  # sanitize
        canonicalize_license_expression(expr)
    raise

Prevention

When it happens

Trigger: Expressions like `LicenseRef-My Org` (space), `LicenseRef-foo_bar` (underscore not allowed), `LicenseRef-äö` (non-ASCII), or `LicenseRef-a/b` (slash). The regex `^[A-Za-z0-9.-]*$` rejects these.

Common situations: Using underscores or spaces in custom license identifiers; Unicode characters in proprietary license names;slashes or colons from file paths accidentally embedded in the ref.

Related errors


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