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

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

474 statements  

1from __future__ import annotations 

2 

3import io 

4import itertools 

5import numbers 

6import os 

7import re 

8import sys 

9from glob import iglob 

10from pathlib import Path 

11from typing import TYPE_CHECKING, MutableMapping 

12 

13from more_itertools import partition, unique_everseen 

14from packaging.markers import InvalidMarker, Marker 

15from packaging.specifiers import InvalidSpecifier, SpecifierSet 

16from packaging.version import Version 

17 

18from . import ( 

19 _entry_points, 

20 _reqs, 

21 command as _, # noqa: F401 # imported for side-effects 

22) 

23from ._importlib import metadata 

24from .config import pyprojecttoml, setupcfg 

25from .discovery import ConfigDiscovery 

26from .monkey import get_unpatched 

27from .warnings import InformationOnly, SetuptoolsDeprecationWarning 

28 

29import distutils.cmd 

30import distutils.command 

31import distutils.core 

32import distutils.dist 

33import distutils.log 

34from distutils.debug import DEBUG 

35from distutils.errors import DistutilsOptionError, DistutilsSetupError 

36from distutils.fancy_getopt import translate_longopt 

37from distutils.util import strtobool 

38 

39__all__ = ['Distribution'] 

40 

41sequence = tuple, list 

42 

43 

44def check_importable(dist, attr, value): 

45 try: 

46 ep = metadata.EntryPoint(value=value, name=None, group=None) 

47 assert not ep.extras 

48 except (TypeError, ValueError, AttributeError, AssertionError) as e: 

49 raise DistutilsSetupError( 

50 "%r must be importable 'module:attrs' string (got %r)" % (attr, value) 

51 ) from e 

52 

53 

54def assert_string_list(dist, attr, value): 

55 """Verify that value is a string list""" 

56 try: 

57 # verify that value is a list or tuple to exclude unordered 

58 # or single-use iterables 

59 assert isinstance(value, sequence) 

60 # verify that elements of value are strings 

61 assert ''.join(value) != value 

62 except (TypeError, ValueError, AttributeError, AssertionError) as e: 

63 raise DistutilsSetupError( 

64 "%r must be a list of strings (got %r)" % (attr, value) 

65 ) from e 

66 

67 

68def check_nsp(dist, attr, value): 

69 """Verify that namespace packages are valid""" 

70 ns_packages = value 

71 assert_string_list(dist, attr, ns_packages) 

72 for nsp in ns_packages: 

73 if not dist.has_contents_for(nsp): 

74 raise DistutilsSetupError( 

75 "Distribution contains no modules or packages for " 

76 + "namespace package %r" % nsp 

77 ) 

78 parent, sep, child = nsp.rpartition('.') 

79 if parent and parent not in ns_packages: 

80 distutils.log.warn( 

81 "WARNING: %r is declared as a package namespace, but %r" 

82 " is not: please correct this in setup.py", 

83 nsp, 

84 parent, 

85 ) 

86 SetuptoolsDeprecationWarning.emit( 

87 "The namespace_packages parameter is deprecated.", 

88 "Please replace its usage with implicit namespaces (PEP 420).", 

89 see_docs="references/keywords.html#keyword-namespace-packages", 

90 # TODO: define due_date, it may break old packages that are no longer 

91 # maintained (e.g. sphinxcontrib extensions) when installed from source. 

92 # Warning officially introduced in May 2022, however the deprecation 

93 # was mentioned much earlier in the docs (May 2020, see #2149). 

94 ) 

95 

96 

97def check_extras(dist, attr, value): 

98 """Verify that extras_require mapping is valid""" 

99 try: 

100 list(itertools.starmap(_check_extra, value.items())) 

101 except (TypeError, ValueError, AttributeError) as e: 

102 raise DistutilsSetupError( 

103 "'extras_require' must be a dictionary whose values are " 

104 "strings or lists of strings containing valid project/version " 

105 "requirement specifiers." 

106 ) from e 

107 

108 

109def _check_extra(extra, reqs): 

110 name, sep, marker = extra.partition(':') 

111 try: 

112 _check_marker(marker) 

113 except InvalidMarker: 

114 msg = f"Invalid environment marker: {marker} ({extra!r})" 

115 raise DistutilsSetupError(msg) from None 

116 list(_reqs.parse(reqs)) 

117 

118 

119def _check_marker(marker): 

120 if not marker: 

121 return 

