{"record":{"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":270,"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":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/version.py#L252-L288","documentation":"Raised by _validate_epoch() (version.py:265-270) when the epoch value passed to Version.from_parts() or Version.__replace__() is not a non-negative integer. The epoch defaults to 0 if falsy, but must otherwise be an int >= 0. The exception class is InvalidVersion (a ValueError subclass).","triggerScenarios":"Calling Version.from_parts(epoch=-1, release=(1,)) or Version.from_parts(epoch=1.5, release=(1,)) or Version.from_parts(epoch='1', release=(1,)). A string, float, or negative int triggers this.","commonSituations":"Passing an epoch as a string from a config file, a negative epoch due to a computation error, or a float epoch from division.","solutions":["Pass epoch as a non-negative int (0, 1, 2, ...).","Convert string epochs with int(epoch) and validate >= 0 before calling from_parts().","Omit the epoch argument entirely if you want the default of 0."],"exampleFix":"// before\nVersion.from_parts(epoch='2', release=(1, 0))\n// after\nVersion.from_parts(epoch=2, release=(1, 0))","handlingStrategy":"type-guard","validationCode":"from packaging.version import Version, InvalidVersion\n\ndef safe_from_parts(*, release, epoch=0, **kwargs):\n    if not isinstance(epoch, int) or epoch < 0:\n        raise TypeError(f\"epoch must be a non-negative int, got {epoch!r}\")\n    return Version.from_parts(epoch=epoch, release=release, **kwargs)","typeGuard":"def is_valid_epoch(value) -> bool:\n    return (value is None or value == 0) or (isinstance(value, int) and value >= 0)","tryCatchPattern":"from packaging.version import InvalidVersion\n\ntry:\n    v = Version.from_parts(epoch=epoch, release=(1,))\nexcept InvalidVersion as e:\n    if \"epoch\" in str(e):\n        logger.error(\"Invalid epoch value: %s\", epoch)\n    raise","preventionTips":["Always pass epoch as a non-negative int","Convert string epochs with int() before calling","Omit the epoch argument to use the default of 0"],"tags":["version","epoch","packaging","pep440"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}