From 4ca87c15a01b21170bf9472facd4d46be5bbda43 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Oct 2023 13:29:17 -0500 Subject: [PATCH 1/2] Cache construction of packaging version --- src/pip/_vendor/packaging/version.py | 63 +++++++++++++++------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/pip/_vendor/packaging/version.py b/src/pip/_vendor/packaging/version.py index de9a09a4ed3..210423049b3 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,39 +254,43 @@ 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]: + # 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): - - _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + # Generate a key which will be used for sorting + key = _cmpkey( + _version.epoch, + _version.release, + _version.pre, + _version.post, + _version.dev, + _version.local, + ) - def __init__(self, version: str) -> None: + return _version, key - # 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")), - ) +class Version(_BaseVersion): - # 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, - ) + def __init__(self, version: str) -> None: + self._version, self._key = _cached_version(version) def __repr__(self) -> str: return f"" From 2fe52faa043c233d790342a122e560ab20bef893 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Oct 2023 13:54:59 -0500 Subject: [PATCH 2/2] version always get stringified --- src/pip/_vendor/packaging/version.py | 56 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/pip/_vendor/packaging/version.py b/src/pip/_vendor/packaging/version.py index 210423049b3..0ac6674d228 100644 --- a/src/pip/_vendor/packaging/version.py +++ b/src/pip/_vendor/packaging/version.py @@ -257,7 +257,7 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey: _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]: +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: @@ -285,43 +285,45 @@ def _cached_version(version: str) -> Tuple[_Version, CmpKey]: _version.local, ) - return _version, key + parts = [] -class Version(_BaseVersion): + # Epoch + if _version.epoch != 0: + parts.append(f"{_version.epoch}!") - def __init__(self, version: str) -> None: - self._version, self._key = _cached_version(version) + # Release segment + parts.append(".".join(str(x) for x in _version.release)) - def __repr__(self) -> str: - return f"" + # Pre-release + if _version.pre is not None: + parts.append("".join(str(x) for x in _version.pre)) - def __str__(self) -> str: - parts = [] + # Post-release + if _version.post is not None: + parts.append(f".post{_version.post}") - # Epoch - if self.epoch != 0: - parts.append(f"{self.epoch}!") + # Development release + if _version.dev is not None: + parts.append(f".dev{_version.dev}") - # Release segment - parts.append(".".join(str(x) for x in self.release)) + # Local version segment + if _version.local is not None: + parts.append(f"+{_version.local}") - # Pre-release - if self.pre is not None: - parts.append("".join(str(x) for x in self.pre)) + _str = "".join(parts) - # Post-release - if self.post is not None: - parts.append(f".post{self.post}") + return _version, key, _str - # Development release - if self.dev is not None: - parts.append(f".dev{self.dev}") +class Version(_BaseVersion): - # Local version segment - if self.local is not None: - parts.append(f"+{self.local}") + def __init__(self, version: str) -> None: + self._version, self._key, self._str = _cached_version(version) - return "".join(parts) + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._str @property def epoch(self) -> int: