diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-03-07 18:16:39 +0000 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-05-03 14:06:38 +0100 |
| commit | 9cecaca97ce19e5e08304532d0ab7a034155d56c (patch) | |
| tree | ddbb49905873145aa5449d32705d1e308f4b93df | |
| parent | b010f52705b41cd904813076447da3092776462a (diff) | |
| download | python-setuptools-git-9cecaca97ce19e5e08304532d0ab7a034155d56c.tar.gz | |
Extract egg_info-specific processing of requirements from dist to _reqs
| -rw-r--r-- | setuptools/command/_requirestxt.py | 96 | ||||
| -rw-r--r-- | setuptools/dist.py | 88 |
2 files changed, 108 insertions, 76 deletions
diff --git a/setuptools/command/_requirestxt.py b/setuptools/command/_requirestxt.py new file mode 100644 index 00000000..ebed70bc --- /dev/null +++ b/setuptools/command/_requirestxt.py @@ -0,0 +1,96 @@ +"""Helper code used to generate ``requires.txt`` files in the egg-info directory. + +The ``requires.txt`` file has an specific format: + - Environment markers need to be part of the section headers and + should not be part of the requirement spec itself. + +See https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#requires-txt +""" +from collections import defaultdict +from itertools import filterfalse +from typing import Dict, List, Tuple, Mapping, TypeVar + +from .. import _reqs +from ..extern.packaging.requirements import Requirement + + +# dict can work as an ordered set +_T = TypeVar("_T") +_Ordered = Dict[_T, None] +_ordered = dict +_StrOrIter = _reqs._StrOrIter + + +def _prepare( + install_requires: _StrOrIter, extras_require: Mapping[str, _StrOrIter] +) -> Tuple[List[str], Dict[str, List[str]]]: + """Given values for ``install_requires`` and ``extras_require`` + create modified versions in a way that can be written in ``requires.txt`` + """ + extras = _convert_extras_requirements(extras_require) + return _move_install_requirements_markers(install_requires, extras) + + +def _convert_extras_requirements( + extras_require: _StrOrIter, +) -> Mapping[str, _Ordered[Requirement]]: + """ + Convert requirements in `extras_require` of the form + `"extra": ["barbazquux; {marker}"]` to + `"extra:{marker}": ["barbazquux"]`. + """ + output: Mapping[str, _Ordered[Requirement]] = defaultdict(dict) + for section, v in extras_require.items(): + # Do not strip empty sections. + output[section] + for r in _reqs.parse(v): + output[section + _suffix_for(r)].setdefault(r) + + return output + + +def _move_install_requirements_markers( + install_requires: _StrOrIter, extras_require: Mapping[str, _Ordered[Requirement]] +) -> Tuple[List[str], Dict[str, List[str]]]: + """ + The ``requires.txt`` file has an specific format: + - Environment markers need to be part of the section headers and + should not be part of the requirement spec itself. + + Move requirements in ``install_requires`` that are using environment + markers ``extras_require``. + """ + + # divide the install_requires into two sets, simple ones still + # handled by install_requires and more complex ones handled by extras_require. + + inst_reqs = list(_reqs.parse(install_requires)) + simple_reqs = filter(_no_marker, inst_reqs) + complex_reqs = filterfalse(_no_marker, inst_reqs) + simple_install_requires = list(map(str, simple_reqs)) + + for r in complex_reqs: + extras_require[':' + str(r.marker)].setdefault(r) + + expanded_extras = dict( + # list(dict.fromkeys(...)) ensures a list of unique strings + (k, list(dict.fromkeys(str(r) for r in map(_clean_req, v)))) + for k, v in extras_require.items() + ) + + return simple_install_requires, expanded_extras + + +def _suffix_for(req): + """Return the 'extras_require' suffix for a given requirement.""" + return ':' + str(req.marker) if req.marker else '' + + +def _clean_req(req): + """Given a Requirement, remove environment markers and return it""" + req.marker = None + return req + + +def _no_marker(req): + return not req.marker diff --git a/setuptools/dist.py b/setuptools/dist.py index e5a7b8ed..a24e256e 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -20,7 +20,6 @@ from contextlib import suppress from typing import List, Optional, Set, TYPE_CHECKING from pathlib import Path -from collections import defaultdict from email import message_from_file from distutils.errors import DistutilsOptionError, DistutilsSetupError @@ -507,11 +506,6 @@ class Distribution(_Distribution): # sdist (e.g. `version = file: VERSION.txt`) self._referenced_files: Set[str] = set() - # Save the original dependencies before they are processed into the egg format - self._orig_extras_require = {} - self._orig_install_requires = [] - self._tmp_extras_require = defaultdict(ordered_set.OrderedSet) - self.set_defaults = ConfigDiscovery(self) self._set_metadata_defaults(attrs) @@ -588,81 +582,23 @@ class Distribution(_Distribution): if getattr(self, 'python_requires', None): self.metadata.python_requires = self.python_requires - if getattr(self, 'extras_require', None): - # Save original before it is messed by _convert_extras_requirements - self._orig_extras_require = self._orig_extras_require or self.extras_require + self._normalize_requires() + + if self.extras_require: for extra in self.extras_require.keys(): - # Since this gets called multiple times at points where the - # keys have become 'converted' extras, ensure that we are only - # truly adding extras we haven't seen before here. + # Setuptools allows a weird "<name>:<env markers> syntax for extras extra = extra.split(':')[0] if extra: self.metadata.provides_extras.add(extra) - if getattr(self, 'install_requires', None) and not self._orig_install_requires: - # Save original before it is messed by _move_install_requirements_markers - self._orig_install_requires = self.install_requires - - self._convert_extras_requirements() - self._move_install_requirements_markers() - - def _convert_extras_requirements(self): - """ - Convert requirements in `extras_require` of the form - `"extra": ["barbazquux; {marker}"]` to - `"extra:{marker}": ["barbazquux"]`. - """ - spec_ext_reqs = getattr(self, 'extras_require', None) or {} - tmp = defaultdict(ordered_set.OrderedSet) - self._tmp_extras_require = getattr(self, '_tmp_extras_require', tmp) - for section, v in spec_ext_reqs.items(): - # Do not strip empty sections. - self._tmp_extras_require[section] - for r in _reqs.parse(v): - suffix = self._suffix_for(r) - self._tmp_extras_require[section + suffix].append(r) - - @staticmethod - def _suffix_for(req): - """ - For a requirement, return the 'extras_require' suffix for - that requirement. - """ - return ':' + str(req.marker) if req.marker else '' - - def _move_install_requirements_markers(self): - """ - Move requirements in `install_requires` that are using environment - markers `extras_require`. - """ - - # divide the install_requires into two sets, simple ones still - # handled by install_requires and more complex ones handled - # by extras_require. - - def is_simple_req(req): - return not req.marker - - spec_inst_reqs = getattr(self, 'install_requires', None) or () - inst_reqs = list(_reqs.parse(spec_inst_reqs)) - simple_reqs = filter(is_simple_req, inst_reqs) - complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs) - self.install_requires = list(map(str, simple_reqs)) - - for r in complex_reqs: - self._tmp_extras_require[':' + str(r.marker)].append(r) - self.extras_require = dict( - # list(dict.fromkeys(...)) ensures a list of unique strings - (k, list(dict.fromkeys(str(r) for r in map(self._clean_req, v)))) - for k, v in self._tmp_extras_require.items() - ) - - def _clean_req(self, req): - """ - Given a Requirement, remove environment markers and return it. - """ - req.marker = None - return req + def _normalize_requires(self): + """Make sure requirement-related attributes exist and are normalized""" + install_requires = getattr(self, "install_requires", None) or [] + extras_require = getattr(self, "extras_require", None) or {} + self.install_requires = list(map(str, _reqs.parse(install_requires))) + self.extras_require = { + k: list(map(str, _reqs.parse(v or []))) for k, v in extras_require.items() + } def _finalize_license_files(self): """Compute names of all license files which should be included.""" |
