Repository: pypa/setuptools

Issue:
The 'python_requires' field is ignored in setup.cfg (not in setup.py).
I'm using setuptools version '41.0.1.post20190616' (commit 8aeff6b2c9a64e47ad2a22533d7e65c08cd4103f). 

$ cd /tmp/setup/py
$ cat setup.py
from setuptools import setup
setup(python_requires='invalid')
$ python setup.py install
error in setup command: 'python_requires' must be a string containing valid version specifiers; Invalid specifier: 'invalid'


$ cd /tmp/setup/cfg
$ cat setup.cfg 
[metadata]
python_requires=invalid
$ python setup.py install
running install
running bdist_egg
running egg_info
writing UNKNOWN.egg-info/PKG-INFO
writing dependency_links to UNKNOWN.egg-info/dependency_links.txt
writing top-level names to UNKNOWN.egg-info/top_level.txt
reading manifest file 'UNKNOWN.egg-info/SOURCES.txt'
writing manifest file 'UNKNOWN.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
warning: install_lib: 'build/lib' does not exist -- no Python modules to install

creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating 'dist/UNKNOWN-0.0.0-py3.7.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing UNKNOWN-0.0.0-py3.7.egg
Copying UNKNOWN-0.0.0-py3.7.egg to /tmp/setup/cfg/v/lib/python3.7/site-packages
Adding UNKNOWN 0.0.0 to easy-install.pth file

Installed /tmp/setup/cfg/v/lib/python3.7/site-packages/UNKNOWN-0.0.0-py3.7.egg
Processing dependencies for UNKNOWN==0.0.0
Finished processing dependencies for UNKNOWN==0.0.0
$ echo $?
0


I thing "python setup.py install" should also fail when using setup.cfg.
The 'python_requires' field is ignored in setup.cfg (not in setup.py).
I'm using setuptools version '41.0.1.post20190616' (commit 8aeff6b2c9a64e47ad2a22533d7e65c08cd4103f). 

$ cd /tmp/setup/py
$ cat setup.py
from setuptools import setup
setup(python_requires='invalid')
$ python setup.py install
error in setup command: 'python_requires' must be a string containing valid version specifiers; Invalid specifier: 'invalid'


$ cd /tmp/setup/cfg
$ cat setup.cfg 
[metadata]
python_requires=invalid
$ python setup.py install
running install
running bdist_egg
running egg_info
writing UNKNOWN.egg-info/PKG-INFO
writing dependency_links to UNKNOWN.egg-info/dependency_links.txt
writing top-level names to UNKNOWN.egg-info/top_level.txt
reading manifest file 'UNKNOWN.egg-info/SOURCES.txt'
writing manifest file 'UNKNOWN.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
warning: install_lib: 'build/lib' does not exist -- no Python modules to install

creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying UNKNOWN.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating 'dist/UNKNOWN-0.0.0-py3.7.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing UNKNOWN-0.0.0-py3.7.egg
Copying UNKNOWN-0.0.0-py3.7.egg to /tmp/setup/cfg/v/lib/python3.7/site-packages
Adding UNKNOWN 0.0.0 to easy-install.pth file

Installed /tmp/setup/cfg/v/lib/python3.7/site-packages/UNKNOWN-0.0.0-py3.7.egg
Processing dependencies for UNKNOWN==0.0.0
Finished processing dependencies for UNKNOWN==0.0.0
$ echo $?
0


I thing "python setup.py install" should also fail when using setup.cfg.


Discussion:
https://github.com/pypa/setuptools/issues/999 was about a similar thing, so maybe this is a regression.
Note that `python_requires` belong in the `[options]` section. Still the same issue though.

It's not a regression, but something that should have been taken into account when implementing #999.
Related: #1633.
https://github.com/pypa/setuptools/issues/999 was about a similar thing, so maybe this is a regression.
Note that `python_requires` belong in the `[options]` section. Still the same issue though.

It's not a regression, but something that should have been taken into account when implementing #999.
Related: #1633.

Resolving patch:
diff --git a/setuptools/config.py b/setuptools/config.py
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -12,6 +12,7 @@
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
 from setuptools.extern.packaging.version import LegacyVersion, parse
+from setuptools.extern.packaging.specifiers import SpecifierSet
 from setuptools.extern.six import string_types, PY3
 
 
@@ -554,6 +555,7 @@ def parsers(self):
             'packages': self._parse_packages,
             'entry_points': self._parse_file,
             'py_modules': parse_list,
+            'python_requires': SpecifierSet,
         }
 
     def _parse_packages(self, value):


Test patch:
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -819,6 +819,40 @@ def test_data_files(self, tmpdir):
             ]
             assert sorted(dist.data_files) == sorted(expected)
 
+    def test_python_requires_simple(self, tmpdir):
+        fake_env(
+            tmpdir,
+            DALS("""
+            [options]
+            python_requires=>=2.7
+            """),
+        )
+        with get_dist(tmpdir) as dist:
+            dist.parse_config_files()
+
+    def test_python_requires_compound(self, tmpdir):
+        fake_env(
+            tmpdir,
+            DALS("""
+            [options]
+            python_requires=>=2.7,!=3.0.*
+            """),
+        )
+        with get_dist(tmpdir) as dist:
+            dist.parse_config_files()
+
+    def test_python_requires_invalid(self, tmpdir):
+        fake_env(
+            tmpdir,
+            DALS("""
+            [options]
+            python_requires=invalid
+            """),
+        )
+        with pytest.raises(Exception):
+            with get_dist(tmpdir) as dist:
+                dist.parse_config_files()
+
 
 saved_dist_init = _Distribution.__init__