Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/setuptools/__init__.py: 45%

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

115 statements  

1"""Extensions to the 'distutils' for large or complex distributions""" 

2# mypy: disable_error_code=override 

3# Command.reinitialize_command has an extra **kw param that distutils doesn't have 

4# Can't disable on the exact line because distutils doesn't exists on Python 3.12 

5# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any, 

6# and a [unused-ignore] to be raised on 3.12+ 

7 

8from __future__ import annotations 

9 

10import functools 

11import os 

12import re 

13import sys 

14from abc import abstractmethod 

15from collections.abc import Mapping 

16from typing import TYPE_CHECKING, TypeVar, overload 

17 

18sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip 

19# workaround for #4476 

20sys.modules.pop('backports', None) 

21 

22import _distutils_hack.override # noqa: F401 

23 

24from . import logging, monkey 

25from .depends import Require 

26from .discovery import PackageFinder, PEP420PackageFinder 

27from .dist import Distribution 

28from .extension import Extension 

29from .version import __version__ as __version__ 

30from .warnings import SetuptoolsDeprecationWarning 

31 

32import distutils.core 

33from distutils.errors import DistutilsOptionError 

34 

35__all__ = [ 

36 'setup', 

37 'Distribution', 

38 'Command', 

39 'Extension', 

40 'Require', 

41 'SetuptoolsDeprecationWarning', 

42 'find_packages', 

43 'find_namespace_packages', 

44] 

45 

46_CommandT = TypeVar("_CommandT", bound="_Command") 

47 

48bootstrap_install_from = None 

49 

50find_packages = PackageFinder.find 

51find_namespace_packages = PEP420PackageFinder.find 

52 

53 

54def _install_setup_requires(attrs): 

55 # Note: do not use `setuptools.Distribution` directly, as 

56 # our PEP 517 backend patch `distutils.core.Distribution`. 

57 class MinimalDistribution(distutils.core.Distribution): 

58 """ 

59 A minimal version of a distribution for supporting the 

60 fetch_build_eggs interface. 

61 """ 

62 

63 def __init__(self, attrs: Mapping[str, object]): 

64 _incl = 'dependency_links', 'setup_requires' 

65 filtered = {k: attrs[k] for k in set(_incl) & set(attrs)} 

66 super().__init__(filtered) 

67 # Prevent accidentally triggering discovery with incomplete set of attrs 

68 self.set_defaults._disable() 

69 

70 def _get_project_config_files(self, filenames=None): 

71 """Ignore ``pyproject.toml``, they are not related to setup_requires""" 

72 try: 

73 cfg, toml = super()._split_standard_project_metadata(filenames) 

74 except Exception: 

75 return filenames, () 

76 return cfg, () 

77 

78 def finalize_options(self): 

79 """ 

80 Disable finalize_options to avoid building the working set. 

81 Ref #2158. 

82 """ 

83 

84 dist = MinimalDistribution(attrs) 

85 

86 # Honor setup.cfg's options. 

87 dist.parse_config_files(ignore_option_errors=True) 

88 if dist.setup_requires: 

89 _fetch_build_eggs(dist) 

90 

91 

92def _fetch_build_eggs(dist): 

93 try: 

94 dist.fetch_build_eggs(dist.setup_requires) 

95 except Exception as ex: 

96 msg = """ 

97 It is possible a package already installed in your system 

98 contains an version that is invalid according to PEP 440. 

99 You can try `pip install --use-pep517` as a workaround for this problem, 

100 or rely on a new virtual environment. 

101 

102 If the problem refers to a package that is not installed yet, 

103 please contact that package's maintainers or distributors. 

104 """ 

105 if "InvalidVersion" in ex.__class__.__name__: 

106 if hasattr(ex, "add_note"): 

107 ex.add_note(msg) # PEP 678 

108 else: 

109 dist.announce(f"\n{msg}\n") 

110 raise 

111 

112 

113def setup(**attrs): 

114 logging.configure() 

115 # Make sure we have any requirements needed to interpret 'attrs'. 

116 _install_setup_requires(attrs) 

117 return distutils.core.setup(**attrs) 

118 

119 

120setup.__doc__ = distutils.core.setup.__doc__ 

121 

122if TYPE_CHECKING: 

123 from typing_extensions import TypeAlias 

124 

125 # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 

126 _Command: TypeAlias = distutils.core.Command 

127else: 

128 _Command = monkey.get_unpatched(distutils.core.Command) 

129 

130 

131class Command(_Command): 

