[Python-checkins] distutils2: Merge install_distinfo command

tarek.ziade python-checkins at python.org
Sun Aug 8 11:50:46 CEST 2010


tarek.ziade pushed ec1ac1605249 to distutils2:

http://hg.python.org/distutils2/rev/ec1ac1605249
changeset:   435:ec1ac1605249
parent:      434:0a8911f43e3e
parent:      433:aaf2faeb75e5
user:        ?ric Araujo <merwok at netwok.org>
date:        Tue Aug 03 17:29:40 2010 +0200
summary:     Merge install_distinfo command
files:       src/distutils2/_backport/tests/test_pkgutil.py, src/distutils2/command/install_egg_info.py, src/distutils2/tests/support.py, src/distutils2/tests/test_install_distinfo.py

diff --git a/docs/source/pkgutil.rst b/docs/source/pkgutil.rst
--- a/docs/source/pkgutil.rst
+++ b/docs/source/pkgutil.rst
@@ -17,6 +17,17 @@
 first a complete documentation of the functions and classes
 is provided and then several use cases are presented.
 
+Caching
++++++++
+
+For performance purposes, the list of distributions is being internally
+cached. It is enabled by default, but you can turn it off or clear
+it using
+:func:`distutils2._backport.pkgutil.enable_cache`,
+:func:`distutils2._backport.pkgutil.disable_cache` and
+:func:`distutils2._backport.pkgutil.clear_cache`.
+
+
 API Reference
 =============
 
@@ -48,7 +59,7 @@
   print('=====')
   for (path, md5, size) in dist.get_installed_files():
       print('* Path: %s' % path)
-      print('  Hash %s, Size: %s bytes' % (md5, size)) 
+      print('  Hash %s, Size: %s bytes' % (md5, size))
   print('Metadata')
   print('========')
   for key, value in dist.metadata.items():
diff --git a/src/distutils2/_backport/pkgutil.py b/src/distutils2/_backport/pkgutil.py
--- a/src/distutils2/_backport/pkgutil.py
+++ b/src/distutils2/_backport/pkgutil.py
@@ -20,6 +20,7 @@
 import re
 import warnings
 
+
 __all__ = [
     'get_importer', 'iter_importers', 'get_loader', 'find_loader',
     'walk_packages', 'iter_modules', 'get_data',
@@ -27,6 +28,7 @@
     'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
     'get_distributions', 'get_distribution', 'get_file_users',
     'provides_distribution', 'obsoletes_distribution',
+    'enable_cache', 'disable_cache', 'clear_cache'
 ]
 
 
@@ -187,8 +189,8 @@
     searches the current ``sys.path``, plus any modules that are frozen
     or built-in.
 
