{"id":"be98a9c29c89431a","repo":"pypa/pip","slug":"release-must-be-a-non-empty-tuple-of-non-negative","errorCode":null,"errorMessage":"release must be a non-empty tuple of non-negative integers, got {release}","messagePattern":"release must be a non-empty tuple of non-negative integers, got (.+?)","errorType":"validation","errorClass":"InvalidVersion","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/version.py","lineNumber":283,"sourceCode":"\ndef _validate_epoch(value: object, /) -> int:\n    epoch = value or 0\n    if isinstance(epoch, int) and epoch >= 0:\n        return epoch\n    msg = f\"epoch must be non-negative integer, got {epoch}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_release(value: object, /) -> tuple[int, ...]:\n    release = (0,) if value is None else value\n    if (\n        isinstance(release, tuple)\n        and len(release) > 0\n        and all(isinstance(i, int) and i >= 0 for i in release)\n    ):\n        return release\n    msg = f\"release must be a non-empty tuple of non-negative integers, got {release}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_pre(value: object, /) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, tuple) and len(value) == 2:\n        letter, number = value\n        letter = normalize_pre(letter)\n        if letter in {\"a\", \"b\", \"rc\"} and isinstance(number, int) and number >= 0:\n            # type checkers can't infer the Literal type here on letter\n            return (letter, number)  # type: ignore[return-value]\n    msg = f\"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_post(value: object, /) -> tuple[Literal[\"post\"], int] | None:\n    if value is None:\n        return value","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/version.py#L265-L301","documentation":"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`).","triggerScenarios":"`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`.","commonSituations":"Building a release from a parsed string without converting to int; passing a list from JSON; empty release after stripping; floats from computation.","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."],"exampleFix":"// before\nVersion.from_parts(release=[1, 2, 3])  # list -> raises\n\n// after\nVersion.from_parts(release=tuple(int(x) for x in [1, 2, 3]))","handlingStrategy":"type-guard","validationCode":"def is_valid_release(value: object) -> bool:\n    return (\n        isinstance(value, tuple)\n        and len(value) > 0\n        and all(isinstance(i, int) and not isinstance(i, bool) and i >= 0 for i in value)\n    )","typeGuard":"def is_release_tuple(value: object) -> bool:\n    return (\n        isinstance(value, tuple)\n        and len(value) > 0\n        and all(isinstance(i, int) and not isinstance(i, bool) and i >= 0 for i in value)\n    )","tryCatchPattern":"from pip._vendor.packaging.version import Version, InvalidVersion\n\ntry:\n    v = Version.from_parts(release=release)\nexcept InvalidVersion as e:\n    if 'release' in str(e):\n        release = tuple(int(x) for x in release)\n        v = Version.from_parts(release=release)\n    raise","preventionTips":["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."],"tags":["python","packaging","version","pep440","validation","from-parts"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}