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