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 value

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Pass a non-empty tuple of non-negative ints: `release=(1, 2, 3)`.
  2. Convert lists with `tuple(...)` and coerce elements with `int()` first.
  3. Validate each element is an `int >= 0` before calling.
  4. 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

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


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/be98a9c29c89431a.json. Report an issue: GitHub.