Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow file: for requires statements in setup.cfg #3253

Merged
merged 2 commits into from Jun 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3253.change.rst
@@ -0,0 +1 @@
Enabled using ``file:`` for requirements in setup.cfg -- by :user:`akx`
17 changes: 12 additions & 5 deletions docs/userguide/declarative_config.rst
Expand Up @@ -211,13 +211,13 @@ obsoletes list-comma
Options
-------

======================= =================================== =============== =========
======================= =================================== =============== ====================
Key Type Minimum Version Notes
======================= =================================== =============== =========
======================= =================================== =============== ====================
zip_safe bool
setup_requires list-semi 36.7.0
install_requires list-semi
extras_require section [#opt-2]_
install_requires file:, list-semi [#opt-6]_
extras_require file:, section [#opt-2]_, [#opt-6]_
python_requires str 34.4.0
entry_points file:, section 51.0.0
scripts list-comma
Expand All @@ -232,7 +232,7 @@ exclude_package_data section
namespace_packages list-comma [#opt-5]_
py_modules list-comma 34.4.0
data_files section 40.6.0 [#opt-4]_
======================= =================================== =============== =========
======================= =================================== =============== ====================

**Notes**:

Expand Down Expand Up @@ -266,6 +266,13 @@ data_files section 40.6.0 [#
namespaces (:pep:`420`). Check :doc:`the Python Packaging User Guide
<PyPUG:guides/packaging-namespace-packages>` for more information.

.. [#opt-6] ``file:`` directives for reading requirements are supported since version 63.0.
The format for the file resembles a ``requirements.txt`` file,
however please keep in mind that all non-comment lines must conform with :pep:`508`
(``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported).
Library developers should avoid tightly pinning their dependencies to a specific
version (e.g. via a "locked" requirements file).


Compatibility with other tools
==============================
Expand Down
30 changes: 22 additions & 8 deletions setuptools/config/setupcfg.py
Expand Up @@ -568,15 +568,27 @@ def __init__(
self.root_dir = target_obj.src_root
self.package_dir: Dict[str, str] = {} # To be filled by `find_packages`

@classmethod
def _parse_list_semicolon(cls, value):
return cls._parse_list(value, separator=';')

def _parse_file_in_root(self, value):
return self._parse_file(value, root_dir=self.root_dir)

def _parse_requirements_list(self, value):
# Parse a requirements list, either by reading in a `file:`, or a list.
parsed = self._parse_list_semicolon(self._parse_file_in_root(value))
# Filter it to only include lines that are not comments. `parse_list`
# will have stripped each line and filtered out empties.
return [line for line in parsed if not line.startswith("#")]

@property
def parsers(self):
"""Metadata item name to parser function mapping."""
parse_list = self._parse_list
parse_list_semicolon = partial(self._parse_list, separator=';')
parse_bool = self._parse_bool
parse_dict = self._parse_dict
parse_cmdclass = self._parse_cmdclass
parse_file = partial(self._parse_file, root_dir=self.root_dir)

return {
'zip_safe': parse_bool,
Expand All @@ -591,11 +603,11 @@ def parsers(self):
"consider using implicit namespaces instead (PEP 420).",
SetuptoolsDeprecationWarning,
),
'install_requires': parse_list_semicolon,
'setup_requires': parse_list_semicolon,
'tests_require': parse_list_semicolon,
'install_requires': self._parse_requirements_list,
'setup_requires': self._parse_list_semicolon,
'tests_require': self._parse_list_semicolon,
'packages': self._parse_packages,
'entry_points': parse_file,
'entry_points': self._parse_file_in_root,
'py_modules': parse_list,
'python_requires': SpecifierSet,
'cmdclass': parse_cmdclass,
Expand Down Expand Up @@ -682,8 +694,10 @@ def parse_section_extras_require(self, section_options):

:param dict section_options:
"""
parse_list = partial(self._parse_list, separator=';')
parsed = self._parse_section_to_dict(section_options, parse_list)
parsed = self._parse_section_to_dict(
section_options,
self._parse_requirements_list,
)
self['extras_require'] = parsed

def parse_section_data_files(self, section_options):
Expand Down
18 changes: 18 additions & 0 deletions setuptools/tests/config/test_setupcfg.py
Expand Up @@ -884,6 +884,24 @@ def test_cmdclass(self, tmpdir):
assert cmdclass.__module__ == "custom_build"
assert module_path.samefile(inspect.getfile(cmdclass))

def test_requirements_file(self, tmpdir):
fake_env(
tmpdir,
DALS("""
[options]
install_requires = file:requirements.txt
[options.extras_require]
colors = file:requirements-extra.txt
""")
)

tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')
tmpdir.join('requirements-extra.txt').write('colorama')

with get_dist(tmpdir) as dist:
assert dist.install_requires == ['docutils>=0.3']
assert dist.extras_require == {'colors': ['colorama']}


saved_dist_init = _Distribution.__init__

Expand Down