pypa/pip · error · InvalidVersion

Invalid version: {version!r}

Error message

Invalid version: {version!r}

What it means

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`.

Source

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

    def __init__(self, version: str) -> None:
        """Initialize a Version object.

        :param version:
            The string representation of a version which will be parsed and normalized
            before use.
        :raises InvalidVersion:
            If the ``version`` does not conform to PEP 440 in any way then this
            exception will be raised.
        """
        if _SIMPLE_VERSION_INDICATORS.issuperset(version):
            try:
                self._release = tuple(map(int, version.split(".")))
            except ValueError:
                # Empty parts (from "1..2", ".1", etc.) are invalid versions.
                # Any other ValueError (e.g. int str-digits limit) should
                # propagate to the caller.
                if "" in version.split("."):
                    raise InvalidVersion(f"Invalid version: {version!r}") from None
                # TODO: remove "no cover" when Python 3.9 is dropped.
                raise  # pragma: no cover

            self._epoch = 0
            self._pre = None
            self._post = None
            self._dev = None
            self._local = None
            self._key_cache = None
            self._hash_cache = None
            return

        # Validate the version and parse it into pieces
        match = self._regex.fullmatch(version)
        if not match:
            raise InvalidVersion(f"Invalid version: {version!r}")
        self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
        self._release = tuple(map(int, match.group("release").split(".")))

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Validate/normalize the version string before constructing: reject empty parts.
  2. Fix the producer so trailing/double dots are not emitted.
  3. Use `canonicalize_version(s)` or pre-check `'..' not in s and not s.strip('.') and ...`.
  4. Prefer building from structured data via `Version.from_parts(release=(1, 2))`.

Example fix

// before
Version(f"{major}.{minor}.")  # trailing dot -> '' part -> raises

// after
parts = [str(x) for x in (major, minor) if x is not None]
Version('.'.join(parts))
# or
Version.from_parts(release=(major, minor))
Defensive patterns

Strategy: try-catch

Validate before calling

def is_simple_valid_version(s: str) -> bool:
    # Fast-path rejection: only digits and dots, no empty parts
    if not s or not set(s).issubset('.0123456789'):
        return False
    return '' not in s.split('.')

Type guard

def has_no_empty_release_parts(s: object) -> bool:
    if not isinstance(s, str) or not s:
        return False
    if not set(s).issubset('.0123456789'):
        return True  # not the fast path; defer to full validation
    return '' not in s.split('.')

Try / catch

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

try:
    v = Version(version_str)
except InvalidVersion:
    # normalize: drop empty parts, then retry or surface a clear error
    cleaned = '.'.join(p for p in version_str.split('.') if p)
    v = Version(cleaned)

Prevention

When it happens

Trigger: `Version('1..2')`, `Version('.1')`, `Version('1.')`, `Version('')`, `Version('1.2.')`. Any all-digits-and-dots string where `version.split('.')` contains an empty string.

Common situations: String-built versions with a trailing dot from `f"{major}.{minor}."`; concatenation producing `..`; an empty string after stripping; user input not trimmed.

Related errors


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