[Python-checkins] distutils2: Merge upstream.
tarek.ziade
python-checkins at python.org
Sun Aug 8 11:50:47 CEST 2010
tarek.ziade pushed 919bbd329ab3 to distutils2:
http://hg.python.org/distutils2/rev/919bbd329ab3
changeset: 504:919bbd329ab3
parent: 503:3d5db5aa326a
parent: 494:292c0b541b47
user: Alexis Metaireau <ametaireau at gmail.com>
date: Fri Aug 06 17:17:22 2010 +0200
summary: Merge upstream.
files: docs/source/new_commands.rst, src/Modules/_hashopenssl.c, src/Modules/md5.c, src/Modules/md5.h, src/Modules/md5module.c, src/Modules/sha256module.c, src/Modules/sha512module.c, src/Modules/shamodule.c, src/distutils2/command/install_egg_info.py, src/distutils2/depgraph.py, src/distutils2/index/dist.py, src/distutils2/index/simple.py, src/distutils2/index/xmlrpc.py, src/distutils2/metadata.py, src/distutils2/tests/pypi_server.py
diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -1,9 +1,10 @@
syntax: glob
*.py[co]
__pycache__/
+*.so
configure.cache
build/
MANIFEST
dist/
*.swp
-.coverage
\ No newline at end of file
+.coverage
diff --git a/docs/design/pep-0376.txt b/docs/design/pep-0376.txt
--- a/docs/design/pep-0376.txt
+++ b/docs/design/pep-0376.txt
@@ -17,7 +17,7 @@
This PEP proposes various enhancements for Distutils:
- A new format for the .egg-info structure.
-- Some APIs to read the meta-data of a distribution.
+- Some APIs to read the metadata of a distribution.
- A replacement PEP 262.
- An uninstall feature.
diff --git a/docs/source/command_hooks.rst b/docs/source/command_hooks.rst
new file mode 100644
--- /dev/null
+++ b/docs/source/command_hooks.rst
@@ -0,0 +1,31 @@
+=============
+Command hooks
+=============
+
+Distutils2 provides a way of extending its commands by the use of pre- and
+post- command hooks. The hooks are simple Python functions (or any callable
+objects) and are specified in the config file using their full qualified names.
+The pre-hooks are run after the command is finalized (its options are
+processed), but before it is run. The post-hooks are run after the command
+itself. Both types of hooks receive an instance of the command object.
+
+Sample usage of hooks
+=====================
+
+Firstly, you need to make sure your hook is present in the path. This is usually
+done by dropping them to the same folder where `setup.py` file lives ::
+
+ # file: myhooks.py
+ def my_install_hook(install_cmd):
+ print "Oh la la! Someone is installing my project!"
+
+Then, you need to point to it in your `setup.cfg` file, under the appropriate
+command section ::
+
+ [install]
+ pre-hook.project = myhooks.my_install_hook
+
+The hooks defined in different config files (system-wide, user-wide and
+package-wide) do not override each other as long as they are specified with
+different aliases (additional names after the dot). The alias in the example
+above is ``project``.
diff --git a/docs/source/new_commands.rst b/docs/source/commands.rst
rename from docs/source/new_commands.rst
rename to docs/source/commands.rst
--- a/docs/source/new_commands.rst
+++ b/docs/source/commands.rst
@@ -6,6 +6,50 @@
You might recognize some of them from other projects, like Distribute or
Setuptools.
+``upload`` - Upload source and/or binary distributions to PyPI
+==============================================================
+
+The Python Package Index (PyPI) not only stores the package info, but also the
+package data if the author of the package wishes to. The distutils command
+:command:`upload` pushes the distribution files to PyPI.
+
+The command is invoked immediately after building one or more distribution
+files. For example, the command ::
+
+ python setup.py sdist bdist_wininst upload
+
+will cause the source distribution and the Windows installer to be uploaded to
+PyPI. Note that these will be uploaded even if they are built using an earlier
+invocation of :file:`setup.py`, but that only distributions named on the command
+line for the invocation including the :command:`upload` command are uploaded.
+
+The :command:`upload` command uses the username, password, and repository URL
+from the :file:`$HOME/.pypirc` file . If a :command:`register` command was
+previously called in the same command, and if the password was entered in the
+prompt, :command:`upload` will reuse the entered password. This is useful if
+you do not want to store a clear text password in the :file:`$HOME/.pypirc`
+file.
+
+The ``upload`` command has a few options worth noting:
+
+``--sign, -s``
+ Sign each uploaded file using GPG (GNU Privacy Guard). The ``gpg`` program
+ must be available for execution on the system ``PATH``.
+
+``--identity=NAME, -i NAME``
+ Specify the identity or key name for GPG to use when signing. The value of
+ this option will be passed through the ``--local-user`` option of the
+ ``gpg`` program.
+
+``--show-response``
+ Display the full response text from server; this is useful for debugging
+ PyPI problems.
+
+``--repository=URL, -r URL``
+ The URL of the repository to upload to. Defaults to
+ http://pypi.python.org/pypi (i.e., the main PyPI installation).
+
+
``upload_docs`` - Upload package documentation to PyPI
======================================================
@@ -40,7 +84,7 @@
python setup.py upload_docs --upload-dir=docs/build/html
-As with any other ``setuptools`` based command, you can define useful
+As with any other command, you can define useful
defaults in the ``setup.cfg`` of your Python project, e.g.:
.. code-block:: ini
diff --git a/docs/source/index.rst b/docs/source/index.rst
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -14,7 +14,8 @@
metadata
pkgutil
depgraph
- new_commands
+ commands
+ command_hooks
test_framework
projects-index
version
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/DEVNOTES.txt b/src/DEVNOTES.txt
--- a/src/DEVNOTES.txt
+++ b/src/DEVNOTES.txt
@@ -1,10 +1,21 @@
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
+- Distutils2 runs on Python 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
one of these Python versions.
- Always run tests.sh before you push a change. This implies
- that you have all Python versions installed from 2.4 to 2.6.
+ that you have all Python versions installed from 2.4 to 2.7.
+- With Python 2.4, if you want to run tests with runtests.py, or run
+ just one test directly, be sure to run python2.4 setup.py build_ext
+ first, else tests won't find _hashlib or _md5. When using tests.sh,
+ build_ext is automatically done.
+
+- Renaming to do:
+
+ - DistributionMetadata > Metadata or ReleaseMetadata
+ - pkgutil > pkgutil.__init__ + new pkgutil.database (or better name)
+ - RationalizedVersion > Version
+ - suggest_rationalized_version > suggest
diff --git a/src/distutils2/__init__.py b/src/distutils2/__init__.py
--- a/src/distutils2/__init__.py
+++ b/src/distutils2/__init__.py
@@ -20,7 +20,7 @@
__version__ = "1.0a2"
-# when set to True, converts doctests by default too
+# when set to True, converts doctests by default too
run_2to3_on_doctests = True
# Standard package names for fixer packages
lib2to3_fixer_packages = ['lib2to3.fixes']
diff --git a/src/Modules/_hashopenssl.c b/src/distutils2/_backport/_hashopenssl.c
rename from src/Modules/_hashopenssl.c
rename to src/distutils2/_backport/_hashopenssl.c
diff --git a/src/distutils2/_backport/hashlib.py b/src/distutils2/_backport/hashlib.py
--- a/src/distutils2/_backport/hashlib.py
+++ b/src/distutils2/_backport/hashlib.py
@@ -65,20 +65,20 @@
def __get_builtin_constructor(name):
if name in ('SHA1', 'sha1'):
- import _sha
+ from distutils2._backport import _sha
return _sha.new
elif name in ('MD5', 'md5'):
- import _md5
+ from distutils2._backport import _md5
return _md5.new
elif name in ('SHA256', 'sha256', 'SHA224', 'sha224'):
- import _sha256
+ from distutils2._backport import _sha256
bs = name[3:]
if bs == '256':
return _sha256.sha256
elif bs == '224':
return _sha256.sha224
elif name in ('SHA512', 'sha512', 'SHA384', 'sha384'):
- import _sha512
+ from distutils2._backport import _sha512
bs = name[3:]
if bs == '512':
return _sha512.sha512
@@ -122,7 +122,7 @@
try:
- import _hashlib
+ from distutils2._backport import _hashlib
new = __hash_new
__get_hash = __get_openssl_constructor
except ImportError:
diff --git a/src/Modules/md5.c b/src/distutils2/_backport/md5.c
rename from src/Modules/md5.c
rename to src/distutils2/_backport/md5.c
diff --git a/src/Modules/md5.h b/src/distutils2/_backport/md5.h
rename from src/Modules/md5.h
rename to src/distutils2/_backport/md5.h
diff --git a/src/Modules/md5module.c b/src/distutils2/_backport/md5module.c
rename from src/Modules/md5module.c
rename to src/distutils2/_backport/md5module.c
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/Modules/sha256module.c b/src/distutils2/_backport/sha256module.c
rename from src/Modules/sha256module.c
rename to src/distutils2/_backport/sha256module.c
diff --git a/src/Modules/sha512module.c b/src/distutils2/_backport/sha512module.c
rename from src/Modules/sha512module.c
rename to src/distutils2/_backport/sha512module.c
diff --git a/src/Modules/shamodule.c b/src/distutils2/_backport/shamodule.c
rename from src/Modules/shamodule.c
rename to src/distutils2/_backport/shamodule.c
diff --git a/src/distutils2/_backport/tarfile.py b/src/distutils2/_backport/tarfile.py
--- a/src/distutils2/_backport/tarfile.py
+++ b/src/distutils2/_backport/tarfile.py
@@ -56,13 +56,6 @@
if not hasattr(os, 'SEEK_SET'):
os.SEEK_SET = 0
-if sys.platform == 'mac':
- # This module needs work for MacOS9, especially in the area of pathname
- # handling. In many places it is assumed a simple substitution of / by the
- # local os.path.sep is good enough to convert pathnames, but this does not
- # work with the mac rooted:path:name versus :nonrooted:path:name syntax
- raise ImportError, "tarfile does not work for platform==mac"
-
try:
import grp, pwd
except ImportError:
diff --git a/src/distutils2/_backport/tests/__init__.py b/src/distutils2/_backport/tests/__init__.py
--- a/src/distutils2/_backport/tests/__init__.py
+++ b/src/distutils2/_backport/tests/__init__.py
@@ -4,7 +4,7 @@
from distutils2.tests.support import unittest
-here = os.path.dirname(__file__)
+here = os.path.dirname(__file__) or os.curdir
def test_suite():
suite = unittest.TestSuite()
@@ -16,4 +16,5 @@
suite.addTest(module.test_suite())
return suite
-
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
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
@@ -10,7 +10,7 @@
try:
from hashlib import md5
except ImportError:
- from md5 import md5
+ from distutils2._backport.hashlib import md5
from test.test_support import run_unittest, TESTFN
from distutils2.tests.support import unittest
@@ -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/bdist_dumb.py b/src/distutils2/command/bdist_dumb.py
--- a/src/distutils2/command/bdist_dumb.py
+++ b/src/distutils2/command/bdist_dumb.py
@@ -77,9 +77,7 @@
("don't know how to create dumb built distributions " +
"on platform %s") % os.name
- self.set_undefined_options('bdist',
- ('dist_dir', 'dist_dir'),
- ('plat_name', 'plat_name'))
+ self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
def run(self):
if not self.skip_build:
diff --git a/src/distutils2/command/bdist_msi.py b/src/distutils2/command/bdist_msi.py
--- a/src/distutils2/command/bdist_msi.py
+++ b/src/distutils2/command/bdist_msi.py
@@ -153,10 +153,7 @@
else:
self.versions = list(self.all_versions)
- self.set_undefined_options('bdist',
- ('dist_dir', 'dist_dir'),
- ('plat_name', 'plat_name'),
- )
+ self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
if self.pre_install_script:
raise DistutilsOptionError, "the pre-install-script feature is not yet implemented"
diff --git a/src/distutils2/command/bdist_wininst.py b/src/distutils2/command/bdist_wininst.py
--- a/src/distutils2/command/bdist_wininst.py
+++ b/src/distutils2/command/bdist_wininst.py
@@ -100,10 +100,7 @@
" option must be specified" % (short_version,)
self.target_version = short_version
- self.set_undefined_options('bdist',
- ('dist_dir', 'dist_dir'),
- ('plat_name', 'plat_name'),
- )
+ self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
if self.install_script:
for script in self.distribution.scripts:
@@ -177,8 +174,8 @@
# And make an archive relative to the root of the
# pseudo-installation tree.
- from tempfile import mktemp
- archive_basename = mktemp()
+ from tempfile import NamedTemporaryFile
+ archive_basename = NamedTemporaryFile().name
fullname = self.distribution.get_fullname()
arcname = self.make_archive(archive_basename, "zip",
root_dir=self.bdist_dir)
diff --git a/src/distutils2/command/build_clib.py b/src/distutils2/command/build_clib.py
--- a/src/distutils2/command/build_clib.py
+++ b/src/distutils2/command/build_clib.py
@@ -76,9 +76,7 @@
self.set_undefined_options('build',
('build_temp', 'build_clib'),
('build_temp', 'build_temp'),
- ('compiler', 'compiler'),
- ('debug', 'debug'),
- ('force', 'force'))
+ 'compiler', 'debug', 'force')
self.libraries = self.distribution.libraries
if self.libraries:
diff --git a/src/distutils2/command/build_ext.py b/src/distutils2/command/build_ext.py
--- a/src/distutils2/command/build_ext.py
+++ b/src/distutils2/command/build_ext.py
@@ -177,13 +177,8 @@
def finalize_options(self):
self.set_undefined_options('build',
- ('build_lib', 'build_lib'),
- ('build_temp', 'build_temp'),
- ('compiler', 'compiler'),
- ('debug', 'debug'),
- ('force', 'force'),
- ('plat_name', 'plat_name'),
- )
+ 'build_lib', 'build_temp', 'compiler',
+ 'debug', 'force', 'plat_name')
if self.package is None:
self.package = self.distribution.ext_package
@@ -292,7 +287,7 @@
"config"))
else:
# building python standard extensions
- self.library_dirs.append('.')
+ self.library_dirs.append(os.curdir)
# for extensions under Linux or Solaris with a shared Python library,
# Python's library directory must be appended to library_dirs
@@ -305,7 +300,7 @@
self.library_dirs.append(sysconfig.get_config_var('LIBDIR'))
else:
# building python standard extensions
- self.library_dirs.append('.')
+ self.library_dirs.append(os.curdir)
# The argument parsing will result in self.define being a string, but
# it has to be a list of 2-tuples. All the preprocessor symbols
diff --git a/src/distutils2/command/build_py.py b/src/distutils2/command/build_py.py
--- a/src/distutils2/command/build_py.py
+++ b/src/distutils2/command/build_py.py
@@ -95,9 +95,7 @@
self._doctests_2to3 = []
def finalize_options(self):
- self.set_undefined_options('build',
- ('build_lib', 'build_lib'),
- ('force', 'force'))
+ self.set_undefined_options('build', 'build_lib', 'force')
# Get the distribution options that are aliases for build_py
# options -- list of packages and list of modules.
diff --git a/src/distutils2/command/build_scripts.py b/src/distutils2/command/build_scripts.py
--- a/src/distutils2/command/build_scripts.py
+++ b/src/distutils2/command/build_scripts.py
@@ -40,8 +40,7 @@
def finalize_options (self):
self.set_undefined_options('build',
('build_scripts', 'build_dir'),
- ('force', 'force'),
- ('executable', 'executable'))
+ 'force', 'executable')
self.scripts = self.distribution.scripts
def get_source_files(self):
diff --git a/src/distutils2/command/check.py b/src/distutils2/command/check.py
--- a/src/distutils2/command/check.py
+++ b/src/distutils2/command/check.py
@@ -8,13 +8,13 @@
from distutils2.errors import DistutilsSetupError
class check(Command):
- """This command checks the meta-data of the package.
+ """This command checks the metadata of the package.
"""
description = ("perform some checks on the package")
- user_options = [('metadata', 'm', 'Verify meta-data'),
+ user_options = [('metadata', 'm', 'Verify metadata'),
('restructuredtext', 'r',
- ('Checks if long string meta-data syntax '
- 'are reStructuredText-compliant')),
+ ('Checks if long string metadata syntax '
+ 'is reStructuredText-compliant')),
('strict', 's',
'Will exit with an error if a check fails')]
@@ -53,7 +53,7 @@
raise DistutilsSetupError(msg)
def check_metadata(self):
- """Ensures that all required elements of meta-data are supplied.
+ """Ensures that all required elements of metadata are supplied.
name, version, URL, (author and author_email) or
(maintainer and maintainer_email)).
@@ -62,7 +62,7 @@
"""
missing, __ = self.distribution.metadata.check()
if missing != []:
- self.warn("missing required meta-data: %s" % ', '.join(missing))
+ self.warn("missing required metadata: %s" % ', '.join(missing))
def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
diff --git a/src/distutils2/command/clean.py b/src/distutils2/command/clean.py
--- a/src/distutils2/command/clean.py
+++ b/src/distutils2/command/clean.py
@@ -40,13 +40,9 @@
self.all = None
def finalize_options(self):
- self.set_undefined_options('build',
- ('build_base', 'build_base'),
- ('build_lib', 'build_lib'),
- ('build_scripts', 'build_scripts'),
- ('build_temp', 'build_temp'))
- self.set_undefined_options('bdist',
- ('bdist_base', 'bdist_base'))
+ self.set_undefined_options('build', 'build_base', 'build_lib',
+ 'build_scripts', 'build_temp')
+ self.set_undefined_options('bdist', 'bdist_base')
def run(self):
# remove the build/temp.<plat> directory (unless it's already
diff --git a/src/distutils2/command/cmd.py b/src/distutils2/command/cmd.py
--- a/src/distutils2/command/cmd.py
+++ b/src/distutils2/command/cmd.py
@@ -51,6 +51,12 @@
# defined. The canonical example is the "install" command.
sub_commands = []
+ # Pre and post command hooks are run just before or just after the command
+ # itself. They are simple functions that receive the command instance. They
+ # should be specified as dotted strings.
+ pre_hook = None
+ post_hook = None
+
# -- Creation/initialization methods -------------------------------
@@ -297,26 +303,30 @@
else:
return self.__class__.__name__
- def set_undefined_options(self, src_cmd, *option_pairs):
- """Set the values of any "undefined" options from corresponding
- option values in some other command object. "Undefined" here means
- "is None", which is the convention used to indicate that an option
- has not been changed between 'initialize_options()' and
- 'finalize_options()'. Usually called from 'finalize_options()' for
- options that depend on some other command rather than another
- option of the same command. 'src_cmd' is the other command from
- which option values will be taken (a command object will be created
- for it if necessary); the remaining arguments are
- '(src_option,dst_option)' tuples which mean "take the value of
- 'src_option' in the 'src_cmd' command object, and copy it to
- 'dst_option' in the current command object".
+ def set_undefined_options(self, src_cmd, *options):
+ """Set values of undefined options from another command.
+
+ Undefined options are options set to None, which is the convention
+ used to indicate that an option has not been changed between
+ 'initialize_options()' and 'finalize_options()'. This method is
+ usually called from 'finalize_options()' for options that depend on
+ some other command rather than another option of the same command,
+ typically subcommands.
+
+ The 'src_cmd' argument is the other command from which option values
+ will be taken (a command object will be created for it if necessary);
+ the remaining positional arguments are strings that give the name of
+ the option to set. If the name is different on the source and target
+ command, you can pass a tuple with '(name_on_source, name_on_dest)' so
+ that 'self.name_on_dest' will be set from 'src_cmd.name_on_source'.
"""
-
- # Option_pairs: list of (src_option, dst_option) tuples
-
src_cmd_obj = self.distribution.get_command_obj(src_cmd)
src_cmd_obj.ensure_finalized()
- for (src_option, dst_option) in option_pairs:
+ for obj in options:
+ if isinstance(obj, tuple):
+ src_option, dst_option = obj
+ else:
+ src_option, dst_option = obj, obj
if getattr(self, dst_option) is None:
setattr(self, dst_option,
getattr(src_cmd_obj, src_option))
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,23 @@
('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'),
+ ('installer=', None, 'the name of the installer'),
+ ('requested', None, 'generate a REQUESTED file'),
+ ('no-requested', None, 'do not generate a REQUESTED file'),
+ ('no-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 +168,12 @@
self.record = None
+ # .dist-info related options
+ self.no_distinfo = None
+ self.installer = None
+ self.requested = None
+ self.no_record = None
+
# -- Option finalizing methods -------------------------------------
# (This is rather more involved than for most commands,
@@ -299,13 +313,14 @@
self.dump_dirs("after prepending root")
# Find out the build directories, ie. where to install from.
- self.set_undefined_options('build',
- ('build_base', 'build_base'),
- ('build_lib', 'build_lib'))
+ self.set_undefined_options('build', 'build_base', 'build_lib')
# 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 +601,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_data.py b/src/distutils2/command/install_data.py
--- a/src/distutils2/command/install_data.py
+++ b/src/distutils2/command/install_data.py
@@ -37,9 +37,7 @@
def finalize_options(self):
self.set_undefined_options('install',
('install_data', 'install_dir'),
- ('root', 'root'),
- ('force', 'force'),
- )
+ 'root', 'force')
def run(self):
self.mkpath(self.install_dir)
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,175 @@
+"""
+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.
+"""
+
+# This file was created from the code for the former command install_egg_info
+
+import os
+import csv
+import re
+from distutils2.command.cmd import Command
+from distutils2 import log
+from distutils2._backport.shutil import rmtree
+try:
+ import hashlib
+except ImportError:
+ from distutils2._backport import hashlib
+
+
+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-record', None, 'do not generate a RECORD file'),
+ ]
+
+ boolean_options = [
+ 'requested',
+ 'no-record',
+ ]
+
+ negative_opt = {'no-requested': 'requested'}
+
+ def initialize_options(self):
+ self.distinfo_dir = None
+ self.installer = None
+ self.requested = None
+ self.no_record = None
+
+ def finalize_options(self):
+ self.set_undefined_options('install',
+ ('installer', 'installer'),
+ ('requested', 'requested'),
+ ('no_record', 'no_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_record is None:
+ self.no_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_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/command/install_headers.py b/src/distutils2/command/install_headers.py
--- a/src/distutils2/command/install_headers.py
+++ b/src/distutils2/command/install_headers.py
@@ -29,8 +29,7 @@
def finalize_options(self):
self.set_undefined_options('install',
('install_headers', 'install_dir'),
- ('force', 'force'))
-
+ 'force')
def run(self):
headers = self.distribution.headers
diff --git a/src/distutils2/command/install_lib.py b/src/distutils2/command/install_lib.py
--- a/src/distutils2/command/install_lib.py
+++ b/src/distutils2/command/install_lib.py
@@ -68,11 +68,7 @@
self.set_undefined_options('install',
('build_lib', 'build_dir'),
('install_lib', 'install_dir'),
- ('force', 'force'),
- ('compile', 'compile'),
- ('optimize', 'optimize'),
- ('skip_build', 'skip_build'),
- )
+ 'force', 'compile', 'optimize', 'skip_build')
if self.compile is None:
self.compile = 1
diff --git a/src/distutils2/command/install_scripts.py b/src/distutils2/command/install_scripts.py
--- a/src/distutils2/command/install_scripts.py
+++ b/src/distutils2/command/install_scripts.py
@@ -36,9 +36,7 @@
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
self.set_undefined_options('install',
('install_scripts', 'install_dir'),
- ('force', 'force'),
- ('skip_build', 'skip_build'),
- )
+ 'force', 'skip_build')
def run (self):
if not self.skip_build:
diff --git a/src/distutils2/command/register.py b/src/distutils2/command/register.py
--- a/src/distutils2/command/register.py
+++ b/src/distutils2/command/register.py
@@ -21,15 +21,16 @@
class register(Command):
- description = ("register the distribution with the Python package index")
- user_options = [('repository=', 'r',
- "url of repository [default: %s]" % DEFAULT_REPOSITORY),
+ description = "register the distribution with the Python package index"
+ user_options = [
+ ('repository=', 'r',
+ "repository URL [default: %s]" % DEFAULT_REPOSITORY),
('show-response', None,
- 'display full response text from server'),
+ "display full response text from server"),
('list-classifiers', None,
- 'list the valid Trove classifiers'),
+ "list valid Trove classifiers"),
('strict', None ,
- 'Will stop the registering if the meta-data are not fully compliant')
+ "stop the registration if the metadata is not fully compliant")
]
boolean_options = ['show-response', 'verify', 'list-classifiers',
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
@@ -70,8 +70,8 @@
('dist-dir=', 'd',
"directory to put the source distribution archive(s) in "
"[default: dist]"),
- ('medata-check', None,
- "Ensure that all required elements of meta-data "
+ ('check-metadata', None,
+ "Ensure that all required elements of metadata "
"are supplied. Warn if any missing. [default]"),
('owner=', 'u',
"Owner name used when creating a tar file [default: current user]"),
@@ -80,7 +80,7 @@
]
boolean_options = ['use-defaults', 'prune',
- 'manifest-only', 'keep-temp', 'metadata-check']
+ 'manifest-only', 'keep-temp', 'check-metadata']
help_options = [
('help-formats', None,
@@ -362,7 +362,7 @@
'self.keep_temp' is true). The list of archive files created is
stored so it can be retrieved later by 'get_archive_files()'.
"""
- # Don't warn about missing meta-data here -- should be (and is!)
+ # Don't warn about missing metadata here -- should be (and is!)
# done elsewhere.
base_dir = self.distribution.get_fullname()
base_name = os.path.join(self.dist_dir, base_dir)
diff --git a/src/distutils2/command/upload.py b/src/distutils2/command/upload.py
--- a/src/distutils2/command/upload.py
+++ b/src/distutils2/command/upload.py
@@ -11,7 +11,7 @@
try:
from hashlib import md5
except ImportError:
- from md5 import md5
+ from distutils2._backport.hashlib import md5
from distutils2.errors import DistutilsOptionError
from distutils2.util import spawn
@@ -24,16 +24,19 @@
class upload(Command):
- description = "upload binary package to PyPI"
+ description = "upload distribution to PyPI"
- user_options = [('repository=', 'r',
- "url of repository [default: %s]" % \
- DEFAULT_REPOSITORY),
+ user_options = [
+ ('repository=', 'r',
+ "repository URL [default: %s]" % DEFAULT_REPOSITORY),
('show-response', None,
- 'display full response text from server'),
+ "display full response text from server"),
('sign', 's',
- 'sign files to upload using gpg'),
- ('identity=', 'i', 'GPG identity used to sign files'),
+ "sign files to upload using gpg"),
+ ('identity=', 'i',
+ "GPG identity used to sign files"),
+ ('upload-docs', None,
+ "upload documentation too"),
]
boolean_options = ['show-response', 'sign']
@@ -47,6 +50,7 @@
self.show_response = 0
self.sign = False
self.identity = None
+ self.upload_docs = False
def finalize_options(self):
if self.repository is None:
@@ -74,6 +78,12 @@
raise DistutilsOptionError("No dist file created in earlier command")
for command, pyversion, filename in self.distribution.dist_files:
self.upload_file(command, pyversion, filename)
+ if self.upload_docs:
+ upload_docs = self.get_finalized_command("upload_docs")
+ upload_docs.repository = self.repository
+ upload_docs.username = self.username
+ upload_docs.password = self.password
+ upload_docs.run()
# XXX to be refactored with register.post_to_server
def upload_file(self, command, pyversion, filename):
@@ -94,7 +104,7 @@
spawn(gpg_args,
dry_run=self.dry_run)
- # Fill in the data - send all the meta-data in case we need to
+ # Fill in the data - send all the metadata in case we need to
# register a new release
content = open(filename,'rb').read()
diff --git a/src/distutils2/command/upload_docs.py b/src/distutils2/command/upload_docs.py
--- a/src/distutils2/command/upload_docs.py
+++ b/src/distutils2/command/upload_docs.py
@@ -52,16 +52,19 @@
class upload_docs(Command):
user_options = [
- ('repository=', 'r', "url of repository [default: %s]" % DEFAULT_REPOSITORY),
- ('show-response', None, 'display full response text from server'),
- ('upload-dir=', None, 'directory to upload'),
+ ('repository=', 'r',
+ "repository URL [default: %s]" % DEFAULT_REPOSITORY),
+ ('show-response', None,
+ "display full response text from server"),
+ ('upload-dir=', None,
+ "directory to upload"),
]
def initialize_options(self):
self.repository = None
self.realm = None
self.show_response = 0
- self.upload_dir = "build/docs"
+ self.upload_dir = None
self.username = ''
self.password = ''
@@ -70,7 +73,7 @@
self.repository = DEFAULT_REPOSITORY
if self.realm is None:
self.realm = DEFAULT_REALM
- if self.upload_dir == None:
+ if self.upload_dir is None:
build = self.get_finalized_command('build')
self.upload_dir = os.path.join(build.build_base, "docs")
self.announce('Using upload directory %s' % self.upload_dir)
diff --git a/src/distutils2/core.py b/src/distutils2/core.py
--- a/src/distutils2/core.py
+++ b/src/distutils2/core.py
@@ -33,6 +33,7 @@
or: %(script)s cmd --help
"""
+
def gen_usage(script_name):
script = os.path.basename(script_name)
return USAGE % {'script': script}
@@ -59,6 +60,7 @@
'extra_objects', 'extra_compile_args', 'extra_link_args',
'swig_opts', 'export_symbols', 'depends', 'language')
+
def setup(**attrs):
"""The gateway to the Distutils: do everything your setup script needs
to do, in a highly flexible and user-driven way. Briefly: create a
@@ -155,7 +157,7 @@
def run_setup(script_name, script_args=None, stop_after="run"):
"""Run a setup script in a somewhat controlled environment, and
return the Distribution instance that drives things. This is useful
- if you need to find out the distribution meta-data (passed as
+ if you need to find out the distribution metadata (passed as
keyword args from 'script' to 'setup()', or the contents of the
config files or command-line.
@@ -205,8 +207,6 @@
# Hmm, should we do something if exiting with a non-zero code
# (ie. error)?
pass
- except:
- raise
if _setup_distribution is None:
raise RuntimeError, \
diff --git a/src/distutils2/depgraph.py b/src/distutils2/depgraph.py
--- a/src/distutils2/depgraph.py
+++ b/src/distutils2/depgraph.py
@@ -1,5 +1,5 @@
-"""Analyse the relationships between the distributions in the system and generate
-a dependency graph.
+"""Analyse the relationships between the distributions in the system
+and generate a dependency graph.
"""
from distutils2.errors import DistutilsError
diff --git a/src/distutils2/dist.py b/src/distutils2/dist.py
--- a/src/distutils2/dist.py
+++ b/src/distutils2/dist.py
@@ -6,19 +6,17 @@
__revision__ = "$Id: dist.py 77717 2010-01-24 00:33:32Z tarek.ziade $"
-import sys, os, re
-
-try:
- import warnings
-except ImportError:
- warnings = None
+import sys
+import os
+import re
+import warnings
from ConfigParser import RawConfigParser
from distutils2.errors import (DistutilsOptionError, DistutilsArgError,
DistutilsModuleError, DistutilsClassError)
from distutils2.fancy_getopt import FancyGetopt, translate_longopt
-from distutils2.util import check_environ, strtobool
+from distutils2.util import check_environ, strtobool, resolve_dotted_name
from distutils2 import log
from distutils2.metadata import DistributionMetadata
@@ -26,7 +24,7 @@
# the same as a Python NAME -- I don't allow leading underscores. The fact
# that they're very similar is no coincidence; the default naming scheme is
# to look for a Python module named after the command.
-command_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
+command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
class Distribution(object):
@@ -43,7 +41,6 @@
See the code for 'setup()', in core.py, for details.
"""
-
# 'global_options' describes the command-line options that may be
# supplied to the setup script prior to any actual commands.
# Eg. "./setup.py -n" or "./setup.py --quiet" both take advantage of
@@ -116,7 +113,7 @@
('use-2to3', None,
"use 2to3 to make source python 3.x compatible"),
('convert-2to3-doctests', None,
- "use 2to3 to convert doctests in seperate text files"),
+ "use 2to3 to convert doctests in seperate text files"),
]
display_option_names = map(lambda x: translate_longopt(x[0]),
display_options)
@@ -124,10 +121,8 @@
# negative options are options that exclude other options
negative_opt = {'quiet': 'verbose'}
-
# -- Creation/initialization methods -------------------------------
-
- def __init__ (self, attrs=None):
+ def __init__(self, attrs=None):
"""Construct a new Distribution instance: initialize all the
attributes of a Distribution, and then use 'attrs' (a dictionary
mapping attribute names to values) to assign some of those
@@ -145,17 +140,12 @@
for attr in self.display_option_names:
setattr(self, attr, 0)
- # Store the distribution meta-data (name, version, author, and so
+ # Store the distribution metadata (name, version, author, and so
# forth) in a separate object -- we're getting to have enough
# information here (and enough command-line options) that it's
- # worth it. Also delegate 'get_XXX()' methods to the 'metadata'
- # object in a sneaky and underhanded (but efficient!) way.
+ # worth it.
self.metadata = DistributionMetadata()
- #for basename in self.metadata._METHOD_BASENAMES:
- # method_name = "get_" + basename
- # setattr(self, method_name, getattr(self.metadata, method_name))
-
# 'cmdclass' maps command names to class objects, so we
# can 1) quickly figure out which class to instantiate when
# we need to create a new command object, and 2) have a way
@@ -257,10 +247,7 @@
setattr(self, key, val)
else:
msg = "Unknown distribution option: %r" % key
- if warnings is not None:
- warnings.warn(msg)
- else:
- sys.stderr.write(msg + "\n")
+ warnings.warn(msg)
# no-user-cfg is handled before other command line args
# because other args override the config files, and this
@@ -286,10 +273,10 @@
and return the new dictionary; otherwise, return the existing
option dictionary.
"""
- dict = self.command_options.get(command)
- if dict is None:
- dict = self.command_options[command] = {}
- return dict
+ d = self.command_options.get(command)
+ if d is None:
+ d = self.command_options[command] = {}
+ return d
def get_fullname(self):
return self.metadata.get_fullname()
@@ -385,9 +372,21 @@
for opt in options:
if opt != '__name__':
- val = parser.get(section,opt)
+ val = parser.get(section, opt)
opt = opt.replace('-', '_')
- opt_dict[opt] = (filename, val)
+
+ # ... although practicality beats purity :(
+ if opt.startswith("pre_hook.") or opt.startswith("post_hook."):
+ hook_type, alias = opt.split(".")
+ # Hooks are somewhat exceptional, as they are
+ # gathered from many config files (not overriden as
+ # other options).
+ # The option_dict expects {"command": ("filename", # "value")}
+ # so for hooks, we store only the last config file processed
+ hook_dict = opt_dict.setdefault(hook_type, (filename, {}))[1]
+ hook_dict[alias] = val
+ else:
+ opt_dict[opt] = (filename, val)
# Make the RawConfigParser forget everything (so we retain
# the original filenames that options come from)
@@ -402,12 +401,12 @@
try:
if alias:
setattr(self, alias, not strtobool(val))
- elif opt in ('verbose', 'dry_run'): # ugh!
+ elif opt in ('verbose', 'dry_run'): # ugh!
setattr(self, opt, strtobool(val))
else:
setattr(self, opt, val)
except ValueError, msg:
- raise DistutilsOptionError, msg
+ raise DistutilsOptionError(msg)
# -- Command-line parsing methods ----------------------------------
@@ -473,7 +472,7 @@
# Oops, no commands found -- an end-user error
if not self.commands:
- raise DistutilsArgError, "no commands supplied"
+ raise DistutilsArgError("no commands supplied")
# All is well: return true
return 1
@@ -504,7 +503,7 @@
# Pull the current command from the head of the command line
command = args[0]
if not command_re.match(command):
- raise SystemExit, "invalid command name '%s'" % command
+ raise SystemExit("invalid command name '%s'" % command)
self.commands.append(command)
# Dig up the command class that implements this command, so we
@@ -513,22 +512,21 @@
try:
cmd_class = self.get_command_class(command)
except DistutilsModuleError, msg:
- raise DistutilsArgError, msg
+ raise DistutilsArgError(msg)
# Require that the command class be derived from Command -- want
# to be sure that the basic "command" interface is implemented.
if not issubclass(cmd_class, Command):
- raise DistutilsClassError, \
- "command class %s must subclass Command" % cmd_class
+ raise DistutilsClassError(
+ "command class %s must subclass Command" % cmd_class)
# Also make sure that the command object provides a list of its
# known options.
if not (hasattr(cmd_class, 'user_options') and
isinstance(cmd_class.user_options, list)):
- raise DistutilsClassError, \
- ("command class %s must provide " +
- "'user_options' attribute (a list of tuples)") % \
- cmd_class
+ raise DistutilsClassError(
+ ("command class %s must provide "
+ "'user_options' attribute (a list of tuples)") % cmd_class)
# If the command class has a list of negative alias options,
# merge it in with the global negative aliases.
@@ -545,7 +543,6 @@
else:
help_options = []
-
# All commands support the global options too, just by adding
# in 'global_options'.
parser.set_option_table(self.global_options +
@@ -559,10 +556,10 @@
if (hasattr(cmd_class, 'help_options') and
isinstance(cmd_class.help_options, list)):
- help_option_found=0
+ help_option_found = 0
for (help_option, short, desc, func) in cmd_class.help_options:
if hasattr(opts, parser.get_attr_name(help_option)):
- help_option_found=1
+ help_option_found = 1
if hasattr(func, '__call__'):
func()
else:
@@ -588,7 +585,7 @@
objects.
"""
if getattr(self, 'convert_2to3_doctests', None):
- self.convert_2to3_doctests = [os.path.join(p)
+ self.convert_2to3_doctests = [os.path.join(p)
for p in self.convert_2to3_doctests]
else:
self.convert_2to3_doctests = []
@@ -801,7 +798,7 @@
class_name = command
try:
- __import__ (module_name)
+ __import__(module_name)
module = sys.modules[module_name]
except ImportError:
continue
@@ -809,16 +806,15 @@
try:
cls = getattr(module, class_name)
except AttributeError:
- raise DistutilsModuleError, \
- "invalid command '%s' (no class '%s' in module '%s')" \
- % (command, class_name, module_name)
+ raise DistutilsModuleError(
+ "invalid command '%s' (no class '%s' in module '%s')" %
+ (command, class_name, module_name))
self.cmdclass[command] = cls
return cls
raise DistutilsModuleError("invalid command '%s'" % command)
-
def get_command_obj(self, command, create=1):
"""Return the command object for 'command'. Normally this object
is cached on a previous call to 'get_command_obj()'; if no command
@@ -881,11 +877,11 @@
elif hasattr(command_obj, option):
setattr(command_obj, option, value)
else:
- raise DistutilsOptionError, \
- ("error in %s: command '%s' has no such option '%s'"
- % (source, command_name, option))
+ raise DistutilsOptionError(
+ "error in %s: command '%s' has no such option '%s'" %
+ (source, command_name, option))
except ValueError, msg:
- raise DistutilsOptionError, msg
+ raise DistutilsOptionError(msg)
def get_reinitialized_command(self, command, reinit_subcommands=0):
"""Reinitializes a command to the state it was in when first
@@ -953,15 +949,23 @@
if self.have_run.get(command):
return
- log.info("running %s", command)
cmd_obj = self.get_command_obj(command)
cmd_obj.ensure_finalized()
+ self.run_command_hooks(cmd_obj, 'pre_hook')
+ log.info("running %s", command)
cmd_obj.run()
+ self.run_command_hooks(cmd_obj, 'post_hook')
self.have_run[command] = 1
+ def run_command_hooks(self, cmd_obj, hook_kind):
+ hooks = getattr(cmd_obj, hook_kind)
+ if hooks is None:
+ return
+ for hook in hooks.values():
+ hook_func = resolve_dotted_name(hook)
+ hook_func(cmd_obj)
# -- Distribution query methods ------------------------------------
-
def has_pure_modules(self):
return len(self.packages or self.py_modules or []) > 0
@@ -988,13 +992,8 @@
not self.has_ext_modules() and
not self.has_c_libraries())
- # -- Metadata query methods ----------------------------------------
- # If you're looking for 'get_name()', 'get_version()', and so forth,
- # they are defined in a sneaky way: the constructor binds self.get_XXX
- # to self.metadata.get_XXX. The actual code is in the
- # DistributionMetadata class, below.
-
+# XXX keep for compat or remove?
def fix_help_options(options):
"""Convert a 4-tuple 'help_options' list as found in various command
diff --git a/src/distutils2/errors.py b/src/distutils2/errors.py
--- a/src/distutils2/errors.py
+++ b/src/distutils2/errors.py
@@ -10,31 +10,38 @@
__revision__ = "$Id: errors.py 75901 2009-10-28 06:45:18Z tarek.ziade $"
+
class DistutilsError(Exception):
"""The root of all Distutils evil."""
+
class DistutilsModuleError(DistutilsError):
"""Unable to load an expected module, or to find an expected class
within some module (in particular, command modules and classes)."""
+
class DistutilsClassError(DistutilsError):
"""Some command class (or possibly distribution class, if anyone
feels a need to subclass Distribution) is found not to be holding
up its end of the bargain, ie. implementing some part of the
"command "interface."""
+
class DistutilsGetoptError(DistutilsError):
"""The option table provided to 'fancy_getopt()' is bogus."""
+
class DistutilsArgError(DistutilsError):
"""Raised by fancy_getopt in response to getopt.error -- ie. an
error in the command line usage."""
+
class DistutilsFileError(DistutilsError):
"""Any problems in the filesystem: expected file not found, etc.
Typically this is for problems that we detect before IOError or
OSError could be raised."""
+
class DistutilsOptionError(DistutilsError):
"""Syntactic/semantic errors in command options, such as use of
mutually conflicting options, or inconsistent options,
@@ -43,60 +50,76 @@
files, or what-have-you -- but if we *know* something originated in
the setup script, we'll raise DistutilsSetupError instead."""
+
class DistutilsSetupError(DistutilsError):
"""For errors that can be definitely blamed on the setup script,
such as invalid keyword arguments to 'setup()'."""
+
class DistutilsPlatformError(DistutilsError):
"""We don't know how to do something on the current platform (but
we do know how to do it on some platform) -- eg. trying to compile
C files on a platform not supported by a CCompiler subclass."""
+
class DistutilsExecError(DistutilsError):
"""Any problems executing an external program (such as the C
compiler, when compiling C files)."""
+
class DistutilsInternalError(DistutilsError):
"""Internal inconsistencies or impossibilities (obviously, this
should never be seen if the code is working!)."""
+
class DistutilsTemplateError(DistutilsError):
"""Syntax error in a file list template."""
+
class DistutilsByteCompileError(DistutilsError):
"""Byte compile error."""
+
# Exception classes used by the CCompiler implementation classes
class CCompilerError(Exception):
"""Some compile/link operation failed."""
+
class PreprocessError(CCompilerError):
"""Failure to preprocess one or more C/C++ files."""
+
class CompileError(CCompilerError):
"""Failure to compile one or more C/C++ source files."""
+
class LibError(CCompilerError):
"""Failure to create a static library from one or more C/C++ object
files."""
+
class LinkError(CCompilerError):
"""Failure to link one or more C/C++ object files into an executable
or shared library file."""
+
class UnknownFileError(CCompilerError):
"""Attempt to process an unknown file type."""
+
class MetadataConflictError(DistutilsError):
"""Attempt to read or write metadata fields that are conflictual."""
+
class MetadataUnrecognizedVersionError(DistutilsError):
"""Unknown metadata version number."""
+
class IrrationalVersionError(Exception):
"""This is an irrational version."""
pass
+
class HugeMajorVersionNumError(IrrationalVersionError):
"""An irrational version because the major version number is huge
(often because a year or date was used).
@@ -105,4 +128,3 @@
This guard can be disabled by setting that option False.
"""
pass
-
diff --git a/src/distutils2/extension.py b/src/distutils2/extension.py
--- a/src/distutils2/extension.py
+++ b/src/distutils2/extension.py
@@ -17,6 +17,7 @@
# import that large-ish module (indirectly, through distutils.core) in
# order to do anything.
+
class Extension(object):
"""Just a collection of attributes that describes an extension
module and everything needed to build it (hopefully in a portable
@@ -84,7 +85,7 @@
# When adding arguments to this constructor, be sure to update
# setup_keywords in core.py.
- def __init__ (self, name, sources,
+ def __init__(self, name, sources,
include_dirs=None,
define_macros=None,
undef_macros=None,
@@ -95,11 +96,11 @@
extra_compile_args=None,
extra_link_args=None,
export_symbols=None,
- swig_opts = None,
+ swig_opts=None,
depends=None,
language=None,
optional=None,
- **kw # To catch unknown keywords
+ **kw # To catch unknown keywords
):
if not isinstance(name, str):
raise AssertionError("'name' must be a string")
@@ -134,4 +135,3 @@
options = ', '.join(sorted(options))
msg = "Unknown Extension options: %s" % options
warnings.warn(msg)
-
diff --git a/src/distutils2/fancy_getopt.py b/src/distutils2/fancy_getopt.py
--- a/src/distutils2/fancy_getopt.py
+++ b/src/distutils2/fancy_getopt.py
@@ -30,6 +30,7 @@
# (for use as attributes of some object).
longopt_xlate = string.maketrans('-', '_')
+
class FancyGetopt(object):
"""Wrapper around the standard 'getopt()' module that provides some
handy extra functionality:
@@ -42,7 +43,7 @@
on the command line sets 'verbose' to false
"""
- def __init__ (self, option_table=None):
+ def __init__(self, option_table=None):
# The option table is (currently) a list of tuples. The
# tuples may have 3 or four values:
@@ -180,7 +181,8 @@
self.long_opts.append(long)
if long[-1] == '=': # option takes an argument?
- if short: short = short + ':'
+ if short:
+ short = short + ':'
long = long[0:-1]
self.takes_arg[long] = 1
else:
diff --git a/src/distutils2/index/simple.py b/src/distutils2/index/simple.py
--- a/src/distutils2/index/simple.py
+++ b/src/distutils2/index/simple.py
@@ -25,7 +25,7 @@
from distutils2.version import get_version_predicate
from distutils2 import __version__ as __distutils2_version__
-__all__ = ['Crawler', 'DEFAULT_SIMPLE_INDEX_URL']
+__all__ = ['Crawler', 'DEFAULT_SIMPLE_INDEX_URL']
# -- Constants -----------------------------------------------
DEFAULT_SIMPLE_INDEX_URL = "http://a.pypi.python.org/simple/"
@@ -154,7 +154,7 @@
matching_projects.append(self._get_project(project_name))
return matching_projects
- def get_releases(self, requirements, prefer_final=None,
+ def get_releases(self, requirements, prefer_final=None,
force_update=False):
"""Search for releases and return a ReleaseList object containing
the results.
@@ -167,7 +167,7 @@
if not self._projects.has_key(predicate.name.lower()):
raise ProjectNotFound()
-
+
releases = self._projects.get(predicate.name.lower())
releases.sort_releases(prefer_final=prefer_final)
return releases
@@ -260,7 +260,7 @@
else:
name = release_info['name']
if not name.lower() in self._projects:
- self._projects[name.lower()] = ReleasesList(name,
+ self._projects[name.lower()] = ReleasesList(name,
index=self._index)
if release:
diff --git a/src/distutils2/index/wrapper.py b/src/distutils2/index/wrapper.py
--- a/src/distutils2/index/wrapper.py
+++ b/src/distutils2/index/wrapper.py
@@ -3,7 +3,7 @@
_WRAPPER_MAPPINGS = {'get_release': 'simple',
'get_releases': 'simple',
- 'search_projects': 'simple',
+ 'search_projects': 'simple',
'get_metadata': 'xmlrpc',
'get_distributions': 'simple'}
@@ -45,7 +45,7 @@
:param index: tell wich index to rely on by default.
:param index_classes: a dict of name:class to use as indexes.
- :param indexes: a dict of name:index already instanciated
+ :param indexes: a dict of name:index already instantiated
:param mappings: the mappings to use for this wrapper
"""
@@ -56,7 +56,7 @@
self._indexes = indexes
self._default_index = default_index
- # instanciate the classes and set their _project attribute to the one
+ # instantiate the classes and set their _project attribute to the one
# of the wrapper.
for name, cls in index_classes.items():
obj = self._indexes.setdefault(name, cls())
@@ -77,10 +77,10 @@
# the method is not defined in the mappings, so we try first to get
# it via the default index, and rely on others if needed.
try:
- real_method = getattr(self._indexes[self._default_index],
+ real_method = getattr(self._indexes[self._default_index],
method_name)
except AttributeError:
- other_indexes = [i for i in self._indexes
+ other_indexes = [i for i in self._indexes
if i != self._default_index]
for index in other_indexes:
real_method = getattr(self._indexes[index], method_name, None)
@@ -90,4 +90,4 @@
return switch_index_if_fails(real_method, self)
else:
raise AttributeError("No index have attribute '%s'" % method_name)
-
+
diff --git a/src/distutils2/manifest.py b/src/distutils2/manifest.py
--- a/src/distutils2/manifest.py
+++ b/src/distutils2/manifest.py
@@ -22,7 +22,7 @@
# a \ followed by some spaces + EOL
_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M)
-_COMMENTED_LINE = re.compile('#.*?(?=\n)|^\w*\n|\n(?=$)', re.M|re.S)
+_COMMENTED_LINE = re.compile('#.*?(?=\n)|^\w*\n|\n(?=$)', re.M | re.S)
class Manifest(object):
"""A list of files built by on exploring the filesystem and filtered by
diff --git a/src/distutils2/metadata.py b/src/distutils2/metadata.py
--- a/src/distutils2/metadata.py
+++ b/src/distutils2/metadata.py
@@ -1,13 +1,12 @@
-"""
-Implementation of the Metadata for Python packages
+"""Implementation of the Metadata for Python packages PEPs.
-Supports all Metadata formats (1.0, 1.1, 1.2).
+Supports all metadata formats (1.0, 1.1, 1.2).
"""
-import re
import os
import sys
import platform
+import re
from StringIO import StringIO
from email import message_from_file
from tokenize import tokenize, NAME, OP, STRING, ENDMARKER
@@ -53,12 +52,12 @@
PKG_INFO_PREFERRED_VERSION = '1.0'
_LINE_PREFIX = re.compile('\n \|')
-_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
+_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
'Summary', 'Description',
'Keywords', 'Home-page', 'Author', 'Author-email',
'License')
-_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
+_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
'Supported-Platform', 'Summary', 'Description',
'Keywords', 'Home-page', 'Author', 'Author-email',
'License', 'Classifier', 'Download-URL', 'Obsoletes',
@@ -66,7 +65,7 @@
_314_MARKERS = ('Obsoletes', 'Provides', 'Requires')
-_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
+_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
'Supported-Platform', 'Summary', 'Description',
'Keywords', 'Home-page', 'Author', 'Author-email',
'Maintainer', 'Maintainer-email', 'License',
@@ -78,12 +77,11 @@
'Obsoletes-Dist', 'Requires-External', 'Maintainer',
'Maintainer-email', 'Project-URL')
-_ALL_FIELDS = []
+_ALL_FIELDS = set()
+_ALL_FIELDS.update(_241_FIELDS)
+_ALL_FIELDS.update(_314_FIELDS)
+_ALL_FIELDS.update(_345_FIELDS)
-for field in _241_FIELDS + _314_FIELDS + _345_FIELDS:
- if field in _ALL_FIELDS:
- continue
- _ALL_FIELDS.append(field)
def _version2fieldlist(version):
if version == '1.0':
@@ -94,8 +92,9 @@
return _345_FIELDS
raise MetadataUnrecognizedVersionError(version)
+
def _best_version(fields):
- """Will detect the best version depending on the fields used."""
+ """Detect the best version depending on the fields used."""
def _has_marker(keys, markers):
for marker in markers:
if marker in keys:
@@ -182,23 +181,32 @@
class DistributionMetadata(object):
- """Distribution meta-data class (1.0 or 1.2).
+ """The metadata of a release.
- if from_dict attribute is set, all key/values pairs will be sent to the
- "set" method, building the metadata from the dict.
+ Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can
+ instantiate the class with one of these arguments (or none):
+ - *path*, the path to a METADATA file
+ - *fileobj* give a file-like object with METADATA as content
+ - *mapping* is a dict-like object
"""
+ # TODO document that execution_context and platform_dependent are used
+ # to filter on query, not when setting a key
+ # also document the mapping API and UNKNOWN default key
+
def __init__(self, path=None, platform_dependent=False,
execution_context=None, fileobj=None, mapping=None):
self._fields = {}
self.version = None
self.docutils_support = _HAS_DOCUTILS
self.platform_dependent = platform_dependent
+ self.execution_context = execution_context
+ if [path, fileobj, mapping].count(None) < 2:
+ raise TypeError('path, fileobj and mapping are exclusive')
if path is not None:
self.read(path)
elif fileobj is not None:
self.read_file(fileobj)
- self.execution_context = execution_context
- if mapping:
+ elif mapping is not None:
self.update(mapping)
def _set_best_version(self):
@@ -208,10 +216,6 @@
def _write_field(self, file, name, value):
file.write('%s: %s\n' % (name, value))
- def _write_list(self, file, name, values):
- for value in values:
- self._write_field(file, name, value)
-
def _encode_field(self, value):
if isinstance(value, unicode):
return value.encode(PKG_INFO_ENCODING)
@@ -231,17 +235,15 @@
if name in _ALL_FIELDS:
return name
name = name.replace('-', '_').lower()
- if name in _ATTR2FIELD:
- return _ATTR2FIELD[name]
- return name
+ return _ATTR2FIELD.get(name, name)
def _default_value(self, name):
- if name in _LISTFIELDS + _ELEMENTSFIELD:
+ if name in _LISTFIELDS or name in _ELEMENTSFIELD:
return []
return 'UNKNOWN'
def _check_rst_data(self, data):
- """Returns warnings when the provided data doesn't compile."""
+ """Return warnings when the provided data has syntax errors."""
source_path = StringIO()
parser = Parser()
settings = frontend.OptionParser().get_default_values()
@@ -276,7 +278,7 @@
return _LINE_PREFIX.sub('\n', value)
#
- # Public APIs
+ # Public API
#
def get_fullname(self):
return '%s-%s' % (self['Name'], self['Version'])
@@ -289,7 +291,7 @@
self.read_file(open(filepath))
def read_file(self, fileob):
- """Reads the metadata values from a file object."""
+ """Read the metadata values from a file object."""
msg = message_from_file(fileob)
self.version = msg['metadata-version']
@@ -307,8 +309,7 @@
self.set(field, value)
def write(self, filepath):
- """Write the metadata fields into path.
- """
+ """Write the metadata fields to filepath."""
pkg_info = open(filepath, 'w')
try:
self.write_file(pkg_info)
@@ -316,8 +317,7 @@
pkg_info.close()
def write_file(self, fileobject):
- """Write the PKG-INFO format data to a file object.
- """
+ """Write the PKG-INFO format data to a file object."""
self._set_best_version()
for field in _version2fieldlist(self.version):
values = self.get(field)
@@ -368,17 +368,17 @@
self.update(kwargs)
def set(self, name, value):
- """Controls then sets a metadata field"""
+ """Control then set a metadata field."""
name = self._convert_name(name)
- if (name in _ELEMENTSFIELD + ('Platform',) and
+ if ((name in _ELEMENTSFIELD or name == 'Platform') and
not isinstance(value, (list, tuple))):
if isinstance(value, str):
value = value.split(',')
else:
value = []
elif (name in _LISTFIELDS and
- not isinstance(value, (list, tuple))):
+ not isinstance(value, (list, tuple))):
if isinstance(value, str):
value = [value]
else:
@@ -408,7 +408,7 @@
self._set_best_version()
def get(self, name):
- """Gets a metadata field."""
+ """Get a metadata field."""
name = self._convert_name(name)
if name not in self._fields:
return self._default_value(name)
@@ -443,24 +443,21 @@
return value
def check(self):
- """Checks if the metadata are compliant."""
+ """Check if the metadata is compliant."""
# XXX should check the versions (if the file was loaded)
- missing = []
+ missing, warnings = [], []
for attr in ('Name', 'Version', 'Home-page'):
value = self[attr]
if value == 'UNKNOWN':
missing.append(attr)
if _HAS_DOCUTILS:
- warnings = self._check_rst_data(self['Description'])
- else:
- warnings = []
+ warnings.extend(self._check_rst_data(self['Description']))
# checking metadata 1.2 (XXX needs to check 1.1, 1.0)
if self['Metadata-Version'] != '1.2':
return missing, warnings
-
def is_valid_predicates(value):
for v in value:
if not is_valid_predicate(v.split(';')[0]):
@@ -468,16 +465,15 @@
return True
for fields, controller in ((_PREDICATE_FIELDS, is_valid_predicates),
- (_VERSIONS_FIELDS, is_valid_versions),
- (_VERSION_FIELDS, is_valid_version)):
+ (_VERSIONS_FIELDS, is_valid_versions),
+ (_VERSION_FIELDS, is_valid_version)):
for field in fields:
value = self[field]
if value == 'UNKNOWN':
continue
if not controller(value):
- warnings.append('Wrong value for "%s": %s' \
- % (field, value))
+ warnings.append('Wrong value for %r: %s' % (field, value))
return missing, warnings
@@ -494,7 +490,6 @@
#
# micro-language for PEP 345 environment markers
#
-_STR_LIMIT = "'\""
# allowed operators
_OPERATORS = {'==': lambda x, y: x == y,
@@ -506,17 +501,18 @@
'in': lambda x, y: x in y,
'not in': lambda x, y: x not in y}
+
def _operate(operation, x, y):
return _OPERATORS[operation](x, y)
# restricted set of variables
_VARS = {'sys.platform': sys.platform,
- 'python_version': '%s.%s' % (sys.version_info[0],
- sys.version_info[1]),
- 'python_full_version': sys.version.split()[0],
+ 'python_version': sys.version[:3],
+ 'python_full_version': sys.version.split(' ', 1)[0],
'os.name': os.name,
- 'platform.version': platform.version,
- 'platform.machine': platform.machine}
+ 'platform.version': platform.version(),
+ 'platform.machine': platform.machine()}
+
class _Operation(object):
@@ -539,7 +535,7 @@
def _is_string(self, value):
if value is None or len(value) < 2:
return False
- for delimiter in _STR_LIMIT:
+ for delimiter in '"\'':
if value[0] == value[-1] == delimiter:
return True
return False
@@ -550,7 +546,7 @@
def _convert(self, value):
if value in _VARS:
return self._get_var(value)
- return value.strip(_STR_LIMIT)
+ return value.strip('"\'')
def _check_name(self, value):
if value not in _VARS:
@@ -578,6 +574,7 @@
right = self._convert(self.right)
return _operate(self.op, left, right)
+
class _OR(object):
def __init__(self, left, right=None):
self.left = left
@@ -587,7 +584,7 @@
return self.right is not None
def __repr__(self):
- return 'OR(%s, %s)' % (repr(self.left), repr(self.right))
+ return 'OR(%r, %r)' % (self.left, self.right)
def __call__(self):
return self.left() or self.right()
@@ -602,11 +599,12 @@
return self.right is not None
def __repr__(self):
- return 'AND(%s, %s)' % (repr(self.left), repr(self.right))
+ return 'AND(%r, %r)' % (self.left, self.right)
def __call__(self):
return self.left() and self.right()
+
class _CHAIN(object):
def __init__(self, execution_context=None):
@@ -668,8 +666,9 @@
return False
return True
+
def _interpret(marker, execution_context=None):
- """Interprets a marker and return a result given the environment."""
+ """Interpret a marker and return a result depending on environment."""
marker = marker.strip()
operations = _CHAIN(execution_context)
tokenize(StringIO(marker).readline, operations.eat)
diff --git a/src/distutils2/mkpkg.py b/src/distutils2/mkpkg.py
--- a/src/distutils2/mkpkg.py
+++ b/src/distutils2/mkpkg.py
@@ -30,7 +30,11 @@
#
# Detect scripts (not sure how. #! outside of package?)
-import sys, os, re, shutil, ConfigParser
+import sys
+import os
+import re
+import shutil
+import ConfigParser
helpText = {
@@ -629,19 +633,20 @@
print '\nERROR: You must select "Y" or "N".\n'
-def ask(question, default = None, helptext = None, required = True,
- lengthy = False, multiline = False):
- prompt = '%s: ' % ( question, )
+def ask(question, default=None, helptext=None, required=True,
+ lengthy=False, multiline=False):
+ prompt = '%s: ' % (question,)
if default:
- prompt = '%s [%s]: ' % ( question, default )
+ prompt = '%s [%s]: ' % (question, default)
if default and len(question) + len(default) > 70:
- prompt = '%s\n [%s]: ' % ( question, default )
+ prompt = '%s\n [%s]: ' % (question, default)
if lengthy or multiline:
prompt += '\n >'
- if not helptext: helptext = 'No additional help available.'
- if helptext[0] == '\n': helptext = helptext[1:]
- if helptext[-1] == '\n': helptext = helptext[:-1]
+ if not helptext:
+ helptext = 'No additional help available.'
+
+ helptext = helptext.strip("\n")
while True:
sys.stdout.write(prompt)
@@ -653,12 +658,14 @@
print helptext
print '=' * 70
continue
- if default and not line: return(default)
+ if default and not line:
+ return(default)
if not line and required:
print '*' * 70
print 'This value cannot be empty.'
print '==========================='
- if helptext: print helptext
+ if helptext:
+ print helptext
print '*' * 70
continue
return(line)
@@ -669,7 +676,8 @@
for key in troveList:
subDict = dict
for subkey in key.split(' :: '):
- if not subkey in subDict: subDict[subkey] = {}
+ if not subkey in subDict:
+ subDict[subkey] = {}
subDict = subDict[subkey]
return(dict)
troveDict = buildTroveDict(troveList)
@@ -687,7 +695,8 @@
def lookupOption(self, key):
- if not self.config.has_option('DEFAULT', key): return(None)
+ if not self.config.has_option('DEFAULT', key):
+ return(None)
return(self.config.get('DEFAULT', key))
@@ -706,7 +715,8 @@
self.config.set('DEFAULT', compareKey,
self.setupData[compareKey])
- if not valuesDifferent: return
+ if not valuesDifferent:
+ return
self.config.write(open(os.path.expanduser('~/.pygiver'), 'w'))
@@ -718,7 +728,7 @@
def inspectFile(self, path):
fp = open(path, 'r')
try:
- for line in [ fp.readline() for x in range(10) ]:
+ for line in [fp.readline() for x in range(10)]:
m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line)
if m:
if m.group('major') == '3':
@@ -737,14 +747,14 @@
self.setupData['name'] = m.group(1)
self.setupData['version'] = m.group(2)
- for root, dirs, files in os.walk('.'):
+ for root, dirs, files in os.walk(os.curdir):
for file in files:
- if root == '.' and file == 'setup.py': continue
+ if root == os.curdir and file == 'setup.py': continue
fileName = os.path.join(root, file)
self.inspectFile(fileName)
if file == '__init__.py':
- trySrc = os.path.join('.', 'src')
+ trySrc = os.path.join(os.curdir, 'src')
tmpRoot = root
if tmpRoot.startswith(trySrc):
tmpRoot = tmpRoot[len(trySrc):]
@@ -761,16 +771,16 @@
self.setupData.get('version'), helpText['version'])
self.setupData['description'] = ask('Package description',
self.setupData.get('description'), helpText['description'],
- lengthy = True)
+ lengthy=True)
self.setupData['author'] = ask('Author name',
self.setupData.get('author'), helpText['author'])
self.setupData['author_email'] = ask('Author e-mail address',
self.setupData.get('author_email'), helpText['author_email'])
self.setupData['url'] = ask('Project URL',
- self.setupData.get('url'), helpText['url'], required = False)
+ self.setupData.get('url'), helpText['url'], required=False)
if (askYn('Do you want to set Trove classifiers?',
- helptext = helpText['do_classifier']) == 'y'):
+ helptext=helpText['do_classifier']) == 'y'):
self.setTroveClassifier()
@@ -781,8 +791,10 @@
def setTroveOther(self, classifierDict):
- if askYn('Do you want to set other trove identifiers', 'n',
- helpText['trove_generic']) != 'y': return
+ if askYn('Do you want to set other trove identifiers',
+ 'n',
+ helpText['trove_generic']) != 'y':
+ return
self.walkTrove(classifierDict, [troveDict], '')
@@ -799,7 +811,7 @@
continue
if askYn('Do you want to set items under\n "%s" (%d sub-items)'
- % ( key, len(trove[key]) ), 'n',
+ % (key, len(trove[key])), 'n',
helpText['trove_generic']) == 'y':
self.walkTrove(classifierDict, trovePath + [trove[key]],
desc + ' :: ' + key)
@@ -808,15 +820,18 @@
def setTroveLicense(self, classifierDict):
while True:
license = ask('What license do you use',
- helptext = helpText['trove_license'], required = False)
- if not license: return
+ helptext=helpText['trove_license'],
+ required=False)
+ if not license:
+ return
licenseWords = license.lower().split(' ')
foundList = []
for index in range(len(troveList)):
troveItem = troveList[index]
- if not troveItem.startswith('License :: '): continue
+ if not troveItem.startswith('License :: '):
+ continue
troveItem = troveItem[11:].lower()
allMatch = True
@@ -824,17 +839,20 @@
if not word in troveItem:
allMatch = False
break
- if allMatch: foundList.append(index)
+ if allMatch:
+ foundList.append(index)
question = 'Matching licenses:\n\n'
for i in xrange(1, len(foundList) + 1):
- question += ' %s) %s\n' % ( i, troveList[foundList[i - 1]] )
+ question += ' %s) %s\n' % (i, troveList[foundList[i - 1]])
question += ('\nType the number of the license you wish to use or '
'? to try again:')
- troveLicense = ask(question, required = False)
+ troveLicense = ask(question, required=False)
- if troveLicense == '?': continue
- if troveLicense == '': return
+ if troveLicense == '?':
+ continue
+ if troveLicense == '':
+ return
foundIndex = foundList[int(troveLicense) - 1]
classifierDict[troveList[foundIndex]] = 1
try:
@@ -856,7 +874,7 @@
6 - Mature
7 - Inactive
-Status''', required = False)
+Status''', required=False)
if devStatus:
try:
key = {
@@ -884,7 +902,8 @@
return modified_pkgs
def writeSetup(self):
- if os.path.exists('setup.py'): shutil.move('setup.py', 'setup.py.old')
+ if os.path.exists('setup.py'):
+ shutil.move('setup.py', 'setup.py.old')
fp = open('setup.py', 'w')
try:
diff --git a/src/distutils2/tests/__init__.py b/src/distutils2/tests/__init__.py
--- a/src/distutils2/tests/__init__.py
+++ b/src/distutils2/tests/__init__.py
@@ -23,7 +23,7 @@
from test.test_support import TESTFN # use TESTFN from stdlib/test_support.
-here = os.path.dirname(__file__)
+here = os.path.dirname(__file__) or os.curdir
verbose = 1
diff --git a/src/distutils2/tests/conversions/02_after.py b/src/distutils2/tests/conversions/02_after.py
--- a/src/distutils2/tests/conversions/02_after.py
+++ b/src/distutils2/tests/conversions/02_after.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf8 -*-
+# -*- encoding: utf-8 -*-
import sys
import os
from distutils2.core import setup, Extension
diff --git a/src/distutils2/tests/conversions/02_before.py b/src/distutils2/tests/conversions/02_before.py
--- a/src/distutils2/tests/conversions/02_before.py
+++ b/src/distutils2/tests/conversions/02_before.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf8 -*-
+# -*- encoding: utf-8 -*-
import sys
import os
from distutils.core import setup, Extension
diff --git a/src/distutils2/tests/pypi_server.py b/src/distutils2/tests/pypi_server.py
--- a/src/distutils2/tests/pypi_server.py
+++ b/src/distutils2/tests/pypi_server.py
@@ -1,14 +1,14 @@
"""Mock PyPI Server implementation, to use in tests.
This module also provides a simple test case to extend if you need to use
-the PyPIServer all along your test case. Be sure to read the documentation
+the PyPIServer all along your test case. Be sure to read the documentation
before any use.
XXX TODO:
The mock server can handle simple HTTP request (to simulate a simple index) or
XMLRPC requests, over HTTP. Both does not have the same intergface to deal
-with, and I think it's a pain.
+with, and I think it's a pain.
A good idea could be to re-think a bit the way dstributions are handled in the
mock server. As it should return malformed HTML pages, we need to keep the
@@ -21,12 +21,12 @@
>>> server.startXMLRPC()
Then, the server must have only one port to rely on, eg.
-
+
>>> server.fulladress()
"http://ip:port/"
It could be simple to have one HTTP server, relaying the requests to the two
-implementations (static HTTP and XMLRPC over HTTP).
+implementations (static HTTP and XMLRPC over HTTP).
"""
import Queue
@@ -53,7 +53,7 @@
return use_pypi_server(*server_args, **server_kwargs)
def use_pypi_server(*server_args, **server_kwargs):
- """Decorator to make use of the PyPIServer for test methods,
+ """Decorator to make use of the PyPIServer for test methods,
just when needed, and not for the entire duration of the testcase.
"""
def wrapper(func):
@@ -79,18 +79,18 @@
self.pypi.stop()
class PyPIServer(threading.Thread):
- """PyPI Mock server.
+ """PyPI Mocked server.
Provides a mocked version of the PyPI API's, to ease tests.
Support serving static content and serving previously given text.
"""
def __init__(self, test_static_path=None,
- static_filesystem_paths=["default"],
+ static_filesystem_paths=["default"],
static_uri_paths=["simple"], serve_xmlrpc=False) :
"""Initialize the server.
-
- Default behavior is to start the HTTP server. You can either start the
+
+ Default behavior is to start the HTTP server. You can either start the
xmlrpc server by setting xmlrpc to True. Caution: Only one server will
be started.
@@ -103,9 +103,9 @@
threading.Thread.__init__(self)
self._run = True
self._serve_xmlrpc = serve_xmlrpc
-
+
#TODO allow to serve XMLRPC and HTTP static files at the same time.
- if not self._serve_xmlrpc:
+ if not self._serve_xmlrpc:
self.server = HTTPServer(('', 0), PyPIRequestHandler)
self.server.RequestHandlerClass.pypi_server = self
@@ -114,7 +114,7 @@
self.default_response_status = 200
self.default_response_headers = [('Content-type', 'text/plain')]
self.default_response_data = "hello"
-
+
# initialize static paths / filesystems
self.static_uri_paths = static_uri_paths
if test_static_path is not None:
@@ -201,7 +201,7 @@
# serve the content from local disc if we request an URL beginning
# by a pattern defined in `static_paths`
url_parts = self.path.split("/")
- if (len(url_parts) > 1 and
+ if (len(url_parts) > 1 and
url_parts[1] in self.pypi_server.static_uri_paths):
data = None
# always take the last first.
@@ -239,7 +239,7 @@
try:
status = int(status)
except ValueError:
- # we probably got something like YYY Codename.
+ # we probably got something like YYY Codename.
# Just get the first 3 digits
status = int(status[:3])
@@ -258,15 +258,15 @@
self.server_port = port
class MockDist(object):
- """Fake distribution, used in the Mock PyPI Server"""
+ """Fake distribution, used in the Mock PyPI Server"""
def __init__(self, name, version="1.0", hidden=False, url="http://url/",
type="sdist", filename="", size=10000,
digest="123456", downloads=7, has_sig=False,
- python_version="source", comment="comment",
- author="John Doe", author_email="john at doe.name",
+ python_version="source", comment="comment",
+ author="John Doe", author_email="john at doe.name",
maintainer="Main Tayner", maintainer_email="maintainer_mail",
project_url="http://project_url/", homepage="http://homepage/",
- keywords="", platform="UNKNOWN", classifiers=[], licence="",
+ keywords="", platform="UNKNOWN", classifiers=[], licence="",
description="Description", summary="Summary", stable_version="",
ordering="", documentation_id="", code_kwalitee_id="",
installability_id="", obsoletes=[], obsoletes_dist=[],
@@ -277,7 +277,7 @@
self.name = name
self.version = version
self.hidden = hidden
-
+
# URL infos
self.url = url
self.digest = digest
@@ -286,7 +286,7 @@
self.python_version = python_version
self.comment = comment
self.type = type
-
+
# metadata
self.author = author
self.author_email = author_email
@@ -305,7 +305,7 @@
self.cheesecake_documentation_id = documentation_id
self.cheesecake_code_kwalitee_id = code_kwalitee_id
self.cheesecake_installability_id = installability_id
-
+
self.obsoletes = obsoletes
self.obsoletes_dist = obsoletes_dist
self.provides = provides
@@ -314,7 +314,7 @@
self.requires_dist = requires_dist
self.requires_external = requires_external
self.requires_python = requires_python
-
+
def url_infos(self):
return {
'url': self.url,
@@ -330,38 +330,38 @@
def metadata(self):
return {
- 'maintainer': self.maintainer,
- 'project_url': [self.project_url],
- 'maintainer_email': self.maintainer_email,
- 'cheesecake_code_kwalitee_id': self.cheesecake_code_kwalitee_id,
- 'keywords': self.keywords,
- 'obsoletes_dist': self.obsoletes_dist,
- 'requires_external': self.requires_external,
- 'author': self.author,
+ 'maintainer': self.maintainer,
+ 'project_url': [self.project_url],
+ 'maintainer_email': self.maintainer_email,
+ 'cheesecake_code_kwalitee_id': self.cheesecake_code_kwalitee_id,
+ 'keywords': self.keywords,
+ 'obsoletes_dist': self.obsoletes_dist,
+ 'requires_external': self.requires_external,
+ 'author': self.author,
'author_email': self.author_email,
- 'download_url': self.url,
- 'platform': self.platform,
- 'version': self.version,
- 'obsoletes': self.obsoletes,
- 'provides': self.provides,
- 'cheesecake_documentation_id': self.cheesecake_documentation_id,
- '_pypi_hidden': self.hidden,
- 'description': self.description,
- '_pypi_ordering': 19,
- 'requires_dist': self.requires_dist,
- 'requires_python': self.requires_python,
- 'classifiers': [],
- 'name': self.name,
- 'licence': self.licence,
- 'summary': self.summary,
- 'home_page': self.homepage,
- 'stable_version': self.stable_version,
+ 'download_url': self.url,
+ 'platform': self.platform,
+ 'version': self.version,
+ 'obsoletes': self.obsoletes,
+ 'provides': self.provides,
+ 'cheesecake_documentation_id': self.cheesecake_documentation_id,
+ '_pypi_hidden': self.hidden,
+ 'description': self.description,
+ '_pypi_ordering': 19,
+ 'requires_dist': self.requires_dist,
+ 'requires_python': self.requires_python,
+ 'classifiers': [],
+ 'name': self.name,
+ 'licence': self.licence,
+ 'summary': self.summary,
+ 'home_page': self.homepage,
+ 'stable_version': self.stable_version,
'provides_dist': self.provides_dist or "%s (%s)" % (self.name,
self.version),
- 'requires': self.requires,
+ 'requires': self.requires,
'cheesecake_installability_id': self.cheesecake_installability_id,
}
-
+
def search_result(self):
return {
'_pypi_ordering': 0,
@@ -372,7 +372,7 @@
class XMLRPCMockIndex(object):
"""Mock XMLRPC server"""
-
+
def __init__(self, dists=[]):
self._dists = dists
@@ -386,7 +386,7 @@
def set_search_result(self, result):
"""set a predefined search result"""
- self._search_result = result
+ self._search_result = result
def _get_search_results(self):
results = []
@@ -401,7 +401,7 @@
return [r.search_result() for r in results]
def list_package(self):
- return [d.name for d in self._dists]
+ return [d.name for d in self._dists]
def package_releases(self, package_name, show_hidden=False):
if show_hidden:
@@ -411,14 +411,14 @@
# return only un-hidden
return [d.version for d in self._dists if d.name == package_name
and not d.hidden]
-
+
def release_urls(self, package_name, version):
- return [d.url_infos() for d in self._dists
+ return [d.url_infos() for d in self._dists
if d.name == package_name and d.version == version]
def release_data(self, package_name, version):
- release = [d for d in self._dists
- if d.name == package_name and d.version == version]
+ release = [d for d in self._dists
+ if d.name == package_name and d.version == version]
if release:
return release[0].metadata()
else:
diff --git a/src/distutils2/tests/support.py b/src/distutils2/tests/support.py
--- a/src/distutils2/tests/support.py
+++ b/src/distutils2/tests/support.py
@@ -3,6 +3,27 @@
Always import unittest from this module, it will be the right version
(standard library unittest for 2.7 and higher, third-party unittest2
release for older versions).
+
+Three helper classes are provided: LoggingSilencer, TempdirManager and
+EnvironGuard. They are written to be used as mixins, e.g. ::
+
+ from distutils2.tests.support import unittest
+ from distutils2.tests.support import LoggingSilencer
+
+ class SomeTestCase(LoggingSilencer, unittest.TestCase):
+
+If you need to define a setUp method on your test class, you have to
+call the mixin class' setUp method or it won't work (same thing for
+tearDown):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ ... # other setup code
+
+Read each class' docstring to see their purpose and usage.
+
+Also provided is a DummyCommand class, useful to mock commands in the
+tests of another command that needs them (see docstring).
"""
import os
@@ -10,26 +31,36 @@
import shutil
import tempfile
from copy import deepcopy
-import warnings
from distutils2 import log
from distutils2.log import DEBUG, INFO, WARN, ERROR, FATAL
-if sys.version_info >= (2, 7):
- # improved unittest package from 2.7's standard library
+if sys.version_info >= (3, 2):
+ # improved unittest package from 3.2's standard library
import unittest
else:
# external release of same package for older versions
import unittest2 as unittest
+__all__ = ['LoggingSilencer', 'TempdirManager', 'EnvironGuard',
+ 'DummyCommand', 'unittest']
+
+
class LoggingSilencer(object):
+ """TestCase-compatible mixin to catch logging calls.
+
+ Every log message that goes through distutils2.log will get appended to
+ self.logs instead of being printed. You can check that your code logs
+ warnings and errors as documented by inspecting that list; helper methods
+ get_logs and clear_logs are also provided.
+ """
def setUp(self):
super(LoggingSilencer, self).setUp()
- self.threshold = log.set_threshold(log.FATAL)
+ self.threshold = log.set_threshold(FATAL)
# catching warnings
- # when log will be replaced by logging
- # we won't need such monkey-patch anymore
+ # when log is replaced by logging we won't need
+ # such monkey-patching anymore
self._old_log = log.Log._log
log.Log._log = self._log
self.logs = []
@@ -45,6 +76,10 @@
self.logs.append((level, msg, args))
def get_logs(self, *levels):
+ """Return a list of caught messages with level in `levels`.
+
+ Example: self.get_logs(log.WARN, log.DEBUG) -> list
+ """
def _format(msg, args):
if len(args) == 0:
return msg
@@ -53,48 +88,41 @@
in self.logs if level in levels]
def clear_logs(self):
- self.logs = []
+ """Empty the internal list of caught messages."""
+ del self.logs[:]
+
class TempdirManager(object):
- """Mix-in class that handles temporary directories for test cases.
+ """TestCase-compatible mixin to create temporary directories and files.
- This is intended to be used with unittest.TestCase.
+ Directories and files created in a test_* method will be removed after it
+ has run.
"""
def setUp(self):
super(TempdirManager, self).setUp()
- self.tempdirs = []
- self.tempfiles = []
+ self._basetempdir = tempfile.mkdtemp()
def tearDown(self):
super(TempdirManager, self).tearDown()
- while self.tempdirs:
- d = self.tempdirs.pop()
- shutil.rmtree(d, os.name in ('nt', 'cygwin'))
- for file_ in self.tempfiles:
- if os.path.exists(file_):
- os.remove(file_)
+ shutil.rmtree(self._basetempdir, os.name in ('nt', 'cygwin'))
def mktempfile(self):
- """Create a temporary file that will be cleaned up."""
- tempfile_ = tempfile.NamedTemporaryFile()
- self.tempfiles.append(tempfile_.name)
- return tempfile_
+ """Create a read-write temporary file and return it."""
+ fd, fn = tempfile.mkstemp(dir=self._basetempdir)
+ os.close(fd)
+ return open(fn, 'w+')
def mkdtemp(self):
- """Create a temporary directory that will be cleaned up.
-
- Returns the path of the directory.
- """
- d = tempfile.mkdtemp()
- self.tempdirs.append(d)
+ """Create a temporary directory and return its path."""
+ d = tempfile.mkdtemp(dir=self._basetempdir)
return d
def write_file(self, path, content='xxx'):
- """Writes a file in the given path.
+ """Write a file at the given path.
-
- path can be a string or a sequence.
+ path can be a string, a tuple or a list; if it's a tuple or list,
+ os.path.join will be used to produce a path.
"""
if isinstance(path, (list, tuple)):
path = os.path.join(*path)
@@ -105,41 +133,35 @@
f.close()
def create_dist(self, pkg_name='foo', **kw):
- """Will generate a test environment.
+ """Create a stub distribution object and files.
- This function creates:
- - a Distribution instance using keywords
- - a temporary directory with a package structure
+ This function creates a Distribution instance (use keyword arguments
+ to customize it) and a temporary directory with a project structure
+ (currently an empty directory).
- It returns the package directory and the distribution
- instance.
+ It returns the path to the directory and the Distribution instance.
+ You can use TempdirManager.write_file to write any file in that
+ directory, e.g. setup scripts or Python modules.
"""
+ # Late import so that third parties can import support without
+ # loading a ton of distutils2 modules in memory.
from distutils2.dist import Distribution
tmp_dir = self.mkdtemp()
pkg_dir = os.path.join(tmp_dir, pkg_name)
os.mkdir(pkg_dir)
dist = Distribution(attrs=kw)
-
return pkg_dir, dist
-class DummyCommand:
- """Class to store options for retrieval via set_undefined_options()."""
-
- def __init__(self, **kwargs):
- for kw, val in kwargs.items():
- setattr(self, kw, val)
-
- def ensure_finalized(self):
- pass
class EnvironGuard(object):
+ """TestCase-compatible mixin to save and restore the environment."""
def setUp(self):
super(EnvironGuard, self).setUp()
self.old_environ = deepcopy(os.environ)
def tearDown(self):
- for key, value in self.old_environ.items():
+ for key, value in self.old_environ.iteritems():
if os.environ.get(key) != value:
os.environ[key] = value
@@ -148,3 +170,18 @@
del os.environ[key]
super(EnvironGuard, self).tearDown()
+
+
+class DummyCommand(object):
+ """Class to store options for retrieval via set_undefined_options().
+
+ Useful for mocking one dependency command in the tests for another
+ command, see e.g. the dummy build command in test_build_scripts.
+ """
+
+ def __init__(self, **kwargs):
+ for kw, val in kwargs.iteritems():
+ setattr(self, kw, val)
+
+ def ensure_finalized(self):
+ pass
diff --git a/src/distutils2/tests/test_Mixin2to3.py b/src/distutils2/tests/test_Mixin2to3.py
--- a/src/distutils2/tests/test_Mixin2to3.py
+++ b/src/distutils2/tests/test_Mixin2to3.py
@@ -1,6 +1,5 @@
"""Tests for distutils.command.build_py."""
import sys
-import tempfile
import distutils2
from distutils2.tests import support
@@ -10,7 +9,7 @@
class Mixin2to3TestCase(support.TempdirManager, unittest.TestCase):
- @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_convert_code_only(self):
# used to check if code gets converted properly.
code_content = "print 'test'\n"
@@ -27,7 +26,7 @@
self.assertEquals(new_code_content, converted_code_content)
- @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_doctests_only(self):
# used to check if doctests gets converted properly.
doctest_content = '"""\n>>> print test\ntest\n"""\nprint test\n\n'
diff --git a/src/distutils2/tests/test_bdist_msi.py b/src/distutils2/tests/test_bdist_msi.py
--- a/src/distutils2/tests/test_bdist_msi.py
+++ b/src/distutils2/tests/test_bdist_msi.py
@@ -10,8 +10,8 @@
support.LoggingSilencer,
unittest.TestCase):
- @unittest.skipUnless(sys.platform=="win32", "These tests are only for win32")
- def test_minial(self):
+ @unittest.skipUnless(sys.platform == "win32", "runs only on win32")
+ def test_minimal(self):
# minimal test XXX need more tests
from distutils2.command.bdist_msi import bdist_msi
pkg_pth, dist = self.create_dist()
diff --git a/src/distutils2/tests/test_build_ext.py b/src/distutils2/tests/test_build_ext.py
--- a/src/distutils2/tests/test_build_ext.py
+++ b/src/distutils2/tests/test_build_ext.py
@@ -45,7 +45,7 @@
build_ext.USER_BASE = site.USER_BASE
# XXX only works with 2.6 > -- dunno why yet
- @unittest.skipUnless(sys.version_info >= (2, 6,), 'works for >= 2.6')
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_build_ext(self):
global ALREADY_TESTED
xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
@@ -126,11 +126,8 @@
# make sure we get some library dirs under solaris
self.assertTrue(len(cmd.library_dirs) > 0)
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_user_site(self):
- # site.USER_SITE was introduced in 2.6
- if sys.version < '2.6':
- return
-
import site
dist = Distribution({'name': 'xx'})
cmd = build_ext(dist)
diff --git a/src/distutils2/tests/test_build_py.py b/src/distutils2/tests/test_build_py.py
--- a/src/distutils2/tests/test_build_py.py
+++ b/src/distutils2/tests/test_build_py.py
@@ -95,7 +95,7 @@
sys.stdout = old_stdout
@unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
- 'dont_write_bytecode support')
+ 'sys.dont_write_bytecode not supported')
def test_dont_write_bytecode(self):
# makes sure byte_compile is not used
pkg_dir, dist = self.create_dist()
diff --git a/src/distutils2/tests/test_check.py b/src/distutils2/tests/test_check.py
--- a/src/distutils2/tests/test_check.py
+++ b/src/distutils2/tests/test_check.py
@@ -43,7 +43,7 @@
# get an error if there are missing metadata
self.assertRaises(DistutilsSetupError, self._run, {}, **{'strict': 1})
- # and of course, no error when all metadata are present
+ # and of course, no error when all metadata fields are present
cmd = self._run(metadata, strict=1)
self.assertEqual(len(cmd._warnings), 0)
diff --git a/src/distutils2/tests/test_cmd.py b/src/distutils2/tests/test_cmd.py
--- a/src/distutils2/tests/test_cmd.py
+++ b/src/distutils2/tests/test_cmd.py
@@ -98,7 +98,7 @@
def test_ensure_dirname(self):
cmd = self.cmd
- cmd.option1 = os.path.dirname(__file__)
+ cmd.option1 = os.path.dirname(__file__) or os.curdir
cmd.ensure_dirname('option1')
cmd.option2 = 'xxx'
self.assertRaises(DistutilsOptionError, cmd.ensure_dirname, 'option2')
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
@@ -17,7 +17,7 @@
class ConverterTestCase(unittest.TestCase):
- @unittest.skipUnless(not sys.version < '2.6', 'Needs Python >=2.6')
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_conversions(self):
# for all XX_before in the conversions/ dir
# we run the refactoring tool
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
@@ -64,13 +64,13 @@
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,
+ 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")
+ 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):
diff --git a/src/distutils2/tests/test_dist.py b/src/distutils2/tests/test_dist.py
--- a/src/distutils2/tests/test_dist.py
+++ b/src/distutils2/tests/test_dist.py
@@ -1,4 +1,4 @@
-# -*- coding: utf8 -*-
+# -*- coding: utf-8 -*-
"""Tests for distutils2.dist."""
import os
@@ -153,6 +153,27 @@
my_file2 = os.path.join(tmp_dir, 'f2')
dist.metadata.write_file(open(my_file, 'w'))
+ def test_bad_attr(self):
+ cls = Distribution
+
+ # catching warnings
+ warns = []
+ def _warn(msg):
+ warns.append(msg)
+
+ old_warn = warnings.warn
+ warnings.warn = _warn
+ try:
+ dist = cls(attrs={'author': 'xxx',
+ 'name': 'xxx',
+ 'version': 'xxx',
+ 'url': 'xxxx',
+ 'badoptname': 'xxx'})
+ finally:
+ warnings.warn = old_warn
+
+ self.assertTrue(len(warns)==1 and "Unknown distribution" in warns[0])
+
def test_empty_options(self):
# an empty options dictionary should not stay in the
# list of attributes
@@ -176,6 +197,21 @@
self.assertEqual(len(warns), 0)
+ def test_non_empty_options(self):
+ # TODO: how to actually use options is not documented except
+ # for a few cryptic comments in dist.py. If this is to stay
+ # in the public API, it deserves some better documentation.
+
+ # Here is an example of how it's used out there:
+ # http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html#specifying-customizations
+ cls = Distribution
+ dist = cls(attrs={'author': 'xxx',
+ 'name': 'xxx',
+ 'version': 'xxx',
+ 'url': 'xxxx',
+ 'options': dict(sdist=dict(owner="root"))})
+ self.assertTrue("owner" in dist.get_option_dict("sdist"))
+
def test_finalize_options(self):
attrs = {'keywords': 'one,two',
@@ -240,6 +276,49 @@
# make sure --no-user-cfg disables the user cfg file
self.assertEqual(len(all_files)-1, len(files))
+ def test_special_hooks_parsing(self):
+ temp_home = self.mkdtemp()
+ config_files = [os.path.join(temp_home, "config1.cfg"),
+ os.path.join(temp_home, "config2.cfg")]
+
+ # Store two aliased hooks in config files
+ self.write_file((temp_home, "config1.cfg"), '[test_dist]\npre-hook.a = type')
+ self.write_file((temp_home, "config2.cfg"), '[test_dist]\npre-hook.b = type')
+
+ sys.argv.extend(["--command-packages",
+ "distutils2.tests",
+ "test_dist"])
+ cmd = self.create_distribution(config_files).get_command_obj("test_dist")
+ self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'})
+
+
+ def test_hooks_get_run(self):
+ temp_home = self.mkdtemp()
+ config_file = os.path.join(temp_home, "config1.cfg")
+
+ self.write_file((temp_home, "config1.cfg"), textwrap.dedent('''
+ [test_dist]
+ pre-hook.test = distutils2.tests.test_dist.DistributionTestCase.log_pre_call
+ post-hook.test = distutils2.tests.test_dist.DistributionTestCase.log_post_call'''))
+
+ sys.argv.extend(["--command-packages",
+ "distutils2.tests",
+ "test_dist"])
+ d = self.create_distribution([config_file])
+ cmd = d.get_command_obj("test_dist")
+
+ # prepare the call recorders
+ record = []
+ DistributionTestCase.log_pre_call = staticmethod(lambda _cmd: record.append(('pre', _cmd)))
+ DistributionTestCase.log_post_call = staticmethod(lambda _cmd: record.append(('post', _cmd)))
+ test_dist.run = lambda _cmd: record.append(('run', _cmd))
+ test_dist.finalize_options = lambda _cmd: record.append(('finalize_options', _cmd))
+
+ d.run_command('test_dist')
+ self.assertEqual(record, [('finalize_options', cmd),
+ ('pre', cmd),
+ ('run', cmd),
+ ('post', cmd)])
class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
unittest.TestCase):
diff --git a/src/distutils2/tests/test_index_dist.py b/src/distutils2/tests/test_index_dist.py
--- a/src/distutils2/tests/test_index_dist.py
+++ b/src/distutils2/tests/test_index_dist.py
@@ -18,7 +18,7 @@
class TestReleaseInfo(unittest.TestCase):
- def test_instanciation(self):
+ def test_instantiation(self):
# Test the DistInfo class provides us the good attributes when
# given on construction
release = ReleaseInfo("FooBar", "1.1")
@@ -106,7 +106,7 @@
})
self.assertEqual(2, len(d.urls))
- def test_comparaison(self):
+ def test_comparison(self):
# Test that we can compare DistInfoributionInfoList
foo1 = ReleaseInfo("foo", "1.0")
foo2 = ReleaseInfo("foo", "2.0")
@@ -123,23 +123,20 @@
def test_download(self, server):
# Download is possible, and the md5 is checked if given
- add_to_tmpdirs = lambda x: self.tempdirs.append(os.path.dirname(x))
-
url = "%s/simple/foobar/foobar-0.1.tar.gz" % server.full_address
# check md5 if given
dist = Dist(url=url, hashname="md5",
hashval="d41d8cd98f00b204e9800998ecf8427e")
- add_to_tmpdirs(dist.download())
+ dist.download(self.mkdtemp())
# a wrong md5 fails
dist2 = Dist(url=url, hashname="md5", hashval="wrongmd5")
- self.assertRaises(HashDoesNotMatch, dist2.download)
- add_to_tmpdirs(dist2.downloaded_location)
+ self.assertRaises(HashDoesNotMatch, dist2.download, self.mkdtemp())
# we can omit the md5 hash
dist3 = Dist(url=url)
- add_to_tmpdirs(dist3.download())
+ dist3.download(self.mkdtemp())
# and specify a temporary location
# for an already downloaded dist
@@ -177,7 +174,7 @@
self.assertIn(releases[0], filtered)
self.assertIn(releases[1], filtered)
- def test_add_release(self):
+ def test_append(self):
# When adding a new item to the list, the behavior is to test if
# a release with the same name and version number already exists,
# and if so, to add a new distribution for it. If the distribution type
diff --git a/src/distutils2/tests/test_index_simple.py b/src/distutils2/tests/test_index_simple.py
--- a/src/distutils2/tests/test_index_simple.py
+++ b/src/distutils2/tests/test_index_simple.py
@@ -222,7 +222,7 @@
# create the index using both servers
crawler = Crawler(server.full_address + "/simple/",
hosts=('*',), timeout=1, # set the timeout to 1s for the tests
- mirrors=[mirror.full_address, ])
+ mirrors=[mirror.full_address])
# this should not raise a timeout
self.assertEqual(4, len(crawler.get_releases("foo")))
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
@@ -75,11 +75,9 @@
check_path(cmd.install_scripts, os.path.join(destination, "bin"))
check_path(cmd.install_data, destination)
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_user_site(self):
- # site.USER_SITE was introduced in 2.6
- if sys.version < '2.6':
- return
-
+ # test install with --user
# preparing the environment for the test
self.old_user_base = get_config_var('userbase')
self.old_user_site = get_path('purelib', '%s_user' % os.name)
@@ -195,11 +193,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,202 @@
+"""Tests for ``distutils2.command.install_distinfo``. """
+
+import os
+import sys
+import csv
+
+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
+
+try:
+ import hashlib
+except ImportError:
+ from distutils2._backport import hashlib
+
+
+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_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') or dir.endswith('.egg-info') or
+ dir.endswith('.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")
diff --git a/src/distutils2/tests/test_install_lib.py b/src/distutils2/tests/test_install_lib.py
--- a/src/distutils2/tests/test_install_lib.py
+++ b/src/distutils2/tests/test_install_lib.py
@@ -57,9 +57,8 @@
# setting up a dist environment
cmd.compile = cmd.optimize = 1
cmd.install_dir = pkg_dir
- f = os.path.join(pkg_dir, 'foo.py')
- self.write_file(f, '# python file')
- cmd.distribution.py_modules = [pkg_dir]
+ f = os.path.join(pkg_dir, '__init__.py')
+ self.write_file(f, '# python package')
cmd.distribution.ext_modules = [Extension('foo', ['xxx'])]
cmd.distribution.packages = [pkg_dir]
cmd.distribution.script_name = 'setup.py'
@@ -74,9 +73,8 @@
# setting up a dist environment
cmd.compile = cmd.optimize = 1
cmd.install_dir = pkg_dir
- f = os.path.join(pkg_dir, 'foo.py')
- self.write_file(f, '# python file')
- cmd.distribution.py_modules = [pkg_dir]
+ f = os.path.join(pkg_dir, '__init__.py')
+ self.write_file(f, '# python package')
cmd.distribution.ext_modules = [Extension('foo', ['xxx'])]
cmd.distribution.packages = [pkg_dir]
cmd.distribution.script_name = 'setup.py'
@@ -84,7 +82,8 @@
# get_input should return 2 elements
self.assertEqual(len(cmd.get_inputs()), 2)
- @unittest.skipUnless(bytecode_support, 'sys.dont_write_bytecode not supported')
+ @unittest.skipUnless(bytecode_support,
+ 'sys.dont_write_bytecode not supported')
def test_dont_write_bytecode(self):
# makes sure byte_compile is not used
pkg_dir, dist = self.create_dist()
diff --git a/src/distutils2/tests/test_metadata.py b/src/distutils2/tests/test_metadata.py
--- a/src/distutils2/tests/test_metadata.py
+++ b/src/distutils2/tests/test_metadata.py
@@ -1,6 +1,7 @@
"""Tests for distutils.command.bdist."""
import os
import sys
+import platform
from StringIO import StringIO
from distutils2.metadata import (DistributionMetadata, _interpret,
@@ -12,25 +13,59 @@
class DistributionMetadataTestCase(LoggingSilencer, unittest.TestCase):
+ def test_instantiation(self):
+ PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
+ fp = open(PKG_INFO)
+ try:
+ contents = fp.read()
+ finally:
+ fp.close()
+ fp = StringIO(contents)
+
+ m = DistributionMetadata()
+ self.assertRaises(MetadataUnrecognizedVersionError, m.items)
+
+ m = DistributionMetadata(PKG_INFO)
+ self.assertEqual(len(m.items()), 22)
+
+ m = DistributionMetadata(fileobj=fp)
+ self.assertEqual(len(m.items()), 22)
+
+ m = DistributionMetadata(mapping=dict(name='Test', version='1.0'))
+ self.assertEqual(len(m.items()), 11)
+
+ d = dict(m.items())
+ self.assertRaises(TypeError, DistributionMetadata,
+ PKG_INFO, fileobj=fp)
+ self.assertRaises(TypeError, DistributionMetadata,
+ PKG_INFO, mapping=d)
+ self.assertRaises(TypeError, DistributionMetadata,
+ fileobj=fp, mapping=d)
+ self.assertRaises(TypeError, DistributionMetadata,
+ PKG_INFO, mapping=m, fileobj=fp)
def test_interpret(self):
- platform = sys.platform
+ sys_platform = sys.platform
version = sys.version.split()[0]
os_name = os.name
+ platform_version = platform.version()
+ platform_machine = platform.machine()
- self.assertTrue(_interpret("sys.platform == '%s'" % platform))
+ self.assertTrue(_interpret("sys.platform == '%s'" % sys_platform))
self.assertTrue(_interpret(
- "sys.platform == '%s' or python_version == '2.4'" % platform))
+ "sys.platform == '%s' or python_version == '2.4'" % sys_platform))
self.assertTrue(_interpret(
"sys.platform == '%s' and python_full_version == '%s'" %
- (platform, version)))
- self.assertTrue(_interpret("'%s' == sys.platform" % platform))
-
+ (sys_platform, version)))
+ self.assertTrue(_interpret("'%s' == sys.platform" % sys_platform))
self.assertTrue(_interpret('os.name == "%s"' % os_name))
+ self.assertTrue(_interpret(
+ 'platform.version == "%s" and platform.machine == "%s"' %
+ (platform_version, platform_machine)))
# stuff that need to raise a syntax error
ops = ('os.name == os.name', 'os.name == 2', "'2' == '2'",
- 'okpjonon', '', 'os.name ==')
+ 'okpjonon', '', 'os.name ==', 'python_version == 2.4')
for op in ops:
self.assertRaises(SyntaxError, _interpret, op)
@@ -62,17 +97,13 @@
PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
metadata = DistributionMetadata(PKG_INFO)
- res = StringIO()
- metadata.write_file(res)
- res.seek(0)
- res = res.read()
- f = open(PKG_INFO)
- try:
- # XXX this is not used
- wanted = f.read()
- finally:
- f.close()
- self.assertTrue('Keywords: keyring,password,crypt' in res)
+ out = StringIO()
+ metadata.write_file(out)
+ out.seek(0)
+ res = DistributionMetadata()
+ res.read_file(out)
+ for k in metadata.keys():
+ self.assertTrue(metadata[k] == res[k])
def test_metadata_markers(self):
# see if we can be platform-aware
@@ -82,6 +113,10 @@
metadata = DistributionMetadata(platform_dependent=True)
metadata.read_file(StringIO(content))
self.assertEqual(metadata['Requires-Dist'], ['bar'])
+ metadata['Name'] = "baz; sys.platform == 'blah'"
+ # FIXME is None or 'UNKNOWN' correct here?
+ # where is that documented?
+ self.assertEquals(metadata['Name'], None)
# test with context
context = {'sys.platform': 'okook'}
@@ -109,15 +144,15 @@
metadata.read_file(out)
self.assertEqual(wanted, metadata['Description'])
- def test_mapper_apis(self):
+ def test_mapping_api(self):
PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
content = open(PKG_INFO).read()
content = content % sys.platform
- metadata = DistributionMetadata()
- metadata.read_file(StringIO(content))
+ metadata = DistributionMetadata(fileobj=StringIO(content))
self.assertIn('Version', metadata.keys())
self.assertIn('0.5', metadata.values())
self.assertIn(('Version', '0.5'), metadata.items())
+ #TODO test update
def test_versions(self):
metadata = DistributionMetadata()
@@ -211,6 +246,10 @@
metadata = DistributionMetadata()
metadata['Version'] = 'rr'
metadata['Requires-dist'] = ['Foo (a)']
+ if metadata.docutils_support:
+ missing, warnings = metadata.check()
+ self.assertEqual(len(warnings), 2)
+ metadata.docutils_support = False
missing, warnings = metadata.check()
self.assertEqual(missing, ['Name', 'Home-page'])
self.assertEqual(len(warnings), 2)
diff --git a/src/distutils2/tests/test_msvc9compiler.py b/src/distutils2/tests/test_msvc9compiler.py
--- a/src/distutils2/tests/test_msvc9compiler.py
+++ b/src/distutils2/tests/test_msvc9compiler.py
@@ -64,7 +64,7 @@
class msvc9compilerTestCase(support.TempdirManager,
unittest.TestCase):
- @unittest.skipUnless(sys.platform=="win32", "These tests are only for win32")
+ @unittest.skipUnless(sys.platform == "win32", "runs only on win32")
def test_no_compiler(self):
# makes sure query_vcvarsall throws
# a DistutilsPlatformError if the compiler
@@ -86,7 +86,7 @@
finally:
msvc9compiler.find_vcvarsall = old_find_vcvarsall
- @unittest.skipUnless(sys.platform=="win32", "These tests are only for win32")
+ @unittest.skipUnless(sys.platform == "win32", "runs only on win32")
def test_reg_class(self):
from distutils2.msvccompiler import get_build_version
if get_build_version() < 8.0:
@@ -110,7 +110,7 @@
keys = Reg.read_keys(HKCU, r'Control Panel')
self.assertTrue('Desktop' in keys)
- @unittest.skipUnless(sys.platform=="win32", "These tests are only for win32")
+ @unittest.skipUnless(sys.platform == "win32", "runs only on win32")
def test_remove_visual_c_ref(self):
from distutils2.msvc9compiler import MSVCCompiler
tempdir = self.mkdtemp()
diff --git a/src/distutils2/tests/test_pypi_versions.py b/src/distutils2/tests/test_pypi_versions.py
--- a/src/distutils2/tests/test_pypi_versions.py
+++ b/src/distutils2/tests/test_pypi_versions.py
@@ -1,43 +1,39 @@
-#
-## test_pypi_versions.py
-##
-## A very simple test to see what percentage of the current pypi packages
-## have versions that can be converted automatically by distutils' new
-## suggest_normalized_version() into PEP-386 compatible versions.
-##
-## Requires : Python 2.5+
-##
-## Written by: ssteinerX at gmail.com
-#
+"""PEP 386 compatibility test with current distributions on PyPI.
+
+A very simple test to see what percentage of the current PyPI packages
+have versions that can be converted automatically by distutils2's new
+suggest_normalized_version into PEP 386-compatible versions.
+"""
+
+# XXX This file does not actually run tests, move it to a script
+
+# Written by ssteinerX at gmail.com
+
+import os
+import xmlrpclib
try:
import cPickle as pickle
-except:
+except ImportError:
import pickle
-import xmlrpclib
-import os.path
-
from distutils2.version import suggest_normalized_version
from distutils2.tests import run_unittest
from distutils2.tests.support import unittest
def test_pypi():
- #
- ## To re-run from scratch, just delete these two .pkl files
- #
+ # FIXME need a better way to do that
+ # To re-run from scratch, just delete these two .pkl files
INDEX_PICKLE_FILE = 'pypi-index.pkl'
VERSION_PICKLE_FILE = 'pypi-version.pkl'
package_info = version_info = []
- #
- ## if there's a saved version of the package list
- ## restore it
- ## else:
- ## pull the list down from pypi
- ## save a pickled version of it
- #
+ # if there's a saved version of the package list
+ # restore it
+ # else:
+ # pull the list down from pypi
+ # save a pickled version of it
if os.path.exists(INDEX_PICKLE_FILE):
print "Loading saved pypi data..."
f = open(INDEX_PICKLE_FILE, 'rb')
@@ -57,13 +53,11 @@
finally:
f.close()
- #
- ## If there's a saved list of the versions from the packages
- ## restore it
- ## else
- ## extract versions from the package list
- ## save a pickled version of it
- #
+ # If there's a saved list of the versions from the packages
+ # restore it
+ # else
+ # extract versions from the package list
+ # save a pickled version of it
versions = []
if os.path.exists(VERSION_PICKLE_FILE):
print "Loading saved version info..."
diff --git a/src/distutils2/tests/test_register.py b/src/distutils2/tests/test_register.py
--- a/src/distutils2/tests/test_register.py
+++ b/src/distutils2/tests/test_register.py
@@ -1,5 +1,5 @@
+# -*- encoding: utf-8 -*-
"""Tests for distutils.command.register."""
-# -*- encoding: utf8 -*-
import sys
import os
import getpass
@@ -161,7 +161,7 @@
# therefore used afterwards by other commands
self.assertEqual(cmd.distribution.password, 'password')
- def test_registering(self):
+ def test_registration(self):
# this test runs choice 2
cmd = self._get_cmd()
inputs = RawInputs('2', 'tarek', 'tarek at ziade.org')
@@ -210,7 +210,7 @@
cmd.strict = 1
self.assertRaises(DistutilsSetupError, cmd.run)
- # metadata are OK but long_description is broken
+ # metadata is OK but long_description is broken
metadata = {'home_page': 'xxx', 'author': 'xxx',
'author_email': u'éxéxé',
'name': 'xxx', 'version': 'xxx',
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
@@ -241,7 +241,7 @@
@unittest.skipUnless(zlib, "requires zlib")
def test_metadata_check_option(self):
- # testing the `medata-check` option
+ # testing the `check-metadata` option
dist, cmd = self.get_cmd(metadata={})
# this should raise some warnings !
@@ -295,7 +295,7 @@
self.assertRaises(DistutilsOptionError, cmd.finalize_options)
@unittest.skipUnless(zlib, "requires zlib")
- @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+ @unittest.skipUnless(UID_GID_SUPPORT, "requires grp and pwd support")
def test_make_distribution_owner_group(self):
# check if tar and gzip are installed
diff --git a/src/distutils2/tests/test_upload.py b/src/distutils2/tests/test_upload.py
--- a/src/distutils2/tests/test_upload.py
+++ b/src/distutils2/tests/test_upload.py
@@ -1,5 +1,5 @@
+# -*- encoding: utf-8 -*-
"""Tests for distutils.command.upload."""
-# -*- encoding: utf8 -*-
import os
import sys
@@ -101,6 +101,38 @@
self.assertEqual(handler.command, 'POST')
self.assertNotIn('\n', headers['authorization'])
+ def test_upload_docs(self):
+ path = os.path.join(self.tmp_dir, 'xxx')
+ self.write_file(path)
+ command, pyversion, filename = 'xxx', '2.6', path
+ dist_files = [(command, pyversion, filename)]
+ docs_path = os.path.join(self.tmp_dir, "build", "docs")
+ os.makedirs(docs_path)
+ self.write_file(os.path.join(docs_path, "index.html"), "yellow")
+ self.write_file(self.rc, PYPIRC)
+
+ # lets run it
+ pkg_dir, dist = self.create_dist(dist_files=dist_files, author=u'dédé')
+
+ cmd = upload(dist)
+ cmd.get_finalized_command("build").run()
+ cmd.upload_docs = True
+ cmd.ensure_finalized()
+ cmd.repository = self.pypi.full_address
+ try:
+ prev_dir = os.getcwd()
+ os.chdir(self.tmp_dir)
+ cmd.run()
+ finally:
+ os.chdir(prev_dir)
+
+ handler, request_data = self.pypi.requests[-1]
+ action, name, content =\
+ request_data.split("----------------GHSKFJDLGDS7543FJKLFHRE75642756743254")[1:4]
+
+ self.assertIn('name=":action"', action)
+ self.assertIn("doc_upload", action)
+
def test_suite():
return unittest.makeSuite(UploadTestCase)
diff --git a/src/distutils2/tests/test_upload_docs.py b/src/distutils2/tests/test_upload_docs.py
--- a/src/distutils2/tests/test_upload_docs.py
+++ b/src/distutils2/tests/test_upload_docs.py
@@ -1,13 +1,19 @@
+# -*- encoding: utf8 -*-
"""Tests for distutils.command.upload_docs."""
-# -*- encoding: utf8 -*-
-import httplib, os, os.path, shutil, sys, tempfile, zipfile
-from cStringIO import StringIO
+import os
+import sys
+import httplib
+import shutil
+import zipfile
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
from distutils2.command import upload_docs as upload_docs_mod
from distutils2.command.upload_docs import (upload_docs, zip_dir,
- encode_multipart)
+ encode_multipart)
from distutils2.core import Distribution
-
from distutils2.errors import DistutilsFileError, DistutilsOptionError
from distutils2.tests import support
@@ -59,7 +65,7 @@
self.cmd = upload_docs(self.dist)
def test_default_uploaddir(self):
- sandbox = tempfile.mkdtemp()
+ sandbox = self.mkdtemp()
previous = os.getcwd()
os.chdir(sandbox)
try:
@@ -72,7 +78,7 @@
def prepare_sample_dir(self, sample_dir=None):
if sample_dir is None:
- sample_dir = tempfile.mkdtemp()
+ sample_dir = self.mkdtemp()
os.mkdir(os.path.join(sample_dir, "docs"))
self.write_file(os.path.join(sample_dir, "docs", "index.html"), "Ce mortel ennui")
self.write_file(os.path.join(sample_dir, "index.html"), "Oh la la")
diff --git a/src/distutils2/tests/test_util.py b/src/distutils2/tests/test_util.py
--- a/src/distutils2/tests/test_util.py
+++ b/src/distutils2/tests/test_util.py
@@ -4,7 +4,6 @@
from copy import copy
from StringIO import StringIO
import subprocess
-import tempfile
import time
from distutils2.tests import captured_stdout
@@ -19,7 +18,7 @@
_find_exe_version, _MAC_OS_X_LD_VERSION,
byte_compile, find_packages, spawn, find_executable,
_nt_quote_args, get_pypirc_path, generate_pypirc,
- read_pypirc)
+ read_pypirc, resolve_dotted_name)
from distutils2 import util
from distutils2.tests import support
@@ -288,7 +287,7 @@
self.assertEqual(res[2], None)
@unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
- 'no dont_write_bytecode support')
+ 'sys.dont_write_bytecode not supported')
def test_dont_write_bytecode(self):
# makes sure byte_compile raise a DistutilsError
# if sys.dont_write_bytecode is True
@@ -301,9 +300,9 @@
def test_newer(self):
self.assertRaises(DistutilsFileError, util.newer, 'xxx', 'xxx')
- self.newer_f1 = tempfile.NamedTemporaryFile()
+ self.newer_f1 = self.mktempfile()
time.sleep(1)
- self.newer_f2 = tempfile.NamedTemporaryFile()
+ self.newer_f2 = self.mktempfile()
self.assertTrue(util.newer(self.newer_f2.name, self.newer_f1.name))
def test_find_packages(self):
@@ -343,7 +342,17 @@
res = find_packages([root], ['pkg1.pkg2'])
self.assertEqual(set(res), set(['pkg1', 'pkg5', 'pkg1.pkg3', 'pkg1.pkg3.pkg6']))
- @unittest.skipUnless(sys.version > '2.6', 'Need Python 2.6 or more')
+ def test_resolve_dotted_name(self):
+ self.assertEqual(UtilTestCase, resolve_dotted_name("distutils2.tests.test_util.UtilTestCase"))
+ self.assertEqual(UtilTestCase.test_resolve_dotted_name,
+ resolve_dotted_name("distutils2.tests.test_util.UtilTestCase.test_resolve_dotted_name"))
+
+ self.assertRaises(ImportError, resolve_dotted_name,
+ "distutils2.tests.test_util.UtilTestCaseNot")
+ self.assertRaises(ImportError, resolve_dotted_name,
+ "distutils2.tests.test_util.UtilTestCase.nonexistent_attribute")
+
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_run_2to3_on_code(self):
content = "print 'test'"
converted_content = "print('test')"
@@ -358,7 +367,7 @@
file_handle.close()
self.assertEquals(new_content, converted_content)
- @unittest.skipUnless(sys.version > '2.6', 'Need Python 2.6 or more')
+ @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
def test_run_2to3_on_doctests(self):
# to check if text files containing doctests only get converted.
content = ">>> print 'test'\ntest\n"
@@ -385,7 +394,7 @@
@unittest.skipUnless(os.name in ('nt', 'posix'),
- 'Runs only under posix or nt')
+ 'runs only under posix or nt')
def test_spawn(self):
tmpdir = self.mkdtemp()
diff --git a/src/distutils2/util.py b/src/distutils2/util.py
--- a/src/distutils2/util.py
+++ b/src/distutils2/util.py
@@ -88,8 +88,8 @@
raise ValueError("path '%s' cannot end with '/'" % pathname)
paths = pathname.split('/')
- while '.' in paths:
- paths.remove('.')
+ while os.curdir in paths:
+ paths.remove(os.curdir)
if not paths:
return os.curdir
return os.path.join(*paths)
@@ -121,15 +121,6 @@
path = path[1:]
return os.path.join(new_root, path)
- elif os.name == 'mac':
- if not os.path.isabs(pathname):
- return os.path.join(new_root, pathname)
- else:
- # Chop off volume name from start of path
- elements = pathname.split(":", 1)
- pathname = ":" + elements[1]
- return os.path.join(new_root, pathname)
-
else:
raise DistutilsPlatformError("nothing known about "
"platform '%s'" % os.name)
@@ -599,7 +590,7 @@
return path[len(root_path) + 1:].replace(os.sep, '.')
-def find_packages(paths=('.',), exclude=()):
+def find_packages(paths=(os.curdir,), exclude=()):
"""Return a list all Python packages found recursively within
directories 'paths'
@@ -646,6 +637,24 @@
return packages
+def resolve_dotted_name(dotted_name):
+ module_name, rest = dotted_name.split('.')[0], dotted_name.split('.')[1:]
+ while len(rest) > 0:
+ try:
+ ret = __import__(module_name)
+ break
+ except ImportError:
+ if rest == []:
+ raise
+ module_name += ('.' + rest[0])
+ rest = rest[1:]
+ while len(rest) > 0:
+ try:
+ ret = getattr(ret, rest.pop(0))
+ except AttributeError:
+ raise ImportError
+ return ret
+
# utility functions for 2to3 support
def run_2to3(files, doctests_only=False, fixer_names=None, options=None,
diff --git a/src/runtests-cov.py b/src/runtests-cov.py
--- a/src/runtests-cov.py
+++ b/src/runtests-cov.py
@@ -5,9 +5,20 @@
"""
import sys
-from os.path import dirname, islink, realpath
+from os.path import dirname, islink, realpath, join, abspath
from optparse import OptionParser
+COVERAGE_FILE = join(dirname(abspath(__file__)), '.coverage')
+
+def get_coverage():
+ """ Return a usable coverage object. """
+ # deferred import because coverage is optional
+ import coverage
+ cov = getattr(coverage, "the_coverage", None)
+ if not cov:
+ cov = coverage.coverage(COVERAGE_FILE)
+ return cov
+
def ignore_prefixes(module):
""" Return a list of prefixes to ignore in the coverage report if
we want to completely skip `module`.
@@ -16,16 +27,17 @@
# distributions, such a Ubuntu, really like to build link farm in
# /usr/lib in order to save a few bytes on the disk.
dirnames = [dirname(module.__file__)]
-
+
pymod = module.__file__.rstrip("c")
if islink(pymod):
dirnames.append(dirname(realpath(pymod)))
return dirnames
+
def parse_opts():
parser = OptionParser(usage="%prog [OPTIONS]",
description="run the distutils2 unittests")
-
+
parser.add_option("-q", "--quiet", help="do not print verbose messages",
action="store_true", default=False)
parser.add_option("-c", "--coverage", action="store_true", default=False,
@@ -36,15 +48,23 @@
default=False,
help=("Show line numbers of statements in each module "
"that weren't executed."))
-
+
opts, args = parser.parse_args()
return opts, args
+
def coverage_report(opts):
- import coverage
from distutils2.tests.support import unittest
- cov = coverage.coverage()
- cov.load()
+ cov = get_coverage()
+ if hasattr(cov, "load"):
+ # running coverage 3.x
+ cov.load()
+ morfs = None
+ else:
+ # running coverage 2.x
+ cov.cache = COVERAGE_FILE
+ cov.restore()
+ morfs = [m for m in cov.cexecuted.keys() if "distutils2" in m]
prefixes = ["runtests", "distutils2/tests", "distutils2/_backport"]
prefixes += ignore_prefixes(unittest)
@@ -63,7 +83,7 @@
# that module is also completely optional
pass
- cov.report(omit_prefixes=prefixes, show_missing=opts.show_missing)
+ cov.report(morfs, omit_prefixes=prefixes, show_missing=opts.show_missing)
def test_main():
@@ -71,11 +91,8 @@
verbose = not opts.quiet
ret = 0
- if opts.coverage or opts.report:
- import coverage
-
if opts.coverage:
- cov = coverage.coverage()
+ cov = get_coverage()
cov.erase()
cov.start()
if not opts.report:
@@ -89,6 +106,7 @@
return ret
+
def run_tests(verbose):
import distutils2.tests
from distutils2.tests import run_unittest, reap_children, TestFailed
@@ -108,12 +126,11 @@
finally:
reap_children()
+
if __name__ == "__main__":
try:
from distutils2.tests.support import unittest
except ImportError:
sys.stderr.write('Error: You have to install unittest2')
sys.exit(1)
-
sys.exit(test_main())
-
diff --git a/src/runtests.py b/src/runtests.py
--- a/src/runtests.py
+++ b/src/runtests.py
@@ -1,9 +1,12 @@
+#!/usr/bin/env python
"""Tests for distutils2.
The tests for distutils2 are defined in the distutils2.tests package.
"""
+
import sys
+
def test_main():
import distutils2.tests
from distutils2.tests import run_unittest, reap_children, TestFailed
@@ -23,12 +26,11 @@
finally:
reap_children()
+
if __name__ == "__main__":
try:
from distutils2.tests.support import unittest
except ImportError:
sys.stderr.write('Error: You have to install unittest2')
sys.exit(1)
-
sys.exit(test_main())
-
diff --git a/src/setup.cfg b/src/setup.cfg
new file mode 100644
--- /dev/null
+++ b/src/setup.cfg
@@ -0,0 +1,3 @@
+[build_ext]
+# needed so that tests work without mucking with sys.path
+inplace = 1
diff --git a/src/setup.py b/src/setup.py
--- a/src/setup.py
+++ b/src/setup.py
@@ -168,29 +168,25 @@
# The _hashlib module wraps optimized implementations
# of hash functions from the OpenSSL library.
- exts.append(Extension('_hashlib', ['_hashopenssl.c'],
+ exts.append(Extension('distutils2._backport._hashlib',
+ ['distutils2/_backport/_hashopenssl.c'],
include_dirs = [ssl_inc_dir],
library_dirs = [os.path.dirname(ssl_lib)],
libraries = oslibs[os.name]))
else:
- exts.append(Extension('_sha', ['shamodule.c']) )
- exts.append(Extension('_md5',
- sources=['md5module.c', 'md5.c'],
- depends=['md5.h']) )
+ exts.append(Extension('distutils2._backport._sha',
+ ['distutils2/_backport/shamodule.c']))
+ exts.append(Extension('distutils2._backport._md5',
+ sources=['distutils2/_backport/md5module.c',
+ 'distutils2/_backport/md5.c'],
+ depends=['distutils2/_backport/md5.h']) )
if (not ssl_lib or openssl_ver < 0x00908000):
# OpenSSL doesn't do these until 0.9.8 so we'll bring our own
- exts.append(Extension('_sha256', ['sha256module.c']))
- exts.append(Extension('_sha512', ['sha512module.c']))
-
- def prepend_modules(filename):
- return os.path.join('Modules', filename)
-
- # all the C code is in the Modules subdirectory, prepend the path
- for ext in exts:
- ext.sources = [prepend_modules(fn) for fn in ext.sources]
- if hasattr(ext, 'depends') and ext.depends is not None:
- ext.depends = [prepend_modules(fn) for fn in ext.depends]
+ exts.append(Extension('distutils2._backport._sha256',
+ ['distutils2/_backport/sha256module.c']))
+ exts.append(Extension('distutils2._backport._sha512',
+ ['distutils2/_backport/sha512module.c']))
return exts
diff --git a/src/tests.sh b/src/tests.sh
--- a/src/tests.sh
+++ b/src/tests.sh
@@ -1,20 +1,18 @@
#!/bin/sh
echo -n "Running tests for Python 2.4... "
-rm -rf *.so
-python2.4 setup.py build_ext -i -q 2> /dev/null > /dev/null
+rm -f distutils2/_backport/_hashlib.so
+python2.4 setup.py build_ext -f -q 2> /dev/null > /dev/null
python2.4 -Wd runtests.py -q 2> /dev/null
-rm -rf *.so
if [ $? -ne 0 ];then
echo "Failed"
+ rm -f distutils2/_backport/_hashlib.so
exit 1
else
echo "Success"
fi
echo -n "Running tests for Python 2.5... "
-python2.5 setup.py build_ext -i -q 2> /dev/null > /dev/null
python2.5 -Wd runtests.py -q 2> /dev/null
-rm -rf *.so
if [ $? -ne 0 ];then
echo "Failed"
exit 1
@@ -23,7 +21,7 @@
fi
echo -n "Running tests for Python 2.6... "
-python2.6 -Wd -bb -3 runtests.py -q 2> /dev/null
+python2.6 -Wd runtests.py -q 2> /dev/null
if [ $? -ne 0 ];then
echo "Failed"
exit 1
--
Repository URL: http://hg.python.org/distutils2
More information about the Python-checkins
mailing list