132 """ 

133 Setuptools internal actions are organized using a *command design pattern*. 

134 This means that each action (or group of closely related actions) executed during 

135 the build should be implemented as a ``Command`` subclass. 

136 

137 These commands are abstractions and do not necessarily correspond to a command that 

138 can (or should) be executed via a terminal, in a CLI fashion (although historically 

139 they would). 

140 

141 When creating a new command from scratch, custom defined classes **SHOULD** inherit 

142 from ``setuptools.Command`` and implement a few mandatory methods. 

143 Between these mandatory methods, are listed: 

144 :meth:`initialize_options`, :meth:`finalize_options` and :meth:`run`. 

145 

146 A useful analogy for command classes is to think of them as subroutines with local 

147 variables called "options". The options are "declared" in :meth:`initialize_options` 

148 and "defined" (given their final values, aka "finalized") in :meth:`finalize_options`, 

149 both of which must be defined by every command class. The "body" of the subroutine, 

150 (where it does all the work) is the :meth:`run` method. 

151 Between :meth:`initialize_options` and :meth:`finalize_options`, ``setuptools`` may set 

152 the values for options/attributes based on user's input (or circumstance), 

153 which means that the implementation should be careful to not overwrite values in 

154 :meth:`finalize_options` unless necessary. 

155 

156 Please note that other commands (or other parts of setuptools) may also overwrite 

157 the values of the command's options/attributes multiple times during the build 

158 process. 

159 Therefore it is important to consistently implement :meth:`initialize_options` and 

160 :meth:`finalize_options`. For example, all derived attributes (or attributes that 

161 depend on the value of other attributes) **SHOULD** be recomputed in 

162 :meth:`finalize_options`. 

163 

164 When overwriting existing commands, custom defined classes **MUST** abide by the 

165 same APIs implemented by the original class. They also **SHOULD** inherit from the 

166 original class. 

167 """ 

168 

169 command_consumes_arguments = False 

170 distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution 

171 

172 def __init__(self, dist: Distribution, **kw): 

173 """ 

174 Construct the command for dist, updating 

175 vars(self) with any keyword parameters. 

176 """ 

177 super().__init__(dist) 

178 vars(self).update(kw) 

179 

180 def _ensure_stringlike(self, option, what, default=None): 

181 val = getattr(self, option) 

182 if val is None: 

183 setattr(self, option, default) 

184 return default 

185 elif not isinstance(val, str): 

186 raise DistutilsOptionError( 

187 "'%s' must be a %s (got `%s`)" % (option, what, val) 

188 ) 

189 return val 

190 

191 def ensure_string_list(self, option): 

192 r"""Ensure that 'option' is a list of strings. If 'option' is 

193 currently a string, we split it either on /,\s*/ or /\s+/, so 

194 "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become 

195 ["foo", "bar", "baz"]. 

196 

197 .. 

198 TODO: This method seems to be similar to the one in ``distutils.cmd`` 

199 Probably it is just here for backward compatibility with old Python versions? 

200 

201 :meta private: 

202 """ 

203 val = getattr(self, option) 

204 if val is None: 

205 return 

206 elif isinstance(val, str): 

207 setattr(self, option, re.split(r',\s*|\s+', val)) 

208 else: 

209 if isinstance(val, list): 

210 ok = all(isinstance(v, str) for v in val) 

211 else: 

212 ok = False 

213 if not ok: 

214 raise DistutilsOptionError( 

215 "'%s' must be a list of strings (got %r)" % (option, val) 

216 ) 

217 

218 @overload 

219 def reinitialize_command( 

220 self, command: str, reinit_subcommands: bool = False, **kw 

221 ) -> _Command: ... 

222 @overload 

223 def reinitialize_command( 

224 self, command: _CommandT, reinit_subcommands: bool = False, **kw 

225 ) -> _CommandT: ... 

226 def reinitialize_command( 

227 self, command: str | _Command, reinit_subcommands: bool = False, **kw 

228 ) -> _Command: 

229 cmd = _Command.reinitialize_command(self, command, reinit_subcommands) 

230 vars(cmd).update(kw) 

231 return cmd 

232 

233 @abstractmethod 

234 def initialize_options(self) -> None: 

235 """ 

236 Set or (reset) all options/attributes/caches used by the command 

237 to their default values. Note that these values may be overwritten during 

238 the build. 

239 """ 

240 raise NotImplementedError 

241 

242 @abstractmethod 

243 def finalize_options(self) -> None: 

244 """ 

245 Set final values for all options/attributes used by the command. 

246 Most of the time, each option/attribute/cache should only be set if it does not 

247 have any value yet (e.g. ``if self.attr is None: self.attr = val``). 

248 """ 

249 raise NotImplementedError 

250 

251 @abstractmethod 

252 def run(self) -> None: 

253 """ 

254 Execute the actions intended by the command. 

255 (Side effects **SHOULD** only take place when :meth:`run` is executed, 

256 for example, creating new files or writing to the terminal output). 

257 """ 

258 raise NotImplementedError 

259 

260 

261def _find_all_simple(path): 

262 """ 

263 Find all files under 'path' 

264 """ 

265 results = ( 

266 os.path.join(base, file) 

267 for base, dirs, files in os.walk(path, followlinks=True) 

268 for file in files 

269 ) 

270 return filter(os.path.isfile, results) 

271 

272 

273def findall(dir=os.curdir): 

274 """ 

275 Find all files under 'dir' and return the list of full filenames. 

276 Unless dir is '.', return full filenames with dir prepended. 

277 """ 

278 files = _find_all_simple(dir) 

279 if dir == os.curdir: 

280 make_rel = functools.partial(os.path.relpath, start=dir) 

281 files = map(make_rel, files) 

282 return list(files) 

283 

284 

285class sic(str): 

286 """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" 

287 

288 

289# Apply monkey patches 

290monkey.patch_all()