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
- Strip leading/trailing punctuation and replace internal runs of separators before validating, or normalize first.
- If the name may be non-conforming, call canonicalize_name without validate (or pre-check with the regex) and decide policy.
- Catch InvalidName and return a clear 'invalid package name' error to the user with the allowed character rules.
- 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
- Pre-check names against the PEP 503 character class before validate=True.
- Strip leading/trailing separators before validating.
- Only enable validate=True when you control or have screened the input.
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
- Compressed tag set would generate
- Invalid license expression
- Invalid licenseref
- Invalid sdist filename
- Invalid specifier
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 trailingView on GitHub (pinned to f399c37189)