_requirestxt.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """Helper code used to generate ``requires.txt`` files in the egg-info directory.
  2. The ``requires.txt`` file has an specific format:
  3. - Environment markers need to be part of the section headers and
  4. should not be part of the requirement spec itself.
  5. See https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#requires-txt
  6. """
  7. import io
  8. from collections import defaultdict
  9. from itertools import filterfalse
  10. from typing import Dict, List, Tuple, Mapping, TypeVar
  11. from .. import _reqs
  12. from ..extern.jaraco.text import yield_lines
  13. from ..extern.packaging.requirements import Requirement
  14. # dict can work as an ordered set
  15. _T = TypeVar("_T")
  16. _Ordered = Dict[_T, None]
  17. _ordered = dict
  18. _StrOrIter = _reqs._StrOrIter
  19. def _prepare(
  20. install_requires: _StrOrIter, extras_require: Mapping[str, _StrOrIter]
  21. ) -> Tuple[List[str], Dict[str, List[str]]]:
  22. """Given values for ``install_requires`` and ``extras_require``
  23. create modified versions in a way that can be written in ``requires.txt``
  24. """
  25. extras = _convert_extras_requirements(extras_require)
  26. return _move_install_requirements_markers(install_requires, extras)
  27. def _convert_extras_requirements(
  28. extras_require: _StrOrIter,
  29. ) -> Mapping[str, _Ordered[Requirement]]:
  30. """
  31. Convert requirements in `extras_require` of the form
  32. `"extra": ["barbazquux; {marker}"]` to
  33. `"extra:{marker}": ["barbazquux"]`.
  34. """
  35. output: Mapping[str, _Ordered[Requirement]] = defaultdict(dict)
  36. for section, v in extras_require.items():
  37. # Do not strip empty sections.
  38. output[section]
  39. for r in _reqs.parse(v):
  40. output[section + _suffix_for(r)].setdefault(r)
  41. return output
  42. def _move_install_requirements_markers(
  43. install_requires: _StrOrIter, extras_require: Mapping[str, _Ordered[Requirement]]
  44. ) -> Tuple[List[str], Dict[str, List[str]]]:
  45. """
  46. The ``requires.txt`` file has an specific format:
  47. - Environment markers need to be part of the section headers and
  48. should not be part of the requirement spec itself.
  49. Move requirements in ``install_requires`` that are using environment
  50. markers ``extras_require``.
  51. """
  52. # divide the install_requires into two sets, simple ones still
  53. # handled by install_requires and more complex ones handled by extras_require.
  54. inst_reqs = list(_reqs.parse(install_requires))
  55. simple_reqs = filter(_no_marker, inst_reqs)
  56. complex_reqs = filterfalse(_no_marker, inst_reqs)
  57. simple_install_requires = list(map(str, simple_reqs))
  58. for r in complex_reqs:
  59. extras_require[':' + str(r.marker)].setdefault(r)
  60. expanded_extras = dict(
  61. # list(dict.fromkeys(...)) ensures a list of unique strings
  62. (k, list(dict.fromkeys(str(r) for r in map(_clean_req, v))))
  63. for k, v in extras_require.items()
  64. )
  65. return simple_install_requires, expanded_extras
  66. def _suffix_for(req):
  67. """Return the 'extras_require' suffix for a given requirement."""
  68. return ':' + str(req.marker) if req.marker else ''
  69. def _clean_req(req):
  70. """Given a Requirement, remove environment markers and return it"""
  71. r = Requirement(str(req)) # create a copy before modifying
  72. r.marker = None
  73. return r
  74. def _no_marker(req):
  75. return not req.marker
  76. def _write_requirements(stream, reqs):
  77. lines = yield_lines(reqs or ())
  78. def append_cr(line):
  79. return line + '\n'
  80. lines = map(append_cr, lines)
  81. stream.writelines(lines)
  82. def write_requirements(cmd, basename, filename):
  83. dist = cmd.distribution
  84. data = io.StringIO()
  85. install_requires, extras_require = _prepare(
  86. dist.install_requires or (), dist.extras_require or {}
  87. )
  88. _write_requirements(data, install_requires)
  89. for extra in sorted(extras_require):
  90. data.write('\n[{extra}]\n'.format(**vars()))
  91. _write_requirements(data, extras_require[extra])
  92. cmd.write_or_delete_file("requirements", filename, data.getvalue())
  93. def write_setup_requirements(cmd, basename, filename):
  94. data = io.StringIO()
  95. _write_requirements(data, cmd.distribution.setup_requires)
  96. cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())