Skip to content

Commit

Permalink
Cache parsing of version and names
Browse files Browse the repository at this point in the history
extracted from pypa/pip#12316
  • Loading branch information
bdraco committed Oct 4, 2023
1 parent f13c298 commit 4420611
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 20 deletions.
28 changes: 14 additions & 14 deletions src/packaging/specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
)

from .utils import canonicalize_version
from .version import Version
from .version import Version, parse

UnparsedVersion = Union[Version, str]
UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
Expand All @@ -33,7 +33,7 @@

def _coerce_version(version: UnparsedVersion) -> Version:
if not isinstance(version, Version):
version = Version(version)
return parse(version)
return version


Expand Down Expand Up @@ -272,7 +272,7 @@ def prereleases(self) -> bool:

# Parse the version, and if it is a pre-release than this
# specifier allows pre-releases.
if Version(version).is_prerelease:
if parse(version).is_prerelease:
return True

return False
Expand Down Expand Up @@ -425,13 +425,13 @@ def _compare_equal(self, prospective: Version, spec: str) -> bool:
return shortened_prospective == split_spec
else:
# Convert our spec string into a Version
spec_version = Version(spec)
spec_version = parse(spec)

# If the specifier does not have a local segment, then we want to
# act as if the prospective version also does not have a local
# segment.
if not spec_version.local:
prospective = Version(prospective.public)
prospective = parse(prospective.public)

return prospective == spec_version

Expand All @@ -443,20 +443,20 @@ def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
# the prospective version.
return Version(prospective.public) <= Version(spec)
return parse(prospective.public) <= parse(spec)

def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:

# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
# the prospective version.
return Version(prospective.public) >= Version(spec)
return parse(prospective.public) >= parse(spec)

def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:

# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
spec = Version(spec_str)
spec = parse(spec_str)

# Check to see if the prospective version is less than the spec
# version. If it's not we can short circuit and just return False now
Expand All @@ -469,7 +469,7 @@ def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
# versions for the version mentioned in the specifier (e.g. <3.1 should
# not match 3.1.dev0, but should match 3.0.dev0).
if not spec.is_prerelease and prospective.is_prerelease:
if Version(prospective.base_version) == Version(spec.base_version):
if parse(prospective.base_version) == parse(spec.base_version):
return False

# If we've gotten to here, it means that prospective version is both
Expand All @@ -481,7 +481,7 @@ def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:

# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
spec = Version(spec_str)
spec = parse(spec_str)

# Check to see if the prospective version is greater than the spec
# version. If it's not we can short circuit and just return False now
Expand All @@ -494,13 +494,13 @@ def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
# post-release versions for the version mentioned in the specifier
# (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
if not spec.is_postrelease and prospective.is_postrelease:
if Version(prospective.base_version) == Version(spec.base_version):
if parse(prospective.base_version) == parse(spec.base_version):
return False

# Ensure that we do not allow a local version of the version mentioned
# in the specifier, which is technically greater than, to match.
if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version):
if parse(prospective.base_version) == parse(spec.base_version):
return False

# If we've gotten to here, it means that prospective version is both
Expand Down Expand Up @@ -924,7 +924,7 @@ def contains(
"""
# Ensure that our item is a Version instance.
if not isinstance(item, Version):
item = Version(item)
item = parse(item)

# Determine if we're forcing a prerelease or not, if we're not forcing
# one for this particular filter call, then we'll use whatever the
Expand All @@ -942,7 +942,7 @@ def contains(
return False

if installed and item.is_prerelease:
item = Version(item.base_version)
item = parse(item.base_version)

# We simply dispatch to the underlying specs here to make sure that the
# given version is contained within all of them.
Expand Down
7 changes: 5 additions & 2 deletions src/packaging/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

import functools
import re
from typing import FrozenSet, NewType, Tuple, Union, cast

from .tags import Tag, parse_tag
from .version import InvalidVersion, Version
from .version import InvalidVersion, Version, parse

BuildTag = Union[Tuple[()], Tuple[int, str]]
NormalizedName = NewType("NormalizedName", str)
Expand Down Expand Up @@ -40,6 +41,7 @@ class InvalidSdistFilename(ValueError):
_build_tag_regex = re.compile(r"(\d+)(.*)")


@functools.lru_cache(maxsize=4096)
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
if validate and not _validate_regex.match(name):
raise InvalidName(f"name is invalid: {name!r}")
Expand All @@ -52,6 +54,7 @@ def is_normalized_name(name: str) -> bool:
return _normalized_regex.match(name) is not None


@functools.lru_cache(maxsize=4096)
def canonicalize_version(
version: Union[Version, str], *, strip_trailing_zero: bool = True
) -> str:
Expand All @@ -61,7 +64,7 @@ def canonicalize_version(
"""
if isinstance(version, str):
try:
parsed = Version(version)
parsed = parse(version)
except InvalidVersion:
# Legacy versions cannot be normalized
return version
Expand Down
20 changes: 16 additions & 4 deletions src/packaging/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from packaging.version import parse, Version
"""

import functools
import itertools
import re
from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union
Expand Down Expand Up @@ -42,6 +43,7 @@ class _Version(NamedTuple):
local: Optional[LocalType]


@functools.lru_cache(maxsize=4096)
def parse(version: str) -> "Version":
"""Parse the given version string.
Expand Down Expand Up @@ -220,6 +222,12 @@ def __init__(self, version: str) -> None:
self._version.dev,
self._version.local,
)
# Pre-compute the version string since
# its almost always called anyways because
# public will stringify the object and
# we want to cache it to avoid the cost
# of re-computing it.
self._str = self._version_string()

def __repr__(self) -> str:
"""A representation of the Version that shows all internal state.
Expand All @@ -234,7 +242,11 @@ def __str__(self) -> str:
>>> str(Version("1.0a5"))
'1.0a5'
"""
"""
return self._str

def _version_string(self) -> str:
"""Build the version string to be used for __str__."""
parts = []

# Epoch
Expand Down Expand Up @@ -393,7 +405,7 @@ def is_prerelease(self) -> bool:
>>> Version("1.2.3dev1").is_prerelease
True
"""
return self.dev is not None or self.pre is not None
return self._version.dev is not None or self._version.pre is not None

@property
def is_postrelease(self) -> bool:
Expand All @@ -404,7 +416,7 @@ def is_postrelease(self) -> bool:
>>> Version("1.2.3.post1").is_postrelease
True
"""
return self.post is not None
return self._version.post is not None

@property
def is_devrelease(self) -> bool:
Expand All @@ -415,7 +427,7 @@ def is_devrelease(self) -> bool:
>>> Version("1.2.3.dev1").is_devrelease
True
"""
return self.dev is not None
return self._version.dev is not None

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

0 comments on commit 4420611

Please sign in to comment.