Repository: takluyver/flit
Pull request #150 (2017-11-16 18:47:20)

Escape name components for .dist-info directory
The .dist-info folder should be like `pandoc_attributes-0.1.7.dist-info`, not `pandoc-attributes-0.1.7.dist-info`, so that it's unambiguous which bit is distribution name and which bit is version number.

I'm not sure the rules are clearly stated anywhere, but I've applied the same escaping PEP 427 specifies for wheel filenames.

Closes gh-149


Patch:
diff --git a/flit/buildapi.py b/flit/buildapi.py
index 4837d08..53c6315 100644
--- a/flit/buildapi.py
+++ b/flit/buildapi.py
@@ -2,7 +2,7 @@
 import logging
 from pathlib import Path
 
-from .common import Module, make_metadata, write_entry_points
+from .common import Module, make_metadata, write_entry_points, dist_info_name
 from .inifile import read_pkg_ini
 from .wheel import make_wheel_in, _write_wheel_file
 from .sdist import SdistBuilder
@@ -26,8 +26,8 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
     module = Module(ini_info['module'], Path.cwd())
     metadata = make_metadata(module, ini_info)
 
-    dist_version = metadata.name + '-' + metadata.version
-    dist_info = Path(metadata_directory, dist_version + '.dist-info')
+    dist_info = Path(metadata_directory,
+                     dist_info_name(metadata.name, metadata.version))
     dist_info.mkdir()
 
     supports_py2 = not (metadata.requires_python or '')\
diff --git a/flit/common.py b/flit/common.py
index f22f53e..9694ba6 100644
--- a/flit/common.py
+++ b/flit/common.py
@@ -311,3 +311,8 @@ def metadata_and_module_from_ini_path(ini_path):
     metadata = make_metadata(module, ini_info)
     return metadata,module
 
+def dist_info_name(distribution, version):
+    """Get the correct name of the .dist-info folder"""
+    escaped_name = re.sub("[^\w\d.]+", "_", distribution, flags=re.UNICODE)
+    escaped_version = re.sub("[^\w\d.]+", "_", version, flags=re.UNICODE)
+    return '{}-{}.dist-info'.format(escaped_name, escaped_version)
diff --git a/flit/install.py b/flit/install.py
index 5c0345f..d8cdc97 100644
--- a/flit/install.py
+++ b/flit/install.py
@@ -5,6 +5,7 @@ import os
 import csv
 import pathlib
 import random
+import re
 import shutil
 import site
 import sys
@@ -300,9 +301,8 @@ class Installer(object):
     def write_dist_info(self, site_pkgs):
         """Write dist-info folder, according to PEP 376"""
         metadata = common.make_metadata(self.module, self.ini_info)
-
-        dist_info = pathlib.Path(site_pkgs) / '{}-{}.dist-info'.format(
-                                            metadata.name, metadata.version)
+        dist_info = pathlib.Path(site_pkgs) / common.dist_info_name(
+                                                metadata.name, metadata.version)
         try:
             dist_info.mkdir()
         except FileExistsError:
diff --git a/flit/wheel.py b/flit/wheel.py
index 8f95bf6..3471052 100644
--- a/flit/wheel.py
+++ b/flit/wheel.py
@@ -46,7 +46,6 @@ class WheelBuilder:
         self.module = common.Module(self.ini_info['module'], ini_path.parent)
         self.metadata = common.make_metadata(self.module, self.ini_info)
 
-        self.dist_version = self.metadata.name + '-' + self.metadata.version
         self.records = []
         try:
             # If SOURCE_DATE_EPOCH is set (e.g. by Debian), it's used for
@@ -64,6 +63,10 @@ class WheelBuilder:
         self.wheel_zip = zipfile.ZipFile(target_fp, 'w',
                              compression=zipfile.ZIP_DEFLATED)
 
+    @property
+    def dist_info(self):
+        return common.dist_info_name(self.metadata.name, self.metadata.version)
+
     @property
     def wheel_filename(self):
         tag = ('py2.' if self.supports_py2 else '') + 'py3-none-any'
