[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