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

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

72 statements  

1from __future__ import annotations 

2 

3import os 

4import sys 

5import sysconfig 

6from collections.abc import Iterable 

7from types import TracebackType 

8from typing import TYPE_CHECKING 

9 

10from pip._internal.build_env.base import ( 

11 BuildEnvironment, 

12 BuildEnvironmentInstaller, 

13 Prefix, 

14) 

15from pip._internal.exceptions import VenvCreationError, VenvImportError 

16from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds 

17 

18if TYPE_CHECKING: 

19 from pip._internal.req.req_install import InstallRequirement 

20 

21 

22def _get_venv_path_from_sysconfig(name: str, env_dir: str) -> str: 

23 vars = { 

24 "base": env_dir, 

25 "platbase": env_dir, 

26 } 

27 return sysconfig.get_path(name, scheme="venv", vars=vars) 

28 

29 

30class VenvBuildEnvironment(BuildEnvironment): 

31 """A venv-based build environment.""" 

32 

33 def __init__(self, installer: BuildEnvironmentInstaller) -> None: 

34 # We defer this import because certain distributions of Python do not include 

35 # a functional venv out of the box. 

36 try: 

37 import venv 

38 except ImportError: 

39 raise VenvImportError 

40 

41 self._env_path = TempDirectory( 

42 kind=tempdir_kinds.BUILD_ENV, globally_managed=True 

43 ).path 

44 # Use symlinks to support relocatable Python installations on POSIX, including 

45 # python-build-standalone. This matches upstream venv CLI's behaviour. 

46 env = venv.EnvBuilder(symlinks=(os.name != "nt")) 

47 try: 

48 context = env.ensure_directories(self._env_path) 

49 env.create(self._env_path) 

50 except OSError as e: 

51 raise VenvCreationError(str(e)) 

52 

53 if sys.version_info >= (3, 12): 

54 # The context object was only documented in Python 3.12 

55 self.lib_dirs = [context.lib_path] 

56 self._bin_path = context.bin_path 

57 elif sys.version_info[:2] == (3, 11): 

58 # On Python 3.11, we can use sysconfig. 

59 self.lib_dirs = [_get_venv_path_from_sysconfig("purelib", self._env_path)] 

60 self._bin_path = _get_venv_path_from_sysconfig("scripts", self._env_path) 

61 else: 

62 # Otherwise, we need to manually construct all the paths... sigh. 

63 if sys.platform == "win32": 

64 libpath = os.path.join(self._env_path, "Lib", "site-packages") 

65 else: 

66 python = "pypy" if sys.implementation.name == "pypy" else "python" 

67 libpath = os.path.join( 

68 self._env_path, 

69 "lib", 

70 f"{python}{sys.version_info.major}.{sys.version_info.minor}", 

71 "site-packages", 

72 ) 

73 self.lib_dirs = [libpath] 

74 # Same reasoning for try-except as for python_executable below. 

75 try: 

76 self._bin_path = context.bin_path 

77 except AttributeError: 

78 scripts_dir = "Scripts" if os.name == "nt" else "bin" 

79 self._bin_path = os.path.join(self._env_path, scripts_dir) 

80 

81 # There are enough ways trying to construct the Python executable path can go 

82 # wrong that we're better off assuming that the context object has the right 

83 # attributes, and only when they don't exist do we try to guess. 

84 # 

85 # These attributes seem to exist in every CPython version after 3.10.1 and 

86 # are documented to exist on 3.12 and higher. 

87 try: 

88 self.python_executable = context.env_exec_cmd 

89 except AttributeError: 

90 try: 

91 self.python_executable = context.env_exe 

92 except AttributeError: 

93 executable_name = "python.exe" if os.name == "nt" else "python" 

94 self.python_executable = os.path.join(self._bin_path, executable_name) 

95 

96 self._save_env: dict[str, str | None] = {} 

97 self._installer = installer 

98 

99 if not os.path.exists(self.python_executable): 

100 # This error is only likely on Windows due to interference from AV software. 

101 raise VenvCreationError( 

102 f"Python executable failed to copy to {self.python_executable}" 

103 ) 

104 

105 def __enter__(self) -> None: 

106 # We want backend calls to be able to use binaries installed as if this 

107 # virtual environment was "activated". 

108 self._save_env = { 

109 name: os.environ.get(name, None) for name in ("PATH", "PYTHONPATH") 

110 } 

111 

112 new_path = [self._bin_path] 

113 if old_path := self._save_env["PATH"]: 

114 new_path.extend(old_path.split(os.pathsep)) 

115 # However, we don't want a pre-existing PYTHONPATH to influence the 

116 # backend calls. 

117 os.environ.update({"PATH": os.pathsep.join(new_path), "PYTHONPATH": ""}) 

118 

119 def __exit__( 

120 self, 

121 exc_type: type[BaseException] | None, 

122 exc_val: BaseException | None, 

123 exc_tb: TracebackType | None, 

124 ) -> None: 

125 for varname, old_value in self._save_env.items(): 

126 if old_value is None: 

127 os.environ.pop(varname, None) 

128 else: 

129 os.environ[varname] = old_value 

130 

131 def install_requirements( 

132 self, 

133 requirements: Iterable[str], 

134 prefix_as_string: str, 

135 *, 

136 kind: str, 

137 for_req: InstallRequirement | None = None, 

138 ) -> None: 

139 if not requirements: 

140 return 

141 

142 # TODO: when better support for installing to arbitrary Python environments 

143 # is added, replace this prefix hack with that. 

144 prefix = Prefix(self._env_path, venv_executable=self.python_executable) 

145 self._installer.install(requirements, prefix, kind=kind, for_req=for_req)