pypa/pip · error · InvalidName

name is invalid

Error message

name is invalid: {name!r}

What it means

Raised as InvalidName (ValueError subclass) by canonicalize_name (utils.py:115-116) only when validate=True and the name fails _validate_regex, which requires (case-insensitive, ASCII) a single ASCII alphanumeric, or an alnum followed by zero or more [a-z0-9._-] chars then a trailing alnum. Empty strings, names with leading/trailing non-alnum (e.g. '-foo', 'foo.'), or non-ASCII characters are rejected. Without validate=True the name is normalized without this check.

Solutions

  1. Strip leading/trailing punctuation and replace internal runs of separators before validating, or normalize first.
  2. If the name may be non-conforming, call canonicalize_name without validate (or pre-check with the regex) and decide policy.
  3. Catch InvalidName and return a clear 'invalid package name' error to the user with the allowed character rules.
  4. For names from untrusted input, apply your own PEP 503 conformance check ([A-Za-z0-9._-]) before canonicalize_name(validate=True).

Example fix

# before
canonicalize_name('-django', validate=True)  # InvalidName: name is invalid: '-django'

# after - sanitize then validate, or validate after normalization
from packaging.utils import canonicalize_name, InvalidName
name = '-django'.strip('-._')  # 'django'
try:
    canonicalize_name(name, validate=True)
except InvalidName:
    raise ValueError(f'invalid package name: {name!r}')
Defensive patterns

Strategy: validation

Validate before calling

import re
_name_re = re.compile(r'[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]', re.IGNORECASE | re.ASCII)
def is_valid_name(name: str) -> bool:
    return bool(_name_re.fullmatch(name))

def safe_canonicalize(name: str):
    if not is_valid_name(name):
        raise ValueError(f'invalid package name: {name!r}')
    from packaging.utils import canonicalize_name
    return canonicalize_name(name, validate=True)

Type guard

import re
def is_normalized_or_valid(name: str) -> bool:
    pat = re.compile(r'[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]', re.ASCII)
    return bool(pat.fullmatch(name))

Try / catch

from packaging.utils import canonicalize_name, InvalidName
try:
    canonicalize_name(name, validate=True)
except InvalidName:
    name = name.strip('-._')
    canonicalize_name(name, validate=True)

Prevention

When it happens

Trigger: canonicalize_name('', validate=True); canonicalize_name('-django', validate=True) (leading hyphen); canonicalize_name('requests.', validate=True) (trailing dot); canonicalize_name('naïve', validate=True) (non-ASCII); canonicalize_name('a b', validate=True) (space).

Common situations: Validating user-entered project/distribution names from a form, CLI, or package index upload; sanitizing names parsed from a wheel/sdist filename; a migration that turns on validate=True on pre-existing data containing old non-conforming names.

Related errors


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

Appendix: source

Thrown at src/pip/_vendor/packaging/utils.py:116

    >>> from packaging.utils import canonicalize_name
    >>> canonicalize_name("Django")
    'django'
    >>> canonicalize_name("oslo.concurrency")
    'oslo-concurrency'
    >>> canonicalize_name("requests")
    'requests'

    .. versionadded:: 16.2

    .. versionchanged:: 20.4
       The return type was changed to :class:`NormalizedName`.

    .. versionchanged:: 23.2
       Added the *validate* keyword parameter.
    """
    if validate and not _validate_regex.fullmatch(name):
        raise InvalidName(f"name is invalid: {name!r}")
    # Ensure all ``.`` and ``_`` are ``-``
    # Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
    # Much faster than re, and even faster than str.translate
    value = name.lower().replace("_", "-").replace(".", "-")
    # Condense repeats (faster than regex)
    while "--" in value:
        value = value.replace("--", "-")
    return cast("NormalizedName", value)


def is_normalized_name(name: str) -> bool:
    """
    Check if a name is a normalized project name (i.e. a valid name that
    :func:`canonicalize_name` would roundtrip to the same value).

    The roundtrip only characterizes normalized names for *valid* names. A name
    must start and end with an ASCII letter or digit, which
    :func:`canonicalize_name` does not enforce: it leaves a leading or trailing

View on GitHub (pinned to f399c37189)