-    Note that :class:`ImpImporter` does not currently support being used by placement
-    on ``sys.meta_path``.
+    Note that :class:`ImpImporter` does not currently support being used by
+    placement on ``sys.meta_path``.
     """
 
     def __init__(self, path=None):
@@ -577,7 +579,8 @@
     argument should be the name of a package, in standard module format
     (``foo.bar``). The resource argument should be in the form of a relative
     filename, using ``'/'`` as the path separator. The parent directory name
-    ``'..'`` is not allowed, and nor is a rooted name (starting with a ``'/'``).
+    ``'..'`` is not allowed, and nor is a rooted name (starting with a
+    ``'/'``).
 
     The function returns a binary string, which is the contents of the
     specified resource.
@@ -613,6 +616,97 @@
 
 DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
 
+# Cache
+_cache_name = {} # maps names to Distribution instances
+_cache_name_egg = {} # maps names to EggInfoDistribution instances
+_cache_path = {} # maps paths to Distribution instances
+_cache_path_egg = {} # maps paths to EggInfoDistribution instances
+_cache_generated = False # indicates if .dist-info distributions are cached
+_cache_generated_egg = False # indicates if .dist-info and .egg are cached
+_cache_enabled = True
+
+
+def enable_cache():
+    """
+    Enables the internal cache.
+
+    Note that this function will not clear the cache in any case, for that
+    functionality see :func:`clear_cache`.
+    """
+    global _cache_enabled
+
+    _cache_enabled = True
+
+def disable_cache():
+    """
+    Disables the internal cache.
+
+    Note that this function will not clear the cache in any case, for that
+    functionality see :func:`clear_cache`.
+    """
+    global _cache_enabled
+
+    _cache_enabled = False
+
+def clear_cache():
+    """ Clears the internal cache. """
+    global _cache_name, _cache_name_egg, cache_path, _cache_path_egg, \
+           _cache_generated, _cache_generated_egg
+
+    _cache_name = {}
+    _cache_name_egg = {}
+    _cache_path = {}
+    _cache_path_egg = {}
+    _cache_generated = False
+    _cache_generated_egg = False
+
+
+def _yield_distributions(include_dist, include_egg):
+    """
+    Yield .dist-info and .egg(-info) distributions, based on the arguments
+
+    :parameter include_dist: yield .dist-info distributions
+    :parameter include_egg: yield .egg(-info) distributions
+    """
+    for path in sys.path:
+        realpath = os.path.realpath(path)
+        if not os.path.isdir(realpath):
+            continue
+        for dir in os.listdir(realpath):
+            dist_path = os.path.join(realpath, dir)
+            if include_dist and dir.endswith('.dist-info'):
+                yield Distribution(dist_path)
+            elif include_egg and (dir.endswith('.egg-info') or
+                                  dir.endswith('.egg')):
+                yield EggInfoDistribution(dist_path)
+
+
+def _generate_cache(use_egg_info=False):
+    global _cache_generated, _cache_generated_egg
+
+    if _cache_generated_egg or (_cache_generated and not use_egg_info):
+        return
+    else:
+        gen_dist = not _cache_generated
+        gen_egg = use_egg_info
+
+        for dist in _yield_distributions(gen_dist, gen_egg):
+            if isinstance(dist, Distribution):
+                _cache_path[dist.path] = dist
+                if not dist.name in _cache_name:
+                    _cache_name[dist.name] = []
+                _cache_name[dist.name].append(dist)
+            else:
+                _cache_path_egg[dist.path] = dist
+                if not dist.name in _cache_name_egg:
+                    _cache_name_egg[dist.name] = []
+                _cache_name_egg[dist.name].append(dist)
+
+        if gen_dist:
+            _cache_generated = True
+        if gen_egg:
+            _cache_generated_egg = True
+
 
 class Distribution(object):
     """Created with the *path* of the ``.dist-info`` directory provided to the
@@ -627,15 +721,23 @@
     """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
     the distribution's ``METADATA`` file."""
     requested = False
-    """A boolean that indicates whether the ``REQUESTED`` metadata file is present
-    (in other words, whether the package was installed by user request)."""
+    """A boolean that indicates whether the ``REQUESTED`` metadata file is
+    present (in other words, whether the package was installed by user
+    request or it was installed as a dependency)."""
 
     def __init__(self, path):
+        if _cache_enabled and path in _cache_path:
+            self.metadata = _cache_path[path].metadata
+        else:
+            metadata_path = os.path.join(path, 'METADATA')
+            self.metadata = DistributionMetadata(path=metadata_path)
+
         self.path = path
-        metadata_path = os.path.join(path, 'METADATA')
-        self.metadata = DistributionMetadata(path=metadata_path)
         self.name = self.metadata['name']
 
+        if _cache_enabled and not path in _cache_path:
+            _cache_path[path] = self
+
     def _get_records(self, local=False):
         RECORD = os.path.join(self.path, 'RECORD')
         record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',')
@@ -756,6 +858,11 @@
     def __init__(self, path):
         self.path = path
 
+        if _cache_enabled and path in _cache_path_egg:
+            self.metadata = _cache_path_egg[path].metadata
+            self.name = self.metadata['Name']
+            return
+
         # reused from Distribute's pkg_resources
         def yield_lines(strs):
             """Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
@@ -787,7 +894,7 @@
                     requires = zipf.get_data('EGG-INFO/requires.txt')
                 except IOError:
                     requires = None
-            self.name = self.metadata['name']
+            self.name = self.metadata['Name']
         elif path.endswith('.egg-info'):
             if os.path.isdir(path):
                 path = os.path.join(path, 'PKG-INFO')
@@ -840,6 +947,9 @@
             else:
                 self.metadata['Requires'] += reqs
 
+        if _cache_enabled:
+            _cache_path_egg[self.path] = self
+
     def get_installed_files(self, local=False):
         return []
 
@@ -898,17 +1008,17 @@
 
     :rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution`
             instances"""