122 m = Marker(marker) 

123 m.evaluate() 

124 

125 

126def assert_bool(dist, attr, value): 

127 """Verify that value is True, False, 0, or 1""" 

128 if bool(value) != value: 

129 tmpl = "{attr!r} must be a boolean value (got {value!r})" 

130 raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) 

131 

132 

133def invalid_unless_false(dist, attr, value): 

134 if not value: 

135 DistDeprecationWarning.emit(f"{attr} is ignored.") 

136 # TODO: should there be a `due_date` here? 

137 return 

138 raise DistutilsSetupError(f"{attr} is invalid.") 

139 

140 

141def check_requirements(dist, attr, value): 

142 """Verify that install_requires is a valid requirements list""" 

143 try: 

144 list(_reqs.parse(value)) 

145 if isinstance(value, (dict, set)): 

146 raise TypeError("Unordered types are not allowed") 

147 except (TypeError, ValueError) as error: 

148 tmpl = ( 

149 "{attr!r} must be a string or list of strings " 

150 "containing valid project/version requirement specifiers; {error}" 

151 ) 

152 raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error 

153 

154 

155def check_specifier(dist, attr, value): 

156 """Verify that value is a valid version specifier""" 

157 try: 

158 SpecifierSet(value) 

159 except (InvalidSpecifier, AttributeError) as error: 

160 tmpl = "{attr!r} must be a string containing valid version specifiers; {error}" 

161 raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error 

162 

163 

164def check_entry_points(dist, attr, value): 

165 """Verify that entry_points map is parseable""" 

166 try: 

167 _entry_points.load(value) 

168 except Exception as e: 

169 raise DistutilsSetupError(e) from e 

170 

171 

172def check_package_data(dist, attr, value): 

173 """Verify that value is a dictionary of package names to glob lists""" 

174 if not isinstance(value, dict): 

175 raise DistutilsSetupError( 

176 "{!r} must be a dictionary mapping package names to lists of " 

177 "string wildcard patterns".format(attr) 

178 ) 

179 for k, v in value.items(): 

180 if not isinstance(k, str): 

181 raise DistutilsSetupError( 

182 "keys of {!r} dict must be strings (got {!r})".format(attr, k) 

183 ) 

184 assert_string_list(dist, 'values of {!r} dict'.format(attr), v) 

185 

186 

187def check_packages(dist, attr, value): 

188 for pkgname in value: 

189 if not re.match(r'\w+(\.\w+)*', pkgname): 

190 distutils.log.warn( 

191 "WARNING: %r not a valid package name; please use only " 

192 ".-separated package names in setup.py", 

193 pkgname, 

194 ) 

195 

196 

197if TYPE_CHECKING: 

198 from typing_extensions import TypeAlias 

199 

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

201 _Distribution: TypeAlias = distutils.core.Distribution 

202else: 

203 _Distribution = get_unpatched(distutils.core.Distribution) 

204 

205 

206class Distribution(_Distribution): 

207 """Distribution with support for tests and package data 

208 

209 This is an enhanced version of 'distutils.dist.Distribution' that 

210 effectively adds the following new optional keyword arguments to 'setup()': 

211 

212 'install_requires' -- a string or sequence of strings specifying project 

213 versions that the distribution requires when installed, in the format 

214 used by 'pkg_resources.require()'. They will be installed 

215 automatically when the package is installed. If you wish to use 

216 packages that are not available in PyPI, or want to give your users an 

217 alternate download location, you can add a 'find_links' option to the 

218 '[easy_install]' section of your project's 'setup.cfg' file, and then 

219 setuptools will scan the listed web pages for links that satisfy the 

220 requirements. 

221 

222 'extras_require' -- a dictionary mapping names of optional "extras" to the 

223 additional requirement(s) that using those extras incurs. For example, 

224 this:: 

225 

226 extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) 

227 

228 indicates that the distribution can optionally provide an extra 

229 capability called "reST", but it can only be used if docutils and 

230 reSTedit are installed. If the user installs your package using 

231 EasyInstall and requests one of your extras, the corresponding 

232 additional requirements will be installed if needed. 

233 

234 'package_data' -- a dictionary mapping package names to lists of filenames 

235 or globs to use to find data files contained in the named packages. 

236 If the dictionary has filenames or globs listed under '""' (the empty 

237 string), those names will be searched for in every package, in addition 

238 to any names for the specific package. Data files found using these 

239 names/globs will be installed along with the package, in the same 

240 location as the package. Note that globs are allowed to reference 

241 the contents of non-package subdirectories, as long as you use '/' as 

242 a path separator. (Globs are automatically converted to 

243 platform-specific paths at runtime.) 

244 

245 In addition to these new keywords, this class also has several new methods 

246 for manipulating the distribution's contents. For example, the 'include()' 

247 and 'exclude()' methods can be thought of as in-place add and subtract 

248 commands that add or remove packages, modules, extensions, and so on from 

249 the distribution. 

250 """ 

