Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/pyproject.py: 29%

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

42 statements  

1from __future__ import annotations 

2 

3import os 

4from collections import namedtuple 

5from typing import Any 

6 

7from pip._vendor.packaging.requirements import InvalidRequirement 

8 

9from pip._internal.exceptions import ( 

10 InstallationError, 

11 InvalidPyProjectBuildRequires, 

12 MissingPyProjectBuildRequires, 

13) 

14from pip._internal.utils.compat import tomllib 

15from pip._internal.utils.packaging import get_requirement 

16 

17 

18def _is_list_of_str(obj: Any) -> bool: 

19 return isinstance(obj, list) and all(isinstance(item, str) for item in obj) 

20 

21 

22def make_pyproject_path(unpacked_source_directory: str) -> str: 

23 return os.path.join(unpacked_source_directory, "pyproject.toml") 

24 

25 

26BuildSystemDetails = namedtuple( 

27 "BuildSystemDetails", ["requires", "backend", "check", "backend_path"] 

28) 

29 

30 

31def load_pyproject_toml( 

32 pyproject_toml: str, setup_py: str, req_name: str 

33) -> BuildSystemDetails: 

34 """Load the pyproject.toml file. 

35 

36 Parameters: 

37 pyproject_toml - Location of the project's pyproject.toml file 

38 setup_py - Location of the project's setup.py file 

39 req_name - The name of the requirement we're processing (for 

40 error reporting) 

41 

42 Returns: 

43 None if we should use the legacy code path, otherwise a tuple 

44 ( 

45 requirements from pyproject.toml, 

46 name of PEP 517 backend, 

47 requirements we should check are installed after setting 

48 up the build environment 

49 directory paths to import the backend from (backend-path), 

50 relative to the project root. 

51 ) 

52 """ 

53 has_pyproject = os.path.isfile(pyproject_toml) 

54 has_setup = os.path.isfile(setup_py) 

55 

56 if not has_pyproject and not has_setup: 

57 raise InstallationError( 

58 f"{req_name} does not appear to be a Python project: " 

59 f"neither 'setup.py' nor 'pyproject.toml' found." 

60 ) 

61 

62 if has_pyproject: 

63 with open(pyproject_toml, encoding="utf-8") as f: 

64 pp_toml = tomllib.loads(f.read()) 

65 build_system = pp_toml.get("build-system") 

66 else: 

67 build_system = None 

68 

69 if build_system is None: 

70 # In the absence of any explicit backend specification, we 

71 # assume the setuptools backend that most closely emulates the 

72 # traditional direct setup.py execution, and require wheel and 

73 # a version of setuptools that supports that backend. 

74 

75 build_system = { 

76 "requires": ["setuptools>=40.8.0"], 

77 "build-backend": "setuptools.build_meta:__legacy__", 

78 } 

79 

80 # Ensure that the build-system section in pyproject.toml conforms 

81 # to PEP 518. 

82 

83 # Specifying the build-system table but not the requires key is invalid 

84 if "requires" not in build_system: 

85 raise MissingPyProjectBuildRequires(package=req_name) 

86 

87 # Error out if requires is not a list of strings 

88 requires = build_system["requires"] 

89 if not _is_list_of_str(requires): 

90 raise InvalidPyProjectBuildRequires( 

91 package=req_name, 

92 reason="It is not a list of strings.", 

93 ) 

94 

95 # Each requirement must be valid as per PEP 508 

96 for requirement in requires: 

97 try: 

98 get_requirement(requirement) 

99 except InvalidRequirement as error: 

100 raise InvalidPyProjectBuildRequires( 

101 package=req_name, 

102 reason=f"It contains an invalid requirement: {requirement!r}", 

103 ) from error 

104 

105 backend = build_system.get("build-backend") 

106 backend_path = build_system.get("backend-path", []) 

107 check: list[str] = [] 

108 if backend is None: 

109 # If the user didn't specify a backend, we assume they want to use 

110 # the setuptools backend. But we can't be sure they have included 

111 # a version of setuptools which supplies the backend. So we 

112 # make a note to check that this requirement is present once 

113 # we have set up the environment. 

114 # This is quite a lot of work to check for a very specific case. But 

115 # the problem is, that case is potentially quite common - projects that 

116 # adopted PEP 518 early for the ability to specify requirements to 

117 # execute setup.py, but never considered needing to mention the build 

118 # tools themselves. The original PEP 518 code had a similar check (but 

119 # implemented in a different way). 

120 backend = "setuptools.build_meta:__legacy__" 

121 check = ["setuptools>=40.8.0"] 

122 

123 return BuildSystemDetails(requires, backend, check, backend_path)