pypa/pip · error · InvalidVersion
local must be a valid version string, got {value!r}
Error message
local must be a valid version string, got {value!r} What it means
Raised by `_validate_local(value)` when the `local` argument to `Version.from_parts`/`__replace__` is not `None` and not a string fully matching the local-version pattern `[a-z0-9]+(?:[._-][a-z0-9]+)*` (ASCII, case-insensitive). The local segment is the `+xyz` part of PEP 440 (`1.0+ubuntu.1`); any non-string, or string with invalid punctuation/empty segments, is rejected.
Source
Thrown at src/pip/_vendor/packaging/version.py:323
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 d7d0d0a394)
Solutions
- Pass a string of ASCII alphanumerics separated by `.`, `_`, or `-`, with no empty runs: `local='ubuntu.1'`.
- Strip stray separators and validate against the pattern before calling.
- Use `Version('1.0+ubuntu.1')` for string input.
- Omit `local` (`None`) if there is no local segment.
Example fix
// before
Version.from_parts(release=(1, 0), local='a..b') # empty segment -> raises
// after
Version.from_parts(release=(1, 0), local='a.b')
# or
Version('1.0+a.b') Defensive patterns
Strategy: validation
Validate before calling
import re
_LOCAL_RE = re.compile(r'[a-z0-9]+(?:[._-][a-z0-9]+)*', re.IGNORECASE | re.ASCII)
def is_valid_local(value: object) -> bool:
return value is None or (isinstance(value, str) and bool(_LOCAL_RE.fullmatch(value))) Type guard
import re
_LOCAL_RE = re.compile(r'[a-z0-9]+(?:[._-][a-z0-9]+)*', re.IGNORECASE | re.ASCII)
def is_local_version_string(value: object) -> bool:
return value is None or (isinstance(value, str) and bool(_LOCAL_RE.fullmatch(value))) Try / catch
from pip._vendor.packaging.version import Version, InvalidVersion
try:
v = Version.from_parts(release=release, local=local)
except InvalidVersion as e:
if 'local' in str(e):
local = local.strip('._-') if isinstance(local, str) else None
v = Version.from_parts(release=release, local=local)
raise Prevention
- Pass `local` as ASCII alphanumerics separated by `.`, `_`, or `-` with no empty/leading/trailing separators, or `None`.
- Validate against `[a-z0-9]+(?:[._-][a-z0-9]+)*` before calling.
- For string input, prefer `Version('1.0+local.1')`.
When it happens
Trigger: `Version.from_parts(release=(1,0), local='a.b.')` (trailing separator), `local='a..b'` (empty middle), `local='a b'` (space), `local=123` (not a str), `local=''`. The validator requires `isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value)`.
Common situations: Local segment built with a trailing/leading separator; non-ASCII characters; a non-string passed by mistake; empty string.
Related errors
- epoch must be non-negative integer, got {epoch}
- release must be a non-empty tuple of non-negative integers,
- pre must be a tuple of ('a'|'b'|'rc', non-negative int), got
- post must be non-negative integer, got {value}
- dev must be non-negative integer, got {value}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/223517c19b5222ed.json.
Report an issue: GitHub.