Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/build_env/base.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

50 statements  

1from __future__ import annotations 

2 

3import abc 

4from collections.abc import Iterable 

5from contextlib import AbstractContextManager as ContextManager 

6from typing import TYPE_CHECKING, Literal, Protocol 

7 

8from pip._vendor.packaging.version import Version 

9 

10from pip._internal.locations import get_scheme 

11from pip._internal.metadata import get_default_environment, get_environment 

12from pip._internal.utils.packaging import get_requirement 

13 

14if TYPE_CHECKING: 

15 from pip._internal.req.req_install import InstallRequirement 

16 

17 

18BuildIsolationMode = Literal["off", "virtual", "venv"] 

19 

20 

21def _dedup(a: str, b: str) -> tuple[str] | tuple[str, str]: 

22 return (a, b) if a != b else (a,) 

23 

24 

25class Prefix: 

26 # TODO: simplify this data model when the legacy subprocess installer is removed 

27 def __init__(self, path: str, *, venv_executable: str | None = None) -> None: 

28 self.path = path 

29 self.setup = False 

30 scheme = get_scheme("", prefix=path) 

31 self.bin_dir = scheme.scripts 

32 self.lib_dirs = _dedup(scheme.purelib, scheme.platlib) 

33 self.venv_executable = venv_executable 

34 

35 

36class BuildEnvironmentInstaller(Protocol): 

37 """ 

38 Interface for installing build dependencies into an isolated build 

39 environment. 

40 """ 

41 

42 def install( 

43 self, 

44 requirements: Iterable[str], 

45 prefix: Prefix, 

46 *, 

47 kind: str, 

48 for_req: InstallRequirement | None, 

49 ) -> None: ... 

50 

51 

52class BuildEnvironment(ContextManager[None], metaclass=abc.ABCMeta): 

53 """Creates and manages an isolated environment to install build deps""" 

54 

55 lib_dirs: list[str] 

56 python_executable: str 

57 

58 @abc.abstractmethod 

59 def __init__(self, installer: BuildEnvironmentInstaller): ... 

60 

61 def check_requirements( 

62 self, reqs: Iterable[str] 

63 ) -> tuple[set[tuple[str, str]], set[str]]: 

64 """Return 2 sets: 

65 - conflicting requirements: set of (installed, wanted) reqs tuples 

66 - missing requirements: set of reqs 

67 """ 

68 missing = set() 

69 conflicting = set() 

70 if reqs: 

71 env = ( 

72 get_environment(self.lib_dirs) 

73 if hasattr(self, "lib_dirs") 

74 else get_default_environment() 

75 ) 

76 for req_str in reqs: 

77 req = get_requirement(req_str) 

78 # We're explicitly evaluating with an empty extra value, since build 

79 # environments are not provided any mechanism to select specific extras. 

80 if req.marker is not None and not req.marker.evaluate({"extra": ""}): 

81 continue 

82 dist = env.get_distribution(req.name) 

83 if not dist: 

84 missing.add(req_str) 

85 continue 

86 if isinstance(dist.version, Version): 

87 installed_req_str = f"{req.name}=={dist.version}" 

88 else: 

89 installed_req_str = f"{req.name}==={dist.version}" 

90 if not req.specifier.contains(dist.version, prereleases=True): 

91 conflicting.add((installed_req_str, req_str)) 

92 # FIXME: Consider direct URL? 

93 return conflicting, missing 

94 

95 @abc.abstractmethod 

96 def install_requirements( 

97 self, 

98 requirements: Iterable[str], 

99 prefix_as_string: str, 

100 *, 

101 kind: str, 

102 for_req: InstallRequirement | None = None, 

103 ) -> None: ...