pypa/pip · error · InvalidVersion

local must be a valid version string, got

Error message

local must be a valid version string, got {value!r}

What it means

Raised by _validate_local() (version.py:321-327) when the local version segment passed to Version.from_parts() or Version.__replace__() does not match _LOCAL_PATTERN ([a-z0-9]+(?:[._-][a-z0-9]+)*). Passing None is allowed (means no local segment). The pattern requires alphanumeric segments separated by dots, underscores, or hyphens; leading/trailing separators, spaces, or uppercase-only patterns without alphanumerics fail.

Solutions

  1. Pass local as a string matching [a-z0-9]+(?:[._-][a-z0-9]+)* (e.g. 'abc', 'abc.1', 'ubuntu-1').
  2. Pass None (or omit) to clear the local segment, not an empty string.
  3. Strip leading/trailing separators and remove invalid characters before calling.

Example fix

// before
Version.from_parts(release=(1, 0), local='.abc')
// after
Version.from_parts(release=(1, 0), local='abc')
Defensive patterns

Strategy: type-guard

Validate before calling

import re
from packaging.version import Version, InvalidVersion

_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)

def safe_from_parts(*, release, local=None, **kwargs):
    if local is not None and (not isinstance(local, str) or not _LOCAL_PATTERN.fullmatch(local)):
        raise ValueError(f"local must match [a-z0-9]+(?:[._-][a-z0-9]+)*, got {local!r}")
    return Version.from_parts(release=release, local=local, **kwargs)

Type guard

import re

_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)

def is_valid_local(value) -> bool:
    return value is None or (
        isinstance(value, str) and bool(_LOCAL_PATTERN.fullmatch(value))
    )

Try / catch

from packaging.version import InvalidVersion

try:
    v = Version.from_parts(release=(1,), local=local)
except InvalidVersion as e:
    if "local must be" in str(e):
        logger.error("Invalid local version segment: %s", local)
    raise

Prevention

When it happens

Trigger: Calling Version.from_parts(release=(1,), local='!abc') (invalid character), Version.from_parts(release=(1,), local='.abc') (leading separator), or Version.from_parts(release=(1,), local='') (empty string is not None and fails the pattern).

Common situations: Passing a local version string with invalid characters, an empty string instead of None to clear the segment, or a string with a leading/trailing separator.

Related errors


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

Appendix: source

Thrown at src/pip/_vendor/packaging/version.py:327

    raise InvalidVersion(msg)


def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
    if value is None:
        return value
    if isinstance(value, int) and value >= 0:
        return ("dev", value)
    msg = f"dev must be non-negative integer, got {value}"
    raise InvalidVersion(msg)


def _validate_local(value: object, /) -> LocalType | None:
    if value is None:
        return value
    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
        return _parse_local_version(value)
    msg = f"local must be a valid version string, got {value!r}"
    raise InvalidVersion(msg)


# Backward compatibility for internals before 26.0. Do not use.
class _Version(NamedTuple):
    epoch: int
    release: tuple[int, ...]
    dev: tuple[Literal["dev"], int] | None
    pre: tuple[Literal["a", "b", "rc"], int] | None
    post: tuple[Literal["post"], int] | None
    local: LocalType | None


class Version(_BaseVersion):
    """This class abstracts handling of a project's versions.

    A :class:`Version` instance is comparison aware and can be compared and
    sorted using the standard Python interfaces.

View on GitHub (pinned to f399c37189)