{"record":{"id":"375188ad72aab7df","repo":"pypa/pip","slug":"name-is-invalid-name-r","errorCode":null,"errorMessage":"name is invalid: {name!r}","messagePattern":"name is invalid: (.+?)","errorType":"validation","errorClass":"InvalidName","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/utils.py","lineNumber":116,"sourceCode":"\n    >>> from packaging.utils import canonicalize_name\n    >>> canonicalize_name(\"Django\")\n    'django'\n    >>> canonicalize_name(\"oslo.concurrency\")\n    'oslo-concurrency'\n    >>> canonicalize_name(\"requests\")\n    'requests'\n\n    .. versionadded:: 16.2\n\n    .. versionchanged:: 20.4\n       The return type was changed to :class:`NormalizedName`.\n\n    .. versionchanged:: 23.2\n       Added the *validate* keyword parameter.\n    \"\"\"\n    if validate and not _validate_regex.fullmatch(name):\n        raise InvalidName(f\"name is invalid: {name!r}\")\n    # Ensure all ``.`` and ``_`` are ``-``\n    # Emulates ``re.sub(r\"[-_.]+\", \"-\", name).lower()`` from PEP 503\n    # Much faster than re, and even faster than str.translate\n    value = name.lower().replace(\"_\", \"-\").replace(\".\", \"-\")\n    # Condense repeats (faster than regex)\n    while \"--\" in value:\n        value = value.replace(\"--\", \"-\")\n    return cast(\"NormalizedName\", value)\n\n\ndef is_normalized_name(name: str) -> bool:\n    \"\"\"\n    Check if a name is a normalized project name (i.e. a valid name that\n    :func:`canonicalize_name` would roundtrip to the same value).\n\n    The roundtrip only characterizes normalized names for *valid* names. A name\n    must start and end with an ASCII letter or digit, which\n    :func:`canonicalize_name` does not enforce: it leaves a leading or trailing","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/utils.py#L98-L134","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)."],"exampleFix":"# before\ncanonicalize_name('-django', validate=True)  # InvalidName: name is invalid: '-django'\n\n# after - sanitize then validate, or validate after normalization\nfrom packaging.utils import canonicalize_name, InvalidName\nname = '-django'.strip('-._')  # 'django'\ntry:\n    canonicalize_name(name, validate=True)\nexcept InvalidName:\n    raise ValueError(f'invalid package name: {name!r}')","handlingStrategy":"validation","validationCode":"import re\n_name_re = re.compile(r'[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]', re.IGNORECASE | re.ASCII)\ndef is_valid_name(name: str) -> bool:\n    return bool(_name_re.fullmatch(name))\n\ndef safe_canonicalize(name: str):\n    if not is_valid_name(name):\n        raise ValueError(f'invalid package name: {name!r}')\n    from packaging.utils import canonicalize_name\n    return canonicalize_name(name, validate=True)","typeGuard":"import re\ndef is_normalized_or_valid(name: str) -> bool:\n    pat = re.compile(r'[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]', re.ASCII)\n    return bool(pat.fullmatch(name))","tryCatchPattern":"from packaging.utils import canonicalize_name, InvalidName\ntry:\n    canonicalize_name(name, validate=True)\nexcept InvalidName:\n    name = name.strip('-._')\n    canonicalize_name(name, validate=True)","preventionTips":["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."],"tags":["packaging","utils","validation","pep503","package-name"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}