@@ -153,30 +156,29 @@ class WheelBuilder:
 
     def write_metadata(self):
         log.info('Writing metadata files')
-        dist_info = self.dist_version + '.dist-info'
 
         if self.ini_info['entrypoints']:
-            with self._write_to_zip(dist_info + '/entry_points.txt') as f:
+            with self._write_to_zip(self.dist_info + '/entry_points.txt') as f:
                 common.write_entry_points(self.ini_info['entrypoints'], f)
 
         for base in ('COPYING', 'LICENSE'):
             for path in sorted(self.directory.glob(base + '*')):
-                self._add_file(path, '%s/%s' % (dist_info, path.name))
+                self._add_file(path, '%s/%s' % (self.dist_info, path.name))
 
-        with self._write_to_zip(dist_info + '/WHEEL') as f:
+        with self._write_to_zip(self.dist_info + '/WHEEL') as f:
             _write_wheel_file(f, supports_py2=self.supports_py2)
 
-        with self._write_to_zip(dist_info + '/METADATA') as f:
+        with self._write_to_zip(self.dist_info + '/METADATA') as f:
             self.metadata.write_metadata_file(f)
 
     def write_record(self):
         log.info('Writing the record of files')
         # Write a record of the files in the wheel
-        with self._write_to_zip(self.dist_version + '.dist-info/RECORD') as f:
+        with self._write_to_zip(self.dist_info + '/RECORD') as f:
             for path, hash, size in self.records:
                 f.write('{},sha256={},{}\n'.format(path, hash, size))
             # RECORD itself is recorded with no hash or size
-            f.write(self.dist_version + '.dist-info/RECORD,,\n')
+            f.write(self.dist_info + '/RECORD,,\n')
 
     def build(self):
         try:


Test patch:
diff --git a/tests/samples/altdistname.ini b/tests/samples/altdistname.ini
index 6f5b92e..f819f9a 100644
--- a/tests/samples/altdistname.ini
+++ b/tests/samples/altdistname.ini
@@ -3,4 +3,4 @@ module=package1
 author=Sir Robin
 author-email=robin@camelot.uk
 home-page=http://github.com/sirrobin/package1
-dist-name=packagedist1
+dist-name=package-dist1
diff --git a/tests/test_install.py b/tests/test_install.py
index 750ea51..3cf6476 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -55,7 +55,7 @@ class InstallTests(TestCase):
     def test_dist_name(self):
         Installer(samples_dir / 'altdistname.ini').install_directly()
         assert_isdir(self.tmpdir / 'site-packages' / 'package1')
-        assert_isdir(self.tmpdir / 'site-packages' / 'packagedist1-0.1.dist-info')
+        assert_isdir(self.tmpdir / 'site-packages' / 'package_dist1-0.1.dist-info')
 
     def test_entry_points(self):
         Installer(samples_dir / 'entrypoints_valid.ini').install_directly()
diff --git a/tests/test_wheel.py b/tests/test_wheel.py
index f3e4a32..e5cc1b1 100644
--- a/tests/test_wheel.py
+++ b/tests/test_wheel.py
@@ -7,7 +7,7 @@ from unittest import skipIf
 import zipfile
 
 import pytest
-from testpath import assert_isfile
+from testpath import assert_isfile, assert_isdir
 
 from flit.wheel import wheel_main, WheelBuilder
 from flit.inifile import EntryPointsConflict
@@ -39,7 +39,10 @@ def test_wheel_package():
 def test_dist_name():
     clear_samples_dist()
     wheel_main(samples_dir / 'altdistname.ini')
-    assert_isfile(samples_dir / 'dist/packagedist1-0.1-py2.py3-none-any.whl')
+    res = samples_dir / 'dist/package_dist1-0.1-py2.py3-none-any.whl'
+    assert_isfile(res)
+    with unpack(res) as td:
+        assert_isdir(Path(td, 'package_dist1-0.1.dist-info'))
 
 def test_entry_points():
     clear_samples_dist()