Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache construction of packaging version #12315

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 57 additions & 50 deletions src/pip/_vendor/packaging/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# for complete details.

import collections
import functools
import itertools
import re
import warnings
Expand Down Expand Up @@ -253,70 +254,76 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey:
(?:\+(?P<local>[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"<Version('{self}')>"
# 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"<Version('{self}')>"

return "".join(parts)
def __str__(self) -> str:
return self._str

@property
def epoch(self) -> int:
Expand Down