1from __future__ import annotations
2
3import functools
4import logging
5
6from pip._vendor.packaging import specifiers, version
7from pip._vendor.packaging.requirements import Requirement
8
9logger = logging.getLogger(__name__)
10
11
12@functools.lru_cache(maxsize=32)
13def check_requires_python(
14 requires_python: str | None, version_info: tuple[int, ...]
15) -> bool:
16 """
17 Check if the given Python version matches a "Requires-Python" specifier.
18
19 :param version_info: A 3-tuple of ints representing a Python
20 major-minor-micro version to check (e.g. `sys.version_info[:3]`).
21
22 :return: `True` if the given Python version satisfies the requirement.
23 Otherwise, return `False`.
24
25 :raises InvalidSpecifier: If `requires_python` has an invalid format.
26 """
27 if requires_python is None:
28 # The package provides no information
29 return True
30 requires_python_specifier = specifiers.SpecifierSet(requires_python)
31
32 python_version = version.parse(".".join(map(str, version_info)))
33 return python_version in requires_python_specifier
34
35
36@functools.lru_cache(maxsize=10000)
37def get_requirement(req_string: str) -> Requirement:
38 """Construct a packaging.Requirement object with caching"""
39 # Parsing requirement strings is expensive, and is also expected to happen
40 # with a low diversity of different arguments (at least relative the number
41 # constructed). This method adds a cache to requirement object creation to
42 # minimize repeated parsing of the same string to construct equivalent
43 # Requirement objects.
44 return Requirement(req_string)