251 

252 _DISTUTILS_UNSUPPORTED_METADATA = { 

253 'long_description_content_type': lambda: None, 

254 'project_urls': dict, 

255 'provides_extras': dict, # behaves like an ordered set 

256 'license_file': lambda: None, 

257 'license_files': lambda: None, 

258 'install_requires': list, 

259 'extras_require': dict, 

260 } 

261 

262 # Used by build_py, editable_wheel and install_lib commands for legacy namespaces 

263 namespace_packages: list[str] #: :meta private: DEPRECATED 

264 

265 def __init__(self, attrs: MutableMapping | None = None) -> None: 

266 have_package_data = hasattr(self, "package_data") 

267 if not have_package_data: 

268 self.package_data: dict[str, list[str]] = {} 

269 attrs = attrs or {} 

270 self.dist_files: list[tuple[str, str, str]] = [] 

271 self.include_package_data: bool | None = None 

272 self.exclude_package_data: dict[str, list[str]] | None = None 

273 # Filter-out setuptools' specific options. 

274 self.src_root = attrs.pop("src_root", None) 

275 self.dependency_links = attrs.pop('dependency_links', []) 

276 self.setup_requires = attrs.pop('setup_requires', []) 

277 for ep in metadata.entry_points(group='distutils.setup_keywords'): 

278 vars(self).setdefault(ep.name, None) 

279 

280 metadata_only = set(self._DISTUTILS_UNSUPPORTED_METADATA) 

281 metadata_only -= {"install_requires", "extras_require"} 

282 dist_attrs = {k: v for k, v in attrs.items() if k not in metadata_only} 

283 _Distribution.__init__(self, dist_attrs) 

284 

285 # Private API (setuptools-use only, not restricted to Distribution) 

286 # Stores files that are referenced by the configuration and need to be in the 

287 # sdist (e.g. `version = file: VERSION.txt`) 

288 self._referenced_files: set[str] = set() 

289 

290 self.set_defaults = ConfigDiscovery(self) 

291 

292 self._set_metadata_defaults(attrs) 

293 

294 self.metadata.version = self._normalize_version(self.metadata.version) 

295 self._finalize_requires() 

296 

297 def _validate_metadata(self): 

298 required = {"name"} 

299 provided = { 

300 key 

301 for key in vars(self.metadata) 

302 if getattr(self.metadata, key, None) is not None 

303 } 

304 missing = required - provided 

305 

306 if missing: 

307 msg = f"Required package metadata is missing: {missing}" 

308 raise DistutilsSetupError(msg) 

309 

310 def _set_metadata_defaults(self, attrs): 

311 """ 

312 Fill-in missing metadata fields not supported by distutils. 

313 Some fields may have been set by other tools (e.g. pbr). 

314 Those fields (vars(self.metadata)) take precedence to 

315 supplied attrs. 

316 """ 

317 for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items(): 

318 vars(self.metadata).setdefault(option, attrs.get(option, default())) 

319 

320 @staticmethod 

321 def _normalize_version(version): 

322 from . import sic 

323 

324 if isinstance(version, numbers.Number): 

325 # Some people apparently take "version number" too literally :) 

326 version = str(version) 

327 elif isinstance(version, sic) or version is None: 

328 return version 

329 

330 normalized = str(Version(version)) 

331 if version != normalized: 

332 InformationOnly.emit(f"Normalizing '{version}' to '{normalized}'") 

333 return normalized 

334 return version 

335 

336 def _finalize_requires(self): 

337 """ 

338 Set `metadata.python_requires` and fix environment markers 

339 in `install_requires` and `extras_require`. 

340 """ 

341 if getattr(self, 'python_requires', None): 

342 self.metadata.python_requires = self.python_requires 

343 

344 self._normalize_requires() 

345 self.metadata.install_requires = self.install_requires 

346 self.metadata.extras_require = self.extras_require 

