pypa/pip · error · InvalidVersion

pre must be a tuple of ('a'|'b'|'rc', non-negative int), got

Error message

pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}

What it means

Raised by `_validate_pre(value)` when the `pre` argument to `Version.from_parts`/`__replace__` is not `None` and not a 2-tuple `(letter, number)` where `letter` normalizes to one of `a`/`b`/`rc` and `number` is a non-negative int. `None` is allowed (no pre-release). The letter is run through `normalize_pre`, so `alpha`/`a`/`A` all map to `a`, `beta`/`b` to `b`, `c`/`rc`/`pre`/`preview` to `rc`. Any other letter, wrong arity, or non-int/negative number is rejected.

Source

Thrown at src/pip/_vendor/packaging/version.py:296

        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
    if isinstance(value, int) and value >= 0:
        return ("post", value)
    msg = f"post must be non-negative integer, got {value}"
    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)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Pass a 2-tuple: `pre=('a', 1)` (or `('rc', 2)`, `('b', 3)`).
  2. Use an accepted letter spelling: `'a'`/`'alpha'`, `'b'`/`'beta'`, `'rc'`/`'c'`/`'pre'`/`'preview'`.
  3. Ensure the number is a non-negative `int`.
  4. For string input, prefer `Version('1.0a1')`.

Example fix

// before
Version.from_parts(release=(1, 0), pre='a1')  # str -> raises

// after
Version.from_parts(release=(1, 0), pre=('a', 1))
# or
Version('1.0a1')
Defensive patterns

Strategy: type-guard

Validate before calling

from pip._vendor.packaging.version import normalize_pre

def is_valid_pre(value: object) -> bool:
    if value is None:
        return True
    if not (isinstance(value, tuple) and len(value) == 2):
        return False
    letter, number = value
    try:
        return normalize_pre(letter) in {'a', 'b', 'rc'} and isinstance(number, int) and not isinstance(number, bool) and number >= 0
    except Exception:
        return False

Type guard

from pip._vendor.packaging.version import normalize_pre

def is_pre_tuple(value: object) -> bool:
    if value is None:
        return True
    if not (isinstance(value, tuple) and len(value) == 2):
        return False
    letter, number = value
    try:
        return (normalize_pre(letter) in {'a', 'b', 'rc'}
                and isinstance(number, int) and not isinstance(number, bool) and number >= 0)
    except Exception:
        return False

Try / catch

from pip._vendor.packaging.version import Version, InvalidVersion

try:
    v = Version.from_parts(release=release, pre=pre)
except InvalidVersion as e:
    if 'pre' in str(e):
        pre = None  # or normalize from a string via Version(...)
        v = Version.from_parts(release=release, pre=pre)
    raise

Prevention

When it happens

Trigger: `Version.from_parts(release=(1,0), pre=('gamma', 1))`, `pre=('a',)`, `pre=('a', -1)`, `pre=('a', 1.5)`, `pre=('a', '1')`. The validator checks `isinstance(value, tuple) and len==2` then letter/number.

Common situations: Passing a string pre-release like `'a1'` instead of a tuple; wrong letter (`'alpha'` is fine but `'rc2'` as letter isn't); floats or strings for the number.

Related errors


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