-    for path in sys.path:
-        realpath = os.path.realpath(path)
-        if not os.path.isdir(realpath):
-            continue
-        for dir in os.listdir(realpath):
-            if dir.endswith('.dist-info'):
-                dist = Distribution(os.path.join(realpath, dir))
-                yield dist
-            elif use_egg_info and (dir.endswith('.egg-info') or
-                                   dir.endswith('.egg')):
-                dist = EggInfoDistribution(os.path.join(realpath, dir))
+    if not _cache_enabled:
+        for dist in _yield_distributions(True, use_egg_info):
+            yield dist
+    else:
+        _generate_cache(use_egg_info)
+
+        for dist in _cache_path.itervalues():
+            yield dist
+
+        if use_egg_info:
+            for dist in _cache_path_egg.itervalues():
                 yield dist
 
 
@@ -928,17 +1038,19 @@
     value is expected. If the directory is not found, ``None`` is returned.
 
     :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None"""
-    found = None
-    for dist in get_distributions():
-        if dist.name == name:
-            found = dist
-            break
-    if use_egg_info:
-        for dist in get_distributions(True):
+    if not _cache_enabled:
+        for dist in _yield_distributions(True, use_egg_info):
             if dist.name == name:
-                found = dist
-                break
-    return found
+                return dist
+    else:
+        _generate_cache(use_egg_info)
+
+        if name in _cache_name:
+            return _cache_name[name][0]
+        elif use_egg_info and name in _cache_name_egg:
+            return _cache_name_egg[name][0]
+        else:
+            return None
 
 
 def obsoletes_distribution(name, version=None, use_egg_info=False):
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
--- a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
+++ b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
@@ -2,3 +2,4 @@
 Name: grammar
 Version: 1.0a4
 Requires-Dist: truffles (>=1.2)
+Author: Sherlock Holmes
diff --git a/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info b/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: truffles
+Version: 5.0
diff --git a/src/distutils2/_backport/tests/test_pkgutil.py b/src/distutils2/_backport/tests/test_pkgutil.py
--- a/src/distutils2/_backport/tests/test_pkgutil.py
+++ b/src/distutils2/_backport/tests/test_pkgutil.py
@@ -25,12 +25,14 @@
     except ImportError:
         from unittest2.compatibility import relpath
 
+# Adapted from Python 2.7's trunk
+
 # TODO Add a test for getting a distribution that is provided by another
 # distribution.
 
 # TODO Add a test for absolute pathed RECORD items (e.g. /etc/myapp/config.ini)
 
-# Adapted from Python 2.7's trunk
+
 class TestPkgUtilData(unittest.TestCase):
 
     def setUp(self):
@@ -108,10 +110,14 @@
 
         del sys.modules[pkg]
 
+
 # Adapted from Python 2.7's trunk
+
+
 class TestPkgUtilPEP302(unittest.TestCase):
 
     class MyTestLoader(object):
+
         def load_module(self, fullname):
             # Create an empty module
             mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
@@ -120,13 +126,14 @@
             # Make it a package
             mod.__path__ = []
             # Count how many times the module is reloaded
-            mod.__dict__['loads'] = mod.__dict__.get('loads',0) + 1
+            mod.__dict__['loads'] = mod.__dict__.get('loads', 0) + 1
             return mod
 
         def get_data(self, path):
             return "Hello, world!"
 
     class MyTestImporter(object):
+
         def find_module(self, fullname, path=None):
             return TestPkgUtilPEP302.MyTestLoader()
 
@@ -319,7 +326,7 @@
         current_path = os.path.abspath(os.path.dirname(__file__))
         self.sys_path = sys.path[:]
         self.fake_dists_path = os.path.join(current_path, 'fake_dists')
-        sys.path[0:0] = [self.fake_dists_path]
+        sys.path.insert(0, self.fake_dists_path)
 
     def tearDown(self):
         sys.path[:] = self.sys_path
@@ -366,8 +373,12 @@
             if not isinstance(dist, Distribution):
                 self.fail("item received was not a Distribution instance: "
                     "%s" % type(dist))
-            if dist.name in dict(fake_dists).keys():
+            if dist.name in dict(fake_dists).keys() and \
+               dist.path.startswith(self.fake_dists_path):
                 found_dists.append((dist.name, dist.metadata['version'],))
+            else:
+                # check that it doesn't find anything more than this
+                self.assertFalse(dist.path.startswith(self.fake_dists_path))
             # otherwise we don't care what other distributions are found
 
         # Finally, test that we found all that we were looking for
@@ -375,7 +386,8 @@
 
         # Now, test if the egg-info distributions are found correctly as well
         fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'),
-                       ('banana', '0.4'), ('strawberry', '0.6')]
+                       ('banana', '0.4'), ('strawberry', '0.6'),
+                       ('truffles', '5.0')]
         found_dists = []
 
         dists = [dist for dist in get_distributions(use_egg_info=True)]
@@ -384,8 +396,11 @@
                     isinstance(dist, EggInfoDistribution)):
                 self.fail("item received was not a Distribution or "
                           "EggInfoDistribution instance: %s" % type(dist))
-            if dist.name in dict(fake_dists).keys():
+            if dist.name in dict(fake_dists).keys() and \
+               dist.path.startswith(self.fake_dists_path):
                 found_dists.append((dist.name, dist.metadata['version']))
+            else:
+                self.assertFalse(dist.path.startswith(self.fake_dists_path))
 
         self.assertListEqual(sorted(fake_dists), sorted(found_dists))
 
@@ -485,7 +500,7 @@
 
         l = [dist.name for dist in provides_distribution('truffles', '>1.5',
                                                          use_egg_info=True)]
-        checkLists(l, ['bacon'])
+        checkLists(l, ['bacon', 'truffles'])
 
         l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
         checkLists(l, ['choxie', 'towel-stuff'])
@@ -549,6 +564,33 @@
         l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')]
         checkLists(l, ['towel-stuff'])
 
+    def test_yield_distribution(self):
+        # tests the internal function _yield_distributions
+        from distutils2._backport.pkgutil import _yield_distributions
+        checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y))
+
+        eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'),
+                ('truffles', '5.0'), ('cheese', '2.0.2')]
+        dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'),
+                 ('towel-stuff', '0.1')]
+
+        checkLists([], _yield_distributions(False, False))
+
+        found = [(dist.name, dist.metadata['Version'])
+                for dist in _yield_distributions(False, True)
+                if dist.path.startswith(self.fake_dists_path)]
+        checkLists(eggs, found)
+
+        found = [(dist.name, dist.metadata['Version'])
+                for dist in _yield_distributions(True, False)
+                if dist.path.startswith(self.fake_dists_path)]
+        checkLists(dists, found)
+
+        found = [(dist.name, dist.metadata['Version'])
+                for dist in _yield_distributions(True, True)
+                if dist.path.startswith(self.fake_dists_path)]
+        checkLists(dists + eggs, found)
+
 
 def test_suite():
     suite = unittest.TestSuite()
diff --git a/src/distutils2/command/install.py b/src/distutils2/command/install.py
--- a/src/distutils2/command/install.py
+++ b/src/distutils2/command/install.py
@@ -79,15 +79,26 @@
 
         ('record=', None,
          "filename in which to record list of installed files"),
+
+        # .dist-info related arguments, read by install_dist_info
+        ('no-distinfo', None, 'do not create a .dist-info directory'),
+        ('distinfo-dir=', None,
+                           'directory where the the .dist-info directory will '
+                           'be installed'),
+        ('installer=', None, 'the name of the installer'),
+        ('requested', None, 'generate a REQUESTED file'),
+        ('no-requested', None, 'do not generate a REQUESTED file'),
+        ('no-distinfo-record', None, 'do not generate a RECORD file'),
         ]
 
-    boolean_options = ['compile', 'force', 'skip-build']
+    boolean_options = ['compile', 'force', 'skip-build', 'no-dist-info',
+                       'requested', 'no-dist-record',]
 
     user_options.append(('user', None,
                         "install in user site-package '%s'" % \
                             get_path('purelib', '%s_user' % os.name)))
     boolean_options.append('user')
-    negative_opt = {'no-compile' : 'compile'}
+    negative_opt = {'no-compile' : 'compile', 'no-requested': 'requested'}
 
 
     def initialize_options(self):
@@ -160,6 +171,13 @@
 
         self.record = None
 
+        # .dist-info related options
+        self.no_distinfo = None
+        self.distinfo_dir = None
+        self.installer = None
+        self.requested = None
+        self.no_distinfo_record = None
+
 
     # -- Option finalizing methods -------------------------------------
     # (This is rather more involved than for most commands,
@@ -306,6 +324,9 @@
         # Punt on doc directories for now -- after all, we're punting on
         # documentation completely!
 
+        if self.no_distinfo is None:
+            self.no_distinfo = False
+
     def dump_dirs(self, msg):
         """Dumps the list of user options."""
         from distutils2.fancy_getopt import longopt_xlate
@@ -586,5 +607,7 @@
                     ('install_headers', has_headers),
                     ('install_scripts', has_scripts),
                     ('install_data',    has_data),
-                    ('install_egg_info', lambda self:True),
+                    # keep install_distinfo last, as it needs the record
+                    # with files to be completely generated
+                    ('install_distinfo', lambda self: not self.no_distinfo),
                    ]
diff --git a/src/distutils2/command/install_distinfo.py b/src/distutils2/command/install_distinfo.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/command/install_distinfo.py
@@ -0,0 +1,174 @@
+"""
+distutils.command.install_distinfo
+==================================
+
+:Author: Josip Djolonga
+
+This module implements the ``install_distinfo`` command that creates the
+``.dist-info`` directory for the distribution, as specified in :pep:`376`.
+Usually, you do not have to call this command directly, it gets called
+automatically by the ``install`` command.
+"""
+
+from distutils2.command.cmd import Command
+from distutils2 import log
+from distutils2._backport.shutil import rmtree
+
+
+import csv
+import hashlib
+import os
+import re
+
+
+class install_distinfo(Command):
+    """Install a .dist-info directory for the package"""
+
+    description = 'Install a .dist-info directory for the package'
+
+    user_options = [
+        ('distinfo-dir=', None,
+                           'directory where the the .dist-info directory will '
+                           'be installed'),
+        ('installer=', None, 'the name of the installer'),
+        ('requested', None, 'generate a REQUESTED file'),
+        ('no-requested', None, 'do not generate a REQUESTED file'),
+        ('no-distinfo-record', None, 'do not generate a RECORD file'),
+    ]
+
+    boolean_options = [
+        'requested',
+        'no-dist-record',
+    ]
+
+    negative_opt = {'no-requested': 'requested'}
+
+    def initialize_options(self):
+        self.distinfo_dir = None
+        self.installer = None
+        self.requested = None
+        self.no_distinfo_record = None
+
+    def finalize_options(self):
+        self.set_undefined_options('install',
+                                   ('distinfo_dir', 'distinfo_dir'),
+                                   ('installer', 'installer'),
+                                   ('requested', 'requested'),
+                                   ('no_distinfo_record',
+                                        'no_distinfo_record'))
+
+        self.set_undefined_options('install_lib',
+                                   ('install_dir', 'distinfo_dir'))
+
+        if self.installer is None:
+            self.installer = 'distutils'
+        if self.requested is None:
+            self.requested = True
+        if self.no_distinfo_record is None:
+            self.no_distinfo_record = False
+
+        metadata = self.distribution.metadata
+
+        basename = "%s-%s.dist-info" % (
+            to_filename(safe_name(metadata['Name'])),
+            to_filename(safe_version(metadata['Version'])),
+        )
+
+        self.distinfo_dir = os.path.join(self.distinfo_dir, basename)
+        self.outputs = []
+
+    def run(self):
+        if not self.dry_run:
+            target = self.distinfo_dir
+
+            if os.path.isdir(target) and not os.path.islink(target):
+                rmtree(target)
+            elif os.path.exists(target):
+                self.execute(os.unlink, (self.distinfo_dir,),
+                             "Removing " + target)
+
+            self.execute(os.makedirs, (target,), "Creating " + target)
+
+            metadata_path = os.path.join(self.distinfo_dir, 'METADATA')
+            log.info('Creating %s' % (metadata_path,))
+            self.distribution.metadata.write(metadata_path)
+            self.outputs.append(metadata_path)
+
+            installer_path = os.path.join(self.distinfo_dir, 'INSTALLER')
+            log.info('Creating %s' % (installer_path,))
+            f = open(installer_path, 'w')
+            try:
+                f.write(self.installer)
+            finally:
+                f.close()
+            self.outputs.append(installer_path)
+
+            if self.requested:
+                requested_path = os.path.join(self.distinfo_dir, 'REQUESTED')
+                log.info('Creating %s' % (requested_path,))
+                f = open(requested_path, 'w')
+                f.close()
+                self.outputs.append(requested_path)
+
+            if not self.no_distinfo_record:
+                record_path = os.path.join(self.distinfo_dir, 'RECORD')
+                log.info('Creating %s' % (record_path,))
+                f = open(record_path, 'wb')
+                try:
+                    writer = csv.writer(f, delimiter=',',
+                                           lineterminator=os.linesep,
+                                           quotechar='"')
+
+                    install = self.get_finalized_command('install')
+
+                    for fpath in install.get_outputs():
+                        if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
+                            # do not put size and md5 hash, as in PEP-376
+                            writer.writerow((fpath, '', ''))
+                        else:
+                            size = os.path.getsize(fpath)
+                            fd = open(fpath, 'r')
+                            hash = hashlib.md5()
+                            hash.update(fd.read())
+                            md5sum = hash.hexdigest()
+                            writer.writerow((fpath, md5sum, size))
+
+                    # add the RECORD file itself
+                    writer.writerow((record_path, '', ''))
+                    self.outputs.append(record_path)
+                finally:
+                    f.close()
+
+    def get_outputs(self):
+        return self.outputs
+
+
+# The following routines are taken from setuptools' pkg_resources module and
+# can be replaced by importing them from pkg_resources once it is included
+# in the stdlib.
+
+
+def safe_name(name):
+    """Convert an arbitrary string to a standard distribution name
+
+    Any runs of non-alphanumeric/. characters are replaced with a single '-'.
+    """
+    return re.sub('[^A-Za-z0-9.]+', '-', name)
+
+
+def safe_version(version):
+    """Convert an arbitrary string to a standard version string
+
+    Spaces become dots, and all other non-alphanumeric characters become
+    dashes, with runs of multiple dashes condensed to a single dash.
+    """
+    version = version.replace(' ', '.')
+    return re.sub('[^A-Za-z0-9.]+', '-', version)
+
+
+def to_filename(name):
+    """Convert a project or version name to its filename-escaped form
+
+    Any '-' characters are currently replaced with '_'.
+    """
+    return name.replace('-', '_')
diff --git a/src/distutils2/command/install_egg_info.py b/src/distutils2/command/install_egg_info.py
deleted file mode 100644
--- a/src/distutils2/command/install_egg_info.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""distutils.command.install_egg_info
-
-Implements the Distutils 'install_egg_info' command, for installing
-a package's PKG-INFO metadata."""
-
-
-from distutils2.command.cmd import Command
-from distutils2 import log
-from distutils2._backport.shutil import rmtree
-import os, sys, re
-
-class install_egg_info(Command):
-    """Install an .egg-info file for the package"""
-
-    description = "Install package's PKG-INFO metadata as an .egg-info file"
-    user_options = [
-        ('install-dir=', 'd', "directory to install to"),
-    ]
-
-    def initialize_options(self):
-        self.install_dir = None
-
-    def finalize_options(self):
-        metadata = self.distribution.metadata
-        self.set_undefined_options('install_lib',('install_dir','install_dir'))
-        basename = "%s-%s-py%s.egg-info" % (
-            to_filename(safe_name(metadata['Name'])),
-            to_filename(safe_version(metadata['Version'])),
-            sys.version[:3]
-        )
-        self.target = os.path.join(self.install_dir, basename)
-        self.outputs = [self.target]
-
-    def run(self):
-        target = self.target
-        if os.path.isdir(target) and not os.path.islink(target):
-            if self.dry_run:
-                pass # XXX
-            else:
-                rmtree(target)
-        elif os.path.exists(target):
-            self.execute(os.unlink,(self.target,),"Removing "+target)
-        elif not os.path.isdir(self.install_dir):
-            self.execute(os.makedirs, (self.install_dir,),
-                         "Creating "+self.install_dir)
-        log.info("Writing %s", target)
-        if not self.dry_run:
-            f = open(target, 'w')
-            self.distribution.metadata.write_file(f)
-            f.close()
-
-    def get_outputs(self):
-        return self.outputs
-
-
-# The following routines are taken from setuptools' pkg_resources module and
-# can be replaced by importing them from pkg_resources once it is included
-# in the stdlib.
-
-def safe_name(name):
-    """Convert an arbitrary string to a standard distribution name
-
-    Any runs of non-alphanumeric/. characters are replaced with a single '-'.
-    """
-    return re.sub('[^A-Za-z0-9.]+', '-', name)
-
-
-def safe_version(version):
-    """Convert an arbitrary string to a standard version string
-
-    Spaces become dots, and all other non-alphanumeric characters become
-    dashes, with runs of multiple dashes condensed to a single dash.
-    """
-    version = version.replace(' ','.')
-    return re.sub('[^A-Za-z0-9.]+', '-', version)
-
-
-def to_filename(name):
-    """Convert a project or version name to its filename-escaped form
-
-    Any '-' characters are currently replaced with '_'.
-    """
-    return name.replace('-','_')
diff --git a/src/distutils2/tests/test_install.py b/src/distutils2/tests/test_install.py
--- a/src/distutils2/tests/test_install.py
+++ b/src/distutils2/tests/test_install.py
@@ -195,11 +195,12 @@
         cmd.ensure_finalized()
         cmd.run()
 
