{"id":"ccec0f14437bcae9","repo":"pypa/pip","slug":"invalid-version-version-r","errorCode":null,"errorMessage":"Invalid version: {version!r}","messagePattern":"Invalid version: (.+?)","errorType":"validation","errorClass":"InvalidVersion","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/version.py","lineNumber":422,"sourceCode":"    def __init__(self, version: str) -> None:\n        \"\"\"Initialize a Version object.\n\n        :param version:\n            The string representation of a version which will be parsed and normalized\n            before use.\n        :raises InvalidVersion:\n            If the ``version`` does not conform to PEP 440 in any way then this\n            exception will be raised.\n        \"\"\"\n        if _SIMPLE_VERSION_INDICATORS.issuperset(version):\n            try:\n                self._release = tuple(map(int, version.split(\".\")))\n            except ValueError:\n                # Empty parts (from \"1..2\", \".1\", etc.) are invalid versions.\n                # Any other ValueError (e.g. int str-digits limit) should\n                # propagate to the caller.\n                if \"\" in version.split(\".\"):\n                    raise InvalidVersion(f\"Invalid version: {version!r}\") from None\n                # TODO: remove \"no cover\" when Python 3.9 is dropped.\n                raise  # pragma: no cover\n\n            self._epoch = 0\n            self._pre = None\n            self._post = None\n            self._dev = None\n            self._local = None\n            self._key_cache = None\n            self._hash_cache = None\n            return\n\n        # Validate the version and parse it into pieces\n        match = self._regex.fullmatch(version)\n        if not match:\n            raise InvalidVersion(f\"Invalid version: {version!r}\")\n        self._epoch = int(match.group(\"epoch\")) if match.group(\"epoch\") else 0\n        self._release = tuple(map(int, match.group(\"release\").split(\".\")))","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/version.py#L404-L440","documentation":"Raised by `Version.__init__` on the fast path: when the version string consists only of digits and dots (`_SIMPLE_VERSION_INDICATORS.issuperset(version)`) but contains an empty component when split on `.`, e.g. `1..2`, `.1`, `1.`, or `''`. Empty release components are not valid PEP 440, so the parser raises `InvalidVersion` with `from None` to suppress the inner `ValueError`.","triggerScenarios":"`Version('1..2')`, `Version('.1')`, `Version('1.')`, `Version('')`, `Version('1.2.')`. Any all-digits-and-dots string where `version.split('.')` contains an empty string.","commonSituations":"String-built versions with a trailing dot from `f\"{major}.{minor}.\"`; concatenation producing `..`; an empty string after stripping; user input not trimmed.","solutions":["Validate/normalize the version string before constructing: reject empty parts.","Fix the producer so trailing/double dots are not emitted.","Use `canonicalize_version(s)` or pre-check `'..' not in s and not s.strip('.') and ...`.","Prefer building from structured data via `Version.from_parts(release=(1, 2))`."],"exampleFix":"// before\nVersion(f\"{major}.{minor}.\")  # trailing dot -> '' part -> raises\n\n// after\nparts = [str(x) for x in (major, minor) if x is not None]\nVersion('.'.join(parts))\n# or\nVersion.from_parts(release=(major, minor))","handlingStrategy":"try-catch","validationCode":"def is_simple_valid_version(s: str) -> bool:\n    # Fast-path rejection: only digits and dots, no empty parts\n    if not s or not set(s).issubset('.0123456789'):\n        return False\n    return '' not in s.split('.')","typeGuard":"def has_no_empty_release_parts(s: object) -> bool:\n    if not isinstance(s, str) or not s:\n        return False\n    if not set(s).issubset('.0123456789'):\n        return True  # not the fast path; defer to full validation\n    return '' not in s.split('.')","tryCatchPattern":"from pip._vendor.packaging.version import Version, InvalidVersion\n\ntry:\n    v = Version(version_str)\nexcept InvalidVersion:\n    # normalize: drop empty parts, then retry or surface a clear error\n    cleaned = '.'.join(p for p in version_str.split('.') if p)\n    v = Version(cleaned)","preventionTips":["Strip trailing/double dots from string-built versions before constructing `Version`.","Build versions from structured data via `Version.from_parts(release=(...))` instead of string concatenation.","Pre-check that `split('.')` contains no empty strings."],"tags":["python","packaging","version","pep440","parsing","validation"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}