pypa/pip · error · InvalidVersion
release must be a non-empty tuple of non-negative integers,
Error message
release must be a non-empty tuple of non-negative integers, got {release} What it means
Raised by `_validate_release(value)` when the `release` argument to `Version.from_parts`/`__replace__` is not a non-empty tuple of non-negative ints. `None` is normalized to `(0,)`, but any other type (list, str), an empty tuple, or a tuple containing negatives/floats/strings is rejected. The release segment is the mandatory dot-separated numeric core (e.g. `(1, 2, 3)` for `1.2.3`).
Source
Thrown at src/pip/_vendor/packaging/version.py:283
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:
letter, number = value
letter = normalize_pre(letter)
if letter in {"a", "b", "rc"} and isinstance(number, int) and number >= 0:
# type checkers can't infer the Literal type here on letter
return (letter, number) # type: ignore[return-value]
msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
raise InvalidVersion(msg)
def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
if value is None:
return valueView on GitHub (pinned to d7d0d0a394)
Solutions
- Pass a non-empty tuple of non-negative ints: `release=(1, 2, 3)`.
- Convert lists with `tuple(...)` and coerce elements with `int()` first.
- Validate each element is an `int >= 0` before calling.
- For string input, prefer `Version('1.2.3')` which handles parsing.
Example fix
// before Version.from_parts(release=[1, 2, 3]) # list -> raises // after Version.from_parts(release=tuple(int(x) for x in [1, 2, 3]))
Defensive patterns
Strategy: type-guard
Validate before calling
def is_valid_release(value: object) -> bool:
return (
isinstance(value, tuple)
and len(value) > 0
and all(isinstance(i, int) and not isinstance(i, bool) and i >= 0 for i in value)
) Type guard
def is_release_tuple(value: object) -> bool:
return (
isinstance(value, tuple)
and len(value) > 0
and all(isinstance(i, int) and not isinstance(i, bool) and i >= 0 for i in value)
) Try / catch
from pip._vendor.packaging.version import Version, InvalidVersion
try:
v = Version.from_parts(release=release)
except InvalidVersion as e:
if 'release' in str(e):
release = tuple(int(x) for x in release)
v = Version.from_parts(release=release)
raise Prevention
- Always pass `release` as a non-empty tuple of non-negative ints.
- Convert lists with `tuple(int(x) for x in seq)` before calling.
- Reject bools (which are `int` subclasses) explicitly if they could leak in.
When it happens
Trigger: `Version.from_parts(release=[1,2])` (list not tuple), `release=()` (empty), `release=(1, -2)`, `release=(1, '2')`, `release=(1.0,)` (float). Validator requires `isinstance(release, tuple) and len>0 and all int and >=0`.
Common situations: Building a release from a parsed string without converting to int; passing a list from JSON; empty release after stripping; floats from computation.
Related errors
- epoch must be non-negative integer, got {epoch}
- 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/be98a9c29c89431a.json.
Report an issue: GitHub.