diff --git a/src/pip/_vendor/packaging/version.py b/src/pip/_vendor/packaging/version.py index de9a09a4ed3..0ac6674d228 100644 --- a/src/pip/_vendor/packaging/version.py +++ b/src/pip/_vendor/packaging/version.py @@ -3,6 +3,7 @@ # for complete details. import collections +import functools import itertools import re import warnings @@ -253,70 +254,76 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey: (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version """ +_VERSION_REGEX = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + +@functools.lru_cache(maxsize=4096) +def _cached_version(version: str) -> Tuple[_Version, CmpKey, str]: + # Validate the version and parse it into pieces + match = _VERSION_REGEX.search(version) + if not match: + raise InvalidVersion(f"Invalid version: '{version}'") + + # Store the parsed out pieces of the version + _version = _Version( + epoch=int(match.group("epoch")) if match.group("epoch") else 0, + release=tuple(int(i) for i in match.group("release").split(".")), + pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), + post=_parse_letter_version( + match.group("post_l"), match.group("post_n1") or match.group("post_n2") + ), + dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), + local=_parse_local_version(match.group("local")), + ) -class Version(_BaseVersion): + # Generate a key which will be used for sorting + key = _cmpkey( + _version.epoch, + _version.release, + _version.pre, + _version.post, + _version.dev, + _version.local, + ) - _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + parts = [] - def __init__(self, version: str) -> None: + # Epoch + if _version.epoch != 0: + parts.append(f"{_version.epoch}!") - # Validate the version and parse it into pieces - match = self._regex.search(version) - if not match: - raise InvalidVersion(f"Invalid version: '{version}'") - - # Store the parsed out pieces of the version - self._version = _Version( - epoch=int(match.group("epoch")) if match.group("epoch") else 0, - release=tuple(int(i) for i in match.group("release").split(".")), - pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), - post=_parse_letter_version( - match.group("post_l"), match.group("post_n1") or match.group("post_n2") - ), - dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), - local=_parse_local_version(match.group("local")), - ) + # Release segment + parts.append(".".join(str(x) for x in _version.release)) - # Generate a key which will be used for sorting - self._key = _cmpkey( - self._version.epoch, - self._version.release, - self._version.pre, - self._version.post, - self._version.dev, - self._version.local, - ) + # Pre-release + if _version.pre is not None: + parts.append("".join(str(x) for x in _version.pre)) - def __repr__(self) -> str: - return f"" + # Post-release + if _version.post is not None: + parts.append(f".post{_version.post}") - def __str__(self) -> str: - parts = [] + # Development release + if _version.dev is not None: + parts.append(f".dev{_version.dev}") - # Epoch - if self.epoch != 0: - parts.append(f"{self.epoch}!") + # Local version segment + if _version.local is not None: + parts.append(f"+{_version.local}") - # Release segment - parts.append(".".join(str(x) for x in self.release)) + _str = "".join(parts) - # Pre-release - if self.pre is not None: - parts.append("".join(str(x) for x in self.pre)) + return _version, key, _str - # Post-release - if self.post is not None: - parts.append(f".post{self.post}") +class Version(_BaseVersion): - # Development release - if self.dev is not None: - parts.append(f".dev{self.dev}") + def __init__(self, version: str) -> None: + self._version, self._key, self._str = _cached_version(version) - # Local version segment - if self.local is not None: - parts.append(f"+{self.local}") + def __repr__(self) -> str: + return f"" - return "".join(parts) + def __str__(self) -> str: + return self._str @property def epoch(self) -> int: