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
- Pass local as a string matching [a-z0-9]+(?:[._-][a-z0-9]+)* (e.g. 'abc', 'abc.1', 'ubuntu-1').
- Pass None (or omit) to clear the local segment, not an empty string.
- 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
- Pass local as a string matching [a-z0-9]+(?:[._-][a-z0-9]+)*
- Pass None (not '') to clear the local segment
- Strip leading/trailing separators before calling
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
- dev must be non-negative integer, got
- epoch must be non-negative integer, got
- Invalid sdist filename (invalid version)
- Invalid version
- Invalid wheel filename (invalid version)
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)