347 

348 if self.extras_require: 

349 for extra in self.extras_require.keys(): 

350 # Setuptools allows a weird "<name>:<env markers> syntax for extras 

351 extra = extra.split(':')[0] 

352 if extra: 

353 self.metadata.provides_extras.setdefault(extra) 

354 

355 def _normalize_requires(self): 

356 """Make sure requirement-related attributes exist and are normalized""" 

357 install_requires = getattr(self, "install_requires", None) or [] 

358 extras_require = getattr(self, "extras_require", None) or {} 

359 self.install_requires = list(map(str, _reqs.parse(install_requires))) 

360 self.extras_require = { 

361 k: list(map(str, _reqs.parse(v or []))) for k, v in extras_require.items() 

362 } 

363 

364 def _finalize_license_files(self) -> None: 

365 """Compute names of all license files which should be included.""" 

366 license_files: list[str] | None = self.metadata.license_files 

367 patterns: list[str] = license_files if license_files else [] 

368 

369 license_file: str | None = self.metadata.license_file 

370 if license_file and license_file not in patterns: 

371 patterns.append(license_file) 

372 

373 if license_files is None and license_file is None: 

374 # Default patterns match the ones wheel uses 

375 # See https://wheel.readthedocs.io/en/stable/user_guide.html 

376 # -> 'Including license files in the generated wheel file' 

377 patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*'] 

378 

379 self.metadata.license_files = list( 

380 unique_everseen(self._expand_patterns(patterns)) 

381 ) 

382 

383 @staticmethod 

384 def _expand_patterns(patterns): 

385 """ 

386 >>> list(Distribution._expand_patterns(['LICENSE'])) 

387 ['LICENSE'] 

388 >>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*'])) 

389 ['pyproject.toml', 'LICENSE'] 

390 """ 

391 return ( 

392 path 

393 for pattern in patterns 

394 for path in sorted(iglob(pattern)) 

395 if not path.endswith('~') and os.path.isfile(path) 

396 ) 

397 

398 # FIXME: 'Distribution._parse_config_files' is too complex (14) 

399 def _parse_config_files(self, filenames=None): # noqa: C901 

400 """ 

401 Adapted from distutils.dist.Distribution.parse_config_files, 

402 this method provides the same functionality in subtly-improved 

403 ways. 

404 """ 

405 from configparser import ConfigParser 

406 

407 # Ignore install directory options if we have a venv 

408 ignore_options = ( 

409 [] 

410 if sys.prefix == sys.base_prefix 

411 else [ 

412 'install-base', 

413 'install-platbase', 

414 'install-lib', 

415 'install-platlib', 

416 'install-purelib', 

417 'install-headers', 

418 'install-scripts', 

419 'install-data', 

420 'prefix', 

421 'exec-prefix', 

422 'home', 

423 'user', 

424 'root', 

425 ] 

426 ) 

427 

428 ignore_options = frozenset(ignore_options) 

429 

430 if filenames is None: 

431 filenames = self.find_config_files() 

432 

433 if DEBUG: 

434 self.announce("Distribution.parse_config_files():") 

435 

436 parser = ConfigParser() 

437 parser.optionxform = str 

438 for filename in filenames: 

439 with open(filename, encoding='utf-8') as reader: 

440 if DEBUG: 

441 self.announce(" reading {filename}".format(**locals())) 

442 parser.read_file(reader) 

443 for section in parser.sections(): 

444 options = parser.options(section) 

445 opt_dict = self.get_option_dict(section) 

446 

447 for opt in options: 

448 if opt == '__name__' or opt in ignore_options: 

449 continue 

450 

451 val = parser.get(section, opt) 

452 opt = self.warn_dash_deprecation(opt, section) 

453 opt = self.make_option_lowercase(opt, section) 

454 opt_dict[opt] = (filename, val) 

455 

456 # Make the ConfigParser forget everything (so we retain 

457 # the original filenames that options come from) 

458 parser.__init__() 

459 

460 if 'global' not in self.command_options: 

461 return 

462 

463 # If there was a "global" section in the config file, use it 

464 # to set Distribution options. 

465 

466 for opt, (src, val) in self.command_options['global'].items(): 

467 alias = self.negative_opt.get(opt) 

468 if alias: 

469 val = not strtobool(val) 

470 elif opt in ('verbose', 'dry_run'): # ugh! 

471 val = strtobool(val) 

472 

473 try: 

474 setattr(self, alias or opt, val) 

475 except ValueError as e: 

476 raise DistutilsOptionError(e) from e 

477 

478 def warn_dash_deprecation(self, opt, section): 

479 if section in ( 

480 'options.extras_require', 

481 'options.data_files', 

482 ): 

483 return opt 

484 

485 underscore_opt = opt.replace('-', '_') 

486 commands = list( 

487 itertools.chain( 

488 distutils.command.__all__, 

489 self._setuptools_commands(), 

490 ) 

491 ) 

492 if ( 

493 not section.startswith('options') 

494 and section != 'metadata' 

495 and section not in commands 

496 ): 

497 return underscore_opt 

498 

499 if '-' in opt: 

500 SetuptoolsDeprecationWarning.emit( 

501 "Invalid dash-separated options", 

502 f""" 

503 Usage of dash-separated {opt!r} will not be supported in future 

504 versions. Please use the underscore name {underscore_opt!r} instead. 

505 """, 

506 see_docs="userguide/declarative_config.html", 

507 due_date=(2024, 9, 26), 

508 # Warning initially introduced in 3 Mar 2021 

509 ) 

510 return underscore_opt 

511 

512 def _setuptools_commands(self): 

513 try: 

514 entry_points = metadata.distribution('setuptools').entry_points 

515 return {ep.name for ep in entry_points} # Avoid newer API for compatibility 

516 except metadata.PackageNotFoundError: 

517 # during bootstrapping, distribution doesn't exist 

518 return [] 

519 

520 def make_option_lowercase(self, opt, section): 

521 if section != 'metadata' or opt.islower(): 

522 return opt 

523 

524 lowercase_opt = opt.lower() 

525 SetuptoolsDeprecationWarning.emit( 

526 "Invalid uppercase configuration", 

527 f""" 

528 Usage of uppercase key {opt!r} in {section!r} will not be supported in 

529 future versions. Please use lowercase {lowercase_opt!r} instead. 

530 """, 

531 see_docs="userguide/declarative_config.html", 

532 due_date=(2024, 9, 26), 

533 # Warning initially introduced in 6 Mar 2021 

534 ) 

535 return lowercase_opt 

536 

537 # FIXME: 'Distribution._set_command_options' is too complex (14) 

538 def _set_command_options(self, command_obj, option_dict=None): # noqa: C901 

539 """ 

540 Set the options for 'command_obj' from 'option_dict'. Basically 

541 this means copying elements of a dictionary ('option_dict') to 

542 attributes of an instance ('command'). 

543 

544 'command_obj' must be a Command instance. If 'option_dict' is not 

545 supplied, uses the standard option dictionary for this command 

546 (from 'self.command_options'). 

547 

548 (Adopted from distutils.dist.Distribution._set_command_options) 

549 """ 

550 command_name = command_obj.get_command_name() 

551 if option_dict is None: 

552 option_dict = self.get_option_dict(command_name) 

553 

554 if DEBUG: 

555 self.announce(" setting options for '%s' command:" % command_name) 

556 for option, (source, value) in option_dict.items(): 

557 if DEBUG: 

558 self.announce(" %s = %s (from %s)" % (option, value, source)) 

559 try: 

560 bool_opts = [translate_longopt(o) for o in command_obj.boolean_options] 

561 except AttributeError: 

562 bool_opts = [] 

563 try: 

564 neg_opt = command_obj.negative_opt 

565 except AttributeError: 

566 neg_opt = {} 

567 

568 try: 

569 is_string = isinstance(value, str) 

570 if option in neg_opt and is_string: 

571 setattr(command_obj, neg_opt[option], not strtobool(value)) 

572 elif option in bool_opts and is_string: 

573 setattr(command_obj, option, strtobool(value)) 

574 elif hasattr(command_obj, option): 

575 setattr(command_obj, option, value) 

576 else: 

577 raise DistutilsOptionError( 

578 "error in %s: command '%s' has no such option '%s'" 

579 % (source, command_name, option) 

580 ) 

581 except ValueError as e: 

582 raise DistutilsOptionError(e) from e 

583 

584 def _get_project_config_files(self, filenames): 

585 """Add default file and split between INI and TOML""" 

586 tomlfiles = [] 

587 standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml") 

588 if filenames is not None: 

589 parts = partition(lambda f: Path(f).suffix == ".toml", filenames) 

590 filenames = list(parts[0]) # 1st element => predicate is False 

591 tomlfiles = list(parts[1]) # 2nd element => predicate is True 

592 elif standard_project_metadata.exists(): 

593 tomlfiles = [standard_project_metadata] 

594 return filenames, tomlfiles 

595 

596 def parse_config_files(self, filenames=None, ignore_option_errors=False): 

597 """Parses configuration files from various levels 

598 and loads configuration. 

599 """ 

600 inifiles, tomlfiles = self._get_project_config_files(filenames) 

601 

602 self._parse_config_files(filenames=inifiles) 

603 

604 setupcfg.parse_configuration( 

605 self, self.command_options, ignore_option_errors=ignore_option_errors 

606 ) 

607 for filename in tomlfiles: 

608 pyprojecttoml.apply_configuration(self, filename, ignore_option_errors) 

609 

610 self._finalize_requires() 

611 self._finalize_license_files() 

612 

613 def fetch_build_eggs(self, requires): 

614 """Resolve pre-setup requirements""" 

615 from .installer import _fetch_build_eggs 

616 

617 return _fetch_build_eggs(self, requires) 

618 

619 def finalize_options(self): 

620 """ 

621 Allow plugins to apply arbitrary operations to the 

622 distribution. Each hook may optionally define a 'order' 

623 to influence the order of execution. Smaller numbers 

624 go first and the default is 0. 

625 """ 

626 group = 'setuptools.finalize_distribution_options' 

627 

628 def by_order(hook): 

629 return getattr(hook, 'order', 0) 

630 

631 defined = metadata.entry_points(group=group) 

632 filtered = itertools.filterfalse(self._removed, defined) 

633 loaded = map(lambda e: e.load(), filtered) 

634 for ep in sorted(loaded, key=by_order): 

635 ep(self) 

636 

637 @staticmethod 

638 def _removed(ep): 

639 """ 

640 When removing an entry point, if metadata is loaded 

641 from an older version of Setuptools, that removed 

642 entry point will attempt to be loaded and will fail. 

643 See #2765 for more details. 

644 """ 

645 removed = { 

646 # removed 2021-09-05 

647 '2to3_doctests', 

648 } 

649 return ep.name in removed 

650 

651 def _finalize_setup_keywords(self): 

652 for ep in metadata.entry_points(group='distutils.setup_keywords'): 

653 value = getattr(self, ep.name, None) 

654 if value is not None: 

655 ep.load()(self, ep.name, value) 

656 

657 def get_egg_cache_dir(self): 

658 from . import windows_support 

659 

660 egg_cache_dir = os.path.join(os.curdir, '.eggs') 

661 if not os.path.exists(egg_cache_dir): 

662 os.mkdir(egg_cache_dir) 

663 windows_support.hide_file(egg_cache_dir) 

664 readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') 

665 with open(readme_txt_filename, 'w', encoding="utf-8") as f: 

666 f.write( 

667 'This directory contains eggs that were downloaded ' 

668 'by setuptools to build, test, and run plug-ins.\n\n' 

669 ) 

670 f.write( 

671 'This directory caches those eggs to prevent ' 

672 'repeated downloads.\n\n' 

673 ) 

674 f.write('However, it is safe to delete this directory.\n\n') 

675 

676 return egg_cache_dir 

677 

678 def fetch_build_egg(self, req): 

679 """Fetch an egg needed for building""" 

680 from .installer import fetch_build_egg 

681 

682 return fetch_build_egg(self, req) 

683 

684 def get_command_class(self, command): 

685 """Pluggable version of get_command_class()""" 

686 if command in self.cmdclass: 

687 return self.cmdclass[command] 

688 

689 # Special case bdist_wheel so it's never loaded from "wheel" 

690 if command == 'bdist_wheel': 

691 from .command.bdist_wheel import bdist_wheel 

692 

693 return bdist_wheel 

694 

695 eps = metadata.entry_points(group='distutils.commands', name=command) 

696 for ep in eps: 

697 self.cmdclass[command] = cmdclass = ep.load() 

698 return cmdclass 

699 else: 

700 return _Distribution.get_command_class(self, command) 

701 

702 def print_commands(self): 

703 for ep in metadata.entry_points(group='distutils.commands'): 

704 if ep.name not in self.cmdclass: 

705 cmdclass = ep.load() 

706 self.cmdclass[ep.name] = cmdclass 

707 return _Distribution.print_commands(self) 

708 

709 def get_command_list(self): 

710 for ep in metadata.entry_points(group='distutils.commands'): 

711 if ep.name not in self.cmdclass: 

712 cmdclass = ep.load() 

713 self.cmdclass[ep.name] = cmdclass 

714 return _Distribution.get_command_list(self) 

715 

716 def include(self, **attrs): 

717 """Add items to distribution that are named in keyword arguments 

718 

719 For example, 'dist.include(py_modules=["x"])' would add 'x' to 

720 the distribution's 'py_modules' attribute, if it was not already 

721 there. 

722 

723 Currently, this method only supports inclusion for attributes that are 

724 lists or tuples. If you need to add support for adding to other 

725 attributes in this or a subclass, you can add an '_include_X' method, 

726 where 'X' is the name of the attribute. The method will be called with 

727 the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' 

728 will try to call 'dist._include_foo({"bar":"baz"})', which can then 

729 handle whatever special inclusion logic is needed. 

730 """ 

731 for k, v in attrs.items(): 

732 include = getattr(self, '_include_' + k, None) 

733 if include: 

734 include(v) 

735 else: 

736 self._include_misc(k, v) 

737 

738 def exclude_package(self, package): 

739 """Remove packages, modules, and extensions in named package""" 

740 

741 pfx = package + '.' 

742 if self.packages: 

743 self.packages = [ 

744 p for p in self.packages if p != package and not p.startswith(pfx) 

745 ] 

746 

747 if self.py_modules: 

748 self.py_modules = [ 

749 p for p in self.py_modules if p != package and not p.startswith(pfx) 

750 ] 

751 

752 if self.ext_modules: 

753 self.ext_modules = [ 

754 p 

755 for p in self.ext_modules 

756 if p.name != package and not p.name.startswith(pfx) 

757 ] 

758 

759 def has_contents_for(self, package): 

760 """Return true if 'exclude_package(package)' would do something""" 

761 

762 pfx = package + '.' 

763 

764 for p in self.iter_distribution_names(): 

765 if p == package or p.startswith(pfx): 

766 return True 

767 

768 return False 

769 

770 def _exclude_misc(self, name, value): 

771 """Handle 'exclude()' for list/tuple attrs without a special handler""" 

772 if not isinstance(value, sequence): 

773 raise DistutilsSetupError( 

774 "%s: setting must be a list or tuple (%r)" % (name, value) 

775 ) 

776 try: 

777 old = getattr(self, name) 

778 except AttributeError as e: 

779 raise DistutilsSetupError("%s: No such distribution setting" % name) from e 

780 if old is not None and not isinstance(old, sequence): 

781 raise DistutilsSetupError( 

782 name + ": this setting cannot be changed via include/exclude" 

783 ) 

784 elif old: 

785 setattr(self, name, [item for item in old if item not in value]) 

786 

787 def _include_misc(self, name, value): 

788 """Handle 'include()' for list/tuple attrs without a special handler""" 

789 

790 if not isinstance(value, sequence): 

791 raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value)) 

792 try: 

793 old = getattr(self, name) 

794 except AttributeError as e: 

795 raise DistutilsSetupError("%s: No such distribution setting" % name) from e 

796 if old is None: 

797 setattr(self, name, value) 

798 elif not isinstance(old, sequence): 

799 raise DistutilsSetupError( 

800 name + ": this setting cannot be changed via include/exclude" 

801 ) 

802 else: 

803 new = [item for item in value if item not in old] 

804 setattr(self, name, old + new) 

805 

806 def exclude(self, **attrs): 

807 """Remove items from distribution that are named in keyword arguments 

808 

809 For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from 

810 the distribution's 'py_modules' attribute. Excluding packages uses 

811 the 'exclude_package()' method, so all of the package's contained 

812 packages, modules, and extensions are also excluded. 

813 

814 Currently, this method only supports exclusion from attributes that are 

815 lists or tuples. If you need to add support for excluding from other 

816 attributes in this or a subclass, you can add an '_exclude_X' method, 

817 where 'X' is the name of the attribute. The method will be called with 

818 the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' 

819 will try to call 'dist._exclude_foo({"bar":"baz"})', which can then 

820 handle whatever special exclusion logic is needed. 

821 """ 

822 for k, v in attrs.items(): 

823 exclude = getattr(self, '_exclude_' + k, None) 

824 if exclude: 

825 exclude(v) 

826 else: 

827 self._exclude_misc(k, v) 

828 

829 def _exclude_packages(self, packages): 

830 if not isinstance(packages, sequence): 

831 raise DistutilsSetupError( 

832 "packages: setting must be a list or tuple (%r)" % (packages,) 

833 ) 

834 list(map(self.exclude_package, packages)) 

835 

836 def _parse_command_opts(self, parser, args): 

837 # Remove --with-X/--without-X options when processing command args 

838 self.global_options = self.__class__.global_options 

839 self.negative_opt = self.__class__.negative_opt 

840 

841 # First, expand any aliases 

842 command = args[0] 

843 aliases = self.get_option_dict('aliases') 

844 while command in aliases: 

845 src, alias = aliases[command] 

846 del aliases[command] # ensure each alias can expand only once! 

847 import shlex 

848 

849 args[:1] = shlex.split(alias, True) 

850 command = args[0] 

851 

852 nargs = _Distribution._parse_command_opts(self, parser, args) 

853 

854 # Handle commands that want to consume all remaining arguments 

855 cmd_class = self.get_command_class(command) 

856 if getattr(cmd_class, 'command_consumes_arguments', None): 

857 self.get_option_dict(command)['args'] = ("command line", nargs) 

858 if nargs is not None: 

859 return [] 

860 

861 return nargs 

862 

863 def get_cmdline_options(self): 

864 """Return a '{cmd: {opt:val}}' map of all command-line options 

865 

866 Option names are all long, but do not include the leading '--', and 

867 contain dashes rather than underscores. If the option doesn't take 

868 an argument (e.g. '--quiet'), the 'val' is 'None'. 

869 

870 Note that options provided by config files are intentionally excluded. 

871 """ 

872 

873 d = {} 

874 

875 for cmd, opts in self.command_options.items(): 

876 for opt, (src, val) in opts.items(): 

877 if src != "command line": 

878 continue 

879 

880 opt = opt.replace('_', '-') 

881 

882 if val == 0: 

883 cmdobj = self.get_command_obj(cmd) 

884 neg_opt = self.negative_opt.copy() 

885 neg_opt.update(getattr(cmdobj, 'negative_opt', {})) 

886 for neg, pos in neg_opt.items(): 

887 if pos == opt: 

888 opt = neg 

889 val = None 

890 break 

891 else: 

892 raise AssertionError("Shouldn't be able to get here") 

893 

894 elif val == 1: 

895 val = None 

896 

897 d.setdefault(cmd, {})[opt] = val 

898 

899 return d 

900 

901 def iter_distribution_names(self): 

902 """Yield all packages, modules, and extension names in distribution""" 

903 

904 yield from self.packages or () 

905 

906 yield from self.py_modules or () 

907 

908 for ext in self.ext_modules or (): 

909 if isinstance(ext, tuple): 

910 name, buildinfo = ext 

911 else: 

912 name = ext.name 

913 if name.endswith('module'): 

914 name = name[:-6] 

915 yield name 

916 

917 def handle_display_options(self, option_order): 

918 """If there were any non-global "display-only" options 

919 (--help-commands or the metadata display options) on the command 

920 line, display the requested info and return true; else return 

921 false. 

922 """ 

923 import sys 

924 

925 if self.help_commands: 

926 return _Distribution.handle_display_options(self, option_order) 

927 

928 # Stdout may be StringIO (e.g. in tests) 

929 if not isinstance(sys.stdout, io.TextIOWrapper): 

930 return _Distribution.handle_display_options(self, option_order) 

931 

932 # Don't wrap stdout if utf-8 is already the encoding. Provides 

933 # workaround for #334. 

934 if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): 

935 return _Distribution.handle_display_options(self, option_order) 

936 

937 # Print metadata in UTF-8 no matter the platform 

938 encoding = sys.stdout.encoding 

939 sys.stdout.reconfigure(encoding='utf-8') 

940 try: 

941 return _Distribution.handle_display_options(self, option_order) 

942 finally: 

943 sys.stdout.reconfigure(encoding=encoding) 

944 

945 def run_command(self, command): 

946 self.set_defaults() 

947 # Postpone defaults until all explicit configuration is considered 

948 # (setup() args, config files, command line and plugins) 

949 

950 super().run_command(command) 

951 

952 

953class DistDeprecationWarning(SetuptoolsDeprecationWarning): 

954 """Class for warning about deprecations in dist in 

955 setuptools. Not ignored by default, unlike DeprecationWarning."""