pypa/pip · error · InvalidVersion
epoch must be non-negative integer, got {epoch}
Error message
epoch must be non-negative integer, got {epoch} What it means
Raised by `_validate_epoch(value)` when the `epoch` argument to `Version.from_parts(epoch=...)` or `Version.__replace__(epoch=...)` is not a non-negative `int` (a falsy value like `0`/`None` is allowed and normalized to `0`; any other type or negative int is rejected). PEP 440 epochs are non-negative integers (the `N!` syntax).
Source
Thrown at src/pip/_vendor/packaging/version.py:271
:meta hide-value:
"""
# Validation pattern for local version in replace()
_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)
# Fast path: If a version has only digits and dots then we
# can skip the regex and parse it as a release segment
_SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")
def _validate_epoch(value: object, /) -> int:
epoch = value or 0
if isinstance(epoch, int) and epoch >= 0:
return epoch
msg = f"epoch must be non-negative integer, got {epoch}"
raise InvalidVersion(msg)
def _validate_release(value: object, /) -> tuple[int, ...]:
release = (0,) if value is None else value
if (
isinstance(release, tuple)
and len(release) > 0
and all(isinstance(i, int) and i >= 0 for i in release)
):
return release
msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
raise InvalidVersion(msg)
def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
if value is None:
return value
if isinstance(value, tuple) and len(value) == 2:View on GitHub (pinned to d7d0d0a394)
Solutions
- Pass a non-negative `int`: `epoch=2`.
- Coerce from string with `int(value)` and assert `>= 0` first.
- Omit `epoch` (defaults to `0`) unless you genuinely need a non-zero epoch.
- For string-based input, build the version via `Version('2!1.0')` instead of `from_parts`.
Example fix
// before
Version.from_parts(epoch='2', release=(1, 0)) # str -> raises
// after
Version.from_parts(epoch=int('2'), release=(1, 0))
# or simply
Version('2!1.0') Defensive patterns
Strategy: type-guard
Validate before calling
def is_valid_epoch(value: object) -> bool:
return value is None or (isinstance(value, int) and not isinstance(value, bool) and value >= 0) Type guard
def is_non_negative_int(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool) and value >= 0 Try / catch
from pip._vendor.packaging.version import Version, InvalidVersion
try:
v = Version.from_parts(epoch=epoch, release=release)
except InvalidVersion as e:
if 'epoch' in str(e):
epoch = 0 # or int(epoch) after validation
v = Version.from_parts(epoch=epoch, release=release)
raise Prevention
- Pass `epoch` as a non-negative `int` (not a bool, not a string).
- Coerce string inputs with `int()` and validate `>= 0` before calling.
- Omit `epoch` (defaults to `0`) unless a non-zero epoch is genuinely needed.
When it happens
Trigger: Calling `Version.from_parts(epoch=-1, release=(1,0))`, `Version.from_parts(epoch=1.5, release=(1,0))`, or `Version.from_parts(epoch='1', release=(1,0))`. The validator does `isinstance(epoch, int) and epoch >= 0`.
Common situations: Passing a string version number from config without conversion; a negative epoch from a buggy computation; a float epoch from division.
Related errors
- 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}
- local must be a valid version string, got {value!r}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/335c6d1a2bb42f06.json.
Report an issue: GitHub.