Skip to content

Commit 4fac2b9

Browse files
authored
Merge pull request #10550 from jbylund/joe/cache_requirement_creation
2 parents ae2c9ed + 9732729 commit 4fac2b9

File tree

5 files changed

+37
-7
lines changed

5 files changed

+37
-7
lines changed

news/10550.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cache requirement objects, to improve performance reducing reparses of requirement strings.

src/pip/_internal/req/constructors.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from pip._internal.req.req_install import InstallRequirement
2727
from pip._internal.utils.filetypes import is_archive_file
2828
from pip._internal.utils.misc import is_installable_dir
29+
from pip._internal.utils.packaging import get_requirement
2930
from pip._internal.utils.urls import path_to_url
3031
from pip._internal.vcs import is_url, vcs
3132

@@ -54,7 +55,7 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
5455
def convert_extras(extras: Optional[str]) -> Set[str]:
5556
if not extras:
5657
return set()
57-
return Requirement("placeholder" + extras.lower()).extras
58+
return get_requirement("placeholder" + extras.lower()).extras
5859

5960

6061
def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
@@ -83,7 +84,7 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
8384
return (
8485
package_name,
8586
url_no_extras,
86-
Requirement("placeholder" + extras.lower()).extras,
87+
get_requirement("placeholder" + extras.lower()).extras,
8788
)
8889
else:
8990
return package_name, url_no_extras, set()
@@ -309,7 +310,7 @@ def with_source(text: str) -> str:
309310

310311
def _parse_req_string(req_as_string: str) -> Requirement:
311312
try:
312-
req = Requirement(req_as_string)
313+
req = get_requirement(req_as_string)
313314
except InvalidRequirement:
314315
if os.path.sep in req_as_string:
315316
add_msg = "It looks like a path."
@@ -386,7 +387,7 @@ def install_req_from_req_string(
386387
user_supplied: bool = False,
387388
) -> InstallRequirement:
388389
try:
389-
req = Requirement(req_string)
390+
req = get_requirement(req_string)
390391
except InvalidRequirement:
391392
raise InstallationError(f"Invalid requirement: '{req_string}'")
392393

src/pip/_internal/resolution/resolvelib/factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
)
2020

2121
from pip._vendor.packaging.requirements import InvalidRequirement
22-
from pip._vendor.packaging.requirements import Requirement as PackagingRequirement
2322
from pip._vendor.packaging.specifiers import SpecifierSet
2423
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
2524
from pip._vendor.resolvelib import ResolutionImpossible
@@ -46,6 +45,7 @@
4645
from pip._internal.resolution.base import InstallRequirementProvider
4746
from pip._internal.utils.compatibility_tags import get_supported
4847
from pip._internal.utils.hashes import Hashes
48+
from pip._internal.utils.packaging import get_requirement
4949
from pip._internal.utils.virtualenv import running_under_virtualenv
5050

5151
from .base import Candidate, CandidateVersion, Constraint, Requirement
@@ -365,7 +365,7 @@ def find_candidates(
365365
# If the current identifier contains extras, add explicit candidates
366366
# from entries from extra-less identifier.
367367
with contextlib.suppress(InvalidRequirement):
368-
parsed_requirement = PackagingRequirement(identifier)
368+
parsed_requirement = get_requirement(identifier)
369369
explicit_candidates.update(
370370
self._iter_explicit_candidates_from_base(
371371
requirements.get(parsed_requirement.name, ()),

src/pip/_internal/utils/packaging.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import functools
12
import logging
23
from email.message import Message
34
from email.parser import FeedParser
45
from typing import Optional, Tuple
56

67
from pip._vendor import pkg_resources
78
from pip._vendor.packaging import specifiers, version
9+
from pip._vendor.packaging.requirements import Requirement
810
from pip._vendor.pkg_resources import Distribution
911

1012
from pip._internal.exceptions import NoneMetadataError
@@ -69,3 +71,14 @@ def get_installer(dist: Distribution) -> str:
6971
if line.strip():
7072
return line.strip()
7173
return ""
74+
75+
76+
@functools.lru_cache(maxsize=512)
77+
def get_requirement(req_string: str) -> Requirement:
78+
"""Construct a packaging.Requirement object with caching"""
79+
# Parsing requirement strings is expensive, and is also expected to happen
80+
# with a low diversity of different arguments (at least relative the number
81+
# constructed). This method adds a cache to requirement object creation to
82+
# minimize repeated parsing of the same string to construct equivalent
83+
# Requirement objects.
84+
return Requirement(req_string)

tests/unit/test_packaging.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import pytest
44
from pip._vendor.packaging import specifiers
5+
from pip._vendor.packaging.requirements import Requirement
56

6-
from pip._internal.utils.packaging import check_requires_python
7+
from pip._internal.utils.packaging import check_requires_python, get_requirement
78

89

910
@pytest.mark.parametrize(
@@ -27,3 +28,17 @@ def test_check_requires_python__invalid() -> None:
2728
"""
2829
with pytest.raises(specifiers.InvalidSpecifier):
2930
check_requires_python("invalid", (3, 6, 5))
31+
32+
33+
def test_get_or_create_caching() -> None:
34+
"""test caching of get_or_create requirement"""
35+
teststr = "affinegap==1.10"
36+
from_helper = get_requirement(teststr)
37+
freshly_made = Requirement(teststr)
38+
39+
# Requirement doesn't have an equality operator (yet) so test
40+
# equality of attribute for list of attributes
41+
for iattr in ["name", "url", "extras", "specifier", "marker"]:
42+
assert getattr(from_helper, iattr) == getattr(freshly_made, iattr)
43+
assert get_requirement(teststr) is not Requirement(teststr)
44+
assert get_requirement(teststr) is get_requirement(teststr)

0 commit comments

Comments
 (0)