{"id":"335c6d1a2bb42f06","repo":"pypa/pip","slug":"epoch-must-be-non-negative-integer-got-epoch","errorCode":null,"errorMessage":"epoch must be non-negative integer, got {epoch}","messagePattern":"epoch must be non-negative integer, got (.+?)","errorType":"validation","errorClass":"InvalidVersion","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/version.py","lineNumber":271,"sourceCode":"\n:meta hide-value:\n\"\"\"\n\n\n# Validation pattern for local version in replace()\n_LOCAL_PATTERN = re.compile(r\"[a-z0-9]+(?:[._-][a-z0-9]+)*\", re.IGNORECASE | re.ASCII)\n\n# Fast path: If a version has only digits and dots then we\n# can skip the regex and parse it as a release segment\n_SIMPLE_VERSION_INDICATORS = frozenset(\".0123456789\")\n\n\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:","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/version.py#L253-L289","documentation":"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).","triggerScenarios":"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`.","commonSituations":"Passing a string version number from config without conversion; a negative epoch from a buggy computation; a float epoch from division.","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`."],"exampleFix":"// before\nVersion.from_parts(epoch='2', release=(1, 0))  # str -> raises\n\n// after\nVersion.from_parts(epoch=int('2'), release=(1, 0))\n# or simply\nVersion('2!1.0')","handlingStrategy":"type-guard","validationCode":"def is_valid_epoch(value: object) -> bool:\n    return value is None or (isinstance(value, int) and not isinstance(value, bool) and value >= 0)","typeGuard":"def is_non_negative_int(value: object) -> bool:\n    return isinstance(value, int) and not isinstance(value, bool) and value >= 0","tryCatchPattern":"from pip._vendor.packaging.version import Version, InvalidVersion\n\ntry:\n    v = Version.from_parts(epoch=epoch, release=release)\nexcept InvalidVersion as e:\n    if 'epoch' in str(e):\n        epoch = 0  # or int(epoch) after validation\n        v = Version.from_parts(epoch=epoch, release=release)\n    raise","preventionTips":["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."],"tags":["python","packaging","version","pep440","validation","from-parts"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}