[Python-checkins] distutils2: merge upstream
tarek.ziade
python-checkins at python.org
Sun Jul 4 11:48:39 CEST 2010
tarek.ziade pushed 50b239007c3b to distutils2:
http://hg.python.org/distutils2/rev/50b239007c3b
changeset: 289:50b239007c3b
parent: 288:a8087e4ed26c
parent: 188:b7fd258b33e7
user: Alexis Metaireau <ametaireau at gmail.com>
date: Tue Jun 01 09:28:45 2010 +0200
summary: merge upstream
files: src/distutils2/dist.py
diff --git a/src/CONTRIBUTORS.txt b/src/CONTRIBUTORS.txt
new file mode 100644
--- /dev/null
+++ b/src/CONTRIBUTORS.txt
@@ -0,0 +1,25 @@
+============
+Contributors
+============
+
+Distutils2 is a project that was started and that is maintained by
+Tarek Ziadé, and many people are contributing to the project.
+
+If you did, please add your name below in alphabetical order !
+
+Thanks to:
+
+- Pior Bastida
+- Titus Brown
+- Nicolas Cadou
+- Josip Djolonga
+- Yannick Gringas
+- Carl Meyer
+- Michael Mulich
+- George Peris
+- Sean Reifschneider
+- Erik Rose
+- Brian Rosner
+- Alexandre Vassalotti
+- Martin von Löwis
+
diff --git a/src/DEVNOTES.txt b/src/DEVNOTES.txt
new file mode 100644
--- /dev/null
+++ b/src/DEVNOTES.txt
@@ -0,0 +1,10 @@
+Notes for developers
+====================
+
+- Distutils2 runs from 2.4 to 3.2 (3.x not implemented yet), so
+ make sure you don't use a syntax that doesn't work under
+ a specific Python version.
+
+- Always run tests.sh before you push a change. This implies
+ that you have all Python versions installed.
+
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
@@ -11,16 +11,18 @@
from types import ModuleType
from distutils2.errors import DistutilsError
from distutils2.metadata import DistributionMetadata
-from distutils2.version import suggest_normalized_version
+from distutils2.version import suggest_normalized_version, VersionPredicate
__all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
'walk_packages', 'iter_modules',
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
- 'Distribution', 'distinfo_dirname', 'get_distributions',
- 'get_distribution', 'get_file_users',
+ 'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
+ 'get_distributions', 'get_distribution', 'get_file_users',
+ 'provides_distribution', 'obsoletes_distribution',
]
+
def read_code(stream):
# This helper is needed in order for the PEP 302 emulation to
# correctly handle compiled files
@@ -37,6 +39,7 @@
def simplegeneric(func):
"""Make a trivial single-dispatch generic function"""
registry = {}
+
def wrapper(*args, **kw):
ob = args[0]
try:
@@ -47,6 +50,7 @@
mro = cls.__mro__
except AttributeError:
try:
+
class cls(cls, object):
pass
mro = cls.__mro__[1:]
@@ -128,7 +132,7 @@
# don't traverse path items we've seen before
path = [p for p in path if not seen(p)]
- for item in walk_packages(path, name+'.', onerror):
+ for item in walk_packages(path, name + '.', onerror):
yield item
@@ -206,7 +210,7 @@
for fn in filenames:
modname = inspect.getmodulename(fn)
- if modname=='__init__' or modname in yielded:
+ if modname == '__init__' or modname in yielded:
continue
path = os.path.join(self.path, fn)
@@ -216,7 +220,7 @@
modname = fn
for fn in os.listdir(path):
subname = inspect.getmodulename(fn)
- if subname=='__init__':
+ if subname == '__init__':
ispkg = True
break
else:
@@ -255,7 +259,7 @@
def _reopen(self):
if self.file and self.file.closed:
mod_type = self.etc[2]
- if mod_type==imp.PY_SOURCE:
+ if mod_type == imp.PY_SOURCE:
self.file = open(self.filename, 'rU')
elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION):
self.file = open(self.filename, 'rb')
@@ -270,22 +274,22 @@
def is_package(self, fullname):
fullname = self._fix_name(fullname)
- return self.etc[2]==imp.PKG_DIRECTORY
+ return self.etc[2] == imp.PKG_DIRECTORY
def get_code(self, fullname=None):
fullname = self._fix_name(fullname)
if self.code is None:
mod_type = self.etc[2]
- if mod_type==imp.PY_SOURCE:
+ if mod_type == imp.PY_SOURCE:
source = self.get_source(fullname)
self.code = compile(source, self.filename, 'exec')
- elif mod_type==imp.PY_COMPILED:
+ elif mod_type == imp.PY_COMPILED:
self._reopen()
try:
self.code = read_code(self.file)
finally:
self.file.close()
- elif mod_type==imp.PKG_DIRECTORY:
+ elif mod_type == imp.PKG_DIRECTORY:
self.code = self._get_delegate().get_code()
return self.code
@@ -293,29 +297,28 @@
fullname = self._fix_name(fullname)
if self.source is None:
mod_type = self.etc[2]
- if mod_type==imp.PY_SOURCE:
+ if mod_type == imp.PY_SOURCE:
self._reopen()
try:
self.source = self.file.read()
finally:
self.file.close()
- elif mod_type==imp.PY_COMPILED:
+ elif mod_type == imp.PY_COMPILED:
if os.path.exists(self.filename[:-1]):
f = open(self.filename[:-1], 'rU')
self.source = f.read()
f.close()
- elif mod_type==imp.PKG_DIRECTORY:
+ elif mod_type == imp.PKG_DIRECTORY:
self.source = self._get_delegate().get_source()
return self.source
-
def _get_delegate(self):
return ImpImporter(self.filename).find_module('__init__')
def get_filename(self, fullname=None):
fullname = self._fix_name(fullname)
mod_type = self.etc[2]
- if self.etc[2]==imp.PKG_DIRECTORY:
+ if self.etc[2] == imp.PKG_DIRECTORY:
return self._get_delegate().get_filename()
elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
return self.filename
@@ -339,16 +342,16 @@
fn = fn[plen:].split(os.sep)
- if len(fn)==2 and fn[1].startswith('__init__.py'):
+ if len(fn) == 2 and fn[1].startswith('__init__.py'):
if fn[0] not in yielded:
yielded[fn[0]] = 1
yield fn[0], True
- if len(fn)!=1:
+ if len(fn) != 1:
continue
modname = inspect.getmodulename(fn[0])
- if modname=='__init__':
+ if modname == '__init__':
continue
if modname and '.' not in modname and modname not in yielded:
@@ -436,6 +439,7 @@
if '.' not in fullname:
yield ImpImporter()
+
def get_loader(module_or_name):
"""Get a PEP 302 "loader" object for module_or_name
@@ -461,6 +465,7 @@
fullname = module_or_name
return find_loader(fullname)
+
def find_loader(fullname):
"""Find a PEP 302 "loader" object for fullname
@@ -551,6 +556,7 @@
return path
+
def get_data(package, resource):
"""Get a resource from a package.
@@ -594,6 +600,7 @@
DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
+
class Distribution(object):
"""Created with the *path* of the ``.dist-info`` directory provided to the
constructor. It reads the metadata contained in METADATA when it is
@@ -604,7 +611,7 @@
name = ''
"""The name of the distribution."""
metadata = None
- """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
+ """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
@@ -620,7 +627,7 @@
RECORD = os.path.join(self.path, 'RECORD')
record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',')
for row in record_reader:
- path, md5, size = row[:] + [ None for i in xrange(len(row), 3) ]
+ path, md5, size = row[:] + [None for i in xrange(len(row), 3)]
if local:
path = path.replace('/', os.sep)
path = os.path.join(sys.prefix, path)
@@ -635,7 +642,6 @@
A local absolute path is an absolute path in which occurrences of
``'/'`` have been replaced by the system separator given by ``os.sep``.
-
:parameter local: flag to say if the path should be returned a local
absolute path
:type local: boolean
@@ -643,10 +649,9 @@
"""
return self._get_records(local)
-
def uses(self, path):
"""
- Returns ``True`` if path is listed in RECORD. *path* can be a local
+ Returns ``True`` if path is listed in RECORD. *path* can be a local
absolute path or a relative ``'/'``-separated path.
:rtype: boolean
@@ -663,8 +668,8 @@
``file`` instance for the file pointed by *path*.
:parameter path: a ``'/'``-separated path relative to the ``.dist-info``
- directory or an absolute path; If *path* is an absolute
- path and doesn't start with the ``.dist-info``
+ directory or an absolute path; If *path* is an
+ absolute path and doesn't start with the ``.dist-info``
directory path, a :class:`DistutilsError` is raised
:type path: string
:parameter binary: If *binary* is ``True``, opens the file in read-only
@@ -696,8 +701,8 @@
def get_distinfo_files(self, local=False):
"""
- Iterates over the RECORD entries and returns paths for each line if the
- path is pointing to a file located in the ``.dist-info`` directory or
+ Iterates over the RECORD entries and returns paths for each line if the
+ path is pointing to a file located in the ``.dist-info`` directory or
one of its subdirectories.
:parameter local: If *local* is ``True``, each returned path is
@@ -710,16 +715,42 @@
yield path
+class EggInfoDistribution(object):
+ """Created with the *path* of the ``.egg-info`` directory or file provided
+ to the constructor. It reads the metadata contained in the file itself, or
+ if the given path happens to be a directory, the metadata is read from the
+ file PKG-INFO under that directory."""
+
+ name = ''
+ """The name of the distribution."""
+ metadata = None
+ """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
+ the distribution's METADATA file."""
+
+ def __init__(self, path):
+ if os.path.isdir(path):
+ path = os.path.join(path, 'PKG-INFO')
+ self.metadata = DistributionMetadata(path=path)
+ self.name = self.metadata['name']
+
+ def get_installed_files(self, local=False):
+ return []
+
+ def uses(self, path):
+ return False
+
+
def _normalize_dist_name(name):
"""Returns a normalized name from the given *name*.
:rtype: string"""
return name.replace('-', '_')
+
def distinfo_dirname(name, version):
"""
The *name* and *version* parameters are converted into their
filename-escaped form, i.e. any ``'-'`` characters are replaced with ``'_'``
- other than the one in ``'dist-info'`` and the one separating the name from
+ other than the one in ``'dist-info'`` and the one separating the name from
the version number.
:parameter name: is converted to a standard distribution name by replacing
@@ -743,13 +774,16 @@
normalized_version = version
return '-'.join([name, normalized_version]) + file_extension
-def get_distributions():
+
+def get_distributions(use_egg_info=False):
"""
Provides an iterator that looks for ``.dist-info`` directories in
``sys.path`` and returns :class:`Distribution` instances for each one of
- them.
+ them. If the parameters *use_egg_info* is ``True``, then the ``.egg-info``
+ files and directores are iterated as well.
- :rtype: iterator of :class:`Distribution` instances"""
+ :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):
@@ -758,25 +792,117 @@
if dir.endswith('.dist-info'):
dist = Distribution(os.path.join(realpath, dir))
yield dist
+ elif use_egg_info and dir.endswith('.egg-info'):
+ dist = EggInfoDistribution(os.path.join(realpath, dir))
+ yield dist
-def get_distribution(name):
+
+def get_distribution(name, use_egg_info=False):
"""
Scans all elements in ``sys.path`` and looks for all directories ending with
- ``.dist-info``. Returns a :class:`Distribution` corresponding to the
+ ``.dist-info``. Returns a :class:`Distribution` corresponding to the
``.dist-info`` directory that contains the METADATA that matches *name* for
- the *name* metadata.
+ the *name* metadata field.
+ If no distribution exists with the given *name* and the parameter
+ *use_egg_info* is set to ``True``, then all files and directories ending
+ with ``.egg-info`` are scanned. A :class:`EggInfoDistribution` instance is
+ returned if one is found that has metadata that matches *name* for the
+ *name* metadata field.
This function only returns the first result founded, as no more than one
value is expected. If the directory is not found, ``None`` is returned.
- :rtype: :class:`Distribution` or None"""
+ :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 dist.name == name:
+ found = dist
+ break
return found
+
+def obsoletes_distribution(name, version=None, use_egg_info=False):
+ """
+ Iterates over all distributions to find which distributions obsolete *name*.
+ If a *version* is provided, it will be used to filter the results.
+ If the argument *use_egg_info* is set to ``True``, then ``.egg-info``
+ distributions will be considered as well.
+
+ :type name: string
+ :type version: string
+ :parameter name:
+ """
+ for dist in get_distributions(use_egg_info):
+ obsoleted = dist.metadata['Obsoletes-Dist'] + dist.metadata['Obsoletes']
+ for obs in obsoleted:
+ o_components = obs.split(' ', 1)
+ if len(o_components) == 1 or version is None:
+ if name == o_components[0]:
+ yield dist
+ break
+ else:
+ try:
+ predicate = VersionPredicate(obs)
+ except ValueError:
+ raise DistutilsError(('Distribution %s has ill formed' +
+ ' obsoletes field') % (dist.name,))
+ if name == o_components[0] and predicate.match(version):
+ yield dist
+ break
+
+
+def provides_distribution(name, version=None, use_egg_info=False):
+ """
+ Iterates over all distributions to find which distributions provide *name*.
+ If a *version* is provided, it will be used to filter the results. Scans
+ all elements in ``sys.path`` and looks for all directories ending with
+ ``.dist-info``. Returns a :class:`Distribution` corresponding to the
+ ``.dist-info`` directory that contains a ``METADATA`` that matches *name*
+ for the name metadata. If the argument *use_egg_info* is set to ``True``,
+ then all files and directories ending with ``.egg-info`` are considered
+ as well and returns an :class:`EggInfoDistribution` instance.
+
+ This function only returns the first result founded, since no more than
+ one values are expected. If the directory is not found, returns ``None``.
+
+ :parameter version: a version specifier that indicates the version
+ required, conforming to the format in ``PEP-345``
+
+ :type name: string
+ :type version: string
+ """
+ predicate = None
+ if not version is None:
+ try:
+ predicate = VersionPredicate(name + ' (' + version + ')')
+ except ValueError:
+ raise DistutilsError('Invalid name or version')
+
+ for dist in get_distributions(use_egg_info):
+ provided = dist.metadata['Provides-Dist'] + dist.metadata['Provides']
+
+ for p in provided:
+ p_components = p.split(' ', 1)
+ if len(p_components) == 1 or predicate is None:
+ if name == p_components[0]:
+ yield dist
+ break
+ else:
+ p_name, p_ver = p_components
+ if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')':
+ raise DistutilsError(('Distribution %s has invalid ' +
+ 'provides field') % (dist.name,))
+ p_ver = p_ver[1:-1] # trim off the parenthesis
+ if p_name == name and predicate.match(p_ver):
+ yield dist
+ break
+
+
def get_file_users(path):
"""
Iterates over all distributions to find out which distributions uses
diff --git a/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO b/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: bacon
+Version: 0.1
+Provides-Dist: truffles (2.0)
+Obsoletes-Dist: truffles (>=0.9,<=1.5)
diff --git a/src/distutils2/_backport/tests/fake_dists/cheese-2.0.2.egg-info b/src/distutils2/_backport/tests/fake_dists/cheese-2.0.2.egg-info
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/cheese-2.0.2.egg-info
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: cheese
+Version: 2.0.2
+Provides-Dist: truffles (1.0.2)
+Obsoletes-Dist: truffles (!=1.2,<=2.0)
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
--- a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
+++ b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
@@ -4,3 +4,5 @@
Summary: Chocolate with a kick!
Requires-Dist: towel-stuff (0.1)
Provides-Dist: truffles (1.0)
+Obsoletes-Dist: truffles (<=0.8,>=0.5)
+Obsoletes-Dist: truffles (<=0.9,>=0.6)
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
@@ -1,3 +1,4 @@
Metadata-Version: 1.2
Name: grammar
Version: 1.0a4
+Requires-Dist: truffles (>=1.2)
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
--- a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
+++ b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
@@ -1,3 +1,5 @@
Metadata-Version: 1.2
Name: towel-stuff
Version: 0.1
+Provides-Dist: truffles (1.1.2)
+Obsoletes-Dist: truffles (!=0.8,<1.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
@@ -216,7 +216,9 @@
found_dists = []
# Import the function in question
- from distutils2._backport.pkgutil import get_distributions, Distribution
+ from distutils2._backport.pkgutil import get_distributions, \
+ Distribution, \
+ EggInfoDistribution
# Verify the fake dists have been found.
dists = [ dist for dist in get_distributions() ]
@@ -231,13 +233,31 @@
# Finally, test that we found all that we were looking for
self.assertListEqual(sorted(found_dists), sorted(fake_dists))
+ # Now, test if the egg-info distributions are found correctly as well
+ fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2')]
+ found_dists = []
+
+ dists = [ dist for dist in get_distributions(use_egg_info=True) ]
+ for dist in dists:
+ if not (isinstance(dist, Distribution) or \
+ 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():
+ found_dists.append((dist.name, dist.metadata['version']))
+
+ self.assertListEqual(sorted(fake_dists), sorted(found_dists))
+
+
def test_get_distribution(self):
"""Test for looking up a distribution by name."""
# Test the lookup of the towel-stuff distribution
name = 'towel-stuff' # Note: This is different from the directory name
# Import the function in question
- from distutils2._backport.pkgutil import get_distribution, Distribution
+ from distutils2._backport.pkgutil import get_distribution, \
+ Distribution, \
+ EggInfoDistribution
# Lookup the distribution
dist = get_distribution(name)
@@ -250,6 +270,21 @@
# Verify partial name matching doesn't work
self.assertEqual(None, get_distribution('towel'))
+ # Verify that it does not find egg-info distributions, when not
+ # instructed to
+ self.assertEqual(None, get_distribution('bacon'))
+ self.assertEqual(None, get_distribution('cheese'))
+
+ # Now check that it works well in both situations, when egg-info
+ # is a file and directory respectively.
+ dist = get_distribution('cheese', use_egg_info=True)
+ self.assertTrue(isinstance(dist, EggInfoDistribution))
+ self.assertEqual(dist.name, 'cheese')
+
+ dist = get_distribution('bacon', use_egg_info=True)
+ self.assertTrue(isinstance(dist, EggInfoDistribution))
+ self.assertEqual(dist.name, 'bacon')
+
def test_get_file_users(self):
"""Test the iteration of distributions that use a file."""
from distutils2._backport.pkgutil import get_file_users, Distribution
@@ -260,6 +295,80 @@
self.assertTrue(isinstance(dist, Distribution))
self.assertEqual(dist.name, name)
+ def test_provides(self):
+ """ Test for looking up distributions by what they provide """
+ from distutils2._backport.pkgutil import provides_distribution
+ from distutils2.errors import DistutilsError
+
+ checkLists = lambda x,y: self.assertListEqual(sorted(x), sorted(y))
+
+ l = [dist.name for dist in provides_distribution('truffles')]
+ checkLists(l, ['choxie', 'towel-stuff'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '1.0')]
+ checkLists(l, ['choxie'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '1.0',
+ use_egg_info=True)]
+ checkLists(l, ['choxie', 'cheese'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '1.1.2')]
+ checkLists(l, ['towel-stuff'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '1.1')]
+ checkLists(l, ['towel-stuff'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '!=1.1,<=2.0')]
+ checkLists(l, ['choxie'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '!=1.1,<=2.0',
+ use_egg_info=True)]
+ checkLists(l, ['choxie', 'bacon', 'cheese'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '>1.0')]
+ checkLists(l, ['towel-stuff'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '>1.5')]
+ checkLists(l, [])
+
+ l = [dist.name for dist in provides_distribution('truffles', '>1.5',
+ use_egg_info=True)]
+ checkLists(l, ['bacon'])
+
+ l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
+ checkLists(l, ['choxie', 'towel-stuff'])
+
+ def test_obsoletes(self):
+ """ Test looking for distributions based on what they obsolete """
+ from distutils2._backport.pkgutil import obsoletes_distribution
+ from distutils2.errors import DistutilsError
+
+ checkLists = lambda x,y: self.assertListEqual(sorted(x), sorted(y))
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')]
+ checkLists(l, [])
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '1.0',
+ use_egg_info=True)]
+ checkLists(l, ['cheese', 'bacon'])
+
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '0.8')]
+ checkLists(l, ['choxie'])
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '0.8',
+ use_egg_info=True)]
+ checkLists(l, ['choxie', 'cheese'])
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '0.9.6')]
+ checkLists(l, ['choxie', 'towel-stuff'])
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '0.5.2.3')]
+ checkLists(l, ['choxie', 'towel-stuff'])
+
+ l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')]
+ checkLists(l, ['towel-stuff'])
+
def test_suite():
suite = unittest2.TestSuite()
@@ -273,3 +382,16 @@
if __name__ == "__main__":
test_main()
+
+def test_suite():
+ suite = unittest2.TestSuite()
+ testcase_loader = unittest2.loader.defaultTestLoader.loadTestsFromTestCase
+ suite.addTest(testcase_loader(TestPkgUtilFunctions))
+ suite.addTest(testcase_loader(TestPkgUtilDistribution))
+ return suite
+
+def test_main():
+ run_unittest(test_suite())
+
+if __name__ == "__main__":
+ test_main()
diff --git a/src/distutils2/command/sdist.py b/src/distutils2/command/sdist.py
--- a/src/distutils2/command/sdist.py
+++ b/src/distutils2/command/sdist.py
@@ -121,6 +121,7 @@
self.metadata_check = 1
self.owner = None
self.group = None
+ self.filelist = None
def _check_archive_formats(self, formats):
supported_formats = [name for name, desc in get_archive_formats()]
@@ -152,10 +153,14 @@
if self.dist_dir is None:
self.dist_dir = "dist"
+ if self.filelist is None:
+ self.filelist = Manifest()
+
+
def run(self):
# 'filelist' contains the list of files that will make up the
# manifest
- self.filelist = Manifest()
+ self.filelist.clear()
# Run sub commands
for cmd_name in self.get_sub_commands():
@@ -200,7 +205,7 @@
if self.use_defaults:
self.add_defaults()
if template_exists:
- self.read_template()
+ self.filelist.read_template(self.template)
if self.prune:
self.prune_file_list()
diff --git a/src/distutils2/converter/fixers/fix_imports.py b/src/distutils2/converter/fixers/fix_imports.py
--- a/src/distutils2/converter/fixers/fix_imports.py
+++ b/src/distutils2/converter/fixers/fix_imports.py
@@ -20,6 +20,9 @@
if node.type != syms.import_from:
return
+ if not hasattr(imp, "next_sibling"):
+ imp.next_sibling = imp.get_next_sibling()
+
while not hasattr(imp, 'value'):
imp = imp.children[0]
@@ -34,6 +37,8 @@
next = imp.next_sibling
while next is not None:
pattern.append(next.value)
+ if not hasattr(next, "next_sibling"):
+ next.next_sibling = next.get_next_sibling()
next = next.next_sibling
if pattern == ['import', 'setup']:
imp.value = 'distutils2.core'
diff --git a/src/distutils2/converter/fixers/fix_setup_options.py b/src/distutils2/converter/fixers/fix_setup_options.py
--- a/src/distutils2/converter/fixers/fix_setup_options.py
+++ b/src/distutils2/converter/fixers/fix_setup_options.py
@@ -41,6 +41,10 @@
def _fix_name(self, argument, remove_list):
name = argument.children[0]
+
+ if not hasattr(name, "next_sibling"):
+ name.next_sibling = name.get_next_sibling()
+
sibling = name.next_sibling
if sibling is None or sibling.type != token.EQUAL:
return False
@@ -48,6 +52,8 @@
if name.value in _OLD_NAMES:
name.value = _OLD_NAMES[name.value]
if name.value in _SEQUENCE_NAMES:
+ if not hasattr(sibling, "next_sibling"):
+ sibling.next_sibling = sibling.get_next_sibling()
right_operand = sibling.next_sibling
# replacing string -> list[string]
if right_operand.type == token.STRING:
diff --git a/src/distutils2/depgraph.py b/src/distutils2/depgraph.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/depgraph.py
@@ -0,0 +1,181 @@
+"""
+A dependency graph generator. The graph is represented as an instance of
+:class:`DependencyGraph`, and DOT output is possible as well.
+"""
+
+from distutils2._backport import pkgutil
+from distutils2.errors import DistutilsError
+from distutils2.version import VersionPredicate
+
+__all__ = ['DependencyGraph', 'generate_graph']
+
+
+class DependencyGraph(object):
+ """
+ Represents a dependency graph between distributions.
+
+ The depedency relationships are stored in an *adjacency_list* that maps
+ distributions to a list of ``(other, label)`` tuples where ``other``
+ is a distribution and the edge is labelled with ``label`` (i.e. the version
+ specifier, if such was provided). If any missing depencies are found,
+ they are stored in ``missing``. It maps distributions to a list of
+ requirements that were not provided by any other distributions.
+ """
+
+ def __init__(self):
+ self.adjacency_list = {}
+ self.missing = {}
+
+ def add_distribution(self, distribution):
+ """
+ Add distribution *x* to the graph.
+
+ :type distribution: :class:`pkgutil.Distribution` or
+ :class:`pkgutil.EggInfoDistribution`
+ """
+ self.adjacency_list[distribution] = list()
+ self.missing[distribution] = list()
+
+ def add_edge(self, x, y, label=None):
+ """
+ Add an edge from distribution *x* to distribution *y* with the given
+ *label*.
+
+
+ :type x: :class:`pkgutil.Distribution` or
+ :class:`pkgutil.EggInfoDistribution`
+ :type y: :class:`pkgutil.Distribution` or
+ :class:`pkgutil.EggInfoDistribution`
+ :type label: ``str`` or ``None``
+ """
+ self.adjacency_list[x].append((y, label))
+
+ def add_missing(self, distribution, requirement):
+ """
+ Add a missing *requirement* for the given *distribution*.
+
+ :type distribution: :class:`pkgutil.Distribution` or
+ :class:`pkgutil.EggInfoDistribution`
+ :type requirement: ``str``
+ """
+ self.missing[distribution].append(requirement)
+
+ def to_dot(self, f, skip_disconnected=True):
+ """
+ Writes a DOT output for the graph to the provided *file*.
+ If *skip_disconnected* is set to ``True``, then all distributions
+ that are not dependent on any other distributions are skipped.
+
+ :type f: ``file``
+ ;type skip_disconnected: ``bool``
+ """
+ if not isinstance(f, file):
+ raise TypeError('the argument has to be of type file')
+
+ disconnected = []
+
+ f.write("digraph dependencies {\n")
+ for dist, adjs in self.adjacency_list.iteritems():
+ if len(adjs) == 0 and not skip_disconnected:
+ disconnected.append(dist)
+ for (other, label) in adjs:
+ if not label is None:
+ f.write('"%s" -> "%s" [label="%s"]\n' %
+ (dist.name, other.name, label))
+ else:
+ f.write('"%s" -> "%s"\n' % (dist.name, other.name))
+ if not skip_disconnected and len(disconnected) > 0:
+ f.write('subgraph disconnected {\n')
+ f.write('label = "Disconnected"\n')
+ f.write('bgcolor = red\n')
+
+ for dist in disconnected:
+ f.write('"%s"' % dist.name)
+ f.write('\n')
+ f.write('}\n')
+ f.write('}\n')
+
+
+def generate_graph(dists):
+ """
+ Generates a dependency graph from the given distributions.
+
+ :parameter dists: a list of distributions
+ :type dists: list of :class:`pkgutil.Distribution` and
+ :class:`pkgutil.EggInfoDistribution` instances
+ :rtype: an :class:`DependencyGraph` instance
+ """
+ graph = DependencyGraph()
+ provided = {} # maps names to lists of (version, dist) tuples
+ dists = list(dists) # maybe use generator_tools in future
+
+ # first, build the graph and find out the provides
+ for dist in dists:
+ graph.add_distribution(dist)
+ provides = dist.metadata['Provides-Dist'] + dist.metadata['Provides']
+
+ for p in provides:
+ comps = p.split(" ", 1)
+ name = comps[0]
+ version = None
+ if len(comps) == 2:
+ version = comps[1]
+ if len(version) < 3 or version[0] != '(' or version[-1] != ')':
+ raise DistutilsError('Distribution %s has ill formed' \
+ 'provides field: %s' % (dist.name, p))
+ version = version[1:-1] # trim off parenthesis
+ if not name in provided:
+ provided[name] = []
+ provided[name].append((version, dist))
+
+ # now make the edges
+ for dist in dists:
+ requires = dist.metadata['Requires-Dist'] + dist.metadata['Requires']
+ for req in requires:
+ predicate = VersionPredicate(req)
+ comps = req.split(" ", 1)
+ name = comps[0]
+
+ if not name in provided:
+ graph.add_missing(dist, req)
+ else:
+ for (version, provider) in provided[name]:
+ if predicate.match(version):
+ graph.add_edge(dist, provider, req)
+
+ return graph
+
+
+def dependent_dists(dists, dist):
+ """
+ Recursively generate a list of distributions from *dists* that are
+ dependent on *dist*.
+
+ :param dists: a list of distributions
+ :param dist: a distribution, member of *dists* for which we are interested
+ """
+ if not dist in dists:
+ raise ValueError('The given distribution is not a member of the list')
+ graph = generate_graph(dists)
+
+ dep = [dist]
+ fringe = [dist] # list of nodes we should expand
+ while not len(fringe) == 0:
+ next = graph.adjacency_list[fringe.pop()]
+ for (dist, label) in next:
+ if not dist in dep: # avoid infinite loops
+ dep.append(dist)
+ fringe.append(dist)
+
+ dep.pop()
+ return dep
+
+if __name__ == '__main__':
+ dists = list(pkgutil.get_distributions(use_egg_info=True))
+ graph = generate_graph(dists)
+ for dist, reqs in graph.missing.iteritems():
+ if len(reqs) > 0:
+ print("Missing dependencies for %s: %s" % (dist.name,
+ ", ".join(reqs)))
+ f = open('output.dot', 'w')
+ graph.to_dot(f, True)
diff --git a/src/distutils2/manifest.py b/src/distutils2/manifest.py
--- a/src/distutils2/manifest.py
+++ b/src/distutils2/manifest.py
@@ -54,6 +54,12 @@
for sort_tuple in sortable_files:
self.files.append(os.path.join(*sort_tuple))
+ def clear(self):
+ """Clear all collected files."""
+ self.files = []
+ if self.allfiles is not None:
+ self.allfiles = []
+
def remove_duplicates(self):
# Assumes list has been sorted!
for i in range(len(self.files) - 1, 0, -1):
diff --git a/src/distutils2/tests/test_converter.py b/src/distutils2/tests/test_converter.py
--- a/src/distutils2/tests/test_converter.py
+++ b/src/distutils2/tests/test_converter.py
@@ -36,4 +36,4 @@
return unittest2.makeSuite(ConverterTestCase)
if __name__ == '__main__':
- run_unittest(test_suite())
+ unittest2.main(defaultTest="test_suite")
diff --git a/src/distutils2/tests/test_core.py b/src/distutils2/tests/test_core.py
--- a/src/distutils2/tests/test_core.py
+++ b/src/distutils2/tests/test_core.py
@@ -60,6 +60,19 @@
distutils2.core.run_setup(
self.write_setup(setup_using___file__))
+ def test_run_setup_stop_after(self):
+ f = self.write_setup(setup_using___file__)
+ for s in ['init', 'config', 'commandline', 'run']:
+ distutils2.core.run_setup(f, stop_after=s)
+ self.assertRaises(ValueError, distutils2.core.run_setup,
+ f, stop_after='bob')
+
+ def test_run_setup_args(self):
+ f = self.write_setup(setup_using___file__)
+ d = distutils2.core.run_setup(f, script_args=["--help"],
+ stop_after="init")
+ self.assertEqual(['--help'], d.script_args)
+
def test_run_setup_uses_current_dir(self):
# This tests that the setup script is run with the current directory
# as its own current directory; this was temporarily broken by a
diff --git a/src/distutils2/tests/test_sdist.py b/src/distutils2/tests/test_sdist.py
--- a/src/distutils2/tests/test_sdist.py
+++ b/src/distutils2/tests/test_sdist.py
@@ -343,6 +343,19 @@
finally:
archive.close()
+ def test_get_file_list(self):
+ dist, cmd = self.get_cmd()
+ cmd.finalize_options()
+ cmd.template = os.path.join(self.tmp_dir, 'MANIFEST.in')
+ f = open(cmd.template, 'w')
+ try:
+ f.write('include MANIFEST.in\n')
+ finally:
+ f.close()
+
+ cmd.get_file_list()
+ self.assertIn('MANIFEST.in', cmd.filelist.files)
+
def test_suite():
return unittest2.makeSuite(SDistTestCase)
diff --git a/src/distutils2/tests/test_version.py b/src/distutils2/tests/test_version.py
--- a/src/distutils2/tests/test_version.py
+++ b/src/distutils2/tests/test_version.py
@@ -139,13 +139,17 @@
for predicate in predicates:
v = VersionPredicate(predicate)
- assert VersionPredicate('Hey (>=2.5,<2.7)').match('2.6')
- assert VersionPredicate('Ho').match('2.6')
- assert not VersionPredicate('Hey (>=2.5,!=2.6,<2.7)').match('2.6')
- assert VersionPredicate('Ho (<3.0)').match('2.6')
- assert VersionPredicate('Ho (<3.0,!=2.5)').match('2.6.0')
- assert not VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.0')
-
+ self.assertTrue(VersionPredicate('Hey (>=2.5,<2.7)').match('2.6'))
+ self.assertTrue(VersionPredicate('Ho').match('2.6'))
+ self.assertFalse(VersionPredicate('Hey (>=2.5,!=2.6,<2.7)').match('2.6'))
+ self.assertTrue(VersionPredicate('Ho (<3.0)').match('2.6'))
+ self.assertTrue(VersionPredicate('Ho (<3.0,!=2.5)').match('2.6.0'))
+ self.assertFalse(VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.0'))
+ self.assertTrue(VersionPredicate('Ho (2.5)').match('2.5.4'))
+ self.assertFalse(VersionPredicate('Ho (!=2.5)').match('2.5.2'))
+ self.assertTrue(VersionPredicate('Hey (<=2.5)').match('2.5.9'))
+ self.assertFalse(VersionPredicate('Hey (<=2.5)').match('2.6.0'))
+ self.assertTrue(VersionPredicate('Hey (>=2.5)').match('2.5.1'))
# XXX need to silent the micro version in this case
#assert not VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.3')
diff --git a/src/distutils2/version.py b/src/distutils2/version.py
--- a/src/distutils2/version.py
+++ b/src/distutils2/version.py
@@ -330,10 +330,11 @@
_operators = {"<": lambda x, y: x < y,
">": lambda x, y: x > y,
- "<=": lambda x, y: x <= y,
- ">=": lambda x, y: x >= y,
- "==": lambda x, y: x == y,
- "!=": lambda x, y: x != y}
+ "<=": lambda x, y: str(x).startswith(str(y)) or x < y,
+ ">=": lambda x, y: str(x).startswith(str(y)) or x > y,
+ "==": lambda x, y: str(x).startswith(str(y)),
+ "!=": lambda x, y: not str(x).startswith(str(y)),
+ }
def __init__(self, predicate):
predicate = predicate.strip()
diff --git a/src/runtests.py b/src/runtests.py
--- a/src/runtests.py
+++ b/src/runtests.py
@@ -6,7 +6,7 @@
def test_main():
import distutils2.tests
- from distutils2.tests import run_unittest, reap_children
+ from distutils2.tests import run_unittest, reap_children, TestFailed
from distutils2._backport.tests import test_suite as btest_suite
# just supporting -q right now
# to enable detailed/quiet output
@@ -14,10 +14,15 @@
verbose = sys.argv[-1] != '-q'
else:
verbose = 1
-
- run_unittest([distutils2.tests.test_suite(), btest_suite()],
- verbose_=verbose)
- reap_children()
+ try:
+ try:
+ run_unittest([distutils2.tests.test_suite(), btest_suite()],
+ verbose_=verbose)
+ return 0
+ except TestFailed:
+ return 1
+ finally:
+ reap_children()
if __name__ == "__main__":
try:
@@ -26,4 +31,5 @@
print('!!! You need to install unittest2')
sys.exit(1)
- test_main()
+ sys.exit(test_main())
+
diff --git a/src/tests.sh b/src/tests.sh
--- a/src/tests.sh
+++ b/src/tests.sh
@@ -1,12 +1,28 @@
#!/bin/sh
-echo Testing with Python 2.4....
-python2.4 runtests.py -q
+echo -n "Running tests for Python 2.4..."
+python2.4 runtests.py -q > /dev/null 2> /dev/null
+if [ $? -ne 0 ];then
+ echo "Failed"
+ exit $1
+else
+ echo "Success"
+fi
-echo
-echo Testing with Python 2.5....
-python2.5 runtests.py -q
+echo -n "Running tests for Python 2.5..."
+python2.5 runtests.py -q > /dev/null 2> /dev/null
+if [ $? -ne 0 ];then
+ echo "Failed"
+ exit $1
+else
+ echo "Success"
+fi
-echo
-echo Testing with Python 2.6....
-python2.6 runtests.py -q
+echo -n "Running tests for Python 2.6..."
+python2.6 runtests.py -q > /dev/null 2> /dev/null
+if [ $? -ne 0 ];then
+ echo "Failed"
+ exit $1
+else
+ echo "Success"
+fi
--
Repository URL: http://hg.python.org/distutils2
More information about the Python-checkins
mailing list