Repository: takluyver/flit
Pull request #61 (2015-12-14 16:52:57)

More extensive check os version number
PyPA `packaging` already normalize it, just need to check that normalization===identity.


Patch:
diff --git a/flit/common.py b/flit/common.py
index 128ec6c..3fb6131 100644
--- a/flit/common.py
+++ b/flit/common.py
@@ -6,6 +6,25 @@ from pathlib import Path
 
 log = logging.getLogger(__name__)
 
+import re
+
+
+pep440re = re.compile('^'
+                   '([1-9]\d*!)?'
+                 '(0|[1-9]\d*)'
+               '(.(0|[1-9]\d*))*'
+        '((a|b|rc)(0|[1-9]\d*))?'
+          '(\.post(0|[1-9]\d*))?'
+           '(\.dev(0|[1-9]\d*))?'
+        '$')
+
+def _is_canonical(version)->bool:
+    """
+    Return whether or not the version string is canonical according to Pep 440
+    """
+    return pep440re.match(version) is not None
+
+
 class Module(object):
     """This represents the module/package that we are going to distribute
     """
@@ -62,19 +81,39 @@ def get_info_from_module(target):
         raise NoDocstringError('Cannot package module without docstring, or empty docstring. '
                                 'Please add a docstring to your module.')
     module_version = m.__dict__.get('__version__', None)
-    if not module_version: 
+
+    check_version(module_version)
+
+    docstring_lines = docstring.lstrip().splitlines()
+    return {'summary': docstring_lines[0],
+            'version': m.__version__}
+
+def check_version(version):
+    """
+    Check wether a given version string match Pep 440
+
+    Raise InvalidVersion/NoVersionError With relevant information if 
+    version is invalid. 
+
+    Print a warning if the version is not canonical with respect to Pep440
+
+    Return wether the version is canonical with pep440
+    """
+    if not version: 
         raise NoVersionError('Cannot package module without a version string. '
                              'Please define a `__version__="x.y.z"` in your module.')
-    if not isinstance(module_version, str):
+    if not isinstance(version, str):
         raise InvalidVersion('__version__ must be a string, not {}.'
-                                .format(type(module_version)))
-    if not module_version[0].isdigit():
+                                .format(type(version)))
+    if not version[0].isdigit():
         raise InvalidVersion('__version__ must start with a number. It is {!r}.'
-                                .format(module_version))
+                                .format(version))
+    canonical = _is_canonical(version)
+    if not canonical:
+        log.warn('Version string (%s) does not match Pep440.' % version )
+
+    return canonical
 
-    docstring_lines = docstring.lstrip().splitlines()
-    return {'summary': docstring_lines[0],
-            'version': m.__version__}
 
 script_template = """\
 #!{interpreter}
diff --git a/flit/inifile.py b/flit/inifile.py
index 68eb68d..5c0968b 100644
--- a/flit/inifile.py
+++ b/flit/inifile.py
@@ -102,23 +102,12 @@ def verify_classifiers(classifiers):
 
 
 def read_pkg_ini(path):
-    """Read and check the `flit.ini` file with data about the package.
+    """Read and check the -pkg.ini file with data about the package.
     """
-    cp = _read_pkg_ini(path)
-    return _validate_config(cp, path)
-
-def _read_pkg_ini(path):
     cp = configparser.ConfigParser()
     with path.open() as f:
         cp.read_file(f)
 
-    return cp
-
-
-def _validate_config(cp, path):
-    """
-    Validate a config and return a dict containing `module`,`metadata`,`script`,`entry_point` keys.
-    """
     unknown_sections = set(cp.sections()) - {'metadata', 'scripts'}
     if unknown_sections:
         raise ConfigError('Unknown sections: ' + ', '.join(unknown_sections))
@@ -131,14 +120,14 @@ def _validate_config(cp, path):
         missing = metadata_required_fields - set(md_sect)
         raise ConfigError("Required fields missing: " + '\n'.join(missing))
 
-    module = md_sect.get('module')
+    module = md_sect.pop('module')
     if not module.isidentifier():
         raise ConfigError("Module name %r is not a valid identifier" % module)
 
     md_dict = {}
 
     if 'description-file' in md_sect:
-        description_file = path.parent / md_sect.get('description-file')
+        description_file = path.parent / md_sect.pop('description-file')
         with description_file.open() as f:
             raw_desc =  f.read()
         if description_file.suffix == '.md':
@@ -157,7 +146,7 @@ def _validate_config(cp, path):
         md_dict['description'] =  raw_desc
 
     if 'entry-points-file' in md_sect:
-        entry_points_file = path.parent / md_sect.get('entry-points-file')
+        entry_points_file = path.parent / md_sect.pop('entry-points-file')
         if not entry_points_file.is_file():
             raise FileNotFoundError(entry_points_file)
     else:
@@ -166,8 +155,6 @@ def _validate_config(cp, path):
             entry_points_file = None
 
     for key, value in md_sect.items():
-        if key in {'description-file', 'module', 'entry-points-file'}:
-            continue
         if key not in metadata_allowed_fields:
             closest = difflib.get_close_matches(key, metadata_allowed_fields,
                                                 n=1, cutoff=0.7)


Test patch:
diff --git a/tests/test_common.py b/tests/test_common.py
index 05925d6..7c3224f 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -1,8 +1,9 @@
 import os
 from pathlib import Path
 from unittest import TestCase
+import pytest
 
-from flit.common import Module, get_info_from_module, InvalidVersion
+from flit.common import Module, get_info_from_module, InvalidVersion, check_version
 
 samples_dir = os.path.join(os.path.dirname(__file__), 'samples')
 
@@ -40,3 +41,12 @@ class ModuleTests(TestCase):
 
         with self.assertRaises(InvalidVersion):
             get_info_from_module(Module('invalid_version1', samples_dir))
+
+    def test_version_raise(self):
+        with pytest.raises(InvalidVersion):
+            check_version('a.1.0.beta0')
+
+        assert check_version('4.1.0b1') == True
+        assert check_version('4.1.0beta1') == False
+
+