-        # let's check the RECORD file was created with one
-        # line (the egg info file)
+        # let's check the RECORD file was created with four
+        # lines, one for each .dist-info entry: METADATA,
+        # INSTALLER, REQUSTED, RECORD
         f = open(cmd.record)
         try:
-            self.assertEqual(len(f.readlines()), 1)
+            self.assertEqual(len(f.readlines()), 4)
         finally:
             f.close()
 
diff --git a/src/distutils2/tests/test_install_distinfo.py b/src/distutils2/tests/test_install_distinfo.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/test_install_distinfo.py
@@ -0,0 +1,198 @@
+"""Tests for ``distutils2.command.install_distinfo``. """
+
+import os
+import sys
+import csv
+
+from distutils2._backport import hashlib
+from distutils2.command.install_distinfo import install_distinfo
+from distutils2.core import Command
+from distutils2.metadata import DistributionMetadata
+from distutils2.tests import support
+from distutils2.tests.support import unittest
+
+
+class DummyInstallCmd(Command):
+
+    def __init__(self, dist=None):
+        self.outputs = []
+        self.distribution = dist
+
+    def __getattr__(self, name):
+            return None
+
+    def ensure_finalized(self):
+        pass
+
+    def get_outputs(self):
+        return self.outputs + \
+               self.get_finalized_command('install_distinfo').get_outputs()
+
+
+class InstallDistinfoTestCase(support.TempdirManager,
+                              support.LoggingSilencer,
+                              support.EnvironGuard,
+                              unittest.TestCase):
+
+    checkLists = lambda self, x, y: self.assertListEqual(sorted(x), sorted(y))
+
+    def test_empty_install(self):
+        pkg_dir, dist = self.create_dist(name='foo',
+                                         version='1.0')
+        install_dir = self.mkdtemp()
+
+        install = DummyInstallCmd(dist)
+        dist.command_obj['install'] = install
+
+        cmd = install_distinfo(dist)
+        dist.command_obj['install_distinfo'] = cmd
+
+        cmd.initialize_options()
+        cmd.distinfo_dir = install_dir
+        cmd.ensure_finalized()
+        cmd.run()
+
+        self.checkLists(os.listdir(install_dir), ['foo-1.0.dist-info'])
+
+        dist_info = os.path.join(install_dir, 'foo-1.0.dist-info')
+        self.checkLists(os.listdir(dist_info),
+                        ['METADATA', 'RECORD', 'REQUESTED', 'INSTALLER'])
+        self.assertEqual(open(os.path.join(dist_info, 'INSTALLER')).read(),
+                         'distutils')
+        self.assertEqual(open(os.path.join(dist_info, 'REQUESTED')).read(),
+                         '')
+        meta_path = os.path.join(dist_info, 'METADATA')
+        self.assertTrue(DistributionMetadata(path=meta_path).check())
+
+    def test_installer(self):
+        pkg_dir, dist = self.create_dist(name='foo',
+                                         version='1.0')
+        install_dir = self.mkdtemp()
+
+        install = DummyInstallCmd(dist)
+        dist.command_obj['install'] = install
+
+        cmd = install_distinfo(dist)
+        dist.command_obj['install_distinfo'] = cmd
+
+        cmd.initialize_options()
+        cmd.distinfo_dir = install_dir
+        cmd.installer = 'bacon-python'
+        cmd.ensure_finalized()
+        cmd.run()
+
+        dist_info = os.path.join(install_dir, 'foo-1.0.dist-info')
+        self.assertEqual(open(os.path.join(dist_info, 'INSTALLER')).read(),
+                         'bacon-python')
+
+    def test_requested(self):
+        pkg_dir, dist = self.create_dist(name='foo',
+                                         version='1.0')
+        install_dir = self.mkdtemp()
+
+        install = DummyInstallCmd(dist)
+        dist.command_obj['install'] = install
+
+        cmd = install_distinfo(dist)
+        dist.command_obj['install_distinfo'] = cmd
+
+        cmd.initialize_options()
+        cmd.distinfo_dir = install_dir
+        cmd.requested = False
+        cmd.ensure_finalized()
+        cmd.run()
+
+        dist_info = os.path.join(install_dir, 'foo-1.0.dist-info')
+        self.checkLists(os.listdir(dist_info),
+                        ['METADATA', 'RECORD', 'INSTALLER'])
+
+    def test_no_record(self):
+        pkg_dir, dist = self.create_dist(name='foo',
+                                         version='1.0')
+        install_dir = self.mkdtemp()
+
+        install = DummyInstallCmd(dist)
+        dist.command_obj['install'] = install
+
+        cmd = install_distinfo(dist)
+        dist.command_obj['install_distinfo'] = cmd
+
+        cmd.initialize_options()
+        cmd.distinfo_dir = install_dir
+        cmd.no_distinfo_record = True
+        cmd.ensure_finalized()
+        cmd.run()
+
+        dist_info = os.path.join(install_dir, 'foo-1.0.dist-info')
+        self.checkLists(os.listdir(dist_info),
+                        ['METADATA', 'REQUESTED', 'INSTALLER'])
+
+    def test_record(self):
+        pkg_dir, dist = self.create_dist(name='foo',
+                                         version='1.0')
+        install_dir = self.mkdtemp()
+
+        install = DummyInstallCmd(dist)
+        dist.command_obj['install'] = install
+
+        fake_dists = os.path.join(os.path.dirname(__file__), '..',
+                                  '_backport', 'tests', 'fake_dists')
+        fake_dists = os.path.realpath(fake_dists)
+
+        # for testing, we simply add all files from _backport's fake_dists
+        dirs = []
+        for dir in os.listdir(fake_dists):
+                full_path = os.path.join(fake_dists, dir)
+                if not dir.endswith(('.egg', '.egg-info', '.dist-info')) \
+                   and os.path.isdir(full_path):
+                        dirs.append(full_path)
+
+        for dir in dirs:
+            for (path, subdirs, files) in os.walk(dir):
+                install.outputs += [os.path.join(path, f) for f in files]
+                install.outputs += [os.path.join('path', f + 'c')
+                                    for f in files if f.endswith('.py')]
+
+
+        cmd = install_distinfo(dist)
+        dist.command_obj['install_distinfo'] = cmd
+
+        cmd.initialize_options()
+        cmd.distinfo_dir = install_dir
+        cmd.ensure_finalized()
+        cmd.run()
+
+        dist_info = os.path.join(install_dir, 'foo-1.0.dist-info')
+
+        expected = []
+        for f in install.get_outputs():
+            if f.endswith('.pyc') or \
+               f == os.path.join(install_dir, 'foo-1.0.dist-info', 'RECORD'):
+                expected.append([f, '', ''])
+            else:
+                size = os.path.getsize(f)
+                md5 = hashlib.md5()
+                md5.update(open(f).read())
+                hash = md5.hexdigest()
+                expected.append([f, hash, str(size)])
+
+        parsed = []
+        f = open(os.path.join(dist_info, 'RECORD'), 'rb')
+        try:
+            reader = csv.reader(f, delimiter=',',
+                                   lineterminator=os.linesep,
+                                   quotechar='"')
+            parsed = list(reader)
+        finally:
+            f.close()
+
+        self.maxDiff = None
+        self.checkLists(parsed, expected)
+
+
+def test_suite():
+    return unittest.makeSuite(InstallDistinfoTestCase)
+
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="test_suite")

--
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list