Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
keewenaw
GitHub Repository: keewenaw/ethereum-wallet-cracker
Path: blob/main/test/lib/python3.9/site-packages/setuptools/config/setupcfg.py
4799 views
1
"""Load setuptools configuration from ``setup.cfg`` files"""
2
import os
3
4
import warnings
5
import functools
6
from collections import defaultdict
7
from functools import partial
8
from functools import wraps
9
from typing import (TYPE_CHECKING, Callable, Any, Dict, Generic, Iterable, List,
10
Optional, Tuple, TypeVar, Union)
11
12
from distutils.errors import DistutilsOptionError, DistutilsFileError
13
from setuptools.extern.packaging.version import Version, InvalidVersion
14
from setuptools.extern.packaging.specifiers import SpecifierSet
15
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
16
17
from . import expand
18
19
if TYPE_CHECKING:
20
from setuptools.dist import Distribution # noqa
21
from distutils.dist import DistributionMetadata # noqa
22
23
_Path = Union[str, os.PathLike]
24
SingleCommandOptions = Dict["str", Tuple["str", Any]]
25
"""Dict that associate the name of the options of a particular command to a
26
tuple. The first element of the tuple indicates the origin of the option value
27
(e.g. the name of the configuration file where it was read from),
28
while the second element of the tuple is the option value itself
29
"""
30
AllCommandOptions = Dict["str", SingleCommandOptions] # cmd name => its options
31
Target = TypeVar("Target", bound=Union["Distribution", "DistributionMetadata"])
32
33
34
def read_configuration(
35
filepath: _Path,
36
find_others=False,
37
ignore_option_errors=False
38
) -> dict:
39
"""Read given configuration file and returns options from it as a dict.
40
41
:param str|unicode filepath: Path to configuration file
42
to get options from.
43
44
:param bool find_others: Whether to search for other configuration files
45
which could be on in various places.
46
47
:param bool ignore_option_errors: Whether to silently ignore
48
options, values of which could not be resolved (e.g. due to exceptions
49
in directives such as file:, attr:, etc.).
50
If False exceptions are propagated as expected.
51
52
:rtype: dict
53
"""
54
from setuptools.dist import Distribution
55
56
dist = Distribution()
57
filenames = dist.find_config_files() if find_others else []
58
handlers = _apply(dist, filepath, filenames, ignore_option_errors)
59
return configuration_to_dict(handlers)
60
61
62
def apply_configuration(dist: "Distribution", filepath: _Path) -> "Distribution":
63
"""Apply the configuration from a ``setup.cfg`` file into an existing
64
distribution object.
65
"""
66
_apply(dist, filepath)
67
dist._finalize_requires()
68
return dist
69
70
71
def _apply(
72
dist: "Distribution", filepath: _Path,
73
other_files: Iterable[_Path] = (),
74
ignore_option_errors: bool = False,
75
) -> Tuple["ConfigHandler", ...]:
76
"""Read configuration from ``filepath`` and applies to the ``dist`` object."""
77
from setuptools.dist import _Distribution
78
79
filepath = os.path.abspath(filepath)
80
81
if not os.path.isfile(filepath):
82
raise DistutilsFileError('Configuration file %s does not exist.' % filepath)
83
84
current_directory = os.getcwd()
85
os.chdir(os.path.dirname(filepath))
86
filenames = [*other_files, filepath]
87
88
try:
89
_Distribution.parse_config_files(dist, filenames=filenames)
90
handlers = parse_configuration(
91
dist, dist.command_options, ignore_option_errors=ignore_option_errors
92
)
93
dist._finalize_license_files()
94
finally:
95
os.chdir(current_directory)
96
97
return handlers
98
99
100
def _get_option(target_obj: Target, key: str):
101
"""
102
Given a target object and option key, get that option from
103
the target object, either through a get_{key} method or
104
from an attribute directly.
105
"""
106
getter_name = 'get_{key}'.format(**locals())
107
by_attribute = functools.partial(getattr, target_obj, key)
108
getter = getattr(target_obj, getter_name, by_attribute)
109
return getter()
110
111
112
def configuration_to_dict(handlers: Tuple["ConfigHandler", ...]) -> dict:
113
"""Returns configuration data gathered by given handlers as a dict.
114
115
:param list[ConfigHandler] handlers: Handlers list,
116
usually from parse_configuration()
117
118
:rtype: dict
119
"""
120
config_dict: dict = defaultdict(dict)
121
122
for handler in handlers:
123
for option in handler.set_options:
124
value = _get_option(handler.target_obj, option)
125
config_dict[handler.section_prefix][option] = value
126
127
return config_dict
128
129
130
def parse_configuration(
131
distribution: "Distribution",
132
command_options: AllCommandOptions,
133
ignore_option_errors=False
134
) -> Tuple["ConfigMetadataHandler", "ConfigOptionsHandler"]:
135
"""Performs additional parsing of configuration options
136
for a distribution.
137
138
Returns a list of used option handlers.
139
140
:param Distribution distribution:
141
:param dict command_options:
142
:param bool ignore_option_errors: Whether to silently ignore
143
options, values of which could not be resolved (e.g. due to exceptions
144
in directives such as file:, attr:, etc.).
145
If False exceptions are propagated as expected.
146
:rtype: list
147
"""
148
with expand.EnsurePackagesDiscovered(distribution) as ensure_discovered:
149
options = ConfigOptionsHandler(
150
distribution,
151
command_options,
152
ignore_option_errors,
153
ensure_discovered,
154
)
155
156
options.parse()
157
if not distribution.package_dir:
158
distribution.package_dir = options.package_dir # Filled by `find_packages`
159
160
meta = ConfigMetadataHandler(
161
distribution.metadata,
162
command_options,
163
ignore_option_errors,
164
ensure_discovered,
165
distribution.package_dir,
166
distribution.src_root,
167
)
168
meta.parse()
169
170
return meta, options
171
172
173
class ConfigHandler(Generic[Target]):
174
"""Handles metadata supplied in configuration files."""
175
176
section_prefix: str
177
"""Prefix for config sections handled by this handler.
178
Must be provided by class heirs.
179
180
"""
181
182
aliases: Dict[str, str] = {}
183
"""Options aliases.
184
For compatibility with various packages. E.g.: d2to1 and pbr.
185
Note: `-` in keys is replaced with `_` by config parser.
186
187
"""
188
189
def __init__(
190
self,
191
target_obj: Target,
192
options: AllCommandOptions,
193
ignore_option_errors,
194
ensure_discovered: expand.EnsurePackagesDiscovered,
195
):
196
sections: AllCommandOptions = {}
197
198
section_prefix = self.section_prefix
199
for section_name, section_options in options.items():
200
if not section_name.startswith(section_prefix):
201
continue
202
203
section_name = section_name.replace(section_prefix, '').strip('.')
204
sections[section_name] = section_options
205
206
self.ignore_option_errors = ignore_option_errors
207
self.target_obj = target_obj
208
self.sections = sections
209
self.set_options: List[str] = []
210
self.ensure_discovered = ensure_discovered
211
212
@property
213
def parsers(self):
214
"""Metadata item name to parser function mapping."""
215
raise NotImplementedError(
216
'%s must provide .parsers property' % self.__class__.__name__
217
)
218
219
def __setitem__(self, option_name, value):
220
unknown = tuple()
221
target_obj = self.target_obj
222
223
# Translate alias into real name.
224
option_name = self.aliases.get(option_name, option_name)
225
226
current_value = getattr(target_obj, option_name, unknown)
227
228
if current_value is unknown:
229
raise KeyError(option_name)
230
231
if current_value:
232
# Already inhabited. Skipping.
233
return
234
235
skip_option = False
236
parser = self.parsers.get(option_name)
237
if parser:
238
try:
239
value = parser(value)
240
241
except Exception:
242
skip_option = True
243
if not self.ignore_option_errors:
244
raise
245
246
if skip_option:
247
return
248
249
setter = getattr(target_obj, 'set_%s' % option_name, None)
250
if setter is None:
251
setattr(target_obj, option_name, value)
252
else:
253
setter(value)
254
255
self.set_options.append(option_name)
256
257
@classmethod
258
def _parse_list(cls, value, separator=','):
259
"""Represents value as a list.
260
261
Value is split either by separator (defaults to comma) or by lines.
262
263
:param value:
264
:param separator: List items separator character.
265
:rtype: list
266
"""
267
if isinstance(value, list): # _get_parser_compound case
268
return value
269
270
if '\n' in value:
271
value = value.splitlines()
272
else:
273
value = value.split(separator)
274
275
return [chunk.strip() for chunk in value if chunk.strip()]
276
277
@classmethod
278
def _parse_dict(cls, value):
279
"""Represents value as a dict.
280
281
:param value:
282
:rtype: dict
283
"""
284
separator = '='
285
result = {}
286
for line in cls._parse_list(value):
287
key, sep, val = line.partition(separator)
288
if sep != separator:
289
raise DistutilsOptionError(
290
'Unable to parse option value to dict: %s' % value
291
)
292
result[key.strip()] = val.strip()
293
294
return result
295
296
@classmethod
297
def _parse_bool(cls, value):
298
"""Represents value as boolean.
299
300
:param value:
301
:rtype: bool
302
"""
303
value = value.lower()
304
return value in ('1', 'true', 'yes')
305
306
@classmethod
307
def _exclude_files_parser(cls, key):
308
"""Returns a parser function to make sure field inputs
309
are not files.
310
311
Parses a value after getting the key so error messages are
312
more informative.
313
314
:param key:
315
:rtype: callable
316
"""
317
318
def parser(value):
319
exclude_directive = 'file:'
320
if value.startswith(exclude_directive):
321
raise ValueError(
322
'Only strings are accepted for the {0} field, '
323
'files are not accepted'.format(key)
324
)
325
return value
326
327
return parser
328
329
@classmethod
330
def _parse_file(cls, value, root_dir: _Path):
331
"""Represents value as a string, allowing including text
332
from nearest files using `file:` directive.
333
334
Directive is sandboxed and won't reach anything outside
335
directory with setup.py.
336
337
Examples:
338
file: README.rst, CHANGELOG.md, src/file.txt
339
340
:param str value:
341
:rtype: str
342
"""
343
include_directive = 'file:'
344
345
if not isinstance(value, str):
346
return value
347
348
if not value.startswith(include_directive):
349
return value
350
351
spec = value[len(include_directive) :]
352
filepaths = (path.strip() for path in spec.split(','))
353
return expand.read_files(filepaths, root_dir)
354
355
def _parse_attr(self, value, package_dir, root_dir: _Path):
356
"""Represents value as a module attribute.
357
358
Examples:
359
attr: package.attr
360
attr: package.module.attr
361
362
:param str value:
363
:rtype: str
364
"""
365
attr_directive = 'attr:'
366
if not value.startswith(attr_directive):
367
return value
368
369
attr_desc = value.replace(attr_directive, '')
370
371
# Make sure package_dir is populated correctly, so `attr:` directives can work
372
package_dir.update(self.ensure_discovered.package_dir)
373
return expand.read_attr(attr_desc, package_dir, root_dir)
374
375
@classmethod
376
def _get_parser_compound(cls, *parse_methods):
377
"""Returns parser function to represents value as a list.
378
379
Parses a value applying given methods one after another.
380
381
:param parse_methods:
382
:rtype: callable
383
"""
384
385
def parse(value):
386
parsed = value
387
388
for method in parse_methods:
389
parsed = method(parsed)
390
391
return parsed
392
393
return parse
394
395
@classmethod
396
def _parse_section_to_dict(cls, section_options, values_parser=None):
397
"""Parses section options into a dictionary.
398
399
Optionally applies a given parser to values.
400
401
:param dict section_options:
402
:param callable values_parser:
403
:rtype: dict
404
"""
405
value = {}
406
values_parser = values_parser or (lambda val: val)
407
for key, (_, val) in section_options.items():
408
value[key] = values_parser(val)
409
return value
410
411
def parse_section(self, section_options):
412
"""Parses configuration file section.
413
414
:param dict section_options:
415
"""
416
for (name, (_, value)) in section_options.items():
417
try:
418
self[name] = value
419
420
except KeyError:
421
pass # Keep silent for a new option may appear anytime.
422
423
def parse(self):
424
"""Parses configuration file items from one
425
or more related sections.
426
427
"""
428
for section_name, section_options in self.sections.items():
429
430
method_postfix = ''
431
if section_name: # [section.option] variant
432
method_postfix = '_%s' % section_name
433
434
section_parser_method: Optional[Callable] = getattr(
435
self,
436
# Dots in section names are translated into dunderscores.
437
('parse_section%s' % method_postfix).replace('.', '__'),
438
None,
439
)
440
441
if section_parser_method is None:
442
raise DistutilsOptionError(
443
'Unsupported distribution option section: [%s.%s]'
444
% (self.section_prefix, section_name)
445
)
446
447
section_parser_method(section_options)
448
449
def _deprecated_config_handler(self, func, msg, warning_class):
450
"""this function will wrap around parameters that are deprecated
451
452
:param msg: deprecation message
453
:param warning_class: class of warning exception to be raised
454
:param func: function to be wrapped around
455
"""
456
457
@wraps(func)
458
def config_handler(*args, **kwargs):
459
warnings.warn(msg, warning_class)
460
return func(*args, **kwargs)
461
462
return config_handler
463
464
465
class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
466
467
section_prefix = 'metadata'
468
469
aliases = {
470
'home_page': 'url',
471
'summary': 'description',
472
'classifier': 'classifiers',
473
'platform': 'platforms',
474
}
475
476
strict_mode = False
477
"""We need to keep it loose, to be partially compatible with
478
`pbr` and `d2to1` packages which also uses `metadata` section.
479
480
"""
481
482
def __init__(
483
self,
484
target_obj: "DistributionMetadata",
485
options: AllCommandOptions,
486
ignore_option_errors: bool,
487
ensure_discovered: expand.EnsurePackagesDiscovered,
488
package_dir: Optional[dict] = None,
489
root_dir: _Path = os.curdir
490
):
491
super().__init__(target_obj, options, ignore_option_errors, ensure_discovered)
492
self.package_dir = package_dir
493
self.root_dir = root_dir
494
495
@property
496
def parsers(self):
497
"""Metadata item name to parser function mapping."""
498
parse_list = self._parse_list
499
parse_file = partial(self._parse_file, root_dir=self.root_dir)
500
parse_dict = self._parse_dict
501
exclude_files_parser = self._exclude_files_parser
502
503
return {
504
'platforms': parse_list,
505
'keywords': parse_list,
506
'provides': parse_list,
507
'requires': self._deprecated_config_handler(
508
parse_list,
509
"The requires parameter is deprecated, please use "
510
"install_requires for runtime dependencies.",
511
SetuptoolsDeprecationWarning,
512
),
513
'obsoletes': parse_list,
514
'classifiers': self._get_parser_compound(parse_file, parse_list),
515
'license': exclude_files_parser('license'),
516
'license_file': self._deprecated_config_handler(
517
exclude_files_parser('license_file'),
518
"The license_file parameter is deprecated, "
519
"use license_files instead.",
520
SetuptoolsDeprecationWarning,
521
),
522
'license_files': parse_list,
523
'description': parse_file,
524
'long_description': parse_file,
525
'version': self._parse_version,
526
'project_urls': parse_dict,
527
}
528
529
def _parse_version(self, value):
530
"""Parses `version` option value.
531
532
:param value:
533
:rtype: str
534
535
"""
536
version = self._parse_file(value, self.root_dir)
537
538
if version != value:
539
version = version.strip()
540
# Be strict about versions loaded from file because it's easy to
541
# accidentally include newlines and other unintended content
542
try:
543
Version(version)
544
except InvalidVersion:
545
tmpl = (
546
'Version loaded from {value} does not '
547
'comply with PEP 440: {version}'
548
)
549
raise DistutilsOptionError(tmpl.format(**locals()))
550
551
return version
552
553
return expand.version(self._parse_attr(value, self.package_dir, self.root_dir))
554
555
556
class ConfigOptionsHandler(ConfigHandler["Distribution"]):
557
558
section_prefix = 'options'
559
560
def __init__(
561
self,
562
target_obj: "Distribution",
563
options: AllCommandOptions,
564
ignore_option_errors: bool,
565
ensure_discovered: expand.EnsurePackagesDiscovered,
566
):
567
super().__init__(target_obj, options, ignore_option_errors, ensure_discovered)
568
self.root_dir = target_obj.src_root
569
self.package_dir: Dict[str, str] = {} # To be filled by `find_packages`
570
571
@property
572
def parsers(self):
573
"""Metadata item name to parser function mapping."""
574
parse_list = self._parse_list
575
parse_list_semicolon = partial(self._parse_list, separator=';')
576
parse_bool = self._parse_bool
577
parse_dict = self._parse_dict
578
parse_cmdclass = self._parse_cmdclass
579
parse_file = partial(self._parse_file, root_dir=self.root_dir)
580
581
return {
582
'zip_safe': parse_bool,
583
'include_package_data': parse_bool,
584
'package_dir': parse_dict,
585
'scripts': parse_list,
586
'eager_resources': parse_list,
587
'dependency_links': parse_list,
588
'namespace_packages': self._deprecated_config_handler(
589
parse_list,
590
"The namespace_packages parameter is deprecated, "
591
"consider using implicit namespaces instead (PEP 420).",
592
SetuptoolsDeprecationWarning,
593
),
594
'install_requires': parse_list_semicolon,
595
'setup_requires': parse_list_semicolon,
596
'tests_require': parse_list_semicolon,
597
'packages': self._parse_packages,
598
'entry_points': parse_file,
599
'py_modules': parse_list,
600
'python_requires': SpecifierSet,
601
'cmdclass': parse_cmdclass,
602
}
603
604
def _parse_cmdclass(self, value):
605
package_dir = self.ensure_discovered.package_dir
606
return expand.cmdclass(self._parse_dict(value), package_dir, self.root_dir)
607
608
def _parse_packages(self, value):
609
"""Parses `packages` option value.
610
611
:param value:
612
:rtype: list
613
"""
614
find_directives = ['find:', 'find_namespace:']
615
trimmed_value = value.strip()
616
617
if trimmed_value not in find_directives:
618
return self._parse_list(value)
619
620
# Read function arguments from a dedicated section.
621
find_kwargs = self.parse_section_packages__find(
622
self.sections.get('packages.find', {})
623
)
624
625
find_kwargs.update(
626
namespaces=(trimmed_value == find_directives[1]),
627
root_dir=self.root_dir,
628
fill_package_dir=self.package_dir,
629
)
630
631
return expand.find_packages(**find_kwargs)
632
633
def parse_section_packages__find(self, section_options):
634
"""Parses `packages.find` configuration file section.
635
636
To be used in conjunction with _parse_packages().
637
638
:param dict section_options:
639
"""
640
section_data = self._parse_section_to_dict(section_options, self._parse_list)
641
642
valid_keys = ['where', 'include', 'exclude']
643
644
find_kwargs = dict(
645
[(k, v) for k, v in section_data.items() if k in valid_keys and v]
646
)
647
648
where = find_kwargs.get('where')
649
if where is not None:
650
find_kwargs['where'] = where[0] # cast list to single val
651
652
return find_kwargs
653
654
def parse_section_entry_points(self, section_options):
655
"""Parses `entry_points` configuration file section.
656
657
:param dict section_options:
658
"""
659
parsed = self._parse_section_to_dict(section_options, self._parse_list)
660
self['entry_points'] = parsed
661
662
def _parse_package_data(self, section_options):
663
package_data = self._parse_section_to_dict(section_options, self._parse_list)
664
return expand.canonic_package_data(package_data)
665
666
def parse_section_package_data(self, section_options):
667
"""Parses `package_data` configuration file section.
668
669
:param dict section_options:
670
"""
671
self['package_data'] = self._parse_package_data(section_options)
672
673
def parse_section_exclude_package_data(self, section_options):
674
"""Parses `exclude_package_data` configuration file section.
675
676
:param dict section_options:
677
"""
678
self['exclude_package_data'] = self._parse_package_data(section_options)
679
680
def parse_section_extras_require(self, section_options):
681
"""Parses `extras_require` configuration file section.
682
683
:param dict section_options:
684
"""
685
parse_list = partial(self._parse_list, separator=';')
686
parsed = self._parse_section_to_dict(section_options, parse_list)
687
self['extras_require'] = parsed
688
689
def parse_section_data_files(self, section_options):
690
"""Parses `data_files` configuration file section.
691
692
:param dict section_options:
693
"""
694
parsed = self._parse_section_to_dict(section_options, self._parse_list)
695
self['data_files'] = expand.canonic_data_files(parsed, self.root_dir)
696
697