Repository: takluyver/flit (python)

Issue:
requires-python parsing too picky
I had `requires-python = ">= 3.7"` and `flit build` made a wheel tagged `-py2.py3-none-any.whl`

If I put `requires-python = ">=3.7"` then I get the desired `-py3-none-any.whl`


Pull request:
Check for Python 2 support a little more thoroughly
Fixes #227.

Patch:
diff --git a/flit/buildapi.py b/flit/buildapi.py
index 53c6315..f2bc385 100644
--- a/flit/buildapi.py
+++ b/flit/buildapi.py
@@ -30,10 +30,8 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
                      dist_info_name(metadata.name, metadata.version))
     dist_info.mkdir()
 
-    supports_py2 = not (metadata.requires_python or '')\
-                                .startswith(('3', '>3', '>=3'))
     with (dist_info / 'WHEEL').open('w') as f:
-        _write_wheel_file(f, supports_py2=supports_py2)
+        _write_wheel_file(f, supports_py2=metadata.supports_py2)
 
     with (dist_info / 'METADATA').open('w') as f:
         metadata.write_metadata_file(f)
diff --git a/flit/common.py b/flit/common.py
index 246959e..ed20afa 100644
--- a/flit/common.py
+++ b/flit/common.py
@@ -4,6 +4,7 @@ import hashlib
 from importlib.machinery import SourceFileLoader
 import logging
 from pathlib import Path
+import re
 
 log = logging.getLogger(__name__)
 
@@ -286,6 +287,15 @@ class Metadata:
         if self.description is not None:
             fp.write('\n' + self.description + '\n')
 
+    @property
+    def supports_py2(self) -> bool:
+        """Return True if Requires-Python indicates Python 2 support."""
+        for part in (self.requires_python or "").split(","):
+            if re.search(r"^\s*(>\s*(=\s*)?)?[3-9]", part):
+                return False
+        return True
+
+
 def make_metadata(module, ini_info):
     md_dict = {'name': module.name, 'provides': [module.name]}
     md_dict.update(get_info_from_module(module))
diff --git a/flit/wheel.py b/flit/wheel.py
index 3471052..14478f8 100644
--- a/flit/wheel.py
+++ b/flit/wheel.py
@@ -69,7 +69,7 @@ class WheelBuilder:
 
     @property
     def wheel_filename(self):
-        tag = ('py2.' if self.supports_py2 else '') + 'py3-none-any'
+        tag = ('py2.' if self.metadata.supports_py2 else '') + 'py3-none-any'
         return '{}-{}-{}.whl'.format(
                 re.sub("[^\w\d.]+", "_", self.metadata.name, flags=re.UNICODE),
                 re.sub("[^\w\d.]+", "_", self.metadata.version, flags=re.UNICODE),
@@ -94,7 +94,7 @@ class WheelBuilder:
         else:
             # Set timestamps in zipfile for reproducible build
             zinfo = zipfile.ZipInfo(rel_path, self.source_time_stamp)
-        
+
         # Normalize permission bits to either 755 (executable) or 644
         st_mode = os.stat(full_path).st_mode
         new_mode = common.normalize_file_permissions(st_mode)
@@ -149,11 +149,6 @@ class WheelBuilder:
         else:
             self._add_file(str(self.module.path), self.module.path.name)
 
-    @property
-    def supports_py2(self):
-        return not (self.metadata.requires_python or '')\
-                                    .startswith(('3', '>3', '>=3', '~=3'))
-
     def write_metadata(self):
         log.info('Writing metadata files')
 
@@ -166,7 +161,7 @@ class WheelBuilder:
                 self._add_file(path, '%s/%s' % (self.dist_info, path.name))
 
         with self._write_to_zip(self.dist_info + '/WHEEL') as f:
-            _write_wheel_file(f, supports_py2=self.supports_py2)
+            _write_wheel_file(f, supports_py2=self.metadata.supports_py2)
 
         with self._write_to_zip(self.dist_info + '/METADATA') as f:
             self.metadata.write_metadata_file(f)


Test patch:
diff --git a/tests/test_common.py b/tests/test_common.py
index e78355c..6221062 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -72,3 +72,22 @@ class ModuleTests(TestCase):
 def test_normalize_file_permissions():
     assert normalize_file_permissions(0o100664) == 0o100644 # regular file
     assert normalize_file_permissions(0o40775) == 0o40755   # directory
+
+@pytest.mark.parametrize(
+    ("requires_python", "expected_result"),
+    [
+        ("", True),
+        (">2.7", True),
+        ("3", False),
+        (">= 3.7", False),
+        ("<4, > 3.2", False),
+        (">3.4", False),
+        (">=2.7, !=3.0.*, !=3.1.*, !=3.2.*", True),
+    ],
+)
+def test_supports_py2(requires_python, expected_result):
+    from flit.common import Metadata
+    metadata = object.__new__(Metadata)
+    metadata.requires_python = requires_python
+    result = metadata.supports_py2
+    assert result == expected_result