pypa/pip · error · InvalidName

name is invalid: {name!r}

Error message

name is invalid: {name!r}

What it means

Raised by `canonicalize_name(name, *, validate=True)` when `name` does not match the distribution-name regex `^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$` (single alphanumeric char, or alphanumeric start+end with `[a-z0-9._-]` in between). This is the Core-metadata 'Name' spec; a failing value is not a legal PyPI project name. Note `validate` defaults to `False`, so you only hit this by opting in.

Source

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

    If **validate** is true, then the function will check if **name** is a valid
    distribution name before normalizing.

    :param str name: The name to normalize.
    :param bool validate: Check whether the name is a valid distribution name.
    :raises InvalidName: If **validate** is true and the name is not an
        acceptable distribution name.

    >>> from packaging.utils import canonicalize_name
    >>> canonicalize_name("Django")
    'django'
    >>> canonicalize_name("oslo.concurrency")
    'oslo-concurrency'
    >>> canonicalize_name("requests")
    'requests'
    """
    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 already normalized (i.e. :func:`canonicalize_name` would
    roundtrip to the same value).

    :param str name: The name to check.

    >>> from packaging.utils import is_normalized_name

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Sanitize/trim the name before calling: strip, remove leading/trailing `._-`, ensure at least one alphanumeric character.
  2. Drop the `validate=True` flag if you only need normalization and tolerate legacy names.
  3. Fix the upstream source of the name (config file, CLI arg, metadata).
  4. Pre-check with the same regex before calling so you can branch on invalid input.

Example fix

// before
from pip._vendor.packaging.utils import canonicalize_name
canonicalize_name(name.strip(), validate=True)  # raises if name is '.foo'

# after
import re
_NAME_RE = re.compile(r"[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]", re.IGNORECASE | re.ASCII)
if not _NAME_RE.fullmatch(name):
    raise ValueError(f"refusing to canonicalize invalid name: {name!r}
clean = canonicalize_name(name, validate=True)
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))

Type guard

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_distribution_name(name: object) -> bool:
    return isinstance(name, str) and bool(_NAME_RE.fullmatch(name))

Try / catch

from pip._vendor.packaging.utils import canonicalize_name, InvalidName

try:
    norm = canonicalize_name(raw, validate=True)
except InvalidName:
    norm = canonicalize_name(raw.strip().strip('._-'), validate=False)

Prevention

When it happens

Trigger: Calling `canonicalize_name('', validate=True)`, `canonicalize_name('.dotted', validate=True)`, names with leading/trailing dots/underscores/hyphens, empty string, or names containing whitespace or non-ASCII punctuation. The check is `_validate_regex.fullmatch(name)` is falsy.

Common situations: User-supplied or config-derived package names that were never sanitized; names with a trailing `-` from a sloppy split; empty name after stripping; non-ASCII characters; mixing `validate=True` into a previously permissive code path during an upgrade.

Related errors


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