summaryrefslogtreecommitdiff
path: root/src/distutils2
diff options
context:
space:
mode:
authorKonrad Delong <konryd@gmail.com>2010-07-27 15:07:46 +0200
committerKonrad Delong <konryd@gmail.com>2010-07-27 15:07:46 +0200
commit9da67de95ddac89371ae85e609003fee0331723f (patch)
tree21def38a20233c7673fcb5734a17ef473ffafc5d /src/distutils2
parent6b554ce70cd2e6b7a6e0633100de6cb533e46d46 (diff)
parent64aa303aca57a5f5a1e09b43f598eb866cfc8483 (diff)
downloaddisutils2-9da67de95ddac89371ae85e609003fee0331723f.tar.gz
merged upstream
Diffstat (limited to 'src/distutils2')
-rw-r--r--src/distutils2/README4
-rw-r--r--src/distutils2/__init__.py22
-rw-r--r--src/distutils2/_backport/hashlib.py143
-rw-r--r--src/distutils2/_backport/pkgutil.py306
-rw-r--r--src/distutils2/_backport/sysconfig.cfg2
-rw-r--r--src/distutils2/_backport/sysconfig.py13
-rw-r--r--src/distutils2/_backport/tarfile.py30
-rw-r--r--src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO1
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO18
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt0
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt1
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt3
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe1
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt6
-rw-r--r--src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt0
-rw-r--r--src/distutils2/_backport/tests/fake_dists/strawberry-0.6.eggbin0 -> 1402 bytes
-rw-r--r--src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA2
-rw-r--r--src/distutils2/_backport/tests/test_pkgutil.py301
-rw-r--r--src/distutils2/_backport/tests/test_sysconfig.py54
-rw-r--r--src/distutils2/command/bdist.py6
-rw-r--r--src/distutils2/command/bdist_dumb.py13
-rw-r--r--src/distutils2/command/bdist_msi.py4
-rw-r--r--src/distutils2/command/bdist_wininst.py5
-rw-r--r--src/distutils2/command/build_ext.py104
-rw-r--r--src/distutils2/command/build_py.py97
-rw-r--r--src/distutils2/command/cmd.py43
-rw-r--r--src/distutils2/command/register.py7
-rw-r--r--src/distutils2/command/sdist.py19
-rw-r--r--src/distutils2/command/upload.py2
-rw-r--r--src/distutils2/command/upload_docs.py5
-rw-r--r--src/distutils2/compiler/bcppcompiler.py2
-rw-r--r--src/distutils2/compiler/ccompiler.py25
-rw-r--r--src/distutils2/compiler/emxccompiler.py9
-rw-r--r--src/distutils2/compiler/msvc9compiler.py6
-rw-r--r--src/distutils2/compiler/msvccompiler.py4
-rw-r--r--src/distutils2/compiler/unixccompiler.py8
-rw-r--r--src/distutils2/config.py4
-rw-r--r--src/distutils2/converter/fixers/fix_imports.py7
-rw-r--r--src/distutils2/core.py19
-rw-r--r--src/distutils2/depgraph.py132
-rw-r--r--src/distutils2/dist.py90
-rw-r--r--src/distutils2/extension.py8
-rw-r--r--src/distutils2/fancy_getopt.py4
-rw-r--r--src/distutils2/log.py4
-rw-r--r--src/distutils2/metadata.py18
-rwxr-xr-xsrc/distutils2/mkpkg.py79
-rw-r--r--src/distutils2/pypi/__init__.py3
-rw-r--r--src/distutils2/pypi/dist.py315
-rw-r--r--src/distutils2/pypi/errors.py33
-rw-r--r--src/distutils2/pypi/pkg_resources.py2693
-rw-r--r--src/distutils2/pypi/simple.py1098
-rw-r--r--src/distutils2/spawn.py173
-rw-r--r--src/distutils2/tests/__init__.py2
-rw-r--r--src/distutils2/tests/conversions/05_after.py137
-rwxr-xr-xsrc/distutils2/tests/conversions/05_before.py137
-rw-r--r--src/distutils2/tests/pypi_server.py26
-rw-r--r--src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz0
-rw-r--r--src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html3
-rw-r--r--src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz0
-rw-r--r--src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html3
-rw-r--r--src/distutils2/tests/pypiserver/downloads_with_md5/simple/index.html2
-rw-r--r--src/distutils2/tests/pypiserver/foo_bar_baz/simple/bar/index.html6
-rw-r--r--src/distutils2/tests/pypiserver/foo_bar_baz/simple/baz/index.html6
-rw-r--r--src/distutils2/tests/pypiserver/foo_bar_baz/simple/foo/index.html6
-rw-r--r--src/distutils2/tests/pypiserver/foo_bar_baz/simple/index.html3
-rw-r--r--src/distutils2/tests/pypiserver/test_found_links/simple/foobar/index.html8
-rw-r--r--src/distutils2/tests/pypiserver/with_externals/external/external.html (renamed from src/distutils2/tests/pypiserver/test_link_priority/external/external.html)0
-rw-r--r--src/distutils2/tests/pypiserver/with_externals/simple/foobar/index.html (renamed from src/distutils2/tests/pypiserver/test_link_priority/simple/foobar/index.html)2
-rw-r--r--src/distutils2/tests/pypiserver/with_externals/simple/index.html (renamed from src/distutils2/tests/pypiserver/test_link_priority/simple/index.html)0
-rw-r--r--src/distutils2/tests/pypiserver/with_norel_links/external/homepage.html7
-rw-r--r--src/distutils2/tests/pypiserver/with_norel_links/external/nonrel.html1
-rw-r--r--src/distutils2/tests/pypiserver/with_norel_links/simple/foobar/index.html6
-rw-r--r--src/distutils2/tests/pypiserver/with_norel_links/simple/index.html1
-rw-r--r--src/distutils2/tests/pypiserver/with_real_externals/simple/foobar/index.html4
-rw-r--r--src/distutils2/tests/pypiserver/with_real_externals/simple/index.html1
-rw-r--r--src/distutils2/tests/support.py14
-rw-r--r--src/distutils2/tests/test_Mixin2to3.py54
-rw-r--r--src/distutils2/tests/test_bdist.py13
-rw-r--r--src/distutils2/tests/test_bdist_dumb.py8
-rw-r--r--src/distutils2/tests/test_build.py14
-rw-r--r--src/distutils2/tests/test_build_clib.py12
-rw-r--r--src/distutils2/tests/test_build_ext.py96
-rw-r--r--src/distutils2/tests/test_build_py.py12
-rw-r--r--src/distutils2/tests/test_build_scripts.py6
-rw-r--r--src/distutils2/tests/test_ccompiler.py4
-rw-r--r--src/distutils2/tests/test_check.py8
-rw-r--r--src/distutils2/tests/test_cmd.py6
-rw-r--r--src/distutils2/tests/test_config.py19
-rw-r--r--src/distutils2/tests/test_config_cmd.py12
-rw-r--r--src/distutils2/tests/test_converter.py2
-rw-r--r--src/distutils2/tests/test_cygwinccompiler.py18
-rw-r--r--src/distutils2/tests/test_depgraph.py187
-rw-r--r--src/distutils2/tests/test_dist.py80
-rw-r--r--src/distutils2/tests/test_install.py20
-rw-r--r--src/distutils2/tests/test_install_data.py8
-rw-r--r--src/distutils2/tests/test_install_headers.py4
-rw-r--r--src/distutils2/tests/test_install_lib.py8
-rw-r--r--src/distutils2/tests/test_install_scripts.py6
-rw-r--r--src/distutils2/tests/test_manifest.py3
-rw-r--r--src/distutils2/tests/test_metadata.py108
-rw-r--r--src/distutils2/tests/test_msvc9compiler.py18
-rw-r--r--src/distutils2/tests/test_pypi_dist.py247
-rw-r--r--src/distutils2/tests/test_pypi_server.py11
-rw-r--r--src/distutils2/tests/test_pypi_simple.py275
-rw-r--r--src/distutils2/tests/test_pypi_versions.py3
-rw-r--r--src/distutils2/tests/test_register.py14
-rw-r--r--src/distutils2/tests/test_sdist.py35
-rw-r--r--src/distutils2/tests/test_spawn.py60
-rw-r--r--src/distutils2/tests/test_upload.py13
-rw-r--r--src/distutils2/tests/test_util.py133
-rw-r--r--src/distutils2/tests/test_version.py98
-rw-r--r--src/distutils2/util.py346
-rw-r--r--src/distutils2/version.py57
113 files changed, 3590 insertions, 4749 deletions
diff --git a/src/distutils2/README b/src/distutils2/README
index 0416160..4afd700 100644
--- a/src/distutils2/README
+++ b/src/distutils2/README
@@ -1,4 +1,4 @@
-This directory contains the Distutils package.
+This directory contains the Distutils2 package.
There's a full documentation available at:
@@ -8,6 +8,6 @@ The Distutils-SIG web page is also a good starting point:
http://www.python.org/sigs/distutils-sig/
-WARNING: Distutils2 must remain compatible with 2.4
+WARNING: Distutils2 must remain compatible with Python 2.4
$Id: README 70017 2009-02-27 12:53:34Z tarek.ziade $
diff --git a/src/distutils2/__init__.py b/src/distutils2/__init__.py
index 3be2421..0514da7 100644
--- a/src/distutils2/__init__.py
+++ b/src/distutils2/__init__.py
@@ -1,16 +1,26 @@
"""distutils
-The main package for the Python Module Distribution Utilities. Normally
-used from a setup script as
+The main package for the Python Distribution Utilities 2. Setup
+scripts should import the setup function from distutils2.core:
- from distutils.core import setup
+ from distutils2.core import setup
- setup (...)
+ setup(name=..., version=..., ...)
+
+Third-party tools can use parts of Distutils2 as building blocks
+without causing the other modules to be imported:
+
+ import distutils2.version
+ import distutils2.pypi.simple
+ import distutils2.tests.pypi_server
"""
-__all__ = ['__version__', 'setup']
+__all__ = ['__version__']
__revision__ = "$Id: __init__.py 78020 2010-02-06 16:37:32Z benjamin.peterson $"
__version__ = "1.0a2"
-from distutils2.core import setup
+# 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/distutils2/_backport/hashlib.py b/src/distutils2/_backport/hashlib.py
new file mode 100644
index 0000000..48f2cfd
--- /dev/null
+++ b/src/distutils2/_backport/hashlib.py
@@ -0,0 +1,143 @@
+# $Id$
+#
+# Copyright (C) 2005 Gregory P. Smith (greg@krypto.org)
+# Licensed to PSF under a Contributor Agreement.
+#
+
+__doc__ = """hashlib module - A common interface to many hash functions.
+
+new(name, string='') - returns a new hash object implementing the
+ given hash function; initializing the hash
+ using the given string data.
+
+Named constructor functions are also available, these are much faster
+than using new():
+
+md5(), sha1(), sha224(), sha256(), sha384(), and sha512()
+
+More algorithms may be available on your platform but the above are
+guaranteed to exist.
+
+NOTE: If you want the adler32 or crc32 hash functions they are available in
+the zlib module.
+
+Choose your hash function wisely. Some have known collision weaknesses.
+sha384 and sha512 will be slow on 32 bit platforms.
+
+Hash objects have these methods:
+ - update(arg): Update the hash object with the string arg. Repeated calls
+ are equivalent to a single call with the concatenation of all
+ the arguments.
+ - digest(): Return the digest of the strings passed to the update() method
+ so far. This may contain non-ASCII characters, including
+ NUL bytes.
+ - hexdigest(): Like digest() except the digest is returned as a string of
+ double length, containing only hexadecimal digits.
+ - copy(): Return a copy (clone) of the hash object. This can be used to
+ efficiently compute the digests of strings that share a common
+ initial substring.
+
+For example, to obtain the digest of the string 'Nobody inspects the
+spammish repetition':
+
+ >>> import hashlib
+ >>> m = hashlib.md5()
+ >>> m.update("Nobody inspects")
+ >>> m.update(" the spammish repetition")
+ >>> m.digest()
+ '\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9'
+
+More condensed:
+
+ >>> hashlib.sha224("Nobody inspects the spammish repetition").hexdigest()
+ 'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2'
+
+"""
+
+# This tuple and __get_builtin_constructor() must be modified if a new
+# always available algorithm is added.
+__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
+
+algorithms = __always_supported
+
+__all__ = __always_supported + ('new', 'algorithms')
+
+
+def __get_builtin_constructor(name):
+ if name in ('SHA1', 'sha1'):
+ import _sha
+ return _sha.new
+ elif name in ('MD5', 'md5'):
+ import _md5
+ return _md5.new
+ elif name in ('SHA256', 'sha256', 'SHA224', 'sha224'):
+ 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
+ bs = name[3:]
+ if bs == '512':
+ return _sha512.sha512
+ elif bs == '384':
+ return _sha512.sha384
+
+ raise ValueError('unsupported hash type %s' % name)
+
+
+def __get_openssl_constructor(name):
+ try:
+ f = getattr(_hashlib, 'openssl_' + name)
+ # Allow the C module to raise ValueError. The function will be
+ # defined but the hash not actually available thanks to OpenSSL.
+ f()
+ # Use the C function directly (very fast)
+ return f
+ except (AttributeError, ValueError):
+ return __get_builtin_constructor(name)
+
+
+def __py_new(name, string=''):
+ """new(name, string='') - Return a new hashing object using the named algorithm;
+ optionally initialized with a string.
+ """
+ return __get_builtin_constructor(name)(string)
+
+
+def __hash_new(name, string=''):
+ """new(name, string='') - Return a new hashing object using the named algorithm;
+ optionally initialized with a string.
+ """
+ try:
+ return _hashlib.new(name, string)
+ except ValueError:
+ # If the _hashlib module (OpenSSL) doesn't support the named
+ # hash, try using our builtin implementations.
+ # This allows for SHA224/256 and SHA384/512 support even though
+ # the OpenSSL library prior to 0.9.8 doesn't provide them.
+ return __get_builtin_constructor(name)(string)
+
+
+try:
+ import _hashlib
+ new = __hash_new
+ __get_hash = __get_openssl_constructor
+except ImportError:
+ new = __py_new
+ __get_hash = __get_builtin_constructor
+
+for __func_name in __always_supported:
+ # try them all, some may not work due to the OpenSSL
+ # version not supporting that algorithm.
+ try:
+ globals()[__func_name] = __get_hash(__func_name)
+ except ValueError:
+ import logging
+ logging.exception('code for hash %s was not found.', __func_name)
+
+# Cleanup locals()
+del __always_supported, __func_name, __get_hash
+del __py_new, __hash_new, __get_openssl_constructor
diff --git a/src/distutils2/_backport/pkgutil.py b/src/distutils2/_backport/pkgutil.py
index 8fbe24d..b033082 100644
--- a/src/distutils2/_backport/pkgutil.py
+++ b/src/distutils2/_backport/pkgutil.py
@@ -12,10 +12,17 @@ from types import ModuleType
from distutils2.errors import DistutilsError
from distutils2.metadata import DistributionMetadata
from distutils2.version import suggest_normalized_version, VersionPredicate
+import zipimport
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+import re
+import warnings
__all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
- 'walk_packages', 'iter_modules',
+ 'walk_packages', 'iter_modules', 'get_data',
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
'get_distributions', 'get_distribution', 'get_file_users',
@@ -24,7 +31,7 @@ __all__ = [
def read_code(stream):
- # This helper is needed in order for the PEP 302 emulation to
+ # This helper is needed in order for the :pep:`302` emulation to
# correctly handle compiled files
import marshal
@@ -79,32 +86,34 @@ def simplegeneric(func):
def walk_packages(path=None, prefix='', onerror=None):
- """Yields (module_loader, name, ispkg) for all modules recursively
- on path, or, if path is None, all accessible modules.
+ """Yields ``(module_loader, name, ispkg)`` for all modules recursively
+ on *path*, or, if *path* is ``None``, all accessible modules.
- 'path' should be either None or a list of paths to look for
- modules in.
+ :parameter path: should be either ``None`` or a list of paths to look for
+ modules in.
+ :parameter prefix: is a string to output on the front of every module name
+ on output.
- 'prefix' is a string to output on the front of every module name
- on output.
-
- Note that this function must import all *packages* (NOT all
- modules!) on the given path, in order to access the __path__
+ Note that this function must import all packages (NOT all
+ modules!) on the given path, in order to access the ``__path__``
attribute to find submodules.
- 'onerror' is a function which gets called with one argument (the
+ *onerror* is a function which gets called with one argument (the
name of the package which was being imported) if any exception
occurs while trying to import a package. If no onerror function is
- supplied, ImportErrors are caught and ignored, while all other
+ supplied, ``ImportErrors`` are caught and ignored, while all other
exceptions are propagated, terminating the search.
Examples:
- # list all modules python can access
- walk_packages()
+ * list all modules python can access::
+
+ walk_packages()
+
+ * list all submodules of ctypes::
+
+ walk_packages(ctypes.__path__, ctypes.__name__+'.')
- # list all submodules of ctypes
- walk_packages(ctypes.__path__, ctypes.__name__+'.')
"""
def seen(p, m={}):
@@ -137,14 +146,14 @@ def walk_packages(path=None, prefix='', onerror=None):
def iter_modules(path=None, prefix=''):
- """Yields (module_loader, name, ispkg) for all submodules on path,
- or, if path is None, all top-level modules on sys.path.
+ """Yields ``(module_loader, name, ispkg)`` for all submodules on path,
+ or, if *path* is ``None``, all top-level modules on ``sys.path``.
- 'path' should be either None or a list of paths to look for
- modules in.
+ :parameter path: should be either None or a list of paths to look for
+ modules in.
+ :parameter prefix: is a string to output on the front of every module name
+ on output.
- 'prefix' is a string to output on the front of every module name
- on output.
"""
if path is None:
@@ -162,6 +171,7 @@ def iter_modules(path=None, prefix=''):
#@simplegeneric
def iter_importer_modules(importer, prefix=''):
+ ""
if not hasattr(importer, 'iter_modules'):
return []
return importer.iter_modules(prefix)
@@ -169,15 +179,16 @@ def iter_importer_modules(importer, prefix=''):
iter_importer_modules = simplegeneric(iter_importer_modules)
-class ImpImporter:
- """PEP 302 Importer that wraps Python's "classic" import algorithm
+class ImpImporter(object):
+ """:pep:`302` Importer that wraps Python's "classic" import algorithm
- ImpImporter(dirname) produces a PEP 302 importer that searches that
- directory. ImpImporter(None) produces a PEP 302 importer that searches
- the current sys.path, plus any modules that are frozen or built-in.
+ ``ImpImporter(dirname)`` produces a :pep:`302` importer that searches that
+ directory. ``ImpImporter(None)`` produces a :pep:`302` importer that
+ searches the current ``sys.path``, plus any modules that are frozen
+ or built-in.
- Note that 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):
@@ -231,9 +242,9 @@ class ImpImporter:
yield prefix + modname, ispkg
-class ImpLoader:
- """PEP 302 Loader that wraps Python's "classic" import algorithm
- """
+class ImpLoader(object):
+ """:pep:`302` Loader that wraps Python's "classic" import algorithm """
+
code = source = None
def __init__(self, fullname, file, filename, etc):
@@ -365,17 +376,17 @@ except ImportError:
def get_importer(path_item):
- """Retrieve a PEP 302 importer for the given path item
+ """Retrieve a :pep:`302` importer for the given path item
- The returned importer is cached in sys.path_importer_cache
+ The returned importer is cached in ``sys.path_importer_cache``
if it was newly created by a path hook.
If there is no importer, a wrapper around the basic import
machinery is returned. This wrapper is never inserted into
- the importer cache (None is inserted instead).
+ the importer cache (``None`` is inserted instead).
The cache (or part of it) can be cleared manually if a
- rescan of sys.path_hooks is necessary.
+ rescan of ``sys.path_hooks`` is necessary.
"""
try:
importer = sys.path_importer_cache[path_item]
@@ -399,7 +410,7 @@ def get_importer(path_item):
def iter_importers(fullname=""):
- """Yield PEP 302 importers for the given module name
+ """Yield :pep:`302` importers for the given module name
If fullname contains a '.', the importers will be for the package
containing fullname, otherwise they will be importers for sys.meta_path,
@@ -407,20 +418,20 @@ def iter_importers(fullname=""):
the named module is in a package, that package is imported as a side
effect of invoking this function.
- Non PEP 302 mechanisms (e.g. the Windows registry) used by the
+ Non :pep:`302` mechanisms (e.g. the Windows registry) used by the
standard import machinery to find files in alternative locations
- are partially supported, but are searched AFTER sys.path. Normally,
- these locations are searched BEFORE sys.path, preventing sys.path
+ are partially supported, but are searched AFTER ``sys.path``. Normally,
+ these locations are searched BEFORE sys.path, preventing ``sys.path``
entries from shadowing them.
For this to cause a visible difference in behaviour, there must
be a module or package name that is accessible via both sys.path
- and one of the non PEP 302 file system mechanisms. In this case,
+ and one of the non :pep:`302` file system mechanisms. In this case,
the emulation will find the former version, while the builtin
import mechanism will find the latter.
Items of the following types can be affected by this discrepancy:
- imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY
+ ``imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY``
"""
if fullname.startswith('.'):
raise ImportError("Relative module names not supported")
@@ -441,15 +452,15 @@ def iter_importers(fullname=""):
def get_loader(module_or_name):
- """Get a PEP 302 "loader" object for module_or_name
+ """Get a :pep:`302` "loader" object for module_or_name
If the module or package is accessible via the normal import
mechanism, a wrapper around the relevant part of that machinery
is returned. Returns None if the module cannot be found or imported.
If the named module is not already imported, its containing package
- (if any) is imported, in order to establish the package __path__.
+ (if any) is imported, in order to establish the package ``__path__``.
- This function uses iter_importers(), and is thus subject to the same
+ This function uses :func:`iter_importers`, and is thus subject to the same
limitations regarding platform-specific special import locations such
as the Windows registry.
"""
@@ -467,12 +478,13 @@ def get_loader(module_or_name):
def find_loader(fullname):
- """Find a PEP 302 "loader" object for fullname
+ """Find a :pep:`302` "loader" object for fullname
- If fullname contains dots, path must be the containing package's __path__.
- Returns None if the module cannot be found or imported. This function uses
- iter_importers(), and is thus subject to the same limitations regarding
- platform-specific special import locations such as the Windows registry.
+ If fullname contains dots, path must be the containing package's
+ ``__path__``. Returns ``None`` if the module cannot be found or imported.
+ This function uses :func:`iter_importers`, and is thus subject to the same
+ limitations regarding platform-specific special import locations such as
+ the Windows registry.
"""
for importer in iter_importers(fullname):
loader = importer.find_module(fullname)
@@ -485,21 +497,22 @@ def find_loader(fullname):
def extend_path(path, name):
"""Extend a package's path.
- Intended use is to place the following code in a package's __init__.py:
+ Intended use is to place the following code in a package's
+ ``__init__.py``::
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
- This will add to the package's __path__ all subdirectories of
- directories on sys.path named after the package. This is useful
+ This will add to the package's ``__path__`` all subdirectories of
+ directories on ``sys.path`` named after the package. This is useful
if one wants to distribute different parts of a single logical
package as multiple directories.
- It also looks for *.pkg files beginning where * matches the name
- argument. This feature is similar to *.pth files (see site.py),
- except that it doesn't special-case lines starting with 'import'.
- A *.pkg file is trusted at face value: apart from checking for
- duplicates, all entries found in a *.pkg file are added to the
+ It also looks for ``*.pkg`` files beginning where ``*`` matches the name
+ argument. This feature is similar to ``*.pth`` files (see ``site.py``),
+ except that it doesn't special-case lines starting with ``import``.
+ A ``*.pkg`` file is trusted at face value: apart from checking for
+ duplicates, all entries found in a ``*.pkg`` file are added to the
path, regardless of whether they are exist the filesystem. (This
is a feature.)
@@ -512,7 +525,7 @@ def extend_path(path, name):
are not (unicode or 8-bit) strings referring to existing
directories are ignored. Unicode items of sys.path that cause
errors when used as filenames may cause this function to raise an
- exception (in line with os.path.isdir() behavior).
+ exception (in line with ``os.path.isdir()`` behavior).
"""
if not isinstance(path, list):
@@ -560,23 +573,23 @@ def extend_path(path, name):
def get_data(package, resource):
"""Get a resource from a package.
- This is a wrapper round the PEP 302 loader get_data API. The package
+ This is a wrapper round the :pep:`302` loader get_data API. The package
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 '/').
+ (``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 ``'/'``).
The function returns a binary string, which is the contents of the
specified resource.
For packages located in the filesystem, which have already been imported,
- this is the rough equivalent of
+ this is the rough equivalent of::
d = os.path.dirname(sys.modules[package].__file__)
data = open(os.path.join(d, resource), 'rb').read()
- If the package cannot be located or loaded, or it uses a PEP 302 loader
- which does not support get_data(), then None is returned.
+ If the package cannot be located or loaded, or it uses a :pep:`302` loader
+ which does not support :func:`get_data`, then ``None`` is returned.
"""
loader = get_loader(package)
@@ -603,7 +616,7 @@ DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
class Distribution(object):
"""Created with the *path* of the ``.dist-info`` directory provided to the
- constructor. It reads the metadata contained in METADATA when it is
+ constructor. It reads the metadata contained in ``METADATA`` when it is
instantiated."""
# Attribute documenting for Sphinx style documentation, see for more info:
@@ -612,9 +625,9 @@ class Distribution(object):
"""The name of the distribution."""
metadata = None
"""A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
- the distribution's METADATA file."""
+ the distribution's ``METADATA`` file."""
requested = False
- """A boolean that indicates whether the REQUESTED metadata file is present
+ """A boolean that indicates whether the ``REQUESTED`` metadata file is present
(in other words, whether the package was installed by user request)."""
def __init__(self, path):
@@ -635,15 +648,17 @@ class Distribution(object):
def get_installed_files(self, local=False):
"""
- Iterates over the RECORD entries and returns a tuple (path, md5, size)
- for each line. If *local* is ``True``, the returned path is transformed
- into a local absolute path. Otherwise the raw value from RECORD is
- returned.
+ Iterates over the ``RECORD`` entries and returns a tuple
+ ``(path, md5, size)`` for each line. If *local* is ``True``,
+ the returned path is transformed into a local absolute path.
+ Otherwise the raw value from RECORD is returned.
A local absolute path is an absolute path in which occurrences of
``'/'`` have been replaced by the system separator given by ``os.sep``.
+
:parameter local: flag to say if the path should be returned a local
absolute path
+
:type local: boolean
:returns: iterator of (path, md5, size)
"""
@@ -651,7 +666,7 @@ class Distribution(object):
def uses(self, path):
"""
- Returns ``True`` if path is listed in RECORD. *path* can be a local
+ Returns ``True`` if path is listed in ``RECORD``. *path* can be a local
absolute path or a relative ``'/'``-separated path.
:rtype: boolean
@@ -673,8 +688,8 @@ class Distribution(object):
directory path, a :class:`DistutilsError` is raised
:type path: string
:parameter binary: If *binary* is ``True``, opens the file in read-only
- binary mode (rb), otherwise opens it in read-only
- mode (r).
+ binary mode (``rb``), otherwise opens it in read-only
+ mode (``r``).
:rtype: file object
"""
open_flags = 'r'
@@ -701,37 +716,129 @@ class Distribution(object):
def get_distinfo_files(self, local=False):
"""
- Iterates over the RECORD entries and returns paths for each line if the
- path is pointing to a file located in the ``.dist-info`` directory or
- one of its subdirectories.
+ Iterates over the ``RECORD`` entries and returns paths for each line if
+ the path is pointing to a file located in the ``.dist-info`` directory
+ or one of its subdirectories.
:parameter local: If *local* is ``True``, each returned path is
transformed into a local absolute path. Otherwise the
- raw value from RECORD is returned.
+ raw value from ``RECORD`` is returned.
:type local: boolean
:returns: iterator of paths
"""
for path, md5, size in self._get_records(local):
yield path
+ def __eq__(self, other):
+ return isinstance(other, Distribution) and self.path == other.path
+
+ # See http://docs.python.org/reference/datamodel#object.__hash__
+ __hash__ = object.__hash__
+
class EggInfoDistribution(object):
"""Created with the *path* of the ``.egg-info`` directory or file provided
to the constructor. It reads the metadata contained in the file itself, or
if the given path happens to be a directory, the metadata is read from the
- file PKG-INFO under that directory."""
+ file ``PKG-INFO`` under that directory."""
name = ''
"""The name of the distribution."""
metadata = None
"""A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
- the distribution's METADATA file."""
+ the distribution's ``METADATA`` file."""
+ _REQUIREMENT = re.compile( \
+ r'(?P<name>[-A-Za-z0-9_.]+)\s*' \
+ r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' \
+ r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' \
+ r'(?P<extras>\[.*\])?')
def __init__(self, path):
- if os.path.isdir(path):
- path = os.path.join(path, 'PKG-INFO')
- self.metadata = DistributionMetadata(path=path)
- self.name = self.metadata['name']
+ self.path = path
+
+ # reused from Distribute's pkg_resources
+ def yield_lines(strs):
+ """Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
+ if isinstance(strs, basestring):
+ for s in strs.splitlines():
+ s = s.strip()
+ if s and not s.startswith('#'): # skip blank lines/comments
+ yield s
+ else:
+ for ss in strs:
+ for s in yield_lines(ss):
+ yield s
+
+ requires = None
+ if path.endswith('.egg'):
+ if os.path.isdir(path):
+ meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ self.metadata = DistributionMetadata(path=meta_path)
+ try:
+ req_path = os.path.join(path, 'EGG-INFO', 'requires.txt')
+ requires = open(req_path, 'r').read()
+ except IOError:
+ requires = None
+ else:
+ zipf = zipimport.zipimporter(path)
+ fileobj = StringIO.StringIO(zipf.get_data('EGG-INFO/PKG-INFO'))
+ self.metadata = DistributionMetadata(fileobj=fileobj)
+ try:
+ requires = zipf.get_data('EGG-INFO/requires.txt')
+ except IOError:
+ requires = None
+ self.name = self.metadata['name']
+ elif path.endswith('.egg-info'):
+ if os.path.isdir(path):
+ path = os.path.join(path, 'PKG-INFO')
+ try:
+ req_f = open(os.path.join(path, 'requires.txt'), 'r')
+ requires = req_f.read()
+ except IOError:
+ requires = None
+ self.metadata = DistributionMetadata(path=path)
+ self.name = self.metadata['name']
+ else:
+ raise ValueError('The path must end with .egg-info or .egg')
+
+ provides = "%s (%s)" % (self.metadata['name'],
+ self.metadata['version'])
+ if self.metadata['Metadata-Version'] == '1.2':
+ self.metadata['Provides-Dist'] += (provides,)
+ else:
+ self.metadata['Provides'] += (provides,)
+ reqs = []
+ if requires is not None:
+ for line in yield_lines(requires):
+ if line[0] == '[':
+ warnings.warn('distutils2 does not support extensions in requires.txt')
+ break
+ else:
+ match = self._REQUIREMENT.match(line.strip())
+ if not match:
+ raise ValueError('Distribution %s has ill formed '
+ 'requires.txt file (%s)' %
+ (self.name, line))
+ else:
+ if match.group('extras'):
+ s = 'Distribution %s uses extra requirements which'\
+ ' are not supported in distutils' % (self.name)
+ warnings.warn(s)
+ name = match.group('name')
+ version = None
+ if match.group('first'):
+ version = match.group('first')
+ if match.group('rest'):
+ version += match.group('rest')
+ version = version.replace(' ', '') # trim spaces
+ if version is None:
+ reqs.append(name)
+ else:
+ reqs.append('%s (%s)' % (name, version))
+ if self.metadata['Metadata-Version'] == '1.2':
+ self.metadata['Requires-Dist'] += reqs
+ else:
+ self.metadata['Requires'] += reqs
def get_installed_files(self, local=False):
return []
@@ -739,6 +846,13 @@ class EggInfoDistribution(object):
def uses(self, path):
return False
+ def __eq__(self, other):
+ return isinstance(other, EggInfoDistribution) and \
+ self.path == other.path
+
+ # See http://docs.python.org/reference/datamodel#object.__hash__
+ __hash__ = object.__hash__
+
def _normalize_dist_name(name):
"""Returns a normalized name from the given *name*.
@@ -792,7 +906,8 @@ def get_distributions(use_egg_info=False):
if dir.endswith('.dist-info'):
dist = Distribution(os.path.join(realpath, dir))
yield dist
- elif use_egg_info and dir.endswith('.egg-info'):
+ elif use_egg_info and (dir.endswith('.egg-info') or
+ dir.endswith('.egg')):
dist = EggInfoDistribution(os.path.join(realpath, dir))
yield dist
@@ -801,18 +916,18 @@ def get_distribution(name, use_egg_info=False):
"""
Scans all elements in ``sys.path`` and looks for all directories ending with
``.dist-info``. Returns a :class:`Distribution` corresponding to the
- ``.dist-info`` directory that contains the METADATA that matches *name* for
- the *name* metadata field.
+ ``.dist-info`` directory that contains the ``METADATA`` that matches *name*
+ for the *name* metadata field.
If no distribution exists with the given *name* and the parameter
*use_egg_info* is set to ``True``, then all files and directories ending
with ``.egg-info`` are scanned. A :class:`EggInfoDistribution` instance is
returned if one is found that has metadata that matches *name* for the
*name* metadata field.
- This function only returns the first result founded, as no more than one
+ This function only returns the first result found, as no more than one
value is expected. If the directory is not found, ``None`` is returned.
- :rtype: :class:`Distribution` or :class:`EggInfoDistribution: or None"""
+ :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None"""
found = None
for dist in get_distributions():
if dist.name == name:
@@ -867,7 +982,7 @@ def provides_distribution(name, version=None, use_egg_info=False):
then all files and directories ending with ``.egg-info`` are considered
as well and returns an :class:`EggInfoDistribution` instance.
- This function only returns the first result founded, since no more than
+ This function only returns the first result found, since no more than
one values are expected. If the directory is not found, returns ``None``.
:parameter version: a version specifier that indicates the version
@@ -887,7 +1002,7 @@ def provides_distribution(name, version=None, use_egg_info=False):
provided = dist.metadata['Provides-Dist'] + dist.metadata['Provides']
for p in provided:
- p_components = p.split(' ', 1)
+ p_components = p.rsplit(' ', 1)
if len(p_components) == 1 or predicate is None:
if name == p_components[0]:
yield dist
@@ -896,7 +1011,8 @@ def provides_distribution(name, version=None, use_egg_info=False):
p_name, p_ver = p_components
if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')':
raise DistutilsError(('Distribution %s has invalid ' +
- 'provides field') % (dist.name,))
+ 'provides field: %s') \
+ % (dist.name, p))
p_ver = p_ver[1:-1] # trim off the parenthesis
if p_name == name and predicate.match(p_ver):
yield dist
diff --git a/src/distutils2/_backport/sysconfig.cfg b/src/distutils2/_backport/sysconfig.cfg
index 2a2a996..01328db 100644
--- a/src/distutils2/_backport/sysconfig.cfg
+++ b/src/distutils2/_backport/sysconfig.cfg
@@ -1,6 +1,6 @@
[globals]
# These are the useful categories that are sometimes referenced at runtime,
-# using pkgutils.open():
+# using pkgutil.open():
config = {confdir}/{distribution.name} # Configuration files
appdata = {datadir}/{distribution.name} # Non-writable data that is independent of architecture (images, many xml/text files)
appdata.arch = {libdir}/{distribution.name} # Non-writable data that is architecture-dependent (some binary data formats)
diff --git a/src/distutils2/_backport/sysconfig.py b/src/distutils2/_backport/sysconfig.py
index 5d082b7..a240fc4 100644
--- a/src/distutils2/_backport/sysconfig.py
+++ b/src/distutils2/_backport/sysconfig.py
@@ -5,7 +5,7 @@ import sys
import os
import re
from os.path import pardir, abspath
-from ConfigParser import ConfigParser
+from ConfigParser import RawConfigParser
_PREFIX = os.path.normpath(sys.prefix)
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
@@ -14,7 +14,7 @@ _EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
# XXX _CONFIG_DIR will be set by the Makefile later
_CONFIG_DIR = os.path.normpath(os.path.dirname(__file__))
_CONFIG_FILE = os.path.join(_CONFIG_DIR, 'sysconfig.cfg')
-_SCHEMES = ConfigParser()
+_SCHEMES = RawConfigParser()
_SCHEMES.read(_CONFIG_FILE)
_VAR_REPL = re.compile(r'\{([^{]*?)\}')
@@ -580,10 +580,11 @@ def get_platform():
# behaviour.
pass
else:
- m = re.search(
- r'<key>ProductUserVisibleVersion</key>\s*' +
- r'<string>(.*?)</string>', f.read())
- f.close()
+ try:
+ m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
+ r'<string>(.*?)</string>', f.read())
+ finally:
+ f.close()
if m is not None:
macrelease = '.'.join(m.group(1).split('.')[:2])
# else: fall back to the default behaviour
diff --git a/src/distutils2/_backport/tarfile.py b/src/distutils2/_backport/tarfile.py
index 5399846..0d2c49b 100644
--- a/src/distutils2/_backport/tarfile.py
+++ b/src/distutils2/_backport/tarfile.py
@@ -370,7 +370,7 @@ class SubsequentHeaderError(HeaderError):
#---------------------------
# internal stream interface
#---------------------------
-class _LowLevelFile:
+class _LowLevelFile(object):
"""Low-level file object. Supports reading and writing.
It is used instead of a regular file object for streaming
access.
@@ -394,7 +394,7 @@ class _LowLevelFile:
def write(self, s):
os.write(self.fd, s)
-class _Stream:
+class _Stream(object):
"""Class that serves as an adapter between TarFile and
a stream-like object. The stream-like object only
needs to have a read() or write() method and is accessed
@@ -2003,8 +2003,10 @@ class TarFile(object):
# Append the tar header and data to the archive.
if tarinfo.isreg():
f = bltn_open(name, "rb")
- self.addfile(tarinfo, f)
- f.close()
+ try:
+ self.addfile(tarinfo, f)
+ finally:
+ f.close()
elif tarinfo.isdir():
self.addfile(tarinfo)
@@ -2214,9 +2216,11 @@ class TarFile(object):
"""
source = self.extractfile(tarinfo)
target = bltn_open(targetpath, "wb")
- copyfileobj(source, target)
- source.close()
- target.close()
+ try:
+ copyfileobj(source, target)
+ finally:
+ source.close()
+ target.close()
def makeunknown(self, tarinfo, targetpath):
"""Make a file from a TarInfo object with an unknown type
@@ -2423,7 +2427,7 @@ class TarFile(object):
print >> sys.stderr, msg
# class TarFile
-class TarIter:
+class TarIter(object):
"""Iterator Class.
for tarinfo in TarFile(...):
@@ -2460,7 +2464,7 @@ class TarIter:
return tarinfo
# Helper classes for sparse file support
-class _section:
+class _section(object):
"""Base class for _data and _hole.
"""
def __init__(self, offset, size):
@@ -2507,7 +2511,7 @@ class _ringbuffer(list):
#---------------------------------------------
TAR_PLAIN = 0 # zipfile.ZIP_STORED
TAR_GZIPPED = 8 # zipfile.ZIP_DEFLATED
-class TarFileCompat:
+class TarFileCompat(object):
"""TarFile class compatible with standard module zipfile's
ZipFile class.
"""
@@ -2564,8 +2568,10 @@ def is_tarfile(name):
are able to handle, else return False.
"""
try:
- t = open(name)
- t.close()
+ try:
+ t = open(name)
+ finally:
+ t.close()
return True
except TarError:
return False
diff --git a/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO b/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
index 8914d76..a176dfd 100644
--- a/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
+++ b/src/distutils2/_backport/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
@@ -2,4 +2,5 @@ Metadata-Version: 1.2
Name: bacon
Version: 0.1
Provides-Dist: truffles (2.0)
+Provides-Dist: bacon (0.1)
Obsoletes-Dist: truffles (>=0.9,<=1.5)
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO
new file mode 100644
index 0000000..a7e118a
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-Version: 1.0
+Name: banana
+Version: 0.4
+Summary: A yellow fruit
+Home-page: http://en.wikipedia.org/wiki/Banana
+Author: Josip Djolonga
+Author-email: foo@nbar.com
+License: BSD
+Description: A fruit
+Keywords: foo bar
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Scientific/Engineering :: GIS
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt
new file mode 100644
index 0000000..5d3e5f6
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt
@@ -0,0 +1,3 @@
+
+ # -*- Entry points: -*-
+ \ No newline at end of file
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt
new file mode 100644
index 0000000..4354305
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt
@@ -0,0 +1,6 @@
+# this should be ignored
+
+strawberry >=0.5
+
+[section ignored]
+foo ==0.5
diff --git a/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt
diff --git a/src/distutils2/_backport/tests/fake_dists/strawberry-0.6.egg b/src/distutils2/_backport/tests/fake_dists/strawberry-0.6.egg
new file mode 100644
index 0000000..6d160e8
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/strawberry-0.6.egg
Binary files differ
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
index e8b764e..ca46d0a 100644
--- a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
+++ b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
@@ -2,4 +2,6 @@ Metadata-Version: 1.2
Name: towel-stuff
Version: 0.1
Provides-Dist: truffles (1.1.2)
+Provides-Dist: towel-stuff (0.1)
Obsoletes-Dist: truffles (!=0.8,<1.0)
+Requires-Dist: bacon (<=0.2)
diff --git a/src/distutils2/_backport/tests/test_pkgutil.py b/src/distutils2/_backport/tests/test_pkgutil.py
index 817b156..fafb9a8 100644
--- a/src/distutils2/_backport/tests/test_pkgutil.py
+++ b/src/distutils2/_backport/tests/test_pkgutil.py
@@ -3,40 +3,176 @@
import sys
import os
import csv
+import imp
+import tempfile
+import shutil
+import zipfile
try:
from hashlib import md5
except ImportError:
from md5 import md5
from test.test_support import run_unittest, TESTFN
-
-import distutils2._backport.pkgutil
from distutils2.tests.support import unittest
+from distutils2._backport import pkgutil
+
+try:
+ from os.path import relpath
+except ImportError:
+ try:
+ from unittest.compatibility import relpath
+ except ImportError:
+ from unittest2.compatibility import relpath
+
# TODO Add a test for getting a distribution that is provided by another
-# distribution.
+# 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):
+ super(TestPkgUtilData, self).setUp()
+ self.dirname = tempfile.mkdtemp()
+ sys.path.insert(0, self.dirname)
+
+ def tearDown(self):
+ super(TestPkgUtilData, self).tearDown()
+ del sys.path[0]
+ shutil.rmtree(self.dirname)
+
+ def test_getdata_filesys(self):
+ pkg = 'test_getdata_filesys'
+
+ # Include a LF and a CRLF, to test that binary data is read back
+ RESOURCE_DATA = 'Hello, world!\nSecond line\r\nThird line'
+
+ # Make a package with some resources
+ package_dir = os.path.join(self.dirname, pkg)
+ os.mkdir(package_dir)
+ # Empty init.py
+ f = open(os.path.join(package_dir, '__init__.py'), "wb")
+ try:
+ pass
+ finally:
+ f.close()
+ # Resource files, res.txt, sub/res.txt
+ f = open(os.path.join(package_dir, 'res.txt'), "wb")
+ try:
+ f.write(RESOURCE_DATA)
+ finally:
+ f.close()
+ os.mkdir(os.path.join(package_dir, 'sub'))
+ f = open(os.path.join(package_dir, 'sub', 'res.txt'), "wb")
+ try:
+ f.write(RESOURCE_DATA)
+ finally:
+ f.close()
+
+ # Check we can read the resources
+ res1 = pkgutil.get_data(pkg, 'res.txt')
+ self.assertEqual(res1, RESOURCE_DATA)
+ res2 = pkgutil.get_data(pkg, 'sub/res.txt')
+ self.assertEqual(res2, RESOURCE_DATA)
+
+ del sys.modules[pkg]
+
+ def test_getdata_zipfile(self):
+ zip = 'test_getdata_zipfile.zip'
+ pkg = 'test_getdata_zipfile'
+
+ # Include a LF and a CRLF, to test that binary data is read back
+ RESOURCE_DATA = 'Hello, world!\nSecond line\r\nThird line'
+
+ # Make a package with some resources
+ zip_file = os.path.join(self.dirname, zip)
+ z = zipfile.ZipFile(zip_file, 'w')
+ try:
+ # Empty init.py
+ z.writestr(pkg + '/__init__.py', "")
+ # Resource files, res.txt, sub/res.txt
+ z.writestr(pkg + '/res.txt', RESOURCE_DATA)
+ z.writestr(pkg + '/sub/res.txt', RESOURCE_DATA)
+ finally:
+ z.close()
+
+ # Check we can read the resources
+ sys.path.insert(0, zip_file)
+ res1 = pkgutil.get_data(pkg, 'res.txt')
+ self.assertEqual(res1, RESOURCE_DATA)
+ res2 = pkgutil.get_data(pkg, 'sub/res.txt')
+ self.assertEqual(res2, RESOURCE_DATA)
+ del sys.path[0]
+
+ 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))
+ mod.__file__ = "<%s>" % self.__class__.__name__
+ mod.__loader__ = self
+ # Make it a package
+ mod.__path__ = []
+ # Count how many times the module is reloaded
+ 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()
+
+ def setUp(self):
+ super(TestPkgUtilPEP302, self).setUp()
+ sys.meta_path.insert(0, self.MyTestImporter())
+
+ def tearDown(self):
+ del sys.meta_path[0]
+ super(TestPkgUtilPEP302, self).tearDown()
+
+ def test_getdata_pep302(self):
+ # Use a dummy importer/loader
+ self.assertEqual(pkgutil.get_data('foo', 'dummy'), "Hello, world!")
+ del sys.modules['foo']
+
+ def test_alreadyloaded(self):
+ # Ensure that get_data works without reloading - the "loads" module
+ # variable in the example loader should count how many times a reload
+ # occurs.
+ import foo
+ self.assertEqual(foo.loads, 1)
+ self.assertEqual(pkgutil.get_data('foo', 'dummy'), "Hello, world!")
+ self.assertEqual(foo.loads, 1)
+ del sys.modules['foo']
+
+
class TestPkgUtilDistribution(unittest.TestCase):
- """Tests the pkgutil.Distribution class"""
+ # Tests the pkgutil.Distribution class
def setUp(self):
super(TestPkgUtilDistribution, self).setUp()
-
self.fake_dists_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'fake_dists'))
- self.distinfo_dirs = [ os.path.join(self.fake_dists_path, dir)
+
+ self.distinfo_dirs = [os.path.join(self.fake_dists_path, dir)
for dir in os.listdir(self.fake_dists_path)
- if dir.endswith('.dist-info')
- ]
+ if dir.endswith('.dist-info')]
def get_hexdigest(file):
md5_hash = md5()
md5_hash.update(open(file).read())
return md5_hash.hexdigest()
+
def record_pieces(file):
- path = os.path.relpath(file, sys.prefix)
+ path = relpath(file, sys.prefix)
digest = get_hexdigest(file)
size = os.path.getsize(file)
return [path, digest, size]
@@ -51,16 +187,18 @@ class TestPkgUtilDistribution(unittest.TestCase):
for path, dirs, files in os.walk(dist_location):
for f in files:
- record_writer.writerow(record_pieces(os.path.join(path, f)))
+ record_writer.writerow(record_pieces(
+ os.path.join(path, f)))
for file in ['INSTALLER', 'METADATA', 'REQUESTED']:
record_writer.writerow(record_pieces(
os.path.join(distinfo_dir, file)))
- record_writer.writerow([os.path.relpath(record_file, sys.prefix)])
+ record_writer.writerow([relpath(record_file, sys.prefix)])
del record_writer # causes the RECORD file to close
record_reader = csv.reader(open(record_file, 'rb'))
record_data = []
for row in record_reader:
- path, md5_, size = row[:] + [ None for i in xrange(len(row), 3) ]
+ path, md5_, size = row[:] + \
+ [None for i in xrange(len(row), 3)]
record_data.append([path, (md5_, size,)])
self.records[distinfo_dir] = dict(record_data)
@@ -72,8 +210,8 @@ class TestPkgUtilDistribution(unittest.TestCase):
super(TestPkgUtilDistribution, self).tearDown()
def test_instantiation(self):
- """Test the Distribution class's instantiation provides us with usable
- attributes."""
+ # Test the Distribution class's instantiation provides us with usable
+ # attributes.
# Import the Distribution class
from distutils2._backport.pkgutil import distinfo_dirname, Distribution
@@ -91,7 +229,7 @@ class TestPkgUtilDistribution(unittest.TestCase):
self.assertTrue(isinstance(dist.requested, type(bool())))
def test_installed_files(self):
- """Test the iteration of installed files."""
+ # Test the iteration of installed files.
# Test the distribution's installed files
from distutils2._backport.pkgutil import Distribution
for distinfo_dir in self.distinfo_dirs:
@@ -103,16 +241,17 @@ class TestPkgUtilDistribution(unittest.TestCase):
self.assertEqual(size, record_data[path][1])
def test_uses(self):
- """Test to determine if a distribution uses a specified file."""
+ # Test to determine if a distribution uses a specified file.
# Criteria to test against
distinfo_name = 'grammar-1.0a4'
distinfo_dir = os.path.join(self.fake_dists_path,
distinfo_name + '.dist-info')
- true_path = [self.fake_dists_path, distinfo_name, 'grammar', 'utils.py']
- true_path = os.path.relpath(os.path.join(*true_path), sys.prefix)
+ true_path = [self.fake_dists_path, distinfo_name, \
+ 'grammar', 'utils.py']
+ true_path = relpath(os.path.join(*true_path), sys.prefix)
false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff',
'__init__.py']
- false_path = os.path.relpath(os.path.join(*false_path), sys.prefix)
+ false_path = relpath(os.path.join(*false_path), sys.prefix)
# Test if the distribution uses the file in question
from distutils2._backport.pkgutil import Distribution
@@ -121,7 +260,7 @@ class TestPkgUtilDistribution(unittest.TestCase):
self.assertFalse(dist.uses(false_path))
def test_get_distinfo_file(self):
- """Test the retrieval of dist-info file objects."""
+ # Test the retrieval of dist-info file objects.
from distutils2._backport.pkgutil import Distribution
distinfo_name = 'choxie-2.0.0.9'
other_distinfo_name = 'grammar-1.0a4'
@@ -150,10 +289,11 @@ class TestPkgUtilDistribution(unittest.TestCase):
self.assertRaises(DistutilsError, dist.get_distinfo_file,
other_distinfo_file)
# Test for a file that does not exist and should not exist
- self.assertRaises(DistutilsError, dist.get_distinfo_file, 'ENTRYPOINTS')
+ self.assertRaises(DistutilsError, dist.get_distinfo_file, \
+ 'ENTRYPOINTS')
def test_get_distinfo_files(self):
- """Test for the iteration of RECORD path entries."""
+ # Test for the iteration of RECORD path entries.
from distutils2._backport.pkgutil import Distribution
distinfo_name = 'towel_stuff-0.1'
distinfo_dir = os.path.join(self.fake_dists_path,
@@ -161,21 +301,20 @@ class TestPkgUtilDistribution(unittest.TestCase):
dist = Distribution(distinfo_dir)
# Test for the iteration of the raw path
distinfo_record_paths = self.records[distinfo_dir].keys()
- found = [ path for path in dist.get_distinfo_files() ]
+ found = [path for path in dist.get_distinfo_files()]
self.assertEqual(sorted(found), sorted(distinfo_record_paths))
# Test for the iteration of local absolute paths
- distinfo_record_paths = [ os.path.join(sys.prefix, path)
- for path in self.records[distinfo_dir].keys()
- ]
- found = [ path for path in dist.get_distinfo_files(local=True) ]
+ distinfo_record_paths = [os.path.join(sys.prefix, path)
+ for path in self.records[distinfo_dir].keys()]
+ found = [path for path in dist.get_distinfo_files(local=True)]
self.assertEqual(sorted(found), sorted(distinfo_record_paths))
-class TestPkgUtilFunctions(unittest.TestCase):
- """Tests for the new functionality added in PEP 376."""
+class TestPkgUtilPEP376(unittest.TestCase):
+ # Tests for the new functionality added in PEP 376.
def setUp(self):
- super(TestPkgUtilFunctions, self).setUp()
+ super(TestPkgUtilPEP376, self).setUp()
# Setup the path environment with our fake distributions
current_path = os.path.abspath(os.path.dirname(__file__))
self.sys_path = sys.path[:]
@@ -183,18 +322,19 @@ class TestPkgUtilFunctions(unittest.TestCase):
sys.path[0:0] = [self.fake_dists_path]
def tearDown(self):
- super(TestPkgUtilFunctions, self).tearDown()
sys.path[:] = self.sys_path
+ super(TestPkgUtilPEP376, self).tearDown()
def test_distinfo_dirname(self):
- """Given a name and a version, we expect the distinfo_dirname function
- to return a standard distribution information directory name."""
+ # Given a name and a version, we expect the distinfo_dirname function
+ # to return a standard distribution information directory name.
- items = [ # (name, version, standard_dirname)
- # Test for a very simple single word name and decimal version number
+ items = [# (name, version, standard_dirname)
+ # Test for a very simple single word name and decimal
+ # version number
('docutils', '0.5', 'docutils-0.5.dist-info'),
# Test for another except this time with a '-' in the name, which
- # needs to be transformed during the name lookup
+ # needs to be transformed during the name lookup
('python-ldap', '2.5', 'python_ldap-2.5.dist-info'),
# Test for both '-' in the name and a funky version number
('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'),
@@ -209,7 +349,7 @@ class TestPkgUtilFunctions(unittest.TestCase):
self.assertEqual(dirname, standard_dirname)
def test_get_distributions(self):
- """Lookup all distributions found in the ``sys.path``."""
+ # Lookup all distributions found in the ``sys.path``.
# This test could potentially pick up other installed distributions
fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'),
('towel-stuff', '0.1')]
@@ -221,7 +361,7 @@ class TestPkgUtilFunctions(unittest.TestCase):
EggInfoDistribution
# Verify the fake dists have been found.
- dists = [ dist for dist in get_distributions() ]
+ dists = [dist for dist in get_distributions()]
for dist in dists:
if not isinstance(dist, Distribution):
self.fail("item received was not a Distribution instance: "
@@ -234,10 +374,11 @@ class TestPkgUtilFunctions(unittest.TestCase):
self.assertListEqual(sorted(found_dists), sorted(fake_dists))
# Now, test if the egg-info distributions are found correctly as well
- fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2')]
+ fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'),
+ ('banana', '0.4'), ('strawberry', '0.6')]
found_dists = []
- dists = [ dist for dist in get_distributions(use_egg_info=True) ]
+ dists = [dist for dist in get_distributions(use_egg_info=True)]
for dist in dists:
if not (isinstance(dist, Distribution) or \
isinstance(dist, EggInfoDistribution)):
@@ -248,9 +389,8 @@ class TestPkgUtilFunctions(unittest.TestCase):
self.assertListEqual(sorted(fake_dists), sorted(found_dists))
-
def test_get_distribution(self):
- """Test for looking up a distribution by name."""
+ # Test for looking up a distribution by name.
# Test the lookup of the towel-stuff distribution
name = 'towel-stuff' # Note: This is different from the directory name
@@ -274,6 +414,8 @@ class TestPkgUtilFunctions(unittest.TestCase):
# instructed to
self.assertEqual(None, get_distribution('bacon'))
self.assertEqual(None, get_distribution('cheese'))
+ self.assertEqual(None, get_distribution('strawberry'))
+ self.assertEqual(None, get_distribution('banana'))
# Now check that it works well in both situations, when egg-info
# is a file and directory respectively.
@@ -285,8 +427,16 @@ class TestPkgUtilFunctions(unittest.TestCase):
self.assertTrue(isinstance(dist, EggInfoDistribution))
self.assertEqual(dist.name, 'bacon')
+ dist = get_distribution('banana', use_egg_info=True)
+ self.assertTrue(isinstance(dist, EggInfoDistribution))
+ self.assertEqual(dist.name, 'banana')
+
+ dist = get_distribution('strawberry', use_egg_info=True)
+ self.assertTrue(isinstance(dist, EggInfoDistribution))
+ self.assertEqual(dist.name, 'strawberry')
+
def test_get_file_users(self):
- """Test the iteration of distributions that use a file."""
+ # Test the iteration of distributions that use a file.
from distutils2._backport.pkgutil import get_file_users, Distribution
name = 'towel_stuff-0.1'
path = os.path.join(self.fake_dists_path, name,
@@ -296,11 +446,11 @@ class TestPkgUtilFunctions(unittest.TestCase):
self.assertEqual(dist.name, name)
def test_provides(self):
- """ Test for looking up distributions by what they provide """
+ # Test for looking up distributions by what they provide
from distutils2._backport.pkgutil import provides_distribution
from distutils2.errors import DistutilsError
- checkLists = lambda x,y: self.assertListEqual(sorted(x), sorted(y))
+ checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y))
l = [dist.name for dist in provides_distribution('truffles')]
checkLists(l, ['choxie', 'towel-stuff'])
@@ -318,10 +468,12 @@ class TestPkgUtilFunctions(unittest.TestCase):
l = [dist.name for dist in provides_distribution('truffles', '1.1')]
checkLists(l, ['towel-stuff'])
- l = [dist.name for dist in provides_distribution('truffles', '!=1.1,<=2.0')]
+ l = [dist.name for dist in provides_distribution('truffles', \
+ '!=1.1,<=2.0')]
checkLists(l, ['choxie'])
- l = [dist.name for dist in provides_distribution('truffles', '!=1.1,<=2.0',
+ l = [dist.name for dist in provides_distribution('truffles', \
+ '!=1.1,<=2.0',
use_egg_info=True)]
checkLists(l, ['choxie', 'bacon', 'cheese'])
@@ -338,12 +490,39 @@ class TestPkgUtilFunctions(unittest.TestCase):
l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
checkLists(l, ['choxie', 'towel-stuff'])
+ l = [dist.name for dist in provides_distribution('strawberry', '0.6',
+ use_egg_info=True)]
+ checkLists(l, ['strawberry'])
+
+ l = [dist.name for dist in provides_distribution('strawberry', '>=0.5',
+ use_egg_info=True)]
+ checkLists(l, ['strawberry'])
+
+
+ l = [dist.name for dist in provides_distribution('strawberry', '>0.6',
+ use_egg_info=True)]
+ checkLists(l, [])
+
+
+ l = [dist.name for dist in provides_distribution('banana', '0.4',
+ use_egg_info=True)]
+ checkLists(l, ['banana'])
+
+ l = [dist.name for dist in provides_distribution('banana', '>=0.3',
+ use_egg_info=True)]
+ checkLists(l, ['banana'])
+
+
+ l = [dist.name for dist in provides_distribution('banana', '!=0.4',
+ use_egg_info=True)]
+ checkLists(l, [])
+
def test_obsoletes(self):
- """ Test looking for distributions based on what they obsolete """
+ # Test looking for distributions based on what they obsolete
from distutils2._backport.pkgutil import obsoletes_distribution
from distutils2.errors import DistutilsError
- checkLists = lambda x,y: self.assertListEqual(sorted(x), sorted(y))
+ checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y))
l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')]
checkLists(l, [])
@@ -363,7 +542,8 @@ class TestPkgUtilFunctions(unittest.TestCase):
l = [dist.name for dist in obsoletes_distribution('truffles', '0.9.6')]
checkLists(l, ['choxie', 'towel-stuff'])
- l = [dist.name for dist in obsoletes_distribution('truffles', '0.5.2.3')]
+ l = [dist.name for dist in obsoletes_distribution('truffles', \
+ '0.5.2.3')]
checkLists(l, ['choxie', 'towel-stuff'])
l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')]
@@ -372,26 +552,17 @@ class TestPkgUtilFunctions(unittest.TestCase):
def test_suite():
suite = unittest.TestSuite()
- testcase_loader = unittest.loader.defaultTestLoader.loadTestsFromTestCase
- suite.addTest(testcase_loader(TestPkgUtilFunctions))
- suite.addTest(testcase_loader(TestPkgUtilDistribution))
+ load = unittest.defaultTestLoader.loadTestsFromTestCase
+ suite.addTest(load(TestPkgUtilData))
+ suite.addTest(load(TestPkgUtilDistribution))
+ suite.addTest(load(TestPkgUtilPEP302))
+ suite.addTest(load(TestPkgUtilPEP376))
return suite
-def test_main():
- run_unittest(test_suite())
-
-if __name__ == "__main__":
- test_main()
-
-def test_suite():
- suite = unittest.TestSuite()
- testcase_loader = unittest.loader.defaultTestLoader.loadTestsFromTestCase
- suite.addTest(testcase_loader(TestPkgUtilFunctions))
- suite.addTest(testcase_loader(TestPkgUtilDistribution))
- return suite
def test_main():
run_unittest(test_suite())
+
if __name__ == "__main__":
test_main()
diff --git a/src/distutils2/_backport/tests/test_sysconfig.py b/src/distutils2/_backport/tests/test_sysconfig.py
index bb2e6a7..1b79ae9 100644
--- a/src/distutils2/_backport/tests/test_sysconfig.py
+++ b/src/distutils2/_backport/tests/test_sysconfig.py
@@ -8,7 +8,7 @@ import sys
import os
import shutil
from copy import copy, deepcopy
-from ConfigParser import ConfigParser
+from ConfigParser import RawConfigParser
from test.test_support import run_unittest, TESTFN
@@ -88,15 +88,13 @@ class TestSysConfig(unittest.TestCase):
shutil.rmtree(path)
def test_nested_var_substitution(self):
- """Assert that the {curly brace token} expansion pattern will replace
- only the inner {something} on nested expressions like {py{something}} on
- the first pass.
+ # Assert that the {curly brace token} expansion pattern will replace
+ # only the inner {something} on nested expressions like {py{something}} on
+ # the first pass.
- We have no plans to make use of this, but it keeps the option open for
- the future, at the cost only of disallowing { itself as a piece of a
- substitution key (which would be weird).
-
- """
+ # We have no plans to make use of this, but it keeps the option open for
+ # the future, at the cost only of disallowing { itself as a piece of a
+ # substitution key (which would be weird).
self.assertEqual(_subst_vars('{py{version}}', {'version': '31'}), '{py31}')
def test_get_paths(self):
@@ -107,7 +105,7 @@ class TestSysConfig(unittest.TestCase):
wanted.sort()
scheme = scheme.items()
scheme.sort()
- self.assertEquals(scheme, wanted)
+ self.assertEqual(scheme, wanted)
def test_get_config_vars(self):
cvars = get_config_vars()
@@ -120,21 +118,21 @@ class TestSysConfig(unittest.TestCase):
sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) '
'[MSC v.1310 32 bit (Intel)]')
sys.platform = 'win32'
- self.assertEquals(get_platform(), 'win32')
+ self.assertEqual(get_platform(), 'win32')
# windows XP, amd64
os.name = 'nt'
sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) '
'[MSC v.1310 32 bit (Amd64)]')
sys.platform = 'win32'
- self.assertEquals(get_platform(), 'win-amd64')
+ self.assertEqual(get_platform(), 'win-amd64')
# windows XP, itanium
os.name = 'nt'
sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) '
'[MSC v.1310 32 bit (Itanium)]')
sys.platform = 'win32'
- self.assertEquals(get_platform(), 'win-ia64')
+ self.assertEqual(get_platform(), 'win-ia64')
# macbook
os.name = 'posix'
@@ -153,9 +151,9 @@ class TestSysConfig(unittest.TestCase):
maxint = sys.maxint
try:
sys.maxint = 2147483647
- self.assertEquals(get_platform(), 'macosx-10.3-ppc')
+ self.assertEqual(get_platform(), 'macosx-10.3-ppc')
sys.maxint = 9223372036854775807
- self.assertEquals(get_platform(), 'macosx-10.3-ppc64')
+ self.assertEqual(get_platform(), 'macosx-10.3-ppc64')
finally:
sys.maxint = maxint
@@ -173,9 +171,9 @@ class TestSysConfig(unittest.TestCase):
maxint = sys.maxint
try:
sys.maxint = 2147483647
- self.assertEquals(get_platform(), 'macosx-10.3-i386')
+ self.assertEqual(get_platform(), 'macosx-10.3-i386')
sys.maxint = 9223372036854775807
- self.assertEquals(get_platform(), 'macosx-10.3-x86_64')
+ self.assertEqual(get_platform(), 'macosx-10.3-x86_64')
finally:
sys.maxint = maxint
@@ -186,33 +184,33 @@ class TestSysConfig(unittest.TestCase):
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3')
- self.assertEquals(get_platform(), 'macosx-10.4-fat')
+ self.assertEqual(get_platform(), 'macosx-10.4-fat')
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot '
'/Developer/SDKs/MacOSX10.4u.sdk '
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3')
- self.assertEquals(get_platform(), 'macosx-10.4-intel')
+ self.assertEqual(get_platform(), 'macosx-10.4-intel')
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot '
'/Developer/SDKs/MacOSX10.4u.sdk '
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3')
- self.assertEquals(get_platform(), 'macosx-10.4-fat3')
+ self.assertEqual(get_platform(), 'macosx-10.4-fat3')
get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot '
'/Developer/SDKs/MacOSX10.4u.sdk '
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3')
- self.assertEquals(get_platform(), 'macosx-10.4-universal')
+ self.assertEqual(get_platform(), 'macosx-10.4-universal')
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot '
'/Developer/SDKs/MacOSX10.4u.sdk '
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3')
- self.assertEquals(get_platform(), 'macosx-10.4-fat64')
+ self.assertEqual(get_platform(), 'macosx-10.4-fat64')
for arch in ('ppc', 'i386', 'x86_64', 'ppc64'):
get_config_vars()['CFLAGS'] = ('-arch %s -isysroot '
@@ -220,7 +218,7 @@ class TestSysConfig(unittest.TestCase):
'-fno-strict-aliasing -fno-common '
'-dynamic -DNDEBUG -g -O3'%(arch,))
- self.assertEquals(get_platform(), 'macosx-10.4-%s'%(arch,))
+ self.assertEqual(get_platform(), 'macosx-10.4-%s'%(arch,))
# linux debian sarge
os.name = 'posix'
@@ -230,7 +228,7 @@ class TestSysConfig(unittest.TestCase):
self._set_uname(('Linux', 'aglae', '2.6.21.1dedibox-r7',
'#1 Mon Apr 30 17:25:38 CEST 2007', 'i686'))
- self.assertEquals(get_platform(), 'linux-i686')
+ self.assertEqual(get_platform(), 'linux-i686')
# XXX more platforms to tests here
@@ -241,11 +239,11 @@ class TestSysConfig(unittest.TestCase):
def test_get_scheme_names(self):
wanted = ('nt', 'nt_user', 'os2', 'os2_home', 'posix_home',
'posix_prefix', 'posix_user')
- self.assertEquals(get_scheme_names(), wanted)
+ self.assertEqual(get_scheme_names(), wanted)
def test_expand_globals(self):
- config = ConfigParser()
+ config = RawConfigParser()
config.add_section('globals')
config.set('globals', 'foo', 'ok')
config.add_section('posix')
@@ -254,8 +252,8 @@ class TestSysConfig(unittest.TestCase):
_expand_globals(config)
- self.assertEquals(config.get('posix', 'foo'), 'ok')
- self.assertEquals(config.get('posix', 'more'), '/etc/ok')
+ self.assertEqual(config.get('posix', 'foo'), 'ok')
+ self.assertEqual(config.get('posix', 'more'), '/etc/ok')
# we might not have globals after all
# extending again (==no more globals section)
diff --git a/src/distutils2/command/bdist.py b/src/distutils2/command/bdist.py
index 27f9ec3..147a31e 100644
--- a/src/distutils2/command/bdist.py
+++ b/src/distutils2/command/bdist.py
@@ -62,7 +62,7 @@ class bdist(Command):
'os2': 'zip'}
# Establish the preferred order (for the --help-formats option).
- format_commands = ['rpm', 'gztar', 'bztar', 'ztar', 'tar',
+ format_commands = ['gztar', 'bztar', 'ztar', 'tar',
'wininst', 'zip', 'msi']
# And the real information.
@@ -96,7 +96,7 @@ class bdist(Command):
# 'bdist_base' -- parent of per-built-distribution-format
# temporary directories (eg. we'll probably have
- # "build/bdist.<plat>/dumb", "build/bdist.<plat>/rpm", etc.)
+ # "build/bdist.<plat>/dumb", etc.)
if self.bdist_base is None:
build_base = self.get_finalized_command('build').build_base
self.bdist_base = os.path.join(build_base,
@@ -126,7 +126,7 @@ class bdist(Command):
# Reinitialize and run each command.
for i in range(len(self.formats)):
cmd_name = commands[i]
- sub_cmd = self.reinitialize_command(cmd_name)
+ sub_cmd = self.get_reinitialized_command(cmd_name)
# passing the owner and group names for tar archiving
if cmd_name == 'bdist_dumb':
diff --git a/src/distutils2/command/bdist_dumb.py b/src/distutils2/command/bdist_dumb.py
index 429725c..604bcb7 100644
--- a/src/distutils2/command/bdist_dumb.py
+++ b/src/distutils2/command/bdist_dumb.py
@@ -85,7 +85,7 @@ class bdist_dumb (Command):
if not self.skip_build:
self.run_command('build')
- install = self.reinitialize_command('install', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install', reinit_subcommands=1)
install.root = self.bdist_dir
install.skip_build = self.skip_build
install.warn_dir = 0
@@ -115,8 +115,9 @@ class bdist_dumb (Command):
% (repr(install.install_base),
repr(install.install_platbase)))
else:
- archive_root = os.path.join(self.bdist_dir,
- ensure_relative(install.install_base))
+ archive_root = os.path.join(
+ self.bdist_dir,
+ self._ensure_relative(install.install_base))
# Make the archive
filename = self.make_archive(pseudoinstall_root,
@@ -135,3 +136,9 @@ class bdist_dumb (Command):
else:
rmtree(self.bdist_dir)
+ def _ensure_relative(self, path):
+ # copied from dir_util, deleted
+ drive, path = os.path.splitdrive(path)
+ if path[0:1] == os.sep:
+ path = drive + path[1:]
+ return path
diff --git a/src/distutils2/command/bdist_msi.py b/src/distutils2/command/bdist_msi.py
index 158065b..bbbcbcc 100644
--- a/src/distutils2/command/bdist_msi.py
+++ b/src/distutils2/command/bdist_msi.py
@@ -177,12 +177,12 @@ class bdist_msi (Command):
if not self.skip_build:
self.run_command('build')
- install = self.reinitialize_command('install', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install', reinit_subcommands=1)
install.prefix = self.bdist_dir
install.skip_build = self.skip_build
install.warn_dir = 0
- install_lib = self.reinitialize_command('install_lib')
+ install_lib = self.get_reinitialized_command('install_lib')
# we do not want to include pyc or pyo files
install_lib.compile = 0
install_lib.optimize = 0
diff --git a/src/distutils2/command/bdist_wininst.py b/src/distutils2/command/bdist_wininst.py
index 10f8bec..7b55de1 100644
--- a/src/distutils2/command/bdist_wininst.py
+++ b/src/distutils2/command/bdist_wininst.py
@@ -8,6 +8,7 @@ __revision__ = "$Id: bdist_wininst.py 77761 2010-01-26 22:46:15Z tarek.ziade $"
import sys
import os
import string
+from shutil import rmtree
try:
from sysconfig import get_python_version
except ImportError:
@@ -126,13 +127,13 @@ class bdist_wininst (Command):
if not self.skip_build:
self.run_command('build')
- install = self.reinitialize_command('install', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install', reinit_subcommands=1)
install.root = self.bdist_dir
install.skip_build = self.skip_build
install.warn_dir = 0
install.plat_name = self.plat_name
- install_lib = self.reinitialize_command('install_lib')
+ install_lib = self.get_reinitialized_command('install_lib')
# we do not want to include pyc or pyo files
install_lib.compile = 0
install_lib.optimize = 0
diff --git a/src/distutils2/command/build_ext.py b/src/distutils2/command/build_ext.py
index ae11330..7e160ab 100644
--- a/src/distutils2/command/build_ext.py
+++ b/src/distutils2/command/build_ext.py
@@ -188,7 +188,24 @@ class build_ext(Command):
if self.package is None:
self.package = self.distribution.ext_package
+ # Ensure that the list of extensions is valid, i.e. it is a list of
+ # Extension objects.
self.extensions = self.distribution.ext_modules
+ if self.extensions:
+ if not isinstance(self.extensions, (list, tuple)):
+ type_name = (self.extensions is None and 'None'
+ or type(self.extensions).__name__)
+ raise DistutilsSetupError(
+ "'ext_modules' must be a sequence of Extension instances,"
+ " not %s" % (type_name,))
+ for i, ext in enumerate(self.extensions):
+ if isinstance(ext, Extension):
+ continue # OK! (assume type-checking done
+ # by Extension constructor)
+ type_name = (ext is None and 'None' or type(ext).__name__)
+ raise DistutilsSetupError(
+ "'ext_modules' item %d must be an Extension instance,"
+ " not %s" % (i, type_name))
# Make sure Python's include directories (for Python.h, pyconfig.h,
# etc.) are in the include search path.
@@ -396,86 +413,7 @@ class build_ext(Command):
# Now actually compile and link everything.
self.build_extensions()
- def check_extensions_list(self, extensions):
- """Ensure that the list of extensions (presumably provided as a
- command option 'extensions') is valid, i.e. it is a list of
- Extension objects. We also support the old-style list of 2-tuples,
- where the tuples are (ext_name, build_info), which are converted to
- Extension instances here.
-
- Raise DistutilsSetupError if the structure is invalid anywhere;
- just returns otherwise.
- """
- if not isinstance(extensions, list):
- raise DistutilsSetupError, \
- "'ext_modules' option must be a list of Extension instances"
-
- for i, ext in enumerate(extensions):
- if isinstance(ext, Extension):
- continue # OK! (assume type-checking done
- # by Extension constructor)
-
- if not isinstance(ext, tuple) or len(ext) != 2:
- raise DistutilsSetupError, \
- ("each element of 'ext_modules' option must be an "
- "Extension instance or 2-tuple")
-
- ext_name, build_info = ext
-
- log.warn(("old-style (ext_name, build_info) tuple found in "
- "ext_modules for extension '%s'"
- "-- please convert to Extension instance" % ext_name))
-
- if not (isinstance(ext_name, str) and
- extension_name_re.match(ext_name)):
- raise DistutilsSetupError, \
- ("first element of each tuple in 'ext_modules' "
- "must be the extension name (a string)")
-
- if not isinstance(build_info, dict):
- raise DistutilsSetupError, \
- ("second element of each tuple in 'ext_modules' "
- "must be a dictionary (build info)")
-
- # OK, the (ext_name, build_info) dict is type-safe: convert it
- # to an Extension instance.
- ext = Extension(ext_name, build_info['sources'])
-
- # Easy stuff: one-to-one mapping from dict elements to
- # instance attributes.
- for key in ('include_dirs', 'library_dirs', 'libraries',
- 'extra_objects', 'extra_compile_args',
- 'extra_link_args'):
- val = build_info.get(key)
- if val is not None:
- setattr(ext, key, val)
-
- # Medium-easy stuff: same syntax/semantics, different names.
- ext.runtime_library_dirs = build_info.get('rpath')
- if 'def_file' in build_info:
- log.warn("'def_file' element of build info dict "
- "no longer supported")
-
- # Non-trivial stuff: 'macros' split into 'define_macros'
- # and 'undef_macros'.
- macros = build_info.get('macros')
- if macros:
- ext.define_macros = []
- ext.undef_macros = []
- for macro in macros:
- if not (isinstance(macro, tuple) and len(macro) in (1, 2)):
- raise DistutilsSetupError, \
- ("'macros' element of build info dict "
- "must be 1- or 2-tuple")
- if len(macro) == 1:
- ext.undef_macros.append(macro[0])
- elif len(macro) == 2:
- ext.define_macros.append(macro)
-
- extensions[i] = ext
-
def get_source_files(self):
- self.check_extensions_list(self.extensions)
filenames = []
# Wouldn't it be neat if we knew the names of header files too...
@@ -485,11 +423,6 @@ class build_ext(Command):
return filenames
def get_outputs(self):
- # Sanity check the 'extensions' list -- can't assume this is being
- # done in the same run as a 'build_extensions()' call (in fact, we
- # can probably assume that it *isn't*!).
- self.check_extensions_list(self.extensions)
-
# And build the list of output (built) filenames. Note that this
# ignores the 'inplace' flag, and assumes everything goes in the
# "build" tree.
@@ -499,9 +432,6 @@ class build_ext(Command):
return outputs
def build_extensions(self):
- # First, sanity-check the 'extensions' list
- self.check_extensions_list(self.extensions)
-
for ext in self.extensions:
try:
self.build_extension(ext)
diff --git a/src/distutils2/command/build_py.py b/src/distutils2/command/build_py.py
index 821128f..c4c6d68 100644
--- a/src/distutils2/command/build_py.py
+++ b/src/distutils2/command/build_py.py
@@ -6,14 +6,66 @@ __revision__ = "$Id: build_py.py 76956 2009-12-21 01:22:46Z tarek.ziade $"
import os
import sys
+import logging
from glob import glob
+import distutils2
from distutils2.core import Command
from distutils2.errors import DistutilsOptionError, DistutilsFileError
from distutils2.util import convert_path
-from distutils2 import log
-
-class build_py(Command):
+from distutils2.converter.refactor import DistutilsRefactoringTool
+
+# marking public APIs
+__all__ = ['Mixin2to3', 'build_py']
+
+try:
+ from distutils2.util import Mixin2to3 as _Mixin2to3
+ from lib2to3.refactor import get_fixers_from_package
+ _CONVERT = True
+ _KLASS = _Mixin2to3
+except ImportError:
+ _CONVERT = False
+ _KLASS = object
+
+class Mixin2to3(_KLASS):
+ """ The base class which can be used for refactoring. When run under
+ Python 3.0, the run_2to3 method provided by Mixin2to3 is overridden.
+ When run on Python 2.x, it merely creates a class which overrides run_2to3,
+ yet does nothing in particular with it.
+ """
+ if _CONVERT:
+ def _run_2to3(self, files, doctests=[]):
+ """ Takes a list of files and doctests, and performs conversion
+ on those.
+ - First, the files which contain the code(`files`) are converted.
+ - Second, the doctests in `files` are converted.
+ - Thirdly, the doctests in `doctests` are converted.
+ """
+
+ # Convert the ".py" files.
+ logging.info("Converting Python code")
+ _KLASS.run_2to3(self, files)
+
+ # Convert the doctests in the ".py" files.
+ logging.info("Converting doctests with '.py' files")
+ _KLASS.run_2to3(self, files, doctests_only=True)
+
+ # If the following conditions are met, then convert:-
+ # 1. User has specified the 'convert_2to3_doctests' option. So, we
+ # can expect that the list 'doctests' is not empty.
+ # 2. The default is allow distutils2 to allow conversion of text files
+ # containing doctests. It is set as
+ # distutils2.run_2to3_on_doctests
+
+ if doctests != [] and distutils2.run_2to3_on_doctests:
+ logging.info("Converting text files which contain doctests")
+ _KLASS.run_2to3(self, doctests, doctests_only=True)
+ else:
+ # If run on Python 2.x, there is nothing to do.
+ def _run_2to3(self, files, doctests=[]):
+ pass
+
+class build_py(Command, Mixin2to3):
description = "\"build\" pure Python modules (copy to build directory)"
@@ -39,6 +91,8 @@ class build_py(Command):
self.compile = 0
self.optimize = 0
self.force = None
+ self._updated_files = []
+ self._doctests_2to3 = []
def finalize_options(self):
self.set_undefined_options('build',
@@ -93,10 +147,18 @@ class build_py(Command):
self.build_packages()
self.build_package_data()
+ if self.distribution.use_2to3 and self_updated_files:
+ self.run_2to3(self._updated_files, self._doctests_2to3)
+
self.byte_compile(self.get_outputs(include_bytecode=0))
+ # -- Top-level worker functions ------------------------------------
+
def get_data_files(self):
- """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
+ """Generate list of '(package,src_dir,build_dir,filenames)' tuples.
+
+ Helper function for `finalize_options()`.
+ """
data = []
if not self.packages:
return data
@@ -120,7 +182,10 @@ class build_py(Command):
return data
def find_data_files(self, package, src_dir):
- """Return filenames for package's data files in 'src_dir'"""
+ """Return filenames for package's data files in 'src_dir'.
+
+ Helper function for `get_data_files()`.
+ """
globs = (self.package_data.get('', [])
+ self.package_data.get(package, []))
files = []
@@ -132,14 +197,21 @@ class build_py(Command):
return files
def build_package_data(self):
- """Copy data files into build directory"""
+ """Copy data files into build directory.
+
+ Helper function for `run()`.
+ """
for package, src_dir, build_dir, filenames in self.data_files:
for filename in filenames:
target = os.path.join(build_dir, filename)
self.mkpath(os.path.dirname(target))
- self.copy_file(os.path.join(src_dir, filename), target,
- preserve_mode=False)
+ outf, copied = self.copy_file(os.path.join(src_dir, filename),
+ target, preserve_mode=False)
+ if copied and srcfile in self.distribution.convert_2to3.doctests:
+ self._doctests_2to3.append(outf)
+ # XXX - this should be moved to the Distribution class as it is not
+ # only needed for build_py. It also has no dependencies on this class.
def get_package_dir(self, package):
"""Return the directory, relative to the top of the source
distribution, where package 'package' should be found
@@ -181,6 +253,8 @@ class build_py(Command):
return ''
def check_package(self, package, package_dir):
+ """Helper function for `find_package_modules()` and `find_modules()'.
+ """
# Empty dir name means current directory, which we can probably
# assume exists. Also, os.path.exists and isdir don't know about
# my "empty string means current dir" convention, so we have to
@@ -200,8 +274,8 @@ class build_py(Command):
if os.path.isfile(init_py):
return init_py
else:
- log.warn(("package init file '%s' not found " +
- "(or not a regular file)"), init_py)
+ logging.warning(("package init file '%s' not found " +
+ "(or not a regular file)"), init_py)
# Either not in a package at all (__init__.py not expected), or
# __init__.py doesn't exist -- so don't return the filename.
@@ -209,7 +283,8 @@ class build_py(Command):
def check_module(self, module, module_file):
if not os.path.isfile(module_file):
- log.warn("file %s (for module %s) not found", module_file, module)
+ logging.warning("file %s (for module %s) not found",
+ module_file, module)
return False
else:
return True
diff --git a/src/distutils2/command/cmd.py b/src/distutils2/command/cmd.py
index ba88b89..fa6f281 100644
--- a/src/distutils2/command/cmd.py
+++ b/src/distutils2/command/cmd.py
@@ -6,7 +6,7 @@ in the distutils.command package.
__revision__ = "$Id: cmd.py 75192 2009-10-02 23:49:48Z tarek.ziade $"
-import sys, os, re
+import os, re
from distutils2.errors import DistutilsOptionError
from distutils2 import util
from distutils2 import log
@@ -19,7 +19,7 @@ try:
except ImportError:
from distutils2._backport.shutil import make_archive
-class Command:
+class Command(object):
"""Abstract base class for defining command classes, the "worker bees"
of the Distutils. A useful analogy for command classes is to think of
them as subroutines with local variables called "options". The options
@@ -188,6 +188,31 @@ class Command:
"""
log.log(level, msg)
+ # -- External interface --------------------------------------------
+ # (called by outsiders)
+
+ def get_source_files(self):
+ """Return the list of files that are used as inputs to this command,
+ i.e. the files used to generate the output files. The result is used
+ by the `sdist` command in determining the set of default files.
+
+ Command classes should implement this method if they operate on files
+ from the source tree.
+ """
+ return []
+
+ def get_outputs(self):
+ """Return the list of files that would be produced if this command
+ were actually run. Not affected by the "dry-run" flag or whether
+ any other commands have been run.
+
+ Command classes should implement this method if they produce any
+ output files that get consumed by another command. e.g., `build_ext`
+ returns the list of built extension modules, but not any temporary
+ files used in the compilation process.
+ """
+ return []
+
# -- Option validation methods -------------------------------------
# (these are very handy in writing the 'finalize_options()' method)
#
@@ -307,10 +332,8 @@ class Command:
cmd_obj.ensure_finalized()
return cmd_obj
- # XXX rename to 'get_reinitialized_command()'? (should do the
- # same in dist.py, if so)
- def reinitialize_command(self, command, reinit_subcommands=0):
- return self.distribution.reinitialize_command(
+ def get_reinitialized_command(self, command, reinit_subcommands=0):
+ return self.distribution.get_reinitialized_command(
command, reinit_subcommands)
def run_command(self, command):
@@ -350,8 +373,10 @@ class Command:
if os.path.isdir(name) or name == '':
return
if dry_run:
+ head = ''
for part in name.split(os.sep):
- self.log(part)
+ log.info("created directory %s%s", head, part)
+ head += part + os.sep
return
os.makedirs(name, mode)
@@ -386,7 +411,7 @@ class Command:
def spawn(self, cmd, search_path=1, level=1):
"""Spawn an external command respecting dry-run flag."""
- from distutils2.spawn import spawn
+ from distutils2.util import spawn
spawn(cmd, search_path, dry_run= self.dry_run)
def make_archive(self, base_name, format, root_dir=None, base_dir=None,
@@ -422,7 +447,7 @@ class Command:
# If 'outfile' must be regenerated (either because it doesn't
# exist, is out-of-date, or the 'force' flag is true) then
# perform the action that presumably regenerates it
- if self.force or dep_util.newer_group(infiles, outfile):
+ if self.force or util.newer_group(infiles, outfile):
self.execute(func, args, exec_msg, level)
# Otherwise, print the "skip" message
diff --git a/src/distutils2/command/register.py b/src/distutils2/command/register.py
index 8abb3c8..29b87e4 100644
--- a/src/distutils2/command/register.py
+++ b/src/distutils2/command/register.py
@@ -28,8 +28,6 @@ class register(PyPIRCCommand):
boolean_options = PyPIRCCommand.boolean_options + [
'verify', 'list-classifiers', 'strict']
- sub_commands = [('check', lambda self: True)]
-
def initialize_options(self):
PyPIRCCommand.initialize_options(self)
self.list_classifiers = 0
@@ -46,9 +44,8 @@ class register(PyPIRCCommand):
self.finalize_options()
self._set_config()
- # Run sub commands
- for cmd_name in self.get_sub_commands():
- self.run_command(cmd_name)
+ # Check the package metadata
+ self.run_command('check')
if self.dry_run:
self.verify_metadata()
diff --git a/src/distutils2/command/sdist.py b/src/distutils2/command/sdist.py
index bb4cd1f..37f74c5 100644
--- a/src/distutils2/command/sdist.py
+++ b/src/distutils2/command/sdist.py
@@ -18,12 +18,11 @@ except ImportError:
from distutils2._backport.shutil import get_archive_formats
from distutils2.core import Command
-from distutils2 import util
from distutils2.errors import (DistutilsPlatformError, DistutilsOptionError,
- DistutilsTemplateError)
+ DistutilsTemplateError)
from distutils2.manifest import Manifest
from distutils2 import log
-from distutils2.util import convert_path, newer
+from distutils2.util import convert_path
def show_formats():
"""Print all possible values for the 'formats' option (used by
@@ -45,12 +44,6 @@ class sdist(Command):
description = "create a source distribution (tarball, zip file, etc.)"
- def checking_metadata(self):
- """Callable used for the check sub-command.
-
- Placed here so user_options can view it"""
- return self.metadata_check
-
user_options = [
('template=', 't',
"name of manifest template file [default: MANIFEST.in]"),
@@ -100,8 +93,6 @@ class sdist(Command):
default_format = {'posix': 'gztar',
'nt': 'zip' }
- sub_commands = [('check', checking_metadata)]
-
def initialize_options(self):
# 'template' and 'manifest' are, respectively, the names of
# the manifest template and manifest file.
@@ -162,9 +153,9 @@ class sdist(Command):
# manifest
self.filelist.clear()
- # Run sub commands
- for cmd_name in self.get_sub_commands():
- self.run_command(cmd_name)
+ # Check the package metadata
+ if self.metadata_check:
+ self.run_command('check')
# Do whatever it takes to get the list of files to process
# (process the manifest template, read an existing manifest,
diff --git a/src/distutils2/command/upload.py b/src/distutils2/command/upload.py
index 14fe119..796ca73 100644
--- a/src/distutils2/command/upload.py
+++ b/src/distutils2/command/upload.py
@@ -15,7 +15,7 @@ except ImportError:
from distutils2.errors import DistutilsOptionError
from distutils2.core import PyPIRCCommand
-from distutils2.spawn import spawn
+from distutils2.util import spawn
from distutils2 import log
class upload(PyPIRCCommand):
diff --git a/src/distutils2/command/upload_docs.py b/src/distutils2/command/upload_docs.py
index 860f07f..120837b 100644
--- a/src/distutils2/command/upload_docs.py
+++ b/src/distutils2/command/upload_docs.py
@@ -1,4 +1,4 @@
-import base64, httplib, os.path, socket, tempfile, urlparse, zipfile
+import base64, httplib, os.path, socket, urlparse, zipfile
from cStringIO import StringIO
from distutils2 import log
from distutils2.command.upload import upload
@@ -81,7 +81,6 @@ class upload_docs(PyPIRCCommand):
raise DistutilsFileError(mesg % upload_dir)
def run(self):
- tmp_dir = tempfile.mkdtemp()
name = self.distribution.metadata['Name']
version = self.distribution.metadata['Version']
zip_file = zip_dir(self.upload_dir)
@@ -125,7 +124,7 @@ class upload_docs(PyPIRCCommand):
elif r.status == 301:
location = r.getheader('Location')
if location is None:
- location = 'http://packages.python.org/%s/' % meta.get_name()
+ location = 'http://packages.python.org/%s/' % name
self.announce('Upload successful. Visit %s' % location,
log.INFO)
else:
diff --git a/src/distutils2/compiler/bcppcompiler.py b/src/distutils2/compiler/bcppcompiler.py
index 71a116b..7587048 100644
--- a/src/distutils2/compiler/bcppcompiler.py
+++ b/src/distutils2/compiler/bcppcompiler.py
@@ -16,7 +16,7 @@ __revision__ = "$Id: bcppcompiler.py 76956 2009-12-21 01:22:46Z tarek.ziade $"
import os
from distutils2.errors import (DistutilsExecError, CompileError, LibError,
- LinkError, UnknownFileError)
+ LinkError, UnknownFileError)
from distutils2.compiler.ccompiler import CCompiler, gen_preprocess_options
from distutils2.file_util import write_file
from distutils2.dep_util import newer
diff --git a/src/distutils2/compiler/ccompiler.py b/src/distutils2/compiler/ccompiler.py
index 025febd..c9bb129 100644
--- a/src/distutils2/compiler/ccompiler.py
+++ b/src/distutils2/compiler/ccompiler.py
@@ -10,9 +10,8 @@ import os
import re
from distutils2.errors import (CompileError, LinkError, UnknownFileError,
- DistutilsPlatformError, DistutilsModuleError)
-from distutils2.spawn import spawn
-from distutils2.util import split_quoted, execute, newer_group
+ DistutilsPlatformError, DistutilsModuleError)
+from distutils2.util import split_quoted, execute, newer_group, spawn
from distutils2 import log
from shutil import move
@@ -76,7 +75,7 @@ def customize_compiler(compiler):
compiler.shared_lib_extension = so_ext
-class CCompiler:
+class CCompiler(object):
"""Abstract base class to define the interface that must be implemented
by real compiler classes. Also has some utility methods used by
several compiler classes.
@@ -800,14 +799,16 @@ class CCompiler:
library_dirs = []
fd, fname = tempfile.mkstemp(".c", funcname, text=True)
f = os.fdopen(fd, "w")
- for incl in includes:
- f.write("""#include "%s"\n""" % incl)
- f.write("""\
+ try:
+ for incl in includes:
+ f.write("""#include "%s"\n""" % incl)
+ f.write("""\
main (int argc, char **argv) {
%s();
}
""" % funcname)
- f.close()
+ finally:
+ f.close()
try:
objects = self.compile([fname], include_dirs=include_dirs)
except CompileError:
@@ -938,8 +939,10 @@ main (int argc, char **argv) {
if os.path.isdir(name) or name == '':
return
if self.dry_run:
+ head = ''
for part in name.split(os.sep):
- self.log(part)
+ log.info("created directory %s%s", head, part)
+ head += part + os.sep
return
os.makedirs(name, mode)
@@ -1048,7 +1051,7 @@ def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
module_name = "distutils2.compiler." + module_name
__import__ (module_name)
module = sys.modules[module_name]
- klass = vars(module)[class_name]
+ cls = vars(module)[class_name]
except ImportError:
raise DistutilsModuleError, \
"can't compile C/C++ code: unable to load module '%s'" % \
@@ -1061,7 +1064,7 @@ def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
# XXX The None is necessary to preserve backwards compatibility
# with classes that expect verbose to be the first positional
# argument.
- return klass(None, dry_run, force)
+ return cls(None, dry_run, force)
def gen_preprocess_options(macros, include_dirs):
diff --git a/src/distutils2/compiler/emxccompiler.py b/src/distutils2/compiler/emxccompiler.py
index cb6fb16..6247c00 100644
--- a/src/distutils2/compiler/emxccompiler.py
+++ b/src/distutils2/compiler/emxccompiler.py
@@ -25,9 +25,8 @@ import os, sys, copy
from warnings import warn
from distutils2.compiler.unixccompiler import UnixCCompiler
-from distutils2.util import write_file
from distutils2.errors import DistutilsExecError, CompileError, UnknownFileError
-from distutils2.util import get_compiler_versions
+from distutils2.util import get_compiler_versions, write_file
class EMXCCompiler (UnixCCompiler):
@@ -273,8 +272,10 @@ def check_config_h():
# It would probably better to read single lines to search.
# But we do this only once, and it is fast enough
f = open(fn)
- s = f.read()
- f.close()
+ try:
+ s = f.read()
+ finally:
+ f.close()
except IOError, exc:
# if we can't read this file, we cannot say it is wrong
diff --git a/src/distutils2/compiler/msvc9compiler.py b/src/distutils2/compiler/msvc9compiler.py
index 9f98d60..46cffa5 100644
--- a/src/distutils2/compiler/msvc9compiler.py
+++ b/src/distutils2/compiler/msvc9compiler.py
@@ -20,7 +20,7 @@ import sys
import re
from distutils2.errors import (DistutilsExecError, DistutilsPlatformError,
- CompileError, LibError, LinkError)
+ CompileError, LibError, LinkError)
from distutils2.compiler.ccompiler import CCompiler, gen_lib_options
from distutils2 import log
from distutils2.util import get_platform
@@ -50,7 +50,7 @@ PLAT_TO_VCVARS = {
'win-ia64' : 'ia64',
}
-class Reg:
+class Reg(object):
"""Helper class to read values from the registry
"""
@@ -112,7 +112,7 @@ class Reg:
return s
convert_mbcs = staticmethod(convert_mbcs)
-class MacroExpander:
+class MacroExpander(object):
def __init__(self, version):
self.macros = {}
diff --git a/src/distutils2/compiler/msvccompiler.py b/src/distutils2/compiler/msvccompiler.py
index 46f8517..d2b049f 100644
--- a/src/distutils2/compiler/msvccompiler.py
+++ b/src/distutils2/compiler/msvccompiler.py
@@ -15,7 +15,7 @@ import os
import string
from distutils2.errors import (DistutilsExecError, DistutilsPlatformError,
- CompileError, LibError, LinkError)
+ CompileError, LibError, LinkError)
from distutils2.compiler.ccompiler import CCompiler, gen_lib_options
from distutils2 import log
@@ -104,7 +104,7 @@ def convert_mbcs(s):
pass
return s
-class MacroExpander:
+class MacroExpander(object):
def __init__(self, version):
self.macros = {}
diff --git a/src/distutils2/compiler/unixccompiler.py b/src/distutils2/compiler/unixccompiler.py
index 18276f7..7e52959 100644
--- a/src/distutils2/compiler/unixccompiler.py
+++ b/src/distutils2/compiler/unixccompiler.py
@@ -19,10 +19,10 @@ import os, sys
from types import StringType, NoneType
from distutils2.util import newer
-from distutils2.compiler.ccompiler import \
- CCompiler, gen_preprocess_options, gen_lib_options
-from distutils2.errors import \
- DistutilsExecError, CompileError, LibError, LinkError
+from distutils2.compiler.ccompiler import (CCompiler, gen_preprocess_options,
+ gen_lib_options)
+from distutils2.errors import (DistutilsExecError, CompileError,
+ LibError, LinkError)
from distutils2 import log
try:
diff --git a/src/distutils2/config.py b/src/distutils2/config.py
index a56ae02..30ad479 100644
--- a/src/distutils2/config.py
+++ b/src/distutils2/config.py
@@ -4,7 +4,7 @@ Provides the PyPIRCCommand class, the base class for the command classes
that uses .pypirc in the distutils.command package.
"""
import os
-from ConfigParser import ConfigParser
+from ConfigParser import RawConfigParser
from distutils2.command.cmd import Command
@@ -59,7 +59,7 @@ class PyPIRCCommand(Command):
if os.path.exists(rc):
self.announce('Using PyPI login from %s' % rc)
repository = self.repository or self.DEFAULT_REPOSITORY
- config = ConfigParser()
+ config = RawConfigParser()
config.read(rc)
sections = config.sections()
if 'distutils' in sections:
diff --git a/src/distutils2/converter/fixers/fix_imports.py b/src/distutils2/converter/fixers/fix_imports.py
index d765843..38d185e 100644
--- a/src/distutils2/converter/fixers/fix_imports.py
+++ b/src/distutils2/converter/fixers/fix_imports.py
@@ -36,11 +36,16 @@ class FixImports(BaseFix):
pattern = []
next = imp.next_sibling
while next is not None:
+ # Get the first child if we have a Node
+ if not hasattr(next, "value"):
+ next = next.children[0]
pattern.append(next.value)
if not hasattr(next, "next_sibling"):
next.next_sibling = next.get_next_sibling()
next = next.next_sibling
- if pattern == ['import', 'setup']:
+
+ if set(pattern).issubset(set(
+ ['import', ',', 'setup', 'find_packages'])):
imp.value = 'distutils2.core'
imp.changed()
diff --git a/src/distutils2/core.py b/src/distutils2/core.py
index d10a558..a81780b 100644
--- a/src/distutils2/core.py
+++ b/src/distutils2/core.py
@@ -2,8 +2,9 @@
The only module that needs to be imported to use the Distutils; provides
the 'setup' function (which is to be called from the setup script). Also
-indirectly provides the Distribution and Command classes, although they are
-really defined in distutils2.dist and distutils2.cmd.
+exports useful classes so that setup scripts can import them from here
+although they are really defined in other modules: Distribution, Command,
+PyPIRCommand, Extension, find_packages.
"""
__revision__ = "$Id: core.py 77704 2010-01-23 09:23:15Z tarek.ziade $"
@@ -12,7 +13,7 @@ import sys
import os
from distutils2.errors import (DistutilsSetupError, DistutilsArgError,
- DistutilsError, CCompilerError)
+ DistutilsError, CCompilerError)
from distutils2.util import grok_environment_error
# Mainly import these so setup scripts can "from distutils2.core import" them.
@@ -20,6 +21,7 @@ from distutils2.dist import Distribution
from distutils2.command.cmd import Command
from distutils2.config import PyPIRCCommand
from distutils2.extension import Extension
+from distutils2.util import find_packages
# This is a barebones help message generated displayed when the user
# runs the setup script with no arguments at all. More useful help
@@ -47,7 +49,8 @@ setup_keywords = ('distclass', 'script_name', 'script_args', 'options',
'maintainer', 'maintainer_email', 'url', 'license',
'description', 'long_description', 'keywords',
'platforms', 'classifiers', 'download_url',
- 'requires', 'provides', 'obsoletes',
+ 'requires', 'provides', 'obsoletes', 'use_2to3',
+ 'convert_2to3_doctests',
)
# Legal keyword arguments for the Extension constructor
@@ -94,11 +97,7 @@ def setup(**attrs):
# Determine the distribution class -- either caller-supplied or
# our Distribution (see below).
- klass = attrs.get('distclass')
- if klass:
- del attrs['distclass']
- else:
- klass = Distribution
+ distclass = attrs.pop('distclass', Distribution)
if 'script_name' not in attrs:
attrs['script_name'] = os.path.basename(sys.argv[0])
@@ -108,7 +107,7 @@ def setup(**attrs):
# Create the Distribution instance, using the remaining arguments
# (ie. everything except distclass) to initialize it
try:
- _setup_distribution = dist = klass(attrs)
+ _setup_distribution = dist = distclass(attrs)
except DistutilsSetupError, msg:
if 'name' in attrs:
raise SystemExit, "error in %s setup command: %s" % \
diff --git a/src/distutils2/depgraph.py b/src/distutils2/depgraph.py
index 8ab4690..75bc4d3 100644
--- a/src/distutils2/depgraph.py
+++ b/src/distutils2/depgraph.py
@@ -1,47 +1,49 @@
-"""
-A dependency graph generator. The graph is represented as an instance of
-:class:`DependencyGraph`, and DOT output is possible as well.
+"""Analyse the relationships between the distributions in the system and generate
+a dependency graph.
"""
-from distutils2._backport import pkgutil
from distutils2.errors import DistutilsError
from distutils2.version import VersionPredicate
-__all__ = ['DependencyGraph', 'generate_graph']
+__all__ = ['DependencyGraph', 'generate_graph', 'dependent_dists',
+ 'graph_to_dot']
class DependencyGraph(object):
"""
Represents a dependency graph between distributions.
- The depedency relationships are stored in an *adjacency_list* that maps
+ The dependency relationships are stored in an ``adjacency_list`` that maps
distributions to a list of ``(other, label)`` tuples where ``other``
is a distribution and the edge is labelled with ``label`` (i.e. the version
- specifier, if such was provided). If any missing depencies are found,
- they are stored in ``missing``. It maps distributions to a list of
- requirements that were not provided by any other distributions.
+ specifier, if such was provided). Also, for more efficient traversal, for
+ every distribution ``x``, a list of predecessors is kept in
+ ``reverse_list[x]``. An edge from distribution ``a`` to
+ distribution ``b`` means that ``a`` depends on ``b``. If any missing
+ depencies are found, they are stored in ``missing``, which is a dictionary
+ that maps distributions to a list of requirements that were not provided by
+ any other distributions.
"""
def __init__(self):
self.adjacency_list = {}
+ self.reverse_list = {}
self.missing = {}
def add_distribution(self, distribution):
- """
- Add distribution *x* to the graph.
+ """Add the *distribution* to the graph.
:type distribution: :class:`pkgutil.Distribution` or
:class:`pkgutil.EggInfoDistribution`
"""
self.adjacency_list[distribution] = list()
+ self.reverse_list[distribution] = list()
self.missing[distribution] = list()
def add_edge(self, x, y, label=None):
- """
- Add an edge from distribution *x* to distribution *y* with the given
+ """Add an edge from distribution *x* to distribution *y* with the given
*label*.
-
:type x: :class:`pkgutil.Distribution` or
:class:`pkgutil.EggInfoDistribution`
:type y: :class:`pkgutil.Distribution` or
@@ -49,6 +51,9 @@ class DependencyGraph(object):
:type label: ``str`` or ``None``
"""
self.adjacency_list[x].append((y, label))
+ # multiple edges are allowed, so be careful
+ if not x in self.reverse_list[y]:
+ self.reverse_list[y].append(x)
def add_missing(self, distribution, requirement):
"""
@@ -60,45 +65,42 @@ class DependencyGraph(object):
"""
self.missing[distribution].append(requirement)
- def to_dot(self, f, skip_disconnected=True):
- """
- Writes a DOT output for the graph to the provided *file*.
- If *skip_disconnected* is set to ``True``, then all distributions
- that are not dependent on any other distributions are skipped.
- :type f: ``file``
- ;type skip_disconnected: ``bool``
- """
- if not isinstance(f, file):
- raise TypeError('the argument has to be of type file')
-
- disconnected = []
-
- f.write("digraph dependencies {\n")
- for dist, adjs in self.adjacency_list.iteritems():
- if len(adjs) == 0 and not skip_disconnected:
- disconnected.append(dist)
- for (other, label) in adjs:
- if not label is None:
- f.write('"%s" -> "%s" [label="%s"]\n' %
- (dist.name, other.name, label))
- else:
- f.write('"%s" -> "%s"\n' % (dist.name, other.name))
- if not skip_disconnected and len(disconnected) > 0:
- f.write('subgraph disconnected {\n')
- f.write('label = "Disconnected"\n')
- f.write('bgcolor = red\n')
-
- for dist in disconnected:
- f.write('"%s"' % dist.name)
- f.write('\n')
- f.write('}\n')
+def graph_to_dot(graph, f, skip_disconnected=True):
+ """Writes a DOT output for the graph to the provided file *f*.
+
+ If *skip_disconnected* is set to ``True``, then all distributions
+ that are not dependent on any other distribution are skipped.
+
+ :type f: has to support ``file``-like operations
+ :type skip_disconnected: ``bool``
+ """
+ disconnected = []
+
+ f.write("digraph dependencies {\n")
+ for dist, adjs in graph.adjacency_list.iteritems():
+ if len(adjs) == 0 and not skip_disconnected:
+ disconnected.append(dist)
+ for (other, label) in adjs:
+ if not label is None:
+ f.write('"%s" -> "%s" [label="%s"]\n' %
+ (dist.name, other.name, label))
+ else:
+ f.write('"%s" -> "%s"\n' % (dist.name, other.name))
+ if not skip_disconnected and len(disconnected) > 0:
+ f.write('subgraph disconnected {\n')
+ f.write('label = "Disconnected"\n')
+ f.write('bgcolor = red\n')
+
+ for dist in disconnected:
+ f.write('"%s"' % dist.name)
+ f.write('\n')
f.write('}\n')
+ f.write('}\n')
def generate_graph(dists):
- """
- Generates a dependency graph from the given distributions.
+ """Generates a dependency graph from the given distributions.
:parameter dists: a list of distributions
:type dists: list of :class:`pkgutil.Distribution` and
@@ -115,7 +117,7 @@ def generate_graph(dists):
provides = dist.metadata['Provides-Dist'] + dist.metadata['Provides']
for p in provides:
- comps = p.split(" ", 1)
+ comps = p.strip().rsplit(" ", 1)
name = comps[0]
version = None
if len(comps) == 2:
@@ -133,22 +135,26 @@ def generate_graph(dists):
requires = dist.metadata['Requires-Dist'] + dist.metadata['Requires']
for req in requires:
predicate = VersionPredicate(req)
- comps = req.split(" ", 1)
+ comps = req.strip().rsplit(" ", 1)
name = comps[0]
if not name in provided:
graph.add_missing(dist, req)
else:
+ matched = False
for (version, provider) in provided[name]:
if predicate.match(version):
graph.add_edge(dist, provider, req)
+ matched = True
+ break
+ if not matched:
+ graph.add_missing(dist, req)
return graph
def dependent_dists(dists, dist):
- """
- Recursively generate a list of distributions from *dists* that are
+ """Recursively generate a list of distributions from *dists* that are
dependent on *dist*.
:param dists: a list of distributions
@@ -158,24 +164,26 @@ def dependent_dists(dists, dist):
raise ValueError('The given distribution is not a member of the list')
graph = generate_graph(dists)
- dep = [dist]
- fringe = [dist] # list of nodes we should expand
+ dep = [dist] # dependent distributions
+ fringe = graph.reverse_list[dist] # list of nodes we should inspect
+
while not len(fringe) == 0:
- next = graph.adjacency_list[fringe.pop()]
- for (dist, label) in next:
- if not dist in dep: # avoid infinite loops
- dep.append(dist)
- fringe.append(dist)
+ node = fringe.pop()
+ dep.append(node)
+ for prev in graph.reverse_list[node]:
+ if not prev in dep:
+ fringe.append(prev)
- dep.pop()
+ dep.pop(0) # remove dist from dep, was there to prevent infinite loops
return dep
if __name__ == '__main__':
- dists = list(pkgutil.get_distributions(use_egg_info=True))
+ from distutils2._backport.pkgutil import get_distributions
+ dists = list(get_distributions(use_egg_info=True))
graph = generate_graph(dists)
for dist, reqs in graph.missing.iteritems():
if len(reqs) > 0:
print("Missing dependencies for %s: %s" % (dist.name,
", ".join(reqs)))
f = open('output.dot', 'w')
- graph.to_dot(f, True)
+ graph_to_dot(graph, f, True)
diff --git a/src/distutils2/dist.py b/src/distutils2/dist.py
index 98e8cf1..1ea8891 100644
--- a/src/distutils2/dist.py
+++ b/src/distutils2/dist.py
@@ -13,8 +13,10 @@ try:
except ImportError:
warnings = None
+from ConfigParser import RawConfigParser
+
from distutils2.errors import (DistutilsOptionError, DistutilsArgError,
- DistutilsModuleError, DistutilsClassError)
+ DistutilsModuleError, DistutilsClassError)
from distutils2.fancy_getopt import FancyGetopt, translate_longopt
from distutils2.util import check_environ, strtobool
from distutils2 import log
@@ -112,7 +114,11 @@ Common commands: (see '--help-commands' for more)
('tests-require', None,
"print the list of packages/modules required to run the test suite"),
('obsoletes', None,
- "print the list of packages/modules made obsolete")
+ "print the list of packages/modules made obsolete"),
+ ('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"),
]
display_option_names = map(lambda x: translate_longopt(x[0]),
display_options)
@@ -206,6 +212,8 @@ Common commands: (see '--help-commands' for more)
self.scripts = None
self.data_files = None
self.password = ''
+ self.use_2to3 = False
+ self.convert_2to3_doctests = []
# And now initialize bookkeeping stuff that can't be supplied by
# the caller at all. 'command_obj' maps command names to
@@ -250,7 +258,7 @@ Common commands: (see '--help-commands' for more)
elif hasattr(self, key):
setattr(self, key, val)
else:
- msg = "Unknown distribution option: %s" % repr(key)
+ msg = "Unknown distribution option: %r" % key
if warnings is not None:
warnings.warn(msg)
else:
@@ -364,14 +372,12 @@ Common commands: (see '--help-commands' for more)
return files
def parse_config_files(self, filenames=None):
- from ConfigParser import ConfigParser
-
if filenames is None:
filenames = self.find_config_files()
log.debug("Distribution.parse_config_files():")
- parser = ConfigParser()
+ parser = RawConfigParser()
for filename in filenames:
log.debug(" reading %s" % filename)
parser.read(filename)
@@ -385,7 +391,7 @@ Common commands: (see '--help-commands' for more)
opt = opt.replace('-', '_')
opt_dict[opt] = (filename, val)
- # Make the ConfigParser forget everything (so we retain
+ # Make the RawConfigParser forget everything (so we retain
# the original filenames that options come from)
parser.__init__()
@@ -583,15 +589,11 @@ Common commands: (see '--help-commands' for more)
instance, analogous to the .finalize_options() method of Command
objects.
"""
-
- # XXX conversion -- removed
- #for attr in ('keywords', 'platforms'):
- # value = self.metadata.get_field(attr)
- # if value is None:
- # continue
- # if isinstance(value, str):
- # value = [elm.strip() for elm in value.split(',')]
- # setattr(self.metadata, attr, value)
+ if getattr(self, 'convert_2to3_doctests', None):
+ self.convert_2to3_doctests = [os.path.join(p)
+ for p in self.convert_2to3_doctests]
+ else:
+ self.convert_2to3_doctests = []
def _show_help(self, parser, global_options=1, display_options=1,
commands=[]):
@@ -629,16 +631,16 @@ Common commands: (see '--help-commands' for more)
for command in self.commands:
if isinstance(command, type) and issubclass(command, Command):
- klass = command
+ cls = command
else:
- klass = self.get_command_class(command)
- if (hasattr(klass, 'help_options') and
- isinstance(klass.help_options, list)):
- parser.set_option_table(klass.user_options +
- fix_help_options(klass.help_options))
+ cls = self.get_command_class(command)
+ if (hasattr(cls, 'help_options') and
+ isinstance(cls.help_options, list)):
+ parser.set_option_table(cls.user_options +
+ fix_help_options(cls.help_options))
else:
- parser.set_option_table(klass.user_options)
- parser.print_help("Options for '%s' command:" % klass.__name__)
+ parser.set_option_table(cls.user_options)
+ parser.print_help("Options for '%s' command:" % cls.__name__)
print('')
print(gen_usage(self.script_name))
@@ -690,11 +692,11 @@ Common commands: (see '--help-commands' for more)
print(header + ":")
for cmd in commands:
- klass = self.cmdclass.get(cmd)
- if not klass:
- klass = self.get_command_class(cmd)
+ cls = self.cmdclass.get(cmd)
+ if not cls:
+ cls = self.get_command_class(cmd)
try:
- description = klass.description
+ description = cls.description
except AttributeError:
description = "(no description available)"
@@ -756,11 +758,11 @@ Common commands: (see '--help-commands' for more)
rv = []
for cmd in (std_commands + extra_commands):
- klass = self.cmdclass.get(cmd)
- if not klass:
- klass = self.get_command_class(cmd)
+ cls = self.cmdclass.get(cmd)
+ if not cls:
+ cls = self.get_command_class(cmd)
try:
- description = klass.description
+ description = cls.description
except AttributeError:
description = "(no description available)"
rv.append((cmd, description))
@@ -792,13 +794,13 @@ Common commands: (see '--help-commands' for more)
Raises DistutilsModuleError if the expected module could not be
found, or if that module does not define the expected class.
"""
- klass = self.cmdclass.get(command)
- if klass:
- return klass
+ cls = self.cmdclass.get(command)
+ if cls:
+ return cls
for pkgname in self.get_command_packages():
module_name = "%s.%s" % (pkgname, command)
- klass_name = command
+ class_name = command
try:
__import__ (module_name)
@@ -807,14 +809,14 @@ Common commands: (see '--help-commands' for more)
continue
try:
- klass = getattr(module, klass_name)
+ cls = getattr(module, class_name)
except AttributeError:
raise DistutilsModuleError, \
"invalid command '%s' (no class '%s' in module '%s')" \
- % (command, klass_name, module_name)
+ % (command, class_name, module_name)
- self.cmdclass[command] = klass
- return klass
+ self.cmdclass[command] = cls
+ return cls
raise DistutilsModuleError("invalid command '%s'" % command)
@@ -830,8 +832,8 @@ Common commands: (see '--help-commands' for more)
log.debug("Distribution.get_command_obj(): " \
"creating '%s' command object" % command)
- klass = self.get_command_class(command)
- cmd_obj = self.command_obj[command] = klass(self)
+ cls = self.get_command_class(command)
+ cmd_obj = self.command_obj[command] = cls(self)
self.have_run[command] = 0
# Set any options that were supplied in config files
@@ -887,7 +889,7 @@ Common commands: (see '--help-commands' for more)
except ValueError, msg:
raise DistutilsOptionError, msg
- def reinitialize_command(self, command, reinit_subcommands=0):
+ def get_reinitialized_command(self, command, reinit_subcommands=0):
"""Reinitializes a command to the state it was in when first
returned by 'get_command_obj()': ie., initialized but not yet
finalized. This provides the opportunity to sneak option
@@ -922,7 +924,7 @@ Common commands: (see '--help-commands' for more)
if reinit_subcommands:
for sub in command.get_sub_commands():
- self.reinitialize_command(sub, reinit_subcommands)
+ self.get_reinitialized_command(sub, reinit_subcommands)
return command
diff --git a/src/distutils2/extension.py b/src/distutils2/extension.py
index 430b919..05ccd20 100644
--- a/src/distutils2/extension.py
+++ b/src/distutils2/extension.py
@@ -5,14 +5,8 @@ modules in setup scripts."""
__revision__ = "$Id: extension.py 77704 2010-01-23 09:23:15Z tarek.ziade $"
-import os
import warnings
-try:
- import sysconfig
-except ImportError:
- from distutils2._backport import sysconfig
-
# This class is really only used by the "build_ext" command, so it might
# make sense to put it in distutils.command.build_ext. However, that
# module is already big enough, and I want to make this class a bit more
@@ -23,7 +17,7 @@ except ImportError:
# import that large-ish module (indirectly, through distutils.core) in
# order to do anything.
-class Extension:
+class Extension(object):
"""Just a collection of attributes that describes an extension
module and everything needed to build it (hopefully in a portable
way, but there are hooks that let you be as unportable as you need).
diff --git a/src/distutils2/fancy_getopt.py b/src/distutils2/fancy_getopt.py
index 1382b24..e0ac736 100644
--- a/src/distutils2/fancy_getopt.py
+++ b/src/distutils2/fancy_getopt.py
@@ -30,7 +30,7 @@ neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))
# (for use as attributes of some object).
longopt_xlate = string.maketrans('-', '_')
-class FancyGetopt:
+class FancyGetopt(object):
"""Wrapper around the standard 'getopt()' module that provides some
handy extra functionality:
* short and long options are tied together
@@ -473,7 +473,7 @@ def translate_longopt(opt):
return string.translate(opt, longopt_xlate)
-class OptionDummy:
+class OptionDummy(object):
"""Dummy class just used as a place to hold command-line option
values as instance attributes."""
diff --git a/src/distutils2/log.py b/src/distutils2/log.py
index 7588570..1293bf4 100644
--- a/src/distutils2/log.py
+++ b/src/distutils2/log.py
@@ -11,14 +11,14 @@ FATAL = 5
import sys
-class Log:
+class Log(object):
def __init__(self, threshold=WARN):
self.threshold = threshold
def _log(self, level, msg, args):
if level not in (DEBUG, INFO, WARN, ERROR, FATAL):
- raise ValueError('%s wrong log level' % str(level))
+ raise ValueError('%s wrong log level' % level)
if level >= self.threshold:
if args:
diff --git a/src/distutils2/metadata.py b/src/distutils2/metadata.py
index 3d04ac4..55f1020 100644
--- a/src/distutils2/metadata.py
+++ b/src/distutils2/metadata.py
@@ -105,7 +105,6 @@ def _best_version(fields):
keys = fields.keys()
possible_versions = ['1.0', '1.1', '1.2']
-
# first let's try to see if a field is not part of one of the version
for key in keys:
if key not in _241_FIELDS and '1.0' in possible_versions:
@@ -128,9 +127,9 @@ def _best_version(fields):
raise MetadataConflictError('You used incompatible 1.1 and 1.2 fields')
# we have the choice, either 1.0, or 1.2
- # - 1.0 has a broken Summary field but work with all tools
+ # - 1.0 has a broken Summary field but works with all tools
# - 1.1 is to avoid
- # - 1.2 fixes Summary but is not spreaded yet
+ # - 1.2 fixes Summary but is not widespread yet
if not is_1_1 and not is_1_2:
# we couldn't find any specific marker
if PKG_INFO_PREFERRED_VERSION in possible_versions:
@@ -185,14 +184,16 @@ _UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
class DistributionMetadata(object):
"""Distribution meta-data class (1.0 or 1.2).
"""
- def __init__(self, path=None, platform_dependant=False,
- execution_context=None):
+ def __init__(self, path=None, platform_dependent=False,
+ execution_context=None, fileobj=None):
self._fields = {}
self.version = None
self.docutils_support = _HAS_DOCUTILS
- self.platform_dependant = platform_dependant
+ self.platform_dependent = platform_dependent
if path is not None:
self.read(path)
+ elif fileobj is not None:
+ self.read_file(fileobj)
self.execution_context = execution_context
def _set_best_version(self):
@@ -261,7 +262,7 @@ class DistributionMetadata(object):
return reporter.messages
def _platform(self, value):
- if not self.platform_dependant or ';' not in value:
+ if not self.platform_dependent or ';' not in value:
return True, value
value, marker = value.split(';')
return _interpret(marker, self.execution_context), value
@@ -516,7 +517,7 @@ class _Operation(object):
raise NameError(value)
def _nonsense_op(self):
- msg = 'This operation is not supported : "%s"' % str(self)
+ msg = 'This operation is not supported : "%s"' % self
raise SyntaxError(msg)
def __call__(self):
@@ -633,4 +634,3 @@ def _interpret(marker, execution_context=None):
operations = _CHAIN(execution_context)
tokenize(StringIO(marker).readline, operations.eat)
return operations.result()
-
diff --git a/src/distutils2/mkpkg.py b/src/distutils2/mkpkg.py
index 42ea7fe..3fe82e3 100755
--- a/src/distutils2/mkpkg.py
+++ b/src/distutils2/mkpkg.py
@@ -675,7 +675,7 @@ def buildTroveDict(troveList):
troveDict = buildTroveDict(troveList)
-class SetupClass:
+class SetupClass(object):
def __init__(self):
self.config = None
self.classifierDict = {}
@@ -717,14 +717,16 @@ class SetupClass:
def inspectFile(self, path):
fp = open(path, 'r')
- 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':
- self.classifierDict['Programming Language :: Python :: 3'] = 1
- else:
- self.classifierDict['Programming Language :: Python :: 2'] = 1
- fp.close()
+ try:
+ 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':
+ self.classifierDict['Programming Language :: Python :: 3'] = 1
+ else:
+ self.classifierDict['Programming Language :: Python :: 2'] = 1
+ finally:
+ fp.close()
def inspectDirectory(self):
@@ -885,38 +887,33 @@ Status''', required = False)
if os.path.exists('setup.py'): shutil.move('setup.py', 'setup.py.old')
fp = open('setup.py', 'w')
- fp.write('#!/usr/bin/env python\n\n')
- fp.write('from distutils2.core import setup\n\n')
-
- fp.write('from sys import version\n')
- fp.write('if version < \'2.2.3\':\n')
- fp.write(' from distutils2.dist import DistributionMetadata\n')
- fp.write(' DistributionMetadata.classifier = None\n')
- fp.write(' DistributionMetadata.download_url = None\n')
-
- fp.write('setup(name = %s,\n' % repr(self.setupData['name']))
- fp.write(' version = %s,\n' % repr(self.setupData['version']))
- fp.write(' description = %s,\n'
- % repr(self.setupData['description']))
- fp.write(' author = %s,\n' % repr(self.setupData['author']))
- fp.write(' author_email = %s,\n'
- % repr(self.setupData['author_email']))
- if self.setupData['url']:
- fp.write(' url = %s,\n' % repr(self.setupData['url']))
- if self.setupData['classifier']:
- fp.write(' classifier = [\n')
- for classifier in sorted(self.setupData['classifier'].keys()):
- fp.write(' %s,\n' % repr(classifier))
- fp.write(' ],\n')
- if self.setupData['packages']:
- fp.write(' packages = %s,\n'
- % repr(self._dotted_packages(self.setupData['packages'])))
- fp.write(' package_dir = %s,\n'
- % repr(self.setupData['packages']))
- fp.write(' #scripts = [\'path/to/script\']\n')
-
- fp.write(' )\n')
- fp.close()
+ try:
+ fp.write('#!/usr/bin/env python\n\n')
+ fp.write('from distutils2.core import setup\n\n')
+ fp.write('setup(name=%s,\n' % repr(self.setupData['name']))
+ fp.write(' version=%s,\n' % repr(self.setupData['version']))
+ fp.write(' description=%s,\n'
+ % repr(self.setupData['description']))
+ fp.write(' author=%s,\n' % repr(self.setupData['author']))
+ fp.write(' author_email=%s,\n'
+ % repr(self.setupData['author_email']))
+ if self.setupData['url']:
+ fp.write(' url=%s,\n' % repr(self.setupData['url']))
+ if self.setupData['classifier']:
+ fp.write(' classifier=[\n')
+ for classifier in sorted(self.setupData['classifier'].keys()):
+ fp.write(' %s,\n' % repr(classifier))
+ fp.write(' ],\n')
+ if self.setupData['packages']:
+ fp.write(' packages=%s,\n'
+ % repr(self._dotted_packages(self.setupData['packages'])))
+ fp.write(' package_dir=%s,\n'
+ % repr(self.setupData['packages']))
+ fp.write(' #scripts=[\'path/to/script\']\n')
+
+ fp.write(' )\n')
+ finally:
+ fp.close()
os.chmod('setup.py', 0755)
print 'Wrote "setup.py".'
diff --git a/src/distutils2/pypi/__init__.py b/src/distutils2/pypi/__init__.py
index 0872262..88efa14 100644
--- a/src/distutils2/pypi/__init__.py
+++ b/src/distutils2/pypi/__init__.py
@@ -3,5 +3,6 @@
Package containing ways to interact with the PyPI APIs.
"""
-__all__ = ['package_index',
+__all__ = ['simple',
+ 'dist',
]
diff --git a/src/distutils2/pypi/dist.py b/src/distutils2/pypi/dist.py
new file mode 100644
index 0000000..418186e
--- /dev/null
+++ b/src/distutils2/pypi/dist.py
@@ -0,0 +1,315 @@
+"""distutils2.pypi.dist
+
+Provides the PyPIDistribution class thats represents a distribution retrieved
+on PyPI.
+"""
+import re
+import urlparse
+import urllib
+import tempfile
+from operator import attrgetter
+
+try:
+ import hashlib
+except ImportError:
+ from distutils2._backport import hashlib
+
+from distutils2.version import suggest_normalized_version, NormalizedVersion
+from distutils2.pypi.errors import HashDoesNotMatch, UnsupportedHashName
+
+EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz .egg".split()
+MD5_HASH = re.compile(r'^.*#md5=([a-f0-9]+)$')
+
+
+class PyPIDistribution(object):
+ """Represents a distribution retrieved from PyPI.
+
+ This is a simple container for various attributes as name, version,
+ downloaded_location, url etc.
+
+ The PyPIDistribution class is used by the pypi.*Index class to return
+ information about distributions.
+ """
+
+ @classmethod
+ def from_url(cls, url, probable_dist_name=None, is_external=True):
+ """Build a Distribution from a url archive (egg or zip or tgz).
+
+ :param url: complete url of the distribution
+ :param probable_dist_name: A probable name of the distribution.
+ :param is_external: Tell if the url commes from an index or from
+ an external URL.
+ """
+ # if the url contains a md5 hash, get it.
+ md5_hash = None
+ match = MD5_HASH.match(url)
+ if match is not None:
+ md5_hash = match.group(1)
+ # remove the hash
+ url = url.replace("#md5=%s" % md5_hash, "")
+
+ # parse the archive name to find dist name and version
+ archive_name = urlparse.urlparse(url)[2].split('/')[-1]
+ extension_matched = False
+ # remove the extension from the name
+ for ext in EXTENSIONS:
+ if archive_name.endswith(ext):
+ archive_name = archive_name[:-len(ext)]
+ extension_matched = True
+
+ name, version = split_archive_name(archive_name)
+ if extension_matched is True:
+ return PyPIDistribution(name, version, url=url, url_hashname="md5",
+ url_hashval=md5_hash,
+ url_is_external=is_external)
+
+ def __init__(self, name, version, type=None, url=None, url_hashname=None,
+ url_hashval=None, url_is_external=True):
+ """Create a new instance of PyPIDistribution.
+
+ :param name: the name of the distribution
+ :param version: the version of the distribution
+ :param type: the type of the dist (eg. source, bin-*, etc.)
+ :param url: URL where we found this distribution
+ :param url_hashname: the name of the hash we want to use. Refer to the
+ hashlib.new documentation for more information.
+ :param url_hashval: the hash value.
+ :param url_is_external: we need to know if the provided url comes from an
+ index browsing, or from an external resource.
+
+ """
+ self.name = name
+ self.version = NormalizedVersion(version)
+ self.type = type
+ # set the downloaded path to None by default. The goal here
+ # is to not download distributions multiple times
+ self.downloaded_location = None
+ # We store urls in dict, because we need to have a bit more informations
+ # than the simple URL. It will be used later to find the good url to
+ # use.
+ # We have two _url* attributes: _url and _urls. _urls contains a list of
+ # dict for the different urls, and _url contains the choosen url, in
+ # order to dont make the selection process multiple times.
+ self._urls = []
+ self._url = None
+ self.add_url(url, url_hashname, url_hashval, url_is_external)
+
+ def add_url(self, url, hashname=None, hashval=None, is_external=True):
+ """Add a new url to the list of urls"""
+ if hashname is not None:
+ try:
+ hashlib.new(hashname)
+ except ValueError:
+ raise UnsupportedHashName(hashname)
+
+ self._urls.append({
+ 'url': url,
+ 'hashname': hashname,
+ 'hashval': hashval,
+ 'is_external': is_external,
+ })
+ # reset the url selection process
+ self._url = None
+
+ @property
+ def url(self):
+ """Pick up the right url for the list of urls in self.urls"""
+ # We return internal urls over externals.
+ # If there is more than one internal or external, return the first
+ # one.
+ if self._url is None:
+ if len(self._urls) > 1:
+ internals_urls = [u for u in self._urls \
+ if u['is_external'] == False]
+ if len(internals_urls) >= 1:
+ self._url = internals_urls[0]
+ if self._url is None:
+ self._url = self._urls[0]
+ return self._url
+
+ @property
+ def is_source(self):
+ """return if the distribution is a source one or not"""
+ return self.type == 'source'
+
+ @property
+ def is_final(self):
+ """proxy to version.is_final"""
+ return self.version.is_final
+
+ def download(self, path=None):
+ """Download the distribution to a path, and return it.
+
+ If the path is given in path, use this, otherwise, generates a new one
+ """
+ if path is None:
+ path = tempfile.mkdtemp()
+
+ # if we do not have downloaded it yet, do it.
+ if self.downloaded_location is None:
+ url = self.url['url']
+ archive_name = urlparse.urlparse(url)[2].split('/')[-1]
+ filename, headers = urllib.urlretrieve(url,
+ path + "/" + archive_name)
+ self.downloaded_location = filename
+ self._check_md5(filename)
+ return self.downloaded_location
+
+ def _check_md5(self, filename):
+ """Check that the md5 checksum of the given file matches the one in
+ url param"""
+ hashname = self.url['hashname']
+ expected_hashval = self.url['hashval']
+ if not None in (expected_hashval, hashname):
+ f = open(filename)
+ hashval = hashlib.new(hashname)
+ hashval.update(f.read())
+ if hashval.hexdigest() != expected_hashval:
+ raise HashDoesNotMatch("got %s instead of %s"
+ % (hashval.hexdigest(), expected_hashval))
+
+ def __repr__(self):
+ return "%s %s %s %s" \
+ % (self.__class__.__name__, self.name, self.version,
+ self.type or "")
+
+ def _check_is_comparable(self, other):
+ if not isinstance(other, PyPIDistribution):
+ raise TypeError("cannot compare %s and %s"
+ % (type(self).__name__, type(other).__name__))
+ elif self.name != other.name:
+ raise TypeError("cannot compare %s and %s"
+ % (self.name, other.name))
+
+ def __eq__(self, other):
+ self._check_is_comparable(other)
+ return self.version == other.version
+
+ def __lt__(self, other):
+ self._check_is_comparable(other)
+ return self.version < other.version
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __gt__(self, other):
+ return not (self.__lt__(other) or self.__eq__(other))
+
+ def __le__(self, other):
+ return self.__eq__(other) or self.__lt__(other)
+
+ def __ge__(self, other):
+ return self.__eq__(other) or self.__gt__(other)
+
+ # See http://docs.python.org/reference/datamodel#object.__hash__
+ __hash__ = object.__hash__
+
+
+class PyPIDistributions(list):
+ """A container of PyPIDistribution objects.
+
+ Contains methods and facilities to sort and filter distributions.
+ """
+ def __init__(self, list=[]):
+ # To disable the ability to pass lists on instanciation
+ super(PyPIDistributions, self).__init__()
+ for item in list:
+ self.append(item)
+
+ def filter(self, predicate):
+ """Filter the distributions and return a subset of distributions that
+ match the given predicate
+ """
+ return PyPIDistributions(
+ [dist for dist in self if dist.name == predicate.name and
+ predicate.match(dist.version)])
+
+ def get_last(self, predicate, prefer_source=None, prefer_final=None):
+ """Return the most up to date version, that satisfy the given
+ predicate
+ """
+ distributions = self.filter(predicate)
+ distributions.sort_distributions(prefer_source, prefer_final, reverse=True)
+ return distributions[0]
+
+ def get_same_name_and_version(self):
+ """Return lists of PyPIDistribution objects that refer to the same
+ name and version number. This do not consider the type (source, binary,
+ etc.)"""
+ processed = []
+ duplicates = []
+ for dist in self:
+ if (dist.name, dist.version) not in processed:
+ processed.append((dist.name, dist.version))
+ found_duplicates = [d for d in self if d.name == dist.name and
+ d.version == dist.version]
+ if len(found_duplicates) > 1:
+ duplicates.append(found_duplicates)
+ return duplicates
+
+ def append(self, o):
+ """Append a new distribution to the list.
+
+ If a distribution with the same name and version exists, just grab the
+ URL informations and add a new new url for the existing one.
+ """
+ similar_dists = [d for d in self if d.name == o.name and
+ d.version == o.version and d.type == o.type]
+ if len(similar_dists) > 0:
+ dist = similar_dists[0]
+ dist.add_url(**o.url)
+ else:
+ super(PyPIDistributions, self).append(o)
+
+ def sort_distributions(self, prefer_source=True, prefer_final=False,
+ reverse=True, *args, **kwargs):
+ """order the results with the given properties"""
+
+ sort_by = []
+ if prefer_final:
+ sort_by.append("is_final")
+ sort_by.append("version")
+
+ if prefer_source:
+ sort_by.append("is_source")
+
+ super(PyPIDistributions, self).sort(
+ key=lambda i: [getattr(i, arg) for arg in sort_by],
+ reverse=reverse, *args, **kwargs)
+
+
+def split_archive_name(archive_name, probable_name=None):
+ """Split an archive name into two parts: name and version.
+
+ Return the tuple (name, version)
+ """
+ # Try to determine wich part is the name and wich is the version using the
+ # "-" separator. Take the larger part to be the version number then reduce
+ # if this not works.
+ def eager_split(str, maxsplit=2):
+ # split using the "-" separator
+ splits = str.rsplit("-", maxsplit)
+ name = splits[0]
+ version = "-".join(splits[1:])
+ if version.startswith("-"):
+ version = version[1:]
+ if suggest_normalized_version(version) is None and maxsplit >= 0:
+ # we dont get a good version number: recurse !
+ return eager_split(str, maxsplit - 1)
+ else:
+ return (name, version)
+ if probable_name is not None:
+ probable_name = probable_name.lower()
+ name = None
+ if probable_name is not None and probable_name in archive_name:
+ # we get the name from probable_name, if given.
+ name = probable_name
+ version = archive_name.lstrip(name)
+ else:
+ name, version = eager_split(archive_name)
+
+ version = suggest_normalized_version(version)
+ if version != "" and name != "":
+ return (name.lower(), version)
+ else:
+ raise CantParseArchiveName(archive_name)
diff --git a/src/distutils2/pypi/errors.py b/src/distutils2/pypi/errors.py
new file mode 100644
index 0000000..c9b232d
--- /dev/null
+++ b/src/distutils2/pypi/errors.py
@@ -0,0 +1,33 @@
+"""distutils2.pypi.errors
+
+All errors and exceptions raised by PyPiIndex classes.
+"""
+from distutils2.errors import DistutilsError
+
+
+class PyPIError(DistutilsError):
+ """The base class for errors of the pypi python package."""
+
+
+class DistributionNotFound(PyPIError):
+ """No distribution match the given requirements."""
+
+
+class CantParseArchiveName(PyPIError):
+ """An archive name can't be parsed to find distribution name and version"""
+
+
+class DownloadError(PyPIError):
+ """An error has occurs while downloading"""
+
+
+class HashDoesNotMatch(DownloadError):
+ """Compared hashes does not match"""
+
+
+class UnsupportedHashName(PyPIError):
+ """A unsupported hashname has been used"""
+
+
+class UnableToDownload(PyPIError):
+ """All mirrors have been tried, without success"""
diff --git a/src/distutils2/pypi/pkg_resources.py b/src/distutils2/pypi/pkg_resources.py
deleted file mode 100644
index 30dbc18..0000000
--- a/src/distutils2/pypi/pkg_resources.py
+++ /dev/null
@@ -1,2693 +0,0 @@
-"""Package resource API
---------------------
-
-A resource is a logical file contained within a package, or a logical
-subdirectory thereof. The package resource API expects resource names
-to have their path parts separated with ``/``, *not* whatever the local
-path separator is. Do not use os.path operations to manipulate resource
-names being passed into the API.
-
-The package resource API is designed to work with normal filesystem packages,
-.egg files, and unpacked .egg files. It can also work in a limited way with
-.zip files and with custom PEP 302 loaders that support the ``get_data()``
-method.
-"""
-
-import sys, os, zipimport, time, re, imp, types
-from urlparse import urlparse, urlunparse
-
-try:
- frozenset
-except NameError:
- from sets import ImmutableSet as frozenset
-
-# capture these to bypass sandboxing
-from os import utime
-try:
- from os import mkdir, rename, unlink
- WRITE_SUPPORT = True
-except ImportError:
- # no write support, probably under GAE
- WRITE_SUPPORT = False
-
-from os import open as os_open
-from os.path import isdir, split
-
-# This marker is used to simplify the process that checks is the
-# setuptools package was installed by the Setuptools project
-# or by the Distribute project, in case Setuptools creates
-# a distribution with the same version.
-#
-# The bootstrapping script for instance, will check if this
-# attribute is present to decide wether to reinstall the package
-_distribute = True
-
-def _bypass_ensure_directory(name, mode=0777):
- # Sandbox-bypassing version of ensure_directory()
- if not WRITE_SUPPORT:
- raise IOError('"os.mkdir" not supported on this platform.')
- dirname, filename = split(name)
- if dirname and filename and not isdir(dirname):
- _bypass_ensure_directory(dirname)
- mkdir(dirname, mode)
-
-
-
-
-
-
-
-
-def get_supported_platform():
- """Return this platform's maximum compatible version.
-
- distutils.util.get_platform() normally reports the minimum version
- of Mac OS X that would be required to *use* extensions produced by
- distutils. But what we want when checking compatibility is to know the
- version of Mac OS X that we are *running*. To allow usage of packages that
- explicitly require a newer version of Mac OS X, we must also know the
- current version of the OS.
-
- If this condition occurs for any other platform with a version in its
- platform strings, this function should be extended accordingly.
- """
- plat = get_build_platform(); m = macosVersionString.match(plat)
- if m is not None and sys.platform == "darwin":
- try:
- plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3))
- except ValueError:
- pass # not Mac OS X
- return plat
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-__all__ = [
- # Basic resource access and distribution/entry point discovery
- 'require', 'run_script', 'get_provider', 'get_distribution',
- 'load_entry_point', 'get_entry_map', 'get_entry_info', 'iter_entry_points',
- 'resource_string', 'resource_stream', 'resource_filename',
- 'resource_listdir', 'resource_exists', 'resource_isdir',
-
- # Environmental control
- 'declare_namespace', 'working_set', 'add_activation_listener',
- 'find_distributions', 'set_extraction_path', 'cleanup_resources',
- 'get_default_cache',
-
- # Primary implementation classes
- 'Environment', 'WorkingSet', 'ResourceManager',
- 'Distribution', 'Requirement', 'EntryPoint',
-
- # Exceptions
- 'ResolutionError','VersionConflict','DistributionNotFound','UnknownExtra',
- 'ExtractionError',
-
- # Parsing functions and string utilities
- 'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
- 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
- 'safe_extra', 'to_filename',
-
- # filesystem utilities
- 'ensure_directory', 'normalize_path',
-
- # Distribution "precedence" constants
- 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST',
-
- # "Provider" interfaces, implementations, and registration/lookup APIs
- 'IMetadataProvider', 'IResourceProvider', 'FileMetadata',
- 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',
- 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',
- 'register_finder', 'register_namespace_handler', 'register_loader_type',
- 'fixup_namespace_packages', 'get_importer',
-
- # Deprecated/backward compatibility only
- 'run_main', 'AvailableDistributions',
-]
-class ResolutionError(Exception):
- """Abstract base for dependency resolution errors"""
- def __repr__(self):
- return self.__class__.__name__+repr(self.args)
-
-class VersionConflict(ResolutionError):
- """An already-installed version conflicts with the requested version"""
-
-class DistributionNotFound(ResolutionError):
- """A requested distribution was not found"""
-
-class UnknownExtra(ResolutionError):
- """Distribution doesn't have an "extra feature" of the given name"""
-_provider_factories = {}
-
-PY_MAJOR = sys.version[:3]
-EGG_DIST = 3
-BINARY_DIST = 2
-SOURCE_DIST = 1
-CHECKOUT_DIST = 0
-DEVELOP_DIST = -1
-
-def register_loader_type(loader_type, provider_factory):
- """Register `provider_factory` to make providers for `loader_type`
-
- `loader_type` is the type or class of a PEP 302 ``module.__loader__``,
- and `provider_factory` is a function that, passed a *module* object,
- returns an ``IResourceProvider`` for that module.
- """
- _provider_factories[loader_type] = provider_factory
-
-def get_provider(moduleOrReq):
- """Return an IResourceProvider for the named module or requirement"""
- if isinstance(moduleOrReq,Requirement):
- return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
- try:
- module = sys.modules[moduleOrReq]
- except KeyError:
- __import__(moduleOrReq)
- module = sys.modules[moduleOrReq]
- loader = getattr(module, '__loader__', None)
- return _find_adapter(_provider_factories, loader)(module)
-
-def _macosx_vers(_cache=[]):
- if not _cache:
- import platform
- version = platform.mac_ver()[0]
- # fallback for MacPorts
- if version == '':
- import plistlib
- plist = '/System/Library/CoreServices/SystemVersion.plist'
- if os.path.exists(plist):
- if hasattr(plistlib, 'readPlist'):
- plist_content = plistlib.readPlist(plist)
- if 'ProductVersion' in plist_content:
- version = plist_content['ProductVersion']
-
- _cache.append(version.split('.'))
- return _cache[0]
-
-def _macosx_arch(machine):
- return {'PowerPC':'ppc', 'Power_Macintosh':'ppc'}.get(machine,machine)
-
-def get_build_platform():
- """Return this platform's string for platform-specific distributions
-
- XXX Currently this is the same as ``distutils.util.get_platform()``, but it
- needs some hacks for Linux and Mac OS X.
- """
- try:
- from distutils.util import get_platform
- except ImportError:
- from sysconfig import get_platform
-
- plat = get_platform()
- if sys.platform == "darwin" and not plat.startswith('macosx-'):
- try:
- version = _macosx_vers()
- machine = os.uname()[4].replace(" ", "_")
- return "macosx-%d.%d-%s" % (int(version[0]), int(version[1]),
- _macosx_arch(machine))
- except ValueError:
- # if someone is running a non-Mac darwin system, this will fall
- # through to the default implementation
- pass
- return plat
-
-macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
-darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
-get_platform = get_build_platform # XXX backward compat
-
-def compatible_platforms(provided,required):
- """Can code for the `provided` platform run on the `required` platform?
-
- Returns true if either platform is ``None``, or the platforms are equal.
-
- XXX Needs compatibility checks for Linux and other unixy OSes.
- """
- if provided is None or required is None or provided==required:
- return True # easy case
-
- # Mac OS X special cases
- reqMac = macosVersionString.match(required)
- if reqMac:
- provMac = macosVersionString.match(provided)
-
- # is this a Mac package?
- if not provMac:
- # this is backwards compatibility for packages built before
- # setuptools 0.6. All packages built after this point will
- # use the new macosx designation.
- provDarwin = darwinVersionString.match(provided)
- if provDarwin:
- dversion = int(provDarwin.group(1))
- macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))
- if dversion == 7 and macosversion >= "10.3" or \
- dversion == 8 and macosversion >= "10.4":
-
- #import warnings
- #warnings.warn("Mac eggs should be rebuilt to "
- # "use the macosx designation instead of darwin.",
- # category=DeprecationWarning)
- return True
- return False # egg isn't macosx or legacy darwin
-
- # are they the same major version and machine type?
- if provMac.group(1) != reqMac.group(1) or \
- provMac.group(3) != reqMac.group(3):
- return False
-
-
-
- # is the required OS major update >= the provided one?
- if int(provMac.group(2)) > int(reqMac.group(2)):
- return False
-
- return True
-
- # XXX Linux and other platforms' special cases should go here
- return False
-
-
-def run_script(dist_spec, script_name):
- """Locate distribution `dist_spec` and run its `script_name` script"""
- ns = sys._getframe(1).f_globals
- name = ns['__name__']
- ns.clear()
- ns['__name__'] = name
- require(dist_spec)[0].run_script(script_name, ns)
-
-run_main = run_script # backward compatibility
-
-def get_distribution(dist):
- """Return a current distribution object for a Requirement or string"""
- if isinstance(dist,basestring): dist = Requirement.parse(dist)
- if isinstance(dist,Requirement): dist = get_provider(dist)
- if not isinstance(dist,Distribution):
- raise TypeError("Expected string, Requirement, or Distribution", dist)
- return dist
-
-def load_entry_point(dist, group, name):
- """Return `name` entry point of `group` for `dist` or raise ImportError"""
- return get_distribution(dist).load_entry_point(group, name)
-
-def get_entry_map(dist, group=None):
- """Return the entry point map for `group`, or the full entry map"""
- return get_distribution(dist).get_entry_map(group)
-
-def get_entry_info(dist, group, name):
- """Return the EntryPoint object for `group`+`name`, or ``None``"""
- return get_distribution(dist).get_entry_info(group, name)
-
-
-class IMetadataProvider:
-
- def has_metadata(name):
- """Does the package's distribution contain the named metadata?"""
-
- def get_metadata(name):
- """The named metadata resource as a string"""
-
- def get_metadata_lines(name):
- """Yield named metadata resource as list of non-blank non-comment lines
-
- Leading and trailing whitespace is stripped from each line, and lines
- with ``#`` as the first non-blank character are omitted."""
-
- def metadata_isdir(name):
- """Is the named metadata a directory? (like ``os.path.isdir()``)"""
-
- def metadata_listdir(name):
- """List of metadata names in the directory (like ``os.listdir()``)"""
-
- def run_script(script_name, namespace):
- """Execute the named script in the supplied namespace dictionary"""
-
-
-
-
-
-
-
-
-
-
-class IResourceProvider(IMetadataProvider):
- """An object that provides access to package resources"""
-
- def get_resource_filename(manager, resource_name):
- """Return a true filesystem path for `resource_name`
-
- `manager` must be an ``IResourceManager``"""
-
- def get_resource_stream(manager, resource_name):
- """Return a readable file-like object for `resource_name`
-
- `manager` must be an ``IResourceManager``"""
-
- def get_resource_string(manager, resource_name):
- """Return a string containing the contents of `resource_name`
-
- `manager` must be an ``IResourceManager``"""
-
- def has_resource(resource_name):
- """Does the package contain the named resource?"""
-
- def resource_isdir(resource_name):
- """Is the named resource a directory? (like ``os.path.isdir()``)"""
-
- def resource_listdir(resource_name):
- """List of resource names in the directory (like ``os.listdir()``)"""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class WorkingSet(object):
- """A collection of active distributions on sys.path (or a similar list)"""
-
- def __init__(self, entries=None):
- """Create working set from list of path entries (default=sys.path)"""
- self.entries = []
- self.entry_keys = {}
- self.by_key = {}
- self.callbacks = []
-
- if entries is None:
- entries = sys.path
-
- for entry in entries:
- self.add_entry(entry)
-
-
- def add_entry(self, entry):
- """Add a path item to ``.entries``, finding any distributions on it
-
- ``find_distributions(entry,True)`` is used to find distributions
- corresponding to the path entry, and they are added. `entry` is
- always appended to ``.entries``, even if it is already present.
- (This is because ``sys.path`` can contain the same value more than
- once, and the ``.entries`` of the ``sys.path`` WorkingSet should always
- equal ``sys.path``.)
- """
- self.entry_keys.setdefault(entry, [])
- self.entries.append(entry)
- for dist in find_distributions(entry, True):
- self.add(dist, entry, False)
-
-
- def __contains__(self,dist):
- """True if `dist` is the active distribution for its project"""
- return self.by_key.get(dist.key) == dist
-
-
-
-
-
- def find(self, req):
- """Find a distribution matching requirement `req`
-
- If there is an active distribution for the requested project, this
- returns it as long as it meets the version requirement specified by
- `req`. But, if there is an active distribution for the project and it
- does *not* meet the `req` requirement, ``VersionConflict`` is raised.
- If there is no active distribution for the requested project, ``None``
- is returned.
- """
- dist = self.by_key.get(req.key)
- if dist is not None and dist not in req:
- raise VersionConflict(dist,req) # XXX add more info
- else:
- return dist
-
- def iter_entry_points(self, group, name=None):
- """Yield entry point objects from `group` matching `name`
-
- If `name` is None, yields all entry points in `group` from all
- distributions in the working set, otherwise only ones matching
- both `group` and `name` are yielded (in distribution order).
- """
- for dist in self:
- entries = dist.get_entry_map(group)
- if name is None:
- for ep in entries.values():
- yield ep
- elif name in entries:
- yield entries[name]
-
- def run_script(self, requires, script_name):
- """Locate distribution for `requires` and run `script_name` script"""
- ns = sys._getframe(1).f_globals
- name = ns['__name__']
- ns.clear()
- ns['__name__'] = name
- self.require(requires)[0].run_script(script_name, ns)
-
-
-
- def __iter__(self):
- """Yield distributions for non-duplicate projects in the working set
-
- The yield order is the order in which the items' path entries were
- added to the working set.
- """
- seen = {}
- for item in self.entries:
- for key in self.entry_keys[item]:
- if key not in seen:
- seen[key]=1
- yield self.by_key[key]
-
- def add(self, dist, entry=None, insert=True):
- """Add `dist` to working set, associated with `entry`
-
- If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
- On exit from this routine, `entry` is added to the end of the working
- set's ``.entries`` (if it wasn't already present).
-
- `dist` is only added to the working set if it's for a project that
- doesn't already have a distribution in the set. If it's added, any
- callbacks registered with the ``subscribe()`` method will be called.
- """
- if insert:
- dist.insert_on(self.entries, entry)
-
- if entry is None:
- entry = dist.location
- keys = self.entry_keys.setdefault(entry,[])
- keys2 = self.entry_keys.setdefault(dist.location,[])
- if dist.key in self.by_key:
- return # ignore hidden distros
-
- self.by_key[dist.key] = dist
- if dist.key not in keys:
- keys.append(dist.key)
- if dist.key not in keys2:
- keys2.append(dist.key)
- self._added_new(dist)
-
- def resolve(self, requirements, env=None, installer=None, replacement=True):
- """List all distributions needed to (recursively) meet `requirements`
-
- `requirements` must be a sequence of ``Requirement`` objects. `env`,
- if supplied, should be an ``Environment`` instance. If
- not supplied, it defaults to all distributions available within any
- entry or distribution in the working set. `installer`, if supplied,
- will be invoked with each requirement that cannot be met by an
- already-installed distribution; it should return a ``Distribution`` or
- ``None``.
- """
-
- requirements = list(requirements)[::-1] # set up the stack
- processed = {} # set of processed requirements
- best = {} # key -> dist
- to_activate = []
-
- while requirements:
- req = requirements.pop(0) # process dependencies breadth-first
- if _override_setuptools(req) and replacement:
- req = Requirement.parse('distribute')
-
- if req in processed:
- # Ignore cyclic or redundant dependencies
- continue
- dist = best.get(req.key)
- if dist is None:
- # Find the best distribution and add it to the map
- dist = self.by_key.get(req.key)
- if dist is None:
- if env is None:
- env = Environment(self.entries)
- dist = best[req.key] = env.best_match(req, self, installer)
- if dist is None:
- #msg = ("The '%s' distribution was not found on this "
- # "system, and is required by this application.")
- #raise DistributionNotFound(msg % req)
-
- # unfortunately, zc.buildout uses a str(err)
- # to get the name of the distribution here..
- raise DistributionNotFound(req)
- to_activate.append(dist)
- if dist not in req:
- # Oops, the "best" so far conflicts with a dependency
- raise VersionConflict(dist,req) # XXX put more info here
- requirements.extend(dist.requires(req.extras)[::-1])
- processed[req] = True
-
- return to_activate # return list of distros to activate
-
- def find_plugins(self,
- plugin_env, full_env=None, installer=None, fallback=True
- ):
- """Find all activatable distributions in `plugin_env`
-
- Example usage::
-
- distributions, errors = working_set.find_plugins(
- Environment(plugin_dirlist)
- )
- map(working_set.add, distributions) # add plugins+libs to sys.path
- print 'Could not load', errors # display errors
-
- The `plugin_env` should be an ``Environment`` instance that contains
- only distributions that are in the project's "plugin directory" or
- directories. The `full_env`, if supplied, should be an ``Environment``
- contains all currently-available distributions. If `full_env` is not
- supplied, one is created automatically from the ``WorkingSet`` this
- method is called on, which will typically mean that every directory on
- ``sys.path`` will be scanned for distributions.
-
- `installer` is a standard installer callback as used by the
- ``resolve()`` method. The `fallback` flag indicates whether we should
- attempt to resolve older versions of a plugin if the newest version
- cannot be resolved.
-
- This method returns a 2-tuple: (`distributions`, `error_info`), where
- `distributions` is a list of the distributions found in `plugin_env`
- that were loadable, along with any other distributions that are needed
- to resolve their dependencies. `error_info` is a dictionary mapping
- unloadable plugin distributions to an exception instance describing the
- error that occurred. Usually this will be a ``DistributionNotFound`` or
- ``VersionConflict`` instance.
- """
-
- plugin_projects = list(plugin_env)
- plugin_projects.sort() # scan project names in alphabetic order
-
- error_info = {}
- distributions = {}
-
- if full_env is None:
- env = Environment(self.entries)
- env += plugin_env
- else:
- env = full_env + plugin_env
-
- shadow_set = self.__class__([])
- map(shadow_set.add, self) # put all our entries in shadow_set
-
- for project_name in plugin_projects:
-
- for dist in plugin_env[project_name]:
-
- req = [dist.as_requirement()]
-
- try:
- resolvees = shadow_set.resolve(req, env, installer)
-
- except ResolutionError,v:
- error_info[dist] = v # save error info
- if fallback:
- continue # try the next older version of project
- else:
- break # give up on this project, keep going
-
- else:
- map(shadow_set.add, resolvees)
- distributions.update(dict.fromkeys(resolvees))
-
- # success, no need to try any more versions of this project
- break
-
- distributions = list(distributions)
- distributions.sort()
-
- return distributions, error_info
-
-
-
-
-
- def require(self, *requirements):
- """Ensure that distributions matching `requirements` are activated
-
- `requirements` must be a string or a (possibly-nested) sequence
- thereof, specifying the distributions and versions required. The
- return value is a sequence of the distributions that needed to be
- activated to fulfill the requirements; all relevant distributions are
- included, even if they were already activated in this working set.
- """
-
- needed = self.resolve(parse_requirements(requirements))
-
- for dist in needed:
- self.add(dist)
-
- return needed
-
-
- def subscribe(self, callback):
- """Invoke `callback` for all distributions (including existing ones)"""
- if callback in self.callbacks:
- return
- self.callbacks.append(callback)
- for dist in self:
- callback(dist)
-
-
- def _added_new(self, dist):
- for callback in self.callbacks:
- callback(dist)
-
-
-
-
-
-
-
-
-
-
-
-class Environment(object):
- """Searchable snapshot of distributions on a search path"""
-
- def __init__(self, search_path=None, platform=get_supported_platform(), python=PY_MAJOR):
- """Snapshot distributions available on a search path
-
- Any distributions found on `search_path` are added to the environment.
- `search_path` should be a sequence of ``sys.path`` items. If not
- supplied, ``sys.path`` is used.
-
- `platform` is an optional string specifying the name of the platform
- that platform-specific distributions must be compatible with. If
- unspecified, it defaults to the current platform. `python` is an
- optional string naming the desired version of Python (e.g. ``'2.4'``);
- it defaults to the current version.
-
- You may explicitly set `platform` (and/or `python`) to ``None`` if you
- wish to map *all* distributions, not just those compatible with the
- running platform or Python version.
- """
- self._distmap = {}
- self._cache = {}
- self.platform = platform
- self.python = python
- self.scan(search_path)
-
- def can_add(self, dist):
- """Is distribution `dist` acceptable for this environment?
-
- The distribution must match the platform and python version
- requirements specified when this environment was created, or False
- is returned.
- """
- return (self.python is None or dist.py_version is None
- or dist.py_version==self.python) \
- and compatible_platforms(dist.platform,self.platform)
-
- def remove(self, dist):
- """Remove `dist` from the environment"""
- self._distmap[dist.key].remove(dist)
-
- def scan(self, search_path=None):
- """Scan `search_path` for distributions usable in this environment
-
- Any distributions found are added to the environment.
- `search_path` should be a sequence of ``sys.path`` items. If not
- supplied, ``sys.path`` is used. Only distributions conforming to
- the platform/python version defined at initialization are added.
- """
- if search_path is None:
- search_path = sys.path
-
- for item in search_path:
- for dist in find_distributions(item):
- self.add(dist)
-
- def __getitem__(self,project_name):
- """Return a newest-to-oldest list of distributions for `project_name`
- """
- try:
- return self._cache[project_name]
- except KeyError:
- project_name = project_name.lower()
- if project_name not in self._distmap:
- return []
-
- if project_name not in self._cache:
- dists = self._cache[project_name] = self._distmap[project_name]
- _sort_dists(dists)
-
- return self._cache[project_name]
-
- def add(self,dist):
- """Add `dist` if we ``can_add()`` it and it isn't already added"""
- if self.can_add(dist) and dist.has_version():
- dists = self._distmap.setdefault(dist.key,[])
- if dist not in dists:
- dists.append(dist)
- if dist.key in self._cache:
- _sort_dists(self._cache[dist.key])
-
-
- def best_match(self, req, working_set, installer=None):
- """Find distribution best matching `req` and usable on `working_set`
-
- This calls the ``find(req)`` method of the `working_set` to see if a
- suitable distribution is already active. (This may raise
- ``VersionConflict`` if an unsuitable version of the project is already
- active in the specified `working_set`.) If a suitable distribution
- isn't active, this method returns the newest distribution in the
- environment that meets the ``Requirement`` in `req`. If no suitable
- distribution is found, and `installer` is supplied, then the result of
- calling the environment's ``obtain(req, installer)`` method will be
- returned.
- """
- dist = working_set.find(req)
- if dist is not None:
- return dist
- for dist in self[req.key]:
- if dist in req:
- return dist
- return self.obtain(req, installer) # try and download/install
-
- def obtain(self, requirement, installer=None):
- """Obtain a distribution matching `requirement` (e.g. via download)
-
- Obtain a distro that matches requirement (e.g. via download). In the
- base ``Environment`` class, this routine just returns
- ``installer(requirement)``, unless `installer` is None, in which case
- None is returned instead. This method is a hook that allows subclasses
- to attempt other ways of obtaining a distribution before falling back
- to the `installer` argument."""
- if installer is not None:
- return installer(requirement)
-
- def __iter__(self):
- """Yield the unique project names of the available distributions"""
- for key in self._distmap.keys():
- if self[key]: yield key
-
-
-
-
- def __iadd__(self, other):
- """In-place addition of a distribution or environment"""
- if isinstance(other,Distribution):
- self.add(other)
- elif isinstance(other,Environment):
- for project in other:
- for dist in other[project]:
- self.add(dist)
- else:
- raise TypeError("Can't add %r to environment" % (other,))
- return self
-
- def __add__(self, other):
- """Add an environment or distribution to an environment"""
- new = self.__class__([], platform=None, python=None)
- for env in self, other:
- new += env
- return new
-
-
-AvailableDistributions = Environment # XXX backward compatibility
-
-
-class ExtractionError(RuntimeError):
- """An error occurred extracting a resource
-
- The following attributes are available from instances of this exception:
-
- manager
- The resource manager that raised this exception
-
- cache_path
- The base directory for resource extraction
-
- original_error
- The exception instance that caused extraction to fail
- """
-
-
-
-
-class ResourceManager:
- """Manage resource extraction and packages"""
- extraction_path = None
-
- def __init__(self):
- self.cached_files = {}
-
- def resource_exists(self, package_or_requirement, resource_name):
- """Does the named resource exist?"""
- return get_provider(package_or_requirement).has_resource(resource_name)
-
- def resource_isdir(self, package_or_requirement, resource_name):
- """Is the named resource an existing directory?"""
- return get_provider(package_or_requirement).resource_isdir(
- resource_name
- )
-
- def resource_filename(self, package_or_requirement, resource_name):
- """Return a true filesystem path for specified resource"""
- return get_provider(package_or_requirement).get_resource_filename(
- self, resource_name
- )
-
- def resource_stream(self, package_or_requirement, resource_name):
- """Return a readable file-like object for specified resource"""
- return get_provider(package_or_requirement).get_resource_stream(
- self, resource_name
- )
-
- def resource_string(self, package_or_requirement, resource_name):
- """Return specified resource as a string"""
- return get_provider(package_or_requirement).get_resource_string(
- self, resource_name
- )
-
- def resource_listdir(self, package_or_requirement, resource_name):
- """List the contents of the named resource directory"""
- return get_provider(package_or_requirement).resource_listdir(
- resource_name
- )
-
- def extraction_error(self):
- """Give an error message for problems extracting file(s)"""
-
- old_exc = sys.exc_info()[1]
- cache_path = self.extraction_path or get_default_cache()
-
- err = ExtractionError("""Can't extract file(s) to egg cache
-
-The following error occurred while trying to extract file(s) to the Python egg
-cache:
-
- %s
-
-The Python egg cache directory is currently set to:
-
- %s
-
-Perhaps your account does not have write access to this directory? You can
-change the cache directory by setting the PYTHON_EGG_CACHE environment
-variable to point to an accessible directory.
-""" % (old_exc, cache_path)
- )
- err.manager = self
- err.cache_path = cache_path
- err.original_error = old_exc
- raise err
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def get_cache_path(self, archive_name, names=()):
- """Return absolute location in cache for `archive_name` and `names`
-
- The parent directory of the resulting path will be created if it does
- not already exist. `archive_name` should be the base filename of the
- enclosing egg (which may not be the name of the enclosing zipfile!),
- including its ".egg" extension. `names`, if provided, should be a
- sequence of path name parts "under" the egg's extraction location.
-
- This method should only be called by resource providers that need to
- obtain an extraction location, and only for names they intend to
- extract, as it tracks the generated names for possible cleanup later.
- """
- extract_path = self.extraction_path or get_default_cache()
- target_path = os.path.join(extract_path, archive_name+'-tmp', *names)
- try:
- _bypass_ensure_directory(target_path)
- except:
- self.extraction_error()
-
- self.cached_files[target_path] = 1
- return target_path
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def postprocess(self, tempname, filename):
- """Perform any platform-specific postprocessing of `tempname`
-
- This is where Mac header rewrites should be done; other platforms don't
- have anything special they should do.
-
- Resource providers should call this method ONLY after successfully
- extracting a compressed resource. They must NOT call it on resources
- that are already in the filesystem.
-
- `tempname` is the current (temporary) name of the file, and `filename`
- is the name it will be renamed to by the caller after this routine
- returns.
- """
-
- if os.name == 'posix':
- # Make the resource executable
- mode = ((os.stat(tempname).st_mode) | 0555) & 07777
- os.chmod(tempname, mode)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def set_extraction_path(self, path):
- """Set the base path where resources will be extracted to, if needed.
-
- If you do not call this routine before any extractions take place, the
- path defaults to the return value of ``get_default_cache()``. (Which
- is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
- platform-specific fallbacks. See that routine's documentation for more
- details.)
-
- Resources are extracted to subdirectories of this path based upon
- information given by the ``IResourceProvider``. You may set this to a
- temporary directory, but then you must call ``cleanup_resources()`` to
- delete the extracted files when done. There is no guarantee that
- ``cleanup_resources()`` will be able to remove all extracted files.
-
- (Note: you may not change the extraction path for a given resource
- manager once resources have been extracted, unless you first call
- ``cleanup_resources()``.)
- """
- if self.cached_files:
- raise ValueError(
- "Can't change extraction path, files already extracted"
- )
-
- self.extraction_path = path
-
- def cleanup_resources(self, force=False):
- """
- Delete all extracted resource files and directories, returning a list
- of the file and directory names that could not be successfully removed.
- This function does not have any concurrency protection, so it should
- generally only be called when the extraction path is a temporary
- directory exclusive to a single process. This method is not
- automatically called; you must call it explicitly or register it as an
- ``atexit`` function if you wish to ensure cleanup of a temporary
- directory used for extractions.
- """
- # XXX
-
-
-
-def get_default_cache():
- """Determine the default cache location
-
- This returns the ``PYTHON_EGG_CACHE`` environment variable, if set.
- Otherwise, on Windows, it returns a "Python-Eggs" subdirectory of the
- "Application Data" directory. On all other systems, it's "~/.python-eggs".
- """
- try:
- return os.environ['PYTHON_EGG_CACHE']
- except KeyError:
- pass
-
- if os.name!='nt':
- return os.path.expanduser('~/.python-eggs')
-
- app_data = 'Application Data' # XXX this may be locale-specific!
- app_homes = [
- (('APPDATA',), None), # best option, should be locale-safe
- (('USERPROFILE',), app_data),
- (('HOMEDRIVE','HOMEPATH'), app_data),
- (('HOMEPATH',), app_data),
- (('HOME',), None),
- (('WINDIR',), app_data), # 95/98/ME
- ]
-
- for keys, subdir in app_homes:
- dirname = ''
- for key in keys:
- if key in os.environ:
- dirname = os.path.join(dirname, os.environ[key])
- else:
- break
- else:
- if subdir:
- dirname = os.path.join(dirname,subdir)
- return os.path.join(dirname, 'Python-Eggs')
- else:
- raise RuntimeError(
- "Please set the PYTHON_EGG_CACHE enviroment variable"
- )
-
-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 safe_extra(extra):
- """Convert an arbitrary string to a standard 'extra' name
-
- Any runs of non-alphanumeric characters are replaced with a single '_',
- and the result is always lowercased.
- """
- return re.sub('[^A-Za-z0-9.]+', '_', extra).lower()
-
-
-def to_filename(name):
- """Convert a project or version name to its filename-escaped form
-
- Any '-' characters are currently replaced with '_'.
- """
- return name.replace('-','_')
-
-
-
-
-
-
-
-
-class NullProvider:
- """Try to implement resources and metadata for arbitrary PEP 302 loaders"""
-
- egg_name = None
- egg_info = None
- loader = None
-
- def __init__(self, module):
- self.loader = getattr(module, '__loader__', None)
- self.module_path = os.path.dirname(getattr(module, '__file__', ''))
-
- def get_resource_filename(self, manager, resource_name):
- return self._fn(self.module_path, resource_name)
-
- def get_resource_stream(self, manager, resource_name):
- return StringIO(self.get_resource_string(manager, resource_name))
-
- def get_resource_string(self, manager, resource_name):
- return self._get(self._fn(self.module_path, resource_name))
-
- def has_resource(self, resource_name):
- return self._has(self._fn(self.module_path, resource_name))
-
- def has_metadata(self, name):
- return self.egg_info and self._has(self._fn(self.egg_info,name))
-
- if sys.version_info <= (3,):
- def get_metadata(self, name):
- if not self.egg_info:
- return ""
- return self._get(self._fn(self.egg_info,name))
- else:
- def get_metadata(self, name):
- if not self.egg_info:
- return ""
- return self._get(self._fn(self.egg_info,name)).decode("utf-8")
-
- def get_metadata_lines(self, name):
- return yield_lines(self.get_metadata(name))
-
- def resource_isdir(self,resource_name):
- return self._isdir(self._fn(self.module_path, resource_name))
-
- def metadata_isdir(self,name):
- return self.egg_info and self._isdir(self._fn(self.egg_info,name))
-
-
- def resource_listdir(self,resource_name):
- return self._listdir(self._fn(self.module_path,resource_name))
-
- def metadata_listdir(self,name):
- if self.egg_info:
- return self._listdir(self._fn(self.egg_info,name))
- return []
-
- def run_script(self,script_name,namespace):
- script = 'scripts/'+script_name
- if not self.has_metadata(script):
- raise ResolutionError("No script named %r" % script_name)
- script_text = self.get_metadata(script).replace('\r\n','\n')
- script_text = script_text.replace('\r','\n')
- script_filename = self._fn(self.egg_info,script)
- namespace['__file__'] = script_filename
- if os.path.exists(script_filename):
- execfile(script_filename, namespace, namespace)
- else:
- from linecache import cache
- cache[script_filename] = (
- len(script_text), 0, script_text.split('\n'), script_filename
- )
- script_code = compile(script_text,script_filename,'exec')
- exec script_code in namespace, namespace
-
- def _has(self, path):
- raise NotImplementedError(
- "Can't perform this operation for unregistered loader type"
- )
-
- def _isdir(self, path):
- raise NotImplementedError(
- "Can't perform this operation for unregistered loader type"
- )
-
- def _listdir(self, path):
- raise NotImplementedError(
- "Can't perform this operation for unregistered loader type"
- )
-
- def _fn(self, base, resource_name):
- if resource_name:
- return os.path.join(base, *resource_name.split('/'))
- return base
-
- def _get(self, path):
- if hasattr(self.loader, 'get_data'):
- return self.loader.get_data(path)
- raise NotImplementedError(
- "Can't perform this operation for loaders without 'get_data()'"
- )
-
-register_loader_type(object, NullProvider)
-
-
-class EggProvider(NullProvider):
- """Provider based on a virtual filesystem"""
-
- def __init__(self,module):
- NullProvider.__init__(self,module)
- self._setup_prefix()
-
- def _setup_prefix(self):
- # we assume here that our metadata may be nested inside a "basket"
- # of multiple eggs; that's why we use module_path instead of .archive
- path = self.module_path
- old = None
- while path!=old:
- if path.lower().endswith('.egg'):
- self.egg_name = os.path.basename(path)
- self.egg_info = os.path.join(path, 'EGG-INFO')
- self.egg_root = path
- break
- old = path
- path, base = os.path.split(path)
-
-
-
-
-
-
-class DefaultProvider(EggProvider):
- """Provides access to package resources in the filesystem"""
-
- def _has(self, path):
- return os.path.exists(path)
-
- def _isdir(self,path):
- return os.path.isdir(path)
-
- def _listdir(self,path):
- return os.listdir(path)
-
- def get_resource_stream(self, manager, resource_name):
- return open(self._fn(self.module_path, resource_name), 'rb')
-
- def _get(self, path):
- stream = open(path, 'rb')
- try:
- return stream.read()
- finally:
- stream.close()
-
-register_loader_type(type(None), DefaultProvider)
-
-
-class EmptyProvider(NullProvider):
- """Provider that returns nothing for all requests"""
-
- _isdir = _has = lambda self,path: False
- _get = lambda self,path: ''
- _listdir = lambda self,path: []
- module_path = None
-
- def __init__(self):
- pass
-
-empty_provider = EmptyProvider()
-
-
-
-
-class ZipProvider(EggProvider):
- """Resource support for zips and eggs"""
-
- eagers = None
-
- def __init__(self, module):
- EggProvider.__init__(self,module)
- self.zipinfo = zipimport._zip_directory_cache[self.loader.archive]
- self.zip_pre = self.loader.archive+os.sep
-
- def _zipinfo_name(self, fspath):
- # Convert a virtual filename (full path to file) into a zipfile subpath
- # usable with the zipimport directory cache for our target archive
- if fspath.startswith(self.zip_pre):
- return fspath[len(self.zip_pre):]
- raise AssertionError(
- "%s is not a subpath of %s" % (fspath,self.zip_pre)
- )
-
- def _parts(self,zip_path):
- # Convert a zipfile subpath into an egg-relative path part list
- fspath = self.zip_pre+zip_path # pseudo-fs path
- if fspath.startswith(self.egg_root+os.sep):
- return fspath[len(self.egg_root)+1:].split(os.sep)
- raise AssertionError(
- "%s is not a subpath of %s" % (fspath,self.egg_root)
- )
-
- def get_resource_filename(self, manager, resource_name):
- if not self.egg_name:
- raise NotImplementedError(
- "resource_filename() only supported for .egg, not .zip"
- )
- # no need to lock for extraction, since we use temp names
- zip_path = self._resource_to_zip(resource_name)
- eagers = self._get_eager_resources()
- if '/'.join(self._parts(zip_path)) in eagers:
- for name in eagers:
- self._extract_resource(manager, self._eager_to_zip(name))
- return self._extract_resource(manager, zip_path)
-
- def _extract_resource(self, manager, zip_path):
-
- if zip_path in self._index():
- for name in self._index()[zip_path]:
- last = self._extract_resource(
- manager, os.path.join(zip_path, name)
- )
- return os.path.dirname(last) # return the extracted directory name
-
- zip_stat = self.zipinfo[zip_path]
- t,d,size = zip_stat[5], zip_stat[6], zip_stat[3]
- date_time = (
- (d>>9)+1980, (d>>5)&0xF, d&0x1F, # ymd
- (t&0xFFFF)>>11, (t>>5)&0x3F, (t&0x1F) * 2, 0, 0, -1 # hms, etc.
- )
- timestamp = time.mktime(date_time)
-
- try:
- if not WRITE_SUPPORT:
- raise IOError('"os.rename" and "os.unlink" are not supported '
- 'on this platform')
-
- real_path = manager.get_cache_path(
- self.egg_name, self._parts(zip_path)
- )
-
- if os.path.isfile(real_path):
- stat = os.stat(real_path)
- if stat.st_size==size and stat.st_mtime==timestamp:
- # size and stamp match, don't bother extracting
- return real_path
-
- outf, tmpnam = _mkstemp(".$extract", dir=os.path.dirname(real_path))
- os.write(outf, self.loader.get_data(zip_path))
- os.close(outf)
- utime(tmpnam, (timestamp,timestamp))
- manager.postprocess(tmpnam, real_path)
-
- try:
- rename(tmpnam, real_path)
-
- except os.error:
- if os.path.isfile(real_path):
- stat = os.stat(real_path)
-
- if stat.st_size==size and stat.st_mtime==timestamp:
- # size and stamp match, somebody did it just ahead of
- # us, so we're done
- return real_path
- elif os.name=='nt': # Windows, del old file and retry
- unlink(real_path)
- rename(tmpnam, real_path)
- return real_path
- raise
-
- except os.error:
- manager.extraction_error() # report a user-friendly error
-
- return real_path
-
- def _get_eager_resources(self):
- if self.eagers is None:
- eagers = []
- for name in ('native_libs.txt', 'eager_resources.txt'):
- if self.has_metadata(name):
- eagers.extend(self.get_metadata_lines(name))
- self.eagers = eagers
- return self.eagers
-
- def _index(self):
- try:
- return self._dirindex
- except AttributeError:
- ind = {}
- for path in self.zipinfo:
- parts = path.split(os.sep)
- while parts:
- parent = os.sep.join(parts[:-1])
- if parent in ind:
- ind[parent].append(parts[-1])
- break
- else:
- ind[parent] = [parts.pop()]
- self._dirindex = ind
- return ind
-
- def _has(self, fspath):
- zip_path = self._zipinfo_name(fspath)
- return zip_path in self.zipinfo or zip_path in self._index()
-
- def _isdir(self,fspath):
- return self._zipinfo_name(fspath) in self._index()
-
- def _listdir(self,fspath):
- return list(self._index().get(self._zipinfo_name(fspath), ()))
-
- def _eager_to_zip(self,resource_name):
- return self._zipinfo_name(self._fn(self.egg_root,resource_name))
-
- def _resource_to_zip(self,resource_name):
- return self._zipinfo_name(self._fn(self.module_path,resource_name))
-
-register_loader_type(zipimport.zipimporter, ZipProvider)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class FileMetadata(EmptyProvider):
- """Metadata handler for standalone PKG-INFO files
-
- Usage::
-
- metadata = FileMetadata("/path/to/PKG-INFO")
-
- This provider rejects all data and metadata requests except for PKG-INFO,
- which is treated as existing, and will be the contents of the file at
- the provided location.
- """
-
- def __init__(self,path):
- self.path = path
-
- def has_metadata(self,name):
- return name=='PKG-INFO'
-
- def get_metadata(self,name):
- if name=='PKG-INFO':
- f = open(self.path,'rU')
- metadata = f.read()
- f.close()
- return metadata
- raise KeyError("No metadata except PKG-INFO is available")
-
- def get_metadata_lines(self,name):
- return yield_lines(self.get_metadata(name))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class PathMetadata(DefaultProvider):
- """Metadata provider for egg directories
-
- Usage::
-
- # Development eggs:
-
- egg_info = "/path/to/PackageName.egg-info"
- base_dir = os.path.dirname(egg_info)
- metadata = PathMetadata(base_dir, egg_info)
- dist_name = os.path.splitext(os.path.basename(egg_info))[0]
- dist = Distribution(basedir,project_name=dist_name,metadata=metadata)
-
- # Unpacked egg directories:
-
- egg_path = "/path/to/PackageName-ver-pyver-etc.egg"
- metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))
- dist = Distribution.from_filename(egg_path, metadata=metadata)
- """
-
- def __init__(self, path, egg_info):
- self.module_path = path
- self.egg_info = egg_info
-
-
-class EggMetadata(ZipProvider):
- """Metadata provider for .egg files"""
-
- def __init__(self, importer):
- """Create a metadata provider from a zipimporter"""
-
- self.zipinfo = zipimport._zip_directory_cache[importer.archive]
- self.zip_pre = importer.archive+os.sep
- self.loader = importer
- if importer.prefix:
- self.module_path = os.path.join(importer.archive, importer.prefix)
- else:
- self.module_path = importer.archive
- self._setup_prefix()
-
-
-class ImpWrapper:
- """PEP 302 Importer that wraps Python's "normal" import algorithm"""
-
- def __init__(self, path=None):
- self.path = path
-
- def find_module(self, fullname, path=None):
- subname = fullname.split(".")[-1]
- if subname != fullname and self.path is None:
- return None
- if self.path is None:
- path = None
- else:
- path = [self.path]
- try:
- file, filename, etc = imp.find_module(subname, path)
- except ImportError:
- return None
- return ImpLoader(file, filename, etc)
-
-
-class ImpLoader:
- """PEP 302 Loader that wraps Python's "normal" import algorithm"""
-
- def __init__(self, file, filename, etc):
- self.file = file
- self.filename = filename
- self.etc = etc
-
- def load_module(self, fullname):
- try:
- mod = imp.load_module(fullname, self.file, self.filename, self.etc)
- finally:
- if self.file: self.file.close()
- # Note: we don't set __loader__ because we want the module to look
- # normal; i.e. this is just a wrapper for standard import machinery
- return mod
-
-
-
-
-def get_importer(path_item):
- """Retrieve a PEP 302 "importer" for the given path item
-
- If there is no importer, this returns a wrapper around the builtin import
- machinery. The returned importer is only cached if it was created by a
- path hook.
- """
- try:
- importer = sys.path_importer_cache[path_item]
- except KeyError:
- for hook in sys.path_hooks:
- try:
- importer = hook(path_item)
- except ImportError:
- pass
- else:
- break
- else:
- importer = None
-
- sys.path_importer_cache.setdefault(path_item,importer)
- if importer is None:
- try:
- importer = ImpWrapper(path_item)
- except ImportError:
- pass
- return importer
-
-try:
- from pkgutil import get_importer, ImpImporter
-except ImportError:
- pass # Python 2.3 or 2.4, use our own implementation
-else:
- ImpWrapper = ImpImporter # Python 2.5, use pkgutil's implementation
- del ImpLoader, ImpImporter
-
-
-
-
-
-
-_distribution_finders = {}
-
-def register_finder(importer_type, distribution_finder):
- """Register `distribution_finder` to find distributions in sys.path items
-
- `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
- handler), and `distribution_finder` is a callable that, passed a path
- item and the importer instance, yields ``Distribution`` instances found on
- that path item. See ``pkg_resources.find_on_path`` for an example."""
- _distribution_finders[importer_type] = distribution_finder
-
-
-def find_distributions(path_item, only=False):
- """Yield distributions accessible via `path_item`"""
- importer = get_importer(path_item)
- finder = _find_adapter(_distribution_finders, importer)
- return finder(importer, path_item, only)
-
-def find_in_zip(importer, path_item, only=False):
- metadata = EggMetadata(importer)
- if metadata.has_metadata('PKG-INFO'):
- yield Distribution.from_filename(path_item, metadata=metadata)
- if only:
- return # don't yield nested distros
- for subitem in metadata.resource_listdir('/'):
- if subitem.endswith('.egg'):
- subpath = os.path.join(path_item, subitem)
- for dist in find_in_zip(zipimport.zipimporter(subpath), subpath):
- yield dist
-
-register_finder(zipimport.zipimporter, find_in_zip)
-
-def StringIO(*args, **kw):
- """Thunk to load the real StringIO on demand"""
- global StringIO
- try:
- from cStringIO import StringIO
- except ImportError:
- from StringIO import StringIO
- return StringIO(*args,**kw)
-
-def find_nothing(importer, path_item, only=False):
- return ()
-register_finder(object,find_nothing)
-
-def find_on_path(importer, path_item, only=False):
- """Yield distributions accessible on a sys.path directory"""
- path_item = _normalize_cached(path_item)
-
- if os.path.isdir(path_item) and os.access(path_item, os.R_OK):
- if path_item.lower().endswith('.egg'):
- # unpacked egg
- yield Distribution.from_filename(
- path_item, metadata=PathMetadata(
- path_item, os.path.join(path_item,'EGG-INFO')
- )
- )
- else:
- # scan for .egg and .egg-info in directory
- for entry in os.listdir(path_item):
- lower = entry.lower()
- if lower.endswith('.egg-info'):
- fullpath = os.path.join(path_item, entry)
- if os.path.isdir(fullpath):
- # egg-info directory, allow getting metadata
- metadata = PathMetadata(path_item, fullpath)
- else:
- metadata = FileMetadata(fullpath)
- yield Distribution.from_location(
- path_item,entry,metadata,precedence=DEVELOP_DIST
- )
- elif not only and lower.endswith('.egg'):
- for dist in find_distributions(os.path.join(path_item, entry)):
- yield dist
- elif not only and lower.endswith('.egg-link'):
- for line in open(os.path.join(path_item, entry)):
- if not line.strip(): continue
- for item in find_distributions(os.path.join(path_item,line.rstrip())):
- yield item
- break
-register_finder(ImpWrapper,find_on_path)
-
-_namespace_handlers = {}
-_namespace_packages = {}
-
-def register_namespace_handler(importer_type, namespace_handler):
- """Register `namespace_handler` to declare namespace packages
-
- `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
- handler), and `namespace_handler` is a callable like this::
-
- def namespace_handler(importer,path_entry,moduleName,module):
- # return a path_entry to use for child packages
-
- Namespace handlers are only called if the importer object has already
- agreed that it can handle the relevant path item, and they should only
- return a subpath if the module __path__ does not already contain an
- equivalent subpath. For an example namespace handler, see
- ``pkg_resources.file_ns_handler``.
- """
- _namespace_handlers[importer_type] = namespace_handler
-
-def _handle_ns(packageName, path_item):
- """Ensure that named package includes a subpath of path_item (if needed)"""
- importer = get_importer(path_item)
- if importer is None:
- return None
- loader = importer.find_module(packageName)
- if loader is None:
- return None
- module = sys.modules.get(packageName)
- if module is None:
- module = sys.modules[packageName] = types.ModuleType(packageName)
- module.__path__ = []; _set_parent_ns(packageName)
- elif not hasattr(module,'__path__'):
- raise TypeError("Not a package:", packageName)
- handler = _find_adapter(_namespace_handlers, importer)
- subpath = handler(importer,path_item,packageName,module)
- if subpath is not None:
- path = module.__path__; path.append(subpath)
- loader.load_module(packageName); module.__path__ = path
- return subpath
-
-def declare_namespace(packageName):
- """Declare that package 'packageName' is a namespace package"""
-
- imp.acquire_lock()
- try:
- if packageName in _namespace_packages:
- return
-
- path, parent = sys.path, None
- if '.' in packageName:
- parent = '.'.join(packageName.split('.')[:-1])
- declare_namespace(parent)
- __import__(parent)
- try:
- path = sys.modules[parent].__path__
- except AttributeError:
- raise TypeError("Not a package:", parent)
-
- # Track what packages are namespaces, so when new path items are added,
- # they can be updated
- _namespace_packages.setdefault(parent,[]).append(packageName)
- _namespace_packages.setdefault(packageName,[])
-
- for path_item in path:
- # Ensure all the parent's path items are reflected in the child,
- # if they apply
- _handle_ns(packageName, path_item)
-
- finally:
- imp.release_lock()
-
-def fixup_namespace_packages(path_item, parent=None):
- """Ensure that previously-declared namespace packages include path_item"""
- imp.acquire_lock()
- try:
- for package in _namespace_packages.get(parent,()):
- subpath = _handle_ns(package, path_item)
- if subpath: fixup_namespace_packages(subpath,package)
- finally:
- imp.release_lock()
-
-def file_ns_handler(importer, path_item, packageName, module):
- """Compute an ns-package subpath for a filesystem or zipfile importer"""
-
- subpath = os.path.join(path_item, packageName.split('.')[-1])
- normalized = _normalize_cached(subpath)
- for item in module.__path__:
- if _normalize_cached(item)==normalized:
- break
- else:
- # Only return the path if it's not already there
- return subpath
-
-register_namespace_handler(ImpWrapper,file_ns_handler)
-register_namespace_handler(zipimport.zipimporter,file_ns_handler)
-
-
-def null_ns_handler(importer, path_item, packageName, module):
- return None
-
-register_namespace_handler(object,null_ns_handler)
-
-
-def normalize_path(filename):
- """Normalize a file/dir name for comparison purposes"""
- return os.path.normcase(os.path.realpath(filename))
-
-def _normalize_cached(filename,_cache={}):
- try:
- return _cache[filename]
- except KeyError:
- _cache[filename] = result = normalize_path(filename)
- return result
-
-def _set_parent_ns(packageName):
- parts = packageName.split('.')
- name = parts.pop()
- if parts:
- parent = '.'.join(parts)
- setattr(sys.modules[parent], name, sys.modules[packageName])
-
-
-def yield_lines(strs):
- """Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
- if isinstance(strs,basestring):
- for s in strs.splitlines():
- s = s.strip()
- if s and not s.startswith('#'): # skip blank lines/comments
- yield s
- else:
- for ss in strs:
- for s in yield_lines(ss):
- yield s
-
-LINE_END = re.compile(r"\s*(#.*)?$").match # whitespace and comment
-CONTINUE = re.compile(r"\s*\\\s*(#.*)?$").match # line continuation
-DISTRO = re.compile(r"\s*((\w|[-.])+)").match # Distribution or extra
-VERSION = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|[-.])+)").match # ver. info
-COMMA = re.compile(r"\s*,").match # comma between items
-OBRACKET = re.compile(r"\s*\[").match
-CBRACKET = re.compile(r"\s*\]").match
-MODULE = re.compile(r"\w+(\.\w+)*$").match
-EGG_NAME = re.compile(
- r"(?P<name>[^-]+)"
- r"( -(?P<ver>[^-]+) (-py(?P<pyver>[^-]+) (-(?P<plat>.+))? )? )?",
- re.VERBOSE | re.IGNORECASE
-).match
-
-component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
-replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get
-
-def _parse_version_parts(s):
- for part in component_re.split(s):
- part = replace(part,part)
- if not part or part=='.':
- continue
- if part[:1] in '0123456789':
- yield part.zfill(8) # pad for numeric comparison
- else:
- yield '*'+part
-
- yield '*final' # ensure that alpha/beta/candidate are before final
-
-def parse_version(s):
- """Convert a version string to a chronologically-sortable key
-
- This is a rough cross between distutils' StrictVersion and LooseVersion;
- if you give it versions that would work with StrictVersion, then it behaves
- the same; otherwise it acts like a slightly-smarter LooseVersion. It is
- *possible* to create pathological version coding schemes that will fool
- this parser, but they should be very rare in practice.
-
- The returned value will be a tuple of strings. Numeric portions of the
- version are padded to 8 digits so they will compare numerically, but
- without relying on how numbers compare relative to strings. Dots are
- dropped, but dashes are retained. Trailing zeros between alpha segments
- or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
- "2.4". Alphanumeric parts are lower-cased.
-
- The algorithm assumes that strings like "-" and any alpha string that
- alphabetically follows "final" represents a "patch level". So, "2.4-1"
- is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
- considered newer than "2.4-1", which in turn is newer than "2.4".
-
- Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
- come before "final" alphabetically) are assumed to be pre-release versions,
- so that the version "2.4" is considered newer than "2.4a1".
-
- Finally, to handle miscellaneous cases, the strings "pre", "preview", and
- "rc" are treated as if they were "c", i.e. as though they were release
- candidates, and therefore are not as new as a version string that does not
- contain them, and "dev" is replaced with an '@' so that it sorts lower than
- than any other pre-release tag.
- """
- parts = []
- for part in _parse_version_parts(s.lower()):
- if part.startswith('*'):
- if part<'*final': # remove '-' before a prerelease tag
- while parts and parts[-1]=='*final-': parts.pop()
- # remove trailing zeros from each series of numeric parts
- while parts and parts[-1]=='00000000':
- parts.pop()
- parts.append(part)
- return tuple(parts)
-
-class EntryPoint(object):
- """Object representing an advertised importable object"""
-
- def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
- if not MODULE(module_name):
- raise ValueError("Invalid module name", module_name)
- self.name = name
- self.module_name = module_name
- self.attrs = tuple(attrs)
- self.extras = Requirement.parse(("x[%s]" % ','.join(extras))).extras
- self.dist = dist
-
- def __str__(self):
- s = "%s = %s" % (self.name, self.module_name)
- if self.attrs:
- s += ':' + '.'.join(self.attrs)
- if self.extras:
- s += ' [%s]' % ','.join(self.extras)
- return s
-
- def __repr__(self):
- return "EntryPoint.parse(%r)" % str(self)
-
- def load(self, require=True, env=None, installer=None):
- if require: self.require(env, installer)
- entry = __import__(self.module_name, globals(),globals(), ['__name__'])
- for attr in self.attrs:
- try:
- entry = getattr(entry,attr)
- except AttributeError:
- raise ImportError("%r has no %r attribute" % (entry,attr))
- return entry
-
- def require(self, env=None, installer=None):
- if self.extras and not self.dist:
- raise UnknownExtra("Can't require() without a distribution", self)
- map(working_set.add,
- working_set.resolve(self.dist.requires(self.extras),env,installer))
-
-
-
- #@classmethod
- def parse(cls, src, dist=None):
- """Parse a single entry point from string `src`
-
- Entry point syntax follows the form::
-
- name = some.module:some.attr [extra1,extra2]
-
- The entry name and module name are required, but the ``:attrs`` and
- ``[extras]`` parts are optional
- """
- try:
- attrs = extras = ()
- name,value = src.split('=',1)
- if '[' in value:
- value,extras = value.split('[',1)
- req = Requirement.parse("x["+extras)
- if req.specs: raise ValueError
- extras = req.extras
- if ':' in value:
- value,attrs = value.split(':',1)
- if not MODULE(attrs.rstrip()):
- raise ValueError
- attrs = attrs.rstrip().split('.')
- except ValueError:
- raise ValueError(
- "EntryPoint must be in 'name=module:attrs [extras]' format",
- src
- )
- else:
- return cls(name.strip(), value.strip(), attrs, extras, dist)
-
- parse = classmethod(parse)
-
-
-
-
-
-
-
-
- #@classmethod
- def parse_group(cls, group, lines, dist=None):
- """Parse an entry point group"""
- if not MODULE(group):
- raise ValueError("Invalid group name", group)
- this = {}
- for line in yield_lines(lines):
- ep = cls.parse(line, dist)
- if ep.name in this:
- raise ValueError("Duplicate entry point", group, ep.name)
- this[ep.name]=ep
- return this
-
- parse_group = classmethod(parse_group)
-
- #@classmethod
- def parse_map(cls, data, dist=None):
- """Parse a map of entry point groups"""
- if isinstance(data,dict):
- data = data.items()
- else:
- data = split_sections(data)
- maps = {}
- for group, lines in data:
- if group is None:
- if not lines:
- continue
- raise ValueError("Entry points must be listed in groups")
- group = group.strip()
- if group in maps:
- raise ValueError("Duplicate group name", group)
- maps[group] = cls.parse_group(group, lines, dist)
- return maps
-
- parse_map = classmethod(parse_map)
-
-
-def _remove_md5_fragment(location):
- if not location:
- return ''
- parsed = urlparse(location)
- if parsed[-1].startswith('md5='):
- return urlunparse(parsed[:-1] + ('',))
- return location
-
-
-class Distribution(object):
- """Wrap an actual or potential sys.path entry w/metadata"""
- def __init__(self,
- location=None, metadata=None, project_name=None, version=None,
- py_version=PY_MAJOR, platform=None, precedence = EGG_DIST
- ):
- self.project_name = safe_name(project_name or 'Unknown')
- if version is not None:
- self._version = safe_version(version)
- self.py_version = py_version
- self.platform = platform
- self.location = location
- self.precedence = precedence
- self._provider = metadata or empty_provider
-
- #@classmethod
- def from_location(cls,location,basename,metadata=None,**kw):
- project_name, version, py_version, platform = [None]*4
- basename, ext = os.path.splitext(basename)
- if ext.lower() in (".egg",".egg-info"):
- match = EGG_NAME(basename)
- if match:
- project_name, version, py_version, platform = match.group(
- 'name','ver','pyver','plat'
- )
- return cls(
- location, metadata, project_name=project_name, version=version,
- py_version=py_version, platform=platform, **kw
- )
- from_location = classmethod(from_location)
-
-
- hashcmp = property(
- lambda self: (
- getattr(self,'parsed_version',()),
- self.precedence,
- self.key,
- _remove_md5_fragment(self.location),
- self.py_version,
- self.platform
- )
- )
- def __hash__(self): return hash(self.hashcmp)
- def __lt__(self, other):
- return self.hashcmp < other.hashcmp
- def __le__(self, other):
- return self.hashcmp <= other.hashcmp
- def __gt__(self, other):
- return self.hashcmp > other.hashcmp
- def __ge__(self, other):
- return self.hashcmp >= other.hashcmp
- def __eq__(self, other):
- if not isinstance(other, self.__class__):
- # It's not a Distribution, so they are not equal
- return False
- return self.hashcmp == other.hashcmp
- def __ne__(self, other):
- return not self == other
-
- # These properties have to be lazy so that we don't have to load any
- # metadata until/unless it's actually needed. (i.e., some distributions
- # may not know their name or version without loading PKG-INFO)
-
- #@property
- def key(self):
- try:
- return self._key
- except AttributeError:
- self._key = key = self.project_name.lower()
- return key
- key = property(key)
-
- #@property
- def parsed_version(self):
- try:
- return self._parsed_version
- except AttributeError:
- self._parsed_version = pv = parse_version(self.version)
- return pv
-
- parsed_version = property(parsed_version)
-
- #@property
- def version(self):
- try:
- return self._version
- except AttributeError:
- for line in self._get_metadata('PKG-INFO'):
- if line.lower().startswith('version:'):
- self._version = safe_version(line.split(':',1)[1].strip())
- return self._version
- else:
- raise ValueError(
- "Missing 'Version:' header and/or PKG-INFO file", self
- )
- version = property(version)
-
-
-
-
- #@property
- def _dep_map(self):
- try:
- return self.__dep_map
- except AttributeError:
- dm = self.__dep_map = {None: []}
- for name in 'requires.txt', 'depends.txt':
- for extra,reqs in split_sections(self._get_metadata(name)):
- if extra: extra = safe_extra(extra)
- dm.setdefault(extra,[]).extend(parse_requirements(reqs))
- return dm
- _dep_map = property(_dep_map)
-
- def requires(self,extras=()):
- """List of Requirements needed for this distro if `extras` are used"""
- dm = self._dep_map
- deps = []
- deps.extend(dm.get(None,()))
- for ext in extras:
- try:
- deps.extend(dm[safe_extra(ext)])
- except KeyError:
- raise UnknownExtra(
- "%s has no such extra feature %r" % (self, ext)
- )
- return deps
-
- def _get_metadata(self,name):
- if self.has_metadata(name):
- for line in self.get_metadata_lines(name):
- yield line
-
- def activate(self,path=None):
- """Ensure distribution is importable on `path` (default=sys.path)"""
- if path is None: path = sys.path
- self.insert_on(path)
- if path is sys.path:
- fixup_namespace_packages(self.location)
- map(declare_namespace, self._get_metadata('namespace_packages.txt'))
-
-
- def egg_name(self):
- """Return what this distribution's standard .egg filename should be"""
- filename = "%s-%s-py%s" % (
- to_filename(self.project_name), to_filename(self.version),
- self.py_version or PY_MAJOR
- )
-
- if self.platform:
- filename += '-'+self.platform
- return filename
-
- def __repr__(self):
- if self.location:
- return "%s (%s)" % (self,self.location)
- else:
- return str(self)
-
- def __str__(self):
- try: version = getattr(self,'version',None)
- except ValueError: version = None
- version = version or "[unknown version]"
- return "%s %s" % (self.project_name,version)
-
- def __getattr__(self,attr):
- """Delegate all unrecognized public attributes to .metadata provider"""
- if attr.startswith('_'):
- raise AttributeError,attr
- return getattr(self._provider, attr)
-
- #@classmethod
- def from_filename(cls,filename,metadata=None, **kw):
- return cls.from_location(
- _normalize_cached(filename), os.path.basename(filename), metadata,
- **kw
- )
- from_filename = classmethod(from_filename)
-
- def as_requirement(self):
- """Return a ``Requirement`` that matches this distribution exactly"""
- return Requirement.parse('%s==%s' % (self.project_name, self.version))
-
- def load_entry_point(self, group, name):
- """Return the `name` entry point of `group` or raise ImportError"""
- ep = self.get_entry_info(group,name)
- if ep is None:
- raise ImportError("Entry point %r not found" % ((group,name),))
- return ep.load()
-
- def get_entry_map(self, group=None):
- """Return the entry point map for `group`, or the full entry map"""
- try:
- ep_map = self._ep_map
- except AttributeError:
- ep_map = self._ep_map = EntryPoint.parse_map(
- self._get_metadata('entry_points.txt'), self
- )
- if group is not None:
- return ep_map.get(group,{})
- return ep_map
-
- def get_entry_info(self, group, name):
- """Return the EntryPoint object for `group`+`name`, or ``None``"""
- return self.get_entry_map(group).get(name)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def insert_on(self, path, loc = None):
- """Insert self.location in path before its nearest parent directory"""
-
- loc = loc or self.location
-
- if self.project_name == 'setuptools':
- try:
- version = self.version
- except ValueError:
- version = ''
- if '0.7' in version:
- raise ValueError(
- "A 0.7-series setuptools cannot be installed "
- "with distribute. Found one at %s" % str(self.location))
-
- if not loc:
- return
-
- if path is sys.path:
- self.check_version_conflict()
-
- nloc = _normalize_cached(loc)
- bdir = os.path.dirname(nloc)
- npath= map(_normalize_cached, path)
-
- bp = None
- for p, item in enumerate(npath):
- if item==nloc:
- break
- elif item==bdir and self.precedence==EGG_DIST:
- # if it's an .egg, give it precedence over its directory
- path.insert(p, loc)
- npath.insert(p, nloc)
- break
- else:
- path.append(loc)
- return
-
- # p is the spot where we found or inserted loc; now remove duplicates
- while 1:
- try:
- np = npath.index(nloc, p+1)
- except ValueError:
- break
- else:
- del npath[np], path[np]
- p = np # ha!
-
- return
-
-
-
- def check_version_conflict(self):
- if self.key=='distribute':
- return # ignore the inevitable setuptools self-conflicts :(
-
- nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
- loc = normalize_path(self.location)
- for modname in self._get_metadata('top_level.txt'):
- if (modname not in sys.modules or modname in nsp
- or modname in _namespace_packages
- ):
- continue
- if modname in ('pkg_resources', 'setuptools', 'site'):
- continue
- fn = getattr(sys.modules[modname], '__file__', None)
- if fn and (normalize_path(fn).startswith(loc) or
- fn.startswith(self.location)):
- continue
- issue_warning(
- "Module %s was already imported from %s, but %s is being added"
- " to sys.path" % (modname, fn, self.location),
- )
-
- def has_version(self):
- try:
- self.version
- except ValueError:
- issue_warning("Unbuilt egg for "+repr(self))
- return False
- return True
-
- def clone(self,**kw):
- """Copy this distribution, substituting in any changed keyword args"""
- for attr in (
- 'project_name', 'version', 'py_version', 'platform', 'location',
- 'precedence'
- ):
- kw.setdefault(attr, getattr(self,attr,None))
- kw.setdefault('metadata', self._provider)
- return self.__class__(**kw)
-
-
-
-
- #@property
- def extras(self):
- return [dep for dep in self._dep_map if dep]
- extras = property(extras)
-
-
-def issue_warning(*args,**kw):
- level = 1
- g = globals()
- try:
- # find the first stack frame that is *not* code in
- # the pkg_resources module, to use for the warning
- while sys._getframe(level).f_globals is g:
- level += 1
- except ValueError:
- pass
- from warnings import warn
- warn(stacklevel = level+1, *args, **kw)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-def parse_requirements(strs):
- """Yield ``Requirement`` objects for each specification in `strs`
-
- `strs` must be an instance of ``basestring``, or a (possibly-nested)
- iterable thereof.
- """
- # create a steppable iterator, so we can handle \-continuations
- lines = iter(yield_lines(strs))
-
- def scan_list(ITEM,TERMINATOR,line,p,groups,item_name):
-
- items = []
-
- while not TERMINATOR(line,p):
- if CONTINUE(line,p):
- try:
- line = lines.next(); p = 0
- except StopIteration:
- raise ValueError(
- "\\ must not appear on the last nonblank line"
- )
-
- match = ITEM(line,p)
- if not match:
- raise ValueError("Expected "+item_name+" in",line,"at",line[p:])
-
- items.append(match.group(*groups))
- p = match.end()
-
- match = COMMA(line,p)
- if match:
- p = match.end() # skip the comma
- elif not TERMINATOR(line,p):
- raise ValueError(
- "Expected ',' or end-of-list in",line,"at",line[p:]
- )
-
- match = TERMINATOR(line,p)
- if match: p = match.end() # skip the terminator, if any
- return line, p, items
-
- for line in lines:
- match = DISTRO(line)
- if not match:
- raise ValueError("Missing distribution spec", line)
- project_name = match.group(1)
- p = match.end()
- extras = []
-
- match = OBRACKET(line,p)
- if match:
- p = match.end()
- line, p, extras = scan_list(
- DISTRO, CBRACKET, line, p, (1,), "'extra' name"
- )
-
- line, p, specs = scan_list(VERSION,LINE_END,line,p,(1,2),"version spec")
- specs = [(op,safe_version(val)) for op,val in specs]
- yield Requirement(project_name, specs, extras)
-
-
-def _sort_dists(dists):
- tmp = [(dist.hashcmp,dist) for dist in dists]
- tmp.sort()
- dists[::-1] = [d for hc,d in tmp]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class Requirement:
- def __init__(self, project_name, specs, extras):
- """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
- self.unsafe_name, project_name = project_name, safe_name(project_name)
- self.project_name, self.key = project_name, project_name.lower()
- index = [(parse_version(v),state_machine[op],op,v) for op,v in specs]
- index.sort()
- self.specs = [(op,ver) for parsed,trans,op,ver in index]
- self.index, self.extras = index, tuple(map(safe_extra,extras))
- self.hashCmp = (
- self.key, tuple([(op,parsed) for parsed,trans,op,ver in index]),
- frozenset(self.extras)
- )
- self.__hash = hash(self.hashCmp)
-
- def __str__(self):
- specs = ','.join([''.join(s) for s in self.specs])
- extras = ','.join(self.extras)
- if extras: extras = '[%s]' % extras
- return '%s%s%s' % (self.project_name, extras, specs)
-
- def __eq__(self,other):
- return isinstance(other,Requirement) and self.hashCmp==other.hashCmp
-
- def __contains__(self,item):
- if isinstance(item,Distribution):
- if item.key <> self.key: return False
- if self.index: item = item.parsed_version # only get if we need it
- elif isinstance(item,basestring):
- item = parse_version(item)
- last = None
- compare = lambda a, b: (a > b) - (a < b) # -1, 0, 1
- for parsed,trans,op,ver in self.index:
- action = trans[compare(item,parsed)] # Indexing: 0, 1, -1
- if action=='F': return False
- elif action=='T': return True
- elif action=='+': last = True
- elif action=='-' or last is None: last = False
- if last is None: last = True # no rules encountered
- return last
-
-
- def __hash__(self):
- return self.__hash
-
- def __repr__(self): return "Requirement.parse(%r)" % str(self)
-
- #@staticmethod
- def parse(s, replacement=True):
- reqs = list(parse_requirements(s))
- if reqs:
- if len(reqs) == 1:
- founded_req = reqs[0]
- # if asked for setuptools distribution
- # and if distribute is installed, we want to give
- # distribute instead
- if _override_setuptools(founded_req) and replacement:
- distribute = list(parse_requirements('distribute'))
- if len(distribute) == 1:
- return distribute[0]
- return founded_req
- else:
- return founded_req
-
- raise ValueError("Expected only one requirement", s)
- raise ValueError("No requirements found", s)
-
- parse = staticmethod(parse)
-
-state_machine = {
- # =><
- '<' : '--T',
- '<=': 'T-T',
- '>' : 'F+F',
- '>=': 'T+F',
- '==': 'T..',
- '!=': 'F++',
-}
-
-
-def _override_setuptools(req):
- """Return True when distribute wants to override a setuptools dependency.
-
- We want to override when the requirement is setuptools and the version is
- a variant of 0.6.
-
- """
- if req.project_name == 'setuptools':
- if not len(req.specs):
- # Just setuptools: ok
- return True
- for comparator, version in req.specs:
- if comparator in ['==', '>=', '>']:
- if '0.7' in version:
- # We want some setuptools not from the 0.6 series.
- return False
- return True
- return False
-
-
-def _get_mro(cls):
- """Get an mro for a type or classic class"""
- if not isinstance(cls,type):
- class cls(cls,object): pass
- return cls.__mro__[1:]
- return cls.__mro__
-
-def _find_adapter(registry, ob):
- """Return an adapter factory for `ob` from `registry`"""
- for t in _get_mro(getattr(ob, '__class__', type(ob))):
- if t in registry:
- return registry[t]
-
-
-def ensure_directory(path):
- """Ensure that the parent directory of `path` exists"""
- dirname = os.path.dirname(path)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
-
-def split_sections(s):
- """Split a string or iterable thereof into (section,content) pairs
-
- Each ``section`` is a stripped version of the section header ("[section]")
- and each ``content`` is a list of stripped lines excluding blank lines and
- comment-only lines. If there are any such lines before the first section
- header, they're returned in a first ``section`` of ``None``.
- """
- section = None
- content = []
- for line in yield_lines(s):
- if line.startswith("["):
- if line.endswith("]"):
- if section or content:
- yield section, content
- section = line[1:-1].strip()
- content = []
- else:
- raise ValueError("Invalid section heading", line)
- else:
- content.append(line)
-
- # wrap up last segment
- yield section, content
-
-def _mkstemp(*args,**kw):
- from tempfile import mkstemp
- old_open = os.open
- try:
- os.open = os_open # temporarily bypass sandboxing
- return mkstemp(*args,**kw)
- finally:
- os.open = old_open # and then put it back
-
-
-# Set up global resource manager
-_manager = ResourceManager()
-def _initialize(g):
- for name in dir(_manager):
- if not name.startswith('_'):
- g[name] = getattr(_manager, name)
-_initialize(globals())
-
-# Prepare the master working set and make the ``require()`` API available
-working_set = WorkingSet()
-try:
- # Does the main program list any requirements?
- from __main__ import __requires__
-except ImportError:
- pass # No: just use the default working set based on sys.path
-else:
- # Yes: ensure the requirements are met, by prefixing sys.path if necessary
- try:
- working_set.require(__requires__)
- except VersionConflict: # try it without defaults already on sys.path
- working_set = WorkingSet([]) # by starting with an empty path
- for dist in working_set.resolve(
- parse_requirements(__requires__), Environment()
- ):
- working_set.add(dist)
- for entry in sys.path: # add any missing entries from sys.path
- if entry not in working_set.entries:
- working_set.add_entry(entry)
- sys.path[:] = working_set.entries # then copy back to sys.path
-
-require = working_set.require
-iter_entry_points = working_set.iter_entry_points
-add_activation_listener = working_set.subscribe
-run_script = working_set.run_script
-run_main = run_script # backward compatibility
-# Activate all distributions already on sys.path, and ensure that
-# all distributions added to the working set in the future (e.g. by
-# calling ``require()``) will get activated as well.
-add_activation_listener(lambda dist: dist.activate())
-working_set.entries=[]; map(working_set.add_entry,sys.path) # match order
-
diff --git a/src/distutils2/pypi/simple.py b/src/distutils2/pypi/simple.py
index 3079d4f..1a6a35d 100644
--- a/src/distutils2/pypi/simple.py
+++ b/src/distutils2/pypi/simple.py
@@ -1,797 +1,393 @@
-"""distutils2.pypi.package_index
-
-Implement a simple way to use the PyPI API's, by using known URIs from
-PyPI. Works with the API defined on
-http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api ,
-and available at http://pypi.python.org/simple/ .
+"""pypi.simple
+Contains the class "SimpleIndex", a simple spider to find and retrieve
+distributions on the Python Package Index, using it's "simple" API,
+avalaible at http://pypi.python.org/simple/
"""
-# XXX Make a explaination document on how this API is made.
-
-import sys
-import os.path
+from fnmatch import translate
+import httplib
import re
-import urlparse
-import urllib2
-import shutil
import socket
-import cStringIO
-import httplib
-import logging
+import sys
+import urllib2
+import urlparse
-from distutils2.errors import DistutilsError
+from distutils2.version import VersionPredicate
+from distutils2.pypi.dist import (PyPIDistribution, PyPIDistributions,
+ EXTENSIONS)
+from distutils2.pypi.errors import (PyPIError, DistributionNotFound,
+ DownloadError, UnableToDownload)
from distutils2 import __version__ as __distutils2_version__
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-from fnmatch import translate
-
-# -- Constants ----------------------------------------------------------------
-
-__all__ = ['PackageIndex',
- 'distros_for_url',
- 'parse_bdist_wininst',
- 'interpret_distro_name',
-]
+# -- Constants -----------------------------------------------
+PYPI_DEFAULT_INDEX_URL = "http://pypi.python.org/simple/"
+PYPI_DEFAULT_MIRROR_URL = "mirrors.pypi.python.org"
+DEFAULT_HOSTS = ("*",)
+SOCKET_TIMEOUT = 15
+USER_AGENT = "Python-urllib/%s distutils2/%s" % (
+ sys.version[:3], __distutils2_version__)
-_SOCKET_TIMEOUT = 15
-SOURCE_DIST = 1
+# -- Regexps -------------------------------------------------
EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
-# this is here to fix emacs' cruddy broken syntax highlighting
PYPI_MD5 = re.compile(
'<a href="([^"#]+)">([^<]+)</a>\n\s+\\(<a (?:title="MD5 hash"\n\s+)'
- 'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
-)
-URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match
-EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
+ 'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)')
+URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
-# -- Dependencies from distribute ---------------------------------------------
+# This pattern matches a character entity reference (a decimal numeric
+# references, a hexadecimal numeric reference, or a named reference).
+ENTITY_SUB = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub
+REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
-# XXX Remove these dependencies, refactor the package_index codebase to not
-# depends anymore on distribute classes and internal workings.
-from pkg_resources import *
-# -- Base functions -----------------------------------------------------------
+def socket_timeout(timeout=SOCKET_TIMEOUT):
+ """Decorator to add a socket timeout when requesting pages on PyPI.
+ """
+ def _socket_timeout(func):
+ def _socket_timeout(self, *args, **kwargs):
+ old_timeout = socket.getdefaulttimeout()
+ if hasattr(self, "_timeout"):
+ timeout = self._timeout
+ socket.setdefaulttimeout(timeout)
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ socket.setdefaulttimeout(old_timeout)
+ return _socket_timeout
+ return _socket_timeout
-def parse_bdist_wininst(name):
- """Parse a bdist_win package name, and return it's package base name
- and version number if prossible.
- Return a tuple of (base,pyversion) for possible .exe names"""
+class SimpleIndex(object):
+ """Provides useful tools to request the Python Package Index simple API
+
+ :param index_url: the url of the simple index to search on.
+ :param follow_externals: tell if following external links is needed or
+ not. Default is False.
+ :param hosts: a list of hosts allowed to be processed while using
+ follow_externals=True. Default behavior is to follow all
+ hosts.
+ :param follow_externals: tell if following external links is needed or
+ not. Default is False.
+ :param prefer_source: if there is binary and source distributions, the
+ source prevails.
+ :param prefer_final: if the version is not mentioned, and the last
+ version is not a "final" one (alpha, beta, etc.),
+ pick up the last final version.
+ :param mirrors_url: the url to look on for DNS records giving mirror
+ adresses.
+ :param mirrors: a list of mirrors to check out if problems
+ occurs while working with the one given in "url"
+ :param timeout: time in seconds to consider a url has timeouted.
+ """
- lower = name.lower()
- base, py_ver = None, None
+ def __init__(self, index_url=PYPI_DEFAULT_INDEX_URL, hosts=DEFAULT_HOSTS,
+ follow_externals=False, prefer_source=True,
+ prefer_final=False, mirrors_url=PYPI_DEFAULT_MIRROR_URL,
+ mirrors=None, timeout=SOCKET_TIMEOUT):
+ self.follow_externals = follow_externals
+
+ if not index_url.endswith("/"):
+ index_url += "/"
+ self._index_urls = [index_url]
+ # if no mirrors are defined, use the method described in PEP 381.
+ if mirrors is None:
+ try:
+ mirrors = socket.gethostbyname_ex(mirrors_url)[-1]
+ except socket.gaierror:
+ mirrors = []
+ self._index_urls.extend(mirrors)
+ self._current_index_url = 0
+ self._timeout = timeout
+ self._prefer_source = prefer_source
+ self._prefer_final = prefer_final
+
+ # create a regexp to match all given hosts
+ self._allowed_hosts = re.compile('|'.join(map(translate, hosts))).match
+
+ # we keep an index of pages we have processed, in order to avoid
+ # scanning them multple time (eg. if there is multiple pages pointing
+ # on one)
+ self._processed_urls = []
+ self._distributions = {}
+
+ def find(self, requirements, prefer_source=None, prefer_final=None):
+ """Browse the PyPI to find distributions that fullfil the given
+ requirements.
+
+ :param requirements: A project name and it's distribution, using
+ version specifiers, as described in PEP345.
+ :type requirements: You can pass either a version.VersionPredicate
+ or a string.
+ :param prefer_source: if there is binary and source distributions, the
+ source prevails.
+ :param prefer_final: if the version is not mentioned, and the last
+ version is not a "final" one (alpha, beta, etc.),
+ pick up the last final version.
+ """
+ requirements = self._get_version_predicate(requirements)
+ if prefer_source is None:
+ prefer_source = self._prefer_source
+ if prefer_final is None:
+ prefer_final = self._prefer_final
+
+ # process the index for this project
+ self._process_pypi_page(requirements.name)
+
+ # filter with requirements and return the results
+ if requirements.name in self._distributions:
+ dists = self._distributions[requirements.name].filter(requirements)
+ dists.sort_distributions(prefer_source=prefer_source,
+ prefer_final=prefer_final)
+ else:
+ dists = []
- if lower.endswith('.exe'):
- if lower.endswith('.win32.exe'):
- base = name[:-10]
- elif lower.startswith('.win32-py',-16):
- py_ver = name[-7:-4]
- base = name[:-16]
+ return dists
- return base,py_ver
+ def get(self, requirements, *args, **kwargs):
+ """Browse the PyPI index to find distributions that fullfil the
+ given requirements, and return the most recent one.
-def egg_info_for_url(url):
- """
- """
- scheme, server, path, parameters, query, fragment = urlparse.urlparse(url)
- base = urllib2.unquote(path.split('/')[-1])
- if '#' in base:
- base, fragment = base.split('#',1)
- return base,fragment
-
-def distros_for_url(url, metadata=None):
- """Yield egg or source distribution objects that might be found at a URL"""
- base, fragment = egg_info_for_url(url)
- for dist in distros_for_location(url, base, metadata):
- yield dist
- if fragment:
- match = EGG_FRAGMENT.match(fragment)
- if match:
- for dist in interpret_distro_name(url, match.group(1), metadata,
- precedence = CHECKOUT_DIST):
- yield dist
-
-def distros_for_location(location, basename, metadata=None):
- """Yield egg or source distribution objects based on basename"""
- if basename.endswith('.egg.zip'):
- basename = basename[:-4] # strip the .zip
- if basename.endswith('.egg') and '-' in basename:
- # only one, unambiguous interpretation
- return [Distribution.from_location(location, basename, metadata)]
-
- if basename.endswith('.exe'):
- win_base, py_ver = parse_bdist_wininst(basename)
- if win_base is not None:
- return interpret_distro_name(location, win_base, metadata, py_ver,
- BINARY_DIST, "win32")
-
- # Try source distro extensions (.zip, .tgz, etc.)
- for ext in EXTENSIONS:
- if basename.endswith(ext):
- basename = basename[:-len(ext)]
- return interpret_distro_name(location, basename, metadata)
- return [] # no extension matched
-
-def distros_for_filename(filename, metadata=None):
- """Yield possible egg or source distribution objects based on a
- filename.
- """
- return distros_for_location(normalize_path(filename),
- os.path.basename(filename), metadata)
+ You can specify prefer_final and prefer_source arguments here.
+ If not, the default one will be used.
+ """
+ predicate = self._get_version_predicate(requirements)
+ dists = self.find(predicate, *args, **kwargs)
-def interpret_distro_name(location, basename, metadata,
- py_version=None, precedence=SOURCE_DIST, platform=None):
- """Generate alternative interpretations of a source distro name
+ if len(dists) == 0:
+ raise DistributionNotFound(requirements)
- Note: if `location` is a filesystem filename, you should call
- ``pkg_resources.normalize_path()`` on it before passing it to this
- routine!
- """
- # Generate alternative interpretations of a source distro name
- # Because some packages are ambiguous as to name/versions split
- # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc.
- # So, we generate each possible interepretation (e.g. "adns, python-1.1.0"
- # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice,
- # the spurious interpretations should be ignored, because in the event
- # there's also an "adns" package, the spurious "python-1.1.0" version will
- # compare lower than any numeric version number, and is therefore unlikely
- # to match a request for it. It's still a potential problem, though, and
- # in the long run PyPI and the distutils should go for "safe" names and
- # versions in distribution archive names (sdist and bdist).
-
- parts = basename.split('-')
- if not py_version:
- for i,p in enumerate(parts[2:]):
- if len(p)==5 and p.startswith('py2.'):
- return # It's a bdist_dumb, not an sdist -- bail out
-
- for p in range(1,len(parts)+1):
- yield Distribution(
- location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]),
- py_version=py_version, precedence = precedence,
- platform = platform)
+ return dists.get_last(predicate)
-REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
+ def download(self, requirements, temp_path=None, *args, **kwargs):
+ """Download the distribution, using the requirements.
-def find_external_links(url, page):
- """Find rel="homepage" and rel="download" links in `page`, yielding URLs"""
-
- for match in REL.finditer(page):
- tag, rel = match.groups()
- rels = map(str.strip, rel.lower().split(','))
- if 'homepage' in rels or 'download' in rels:
- for match in HREF.finditer(tag):
- yield urlparse.urljoin(url, htmldecode(match.group(1)))
-
- for tag in ("<th>Home Page", "<th>Download URL"):
- pos = page.find(tag)
- if pos!=-1:
- match = HREF.search(page,pos)
- if match:
- yield urlparse.urljoin(url, htmldecode(match.group(1)))
-
-user_agent = "Python-urllib/%s distutils2/%s" % (
- sys.version[:3], __distutils2_version__
-)
-
-class PackageIndex(Environment):
- """A distribution index that scans web pages for download URLs"""
-
- def __init__(self, index_url="http://pypi.python.org/simple", hosts=('*',),
- *args, **kw):
- self._init_logging()
- Environment.__init__(self,*args,**kw)
- self.index_url = index_url + "/"[:not index_url.endswith('/')]
- self.scanned_urls = {}
- self.fetched_urls = {}
- self.package_pages = {}
- self.allows = re.compile('|'.join(map(translate,hosts))).match
- self.to_scan = []
-
- def _init_logging(self):
- import sys
-# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
-
- def process_url(self, url, retrieve=False):
- """Evaluate a URL as a possible download, and maybe retrieve it"""
- if url in self.scanned_urls and not retrieve:
- return
- self.scanned_urls[url] = True
- if not URL_SCHEME(url):
- self.process_filename(url)
- return
+ If more than one distribution match the requirements, use the last
+ version.
+ Download the distribution, and put it in the temp_path. If no temp_path
+ is given, creates and return one.
+
+ Returns the complete absolute path to the downloaded archive.
+
+ :param requirements: The same as the find attribute of `find`.
+
+ You can specify prefer_final and prefer_source arguments here.
+ If not, the default one will be used.
+ """
+ return self.get(requirements, *args, **kwargs)\
+ .download(path=temp_path)
+
+ def _get_version_predicate(self, requirements):
+ """Return a VersionPredicate object, from a string or an already
+ existing object.
+ """
+ if isinstance(requirements, str):
+ requirements = VersionPredicate(requirements)
+ return requirements
+
+ @property
+ def index_url(self):
+ return self._index_urls[self._current_index_url]
+
+ def _switch_to_next_mirror(self):
+ """Switch to the next mirror (eg. point self.index_url to the next
+ url.
+ """
+ # Internally, iter over the _index_url iterable, if we have read all
+ # of the available indexes, raise an exception.
+ if self._current_index_url < len(self._index_urls):
+ self._current_index_url = self._current_index_url + 1
else:
- dists = list(distros_for_url(url))
- if dists:
- if not self.url_ok(url):
- return
- self.debug("Found link: %s", url)
-
- if dists or not retrieve or url in self.fetched_urls:
- map(self.add, dists)
- return # don't need the actual page
-
- if not self.url_ok(url):
- self.fetched_urls[url] = True
- return
-
- self.info("Reading %s", url)
- f = self.open_url(url, "Download error: %s -- Some packages may not be found!")
- if f is None: return
- self.fetched_urls[url] = self.fetched_urls[f.url] = True
-
- if 'html' not in f.headers.get('content-type', '').lower():
- f.close() # not html, we can't process it
- return
-
- base = f.url # handle redirects
- page = f.read()
- if sys.version_info >= (3,):
- charset = f.headers.get_param('charset') or 'latin-1'
- page = page.decode(charset, "ignore")
- f.close()
- for match in HREF.finditer(page):
- link = urlparse.urljoin(base, htmldecode(match.group(1)))
- self.process_url(link)
- if url.startswith(self.index_url) and getattr(f,'code',None)!=404:
- page = self.process_index(url, page)
-
- def process_filename(self, fn, nested=False):
- # process filenames or directories
- if not os.path.exists(fn):
- self.warn("Not found: %s", fn)
- return
-
- if os.path.isdir(fn) and not nested:
- path = os.path.realpath(fn)
- for item in os.listdir(path):
- self.process_filename(os.path.join(path,item), True)
-
- dists = distros_for_filename(fn)
- if dists:
- self.debug("Found: %s", fn)
- map(self.add, dists)
-
- def url_ok(self, url, fatal=False):
- s = URL_SCHEME(url)
- if (s and s.group(1).lower()=='file') or self.allows(urlparse.urlparse(url)[1]):
+ raise UnableToDownload("All mirrors fails")
+
+ def _is_browsable(self, url):
+ """Tell if the given URL can be browsed or not.
+
+ It uses the follow_externals and the hosts list to tell if the given
+ url is browsable or not.
+ """
+ # if _index_url is contained in the given URL, we are browsing the
+ # index, and it's always "browsable".
+ # local files are always considered browable resources
+ if self.index_url in url or urlparse.urlparse(url)[0] == "file":
return True
- msg = "\nLink to % s ***BLOCKED*** by --allow-hosts\n"
- if fatal:
- raise DistutilsError(msg % url)
- else:
- self.warn(msg, url)
+ elif self.follow_externals:
+ if self._allowed_hosts(urlparse.urlparse(url)[1]): # 1 is netloc
+ return True
+ else:
+ return False
return False
- def scan_egg_links(self, search_path):
- for item in search_path:
- if os.path.isdir(item):
- for entry in os.listdir(item):
- if entry.endswith('.egg-link'):
- self.scan_egg_link(item, entry)
-
- def scan_egg_link(self, path, entry):
- lines = filter(None, map(str.strip, open(os.path.join(path, entry))))
- if len(lines)==2:
- for dist in find_distributions(os.path.join(path, lines[0])):
- dist.location = os.path.join(path, *lines)
- dist.precedence = SOURCE_DIST
- self.add(dist)
-
- def process_index(self,url,page):
- """Process the contents of a PyPI page"""
- def scan(link):
- # Process a URL to see if it's for a package page
- if link.startswith(self.index_url):
- parts = map(
- urllib2.unquote, link[len(self.index_url):].split('/')
- )
- if len(parts)==2 and '#' not in parts[1]:
- # it's a package page, sanitize and index it
- pkg = safe_name(parts[0])
- ver = safe_version(parts[1])
- self.package_pages.setdefault(pkg.lower(),{})[link] = True
- return to_filename(pkg), to_filename(ver)
- return None, None
-
- # process an index page into the package-page index
- for match in HREF.finditer(page):
- try:
- scan( urlparse.urljoin(url, htmldecode(match.group(1))) )
- except ValueError:
- pass
-
- pkg, ver = scan(url) # ensure this page is in the page index
- if pkg:
- # process individual package page
- for new_url in find_external_links(url, page):
- # Process the found URL
- base, frag = egg_info_for_url(new_url)
- if base.endswith('.py') and not frag:
- if ver:
- new_url+='#egg=%s-%s' % (pkg,ver)
+ def _is_distribution(self, link):
+ """Tell if the given URL matches to a distribution name or not.
+ """
+ #XXX find a better way to check that links are distributions
+ # Using a regexp ?
+ for ext in EXTENSIONS:
+ if ext in link:
+ return True
+ return False
+
+ def _register_dist(self, dist):
+ """Register a distribution as a part of fetched distributions for
+ SimpleIndex.
+
+ Return the PyPIDistributions object for the specified project name
+ """
+ # Internally, check if a entry exists with the project name, if not,
+ # create a new one, and if exists, add the dist to the pool.
+ if not dist.name in self._distributions:
+ self._distributions[dist.name] = PyPIDistributions()
+ self._distributions[dist.name].append(dist)
+ return self._distributions[dist.name]
+
+ def _process_url(self, url, project_name=None, follow_links=True):
+ """Process an url and search for distributions packages.
+
+ For each URL found, if it's a download, creates a PyPIdistribution
+ object. If it's a homepage and we can follow links, process it too.
+
+ :param url: the url to process
+ :param project_name: the project name we are searching for.
+ :param follow_links: Do not want to follow links more than from one
+ level. This parameter tells if we want to follow
+ the links we find (eg. run recursively this
+ method on it)
+ """
+ f = self._open_url(url)
+ base_url = f.url
+ if url not in self._processed_urls:
+ self._processed_urls.append(url)
+ link_matcher = self._get_link_matcher(url)
+ for link, is_download in link_matcher(f.read(), base_url):
+ if link not in self._processed_urls:
+ if self._is_distribution(link) or is_download:
+ self._processed_urls.append(link)
+ # it's a distribution, so create a dist object
+ dist = PyPIDistribution.from_url(link, project_name,
+ is_external=not self.index_url in url)
+ self._register_dist(dist)
else:
- self.need_version_info(url)
- self.scan_url(new_url)
+ if self._is_browsable(link) and follow_links:
+ self._process_url(link, project_name,
+ follow_links=False)
- return PYPI_MD5.sub(
- lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1,3,2), page
- )
+ def _get_link_matcher(self, url):
+ """Returns the right link matcher function of the given url
+ """
+ if self.index_url in url:
+ return self._simple_link_matcher
else:
- return "" # no sense double-scanning non-package pages
-
- def need_version_info(self, url):
- self.scan_all(
- "Page at %s links to .py file(s) without version info; an index "
- "scan is required.", url
- )
-
- def scan_all(self, msg=None, *args):
- if self.index_url not in self.fetched_urls:
- if msg: self.warn(msg,*args)
- self.info(
- "Scanning index of all packages (this may take a while)"
- )
- self.scan_url(self.index_url)
-
- def find_packages(self, requirement):
- self.scan_url(self.index_url + requirement.unsafe_name+'/')
-
- if not self.package_pages.get(requirement.key):
- # Fall back to safe version of the name
- self.scan_url(self.index_url + requirement.project_name+'/')
-
- if not self.package_pages.get(requirement.key):
- # We couldn't find the target package, so search the index page too
- self.not_found_in_index(requirement)
-
- for url in list(self.package_pages.get(requirement.key,())):
- # scan each page that might be related to the desired package
- self.scan_url(url)
-
- def obtain(self, requirement, installer=None):
- self.prescan(); self.find_packages(requirement)
- for dist in self[requirement.key]:
- if dist in requirement:
- return dist
- self.debug("%s does not match %s", requirement, dist)
- return super(PackageIndex, self).obtain(requirement,installer)
-
- def check_md5(self, cs, info, filename, tfp):
- if re.match('md5=[0-9a-f]{32}$', info):
- self.debug("Validating md5 checksum for %s", filename)
- if cs.hexdigest()<>info[4:]:
- tfp.close()
- os.unlink(filename)
- raise DistutilsError(
- "MD5 validation failed for "+os.path.basename(filename)+
- "; possible download problem?")
-
- def add_find_links(self, urls):
- """Add `urls` to the list that will be prescanned for searches"""
- for url in urls:
- if (
- self.to_scan is None # if we have already "gone online"
- or not URL_SCHEME(url) # or it's a local file/directory
- or url.startswith('file:')
- or list(distros_for_url(url)) # or a direct package link
- ):
- # then go ahead and process it now
- self.scan_url(url)
- else:
- # otherwise, defer retrieval till later
- self.to_scan.append(url)
-
- def prescan(self):
- """Scan urls scheduled for prescanning (e.g. --find-links)"""
- if self.to_scan:
- map(self.scan_url, self.to_scan)
- self.to_scan = None # from now on, go ahead and process immediately
-
- def not_found_in_index(self, requirement):
- if self[requirement.key]: # we've seen at least one distro
- meth, msg = self.info, "Couldn't retrieve index page for %r"
- else: # no distros seen for this name, might be misspelled
- meth, msg = (self.warn,
- "Couldn't find index page for %r (maybe misspelled?)")
- meth(msg, requirement.unsafe_name)
- self.scan_all()
-
- def download(self, spec, tmpdir):
- """Locate and/or download `spec` to `tmpdir`, returning a local path
-
- `spec` may be a ``Requirement`` object, or a string containing a URL,
- an existing local filename, or a project/version requirement spec
- (i.e. the string form of a ``Requirement`` object). If it is the URL
- of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one
- that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is
- automatically created alongside the downloaded file.
-
- If `spec` is a ``Requirement`` object or a string containing a
- project/version requirement spec, this method returns the location of
- a matching distribution (possibly after downloading it to `tmpdir`).
- If `spec` is a locally existing file or directory name, it is simply
- returned unchanged. If `spec` is a URL, it is downloaded to a subpath
- of `tmpdir`, and the local filename is returned. Various errors may be
- raised if a problem occurs during downloading.
+ return self._default_link_matcher
+
+ def _simple_link_matcher(self, content, base_url):
+ """Yield all links with a rel="download" or rel="homepage".
+
+ This matches the simple index requirements for matching links.
+ If follow_externals is set to False, dont yeld the external
+ urls.
"""
- if not isinstance(spec,Requirement):
- scheme = URL_SCHEME(spec)
- if scheme:
- # It's a url, download it to tmpdir
- found = self._download_url(scheme.group(1), spec, tmpdir)
- base, fragment = egg_info_for_url(spec)
- if base.endswith('.py'):
- found = self.gen_setup(found,fragment,tmpdir)
- return found
- elif os.path.exists(spec):
- # Existing file or directory, just return it
- return spec
- else:
- try:
- spec = Requirement.parse(spec)
- except ValueError:
- raise DistutilsError(
- "Not a URL, existing file, or requirement spec: %r" %
- (spec,)
- )
- return getattr(self.fetch_distribution(spec, tmpdir),'location',None)
-
- def fetch_distribution(self,
- requirement, tmpdir, force_scan=False, source=False, develop_ok=False
- ):
- """Obtain a distribution suitable for fulfilling `requirement`
-
- `requirement` must be a ``pkg_resources.Requirement`` instance.
- If necessary, or if the `force_scan` flag is set, the requirement is
- searched for in the (online) package index as well as the locally
- installed packages. If a distribution matching `requirement` is found,
- the returned distribution's ``location`` is the value you would have
- gotten from calling the ``download()`` method with the matching
- distribution's URL or filename. If no matching distribution is found,
- ``None`` is returned.
-
- If the `source` flag is set, only source distributions and source
- checkout links will be considered. Unless the `develop_ok` flag is
- set, development and system eggs (i.e., those using the ``.egg-info``
- format) will be ignored.
+ for match in REL.finditer(content):
+ tag, rel = match.groups()
+ rels = map(str.strip, rel.lower().split(','))
+ if 'homepage' in rels or 'download' in rels:
+ for match in HREF.finditer(tag):
+ url = urlparse.urljoin(base_url,
+ self._htmldecode(match.group(1)))
+ if 'download' in rels or self._is_browsable(url):
+ # yield a list of (url, is_download)
+ yield (urlparse.urljoin(base_url, url),
+ 'download' in rels)
+
+ def _default_link_matcher(self, content, base_url):
+ """Yield all links found on the page.
"""
-
- # process a Requirement
- self.info("Searching for %s", requirement)
- skipped = {}
-
- def find(req):
- # Find a matching distribution; may be called more than once
-
- for dist in self[req.key]:
-
- if dist.precedence==DEVELOP_DIST and not develop_ok:
- if dist not in skipped:
- self.warn("Skipping development or system egg: %s",dist)
- skipped[dist] = 1
- continue
-
- if dist in req and (dist.precedence<=SOURCE_DIST or not source):
- self.info("Best match: %s", dist)
- return dist.clone(
- location=self.download(dist.location, tmpdir)
- )
-
- if force_scan:
- self.prescan()
- self.find_packages(requirement)
-
- dist = find(requirement)
- if dist is None and self.to_scan is not None:
- self.prescan()
- dist = find(requirement)
-
- if dist is None and not force_scan:
- self.find_packages(requirement)
- dist = find(requirement)
-
- if dist is None:
- self.warn(
- "No local packages or download links found for %s%s",
- (source and "a source distribution of " or ""),
- requirement,
- )
- return dist
-
- def fetch(self, requirement, tmpdir, force_scan=False, source=False):
- """Obtain a file suitable for fulfilling `requirement`
-
- DEPRECATED; use the ``fetch_distribution()`` method now instead. For
- backward compatibility, this routine is identical but returns the
- ``location`` of the downloaded distribution instead of a distribution
- object.
+ for match in HREF.finditer(content):
+ url = urlparse.urljoin(base_url, self._htmldecode(match.group(1)))
+ if self._is_browsable(url):
+ yield (url, False)
+
+ def _process_pypi_page(self, name):
+ """Find and process a PyPI page for the given project name.
+
+ :param name: the name of the project to find the page
"""
- dist = self.fetch_distribution(requirement,tmpdir,force_scan,source)
- if dist is not None:
- return dist.location
- return None
-
- def gen_setup(self, filename, fragment, tmpdir):
- match = EGG_FRAGMENT.match(fragment)
- dists = match and [d for d in
- interpret_distro_name(filename, match.group(1), None) if d.version
- ] or []
-
- if len(dists)==1: # unambiguous ``#egg`` fragment
- basename = os.path.basename(filename)
-
- # Make sure the file has been downloaded to the temp dir.
- if os.path.dirname(filename) != tmpdir:
- dst = os.path.join(tmpdir, basename)
- from setuptools.command.easy_install import samefile
- if not samefile(filename, dst):
- shutil.copy2(filename, dst)
- filename=dst
-
- file = open(os.path.join(tmpdir, 'setup.py'), 'w')
- file.write(
- "from setuptools import setup\n"
- "setup(name=%r, version=%r, py_modules=[%r])\n"
- % (
- dists[0].project_name, dists[0].version,
- os.path.splitext(basename)[0]
- )
- )
- file.close()
- return filename
-
- elif match:
- raise DistutilsError(
- "Can't unambiguously interpret project/version identifier %r; "
- "any dashes in the name or version should be escaped using "
- "underscores. %r" % (fragment,dists)
- )
- else:
- raise DistutilsError(
- "Can't process plain .py without an '#egg=name-version'"
- " suffix to enable automatic setup script generation."
- )
-
- dl_blocksize = 8192
- def _download_to(self, url, filename):
- self.info("Downloading %s", url)
- # Download the file
- fp, tfp, info = None, None, None
try:
- if '#' in url:
- url, info = url.split('#', 1)
- fp = self.open_url(url)
- if isinstance(fp, urllib2.HTTPError):
- raise DistutilsError(
- "Can't download %s: %s %s" % (url, fp.code,fp.msg)
- )
- cs = md5()
- headers = fp.info()
- blocknum = 0
- bs = self.dl_blocksize
- size = -1
- if "content-length" in headers:
- size = int(headers["Content-Length"])
- self.reporthook(url, filename, blocknum, bs, size)
- tfp = open(filename,'wb')
- while True:
- block = fp.read(bs)
- if block:
- cs.update(block)
- tfp.write(block)
- blocknum += 1
- self.reporthook(url, filename, blocknum, bs, size)
- else:
- break
- if info: self.check_md5(cs, info, filename, tfp)
- return headers
- finally:
- if fp: fp.close()
- if tfp: tfp.close()
-
- def reporthook(self, url, filename, blocknum, blksize, size):
- pass # no-op
-
- def open_url(self, url, warning=None):
- if url.startswith('file:'):
- return local_open(url)
+ # Browse and index the content of the given PyPI page.
+ url = self.index_url + name + "/"
+ self._process_url(url, name)
+ except DownloadError:
+ # if an error occurs, try with the next index_url
+ # (provided by the mirrors)
+ self._switch_to_next_mirror()
+ self._distributions.clear()
+ self._process_pypi_page(name)
+
+ @socket_timeout()
+ def _open_url(self, url):
+ """Open a urllib2 request, handling HTTP authentication, and local
+ files support.
+
+ """
try:
- return open_with_auth(url)
+ scheme, netloc, path, params, query, frag = urlparse.urlparse(url)
+
+ if scheme in ('http', 'https'):
+ auth, host = urllib2.splituser(netloc)
+ else:
+ auth = None
+
+ # add index.html automatically for filesystem paths
+ if scheme == 'file':
+ if url.endswith('/'):
+ url += "index.html"
+
+ if auth:
+ auth = "Basic " + \
+ urllib2.unquote(auth).encode('base64').strip()
+ new_url = urlparse.urlunparse((
+ scheme, host, path, params, query, frag))
+ request = urllib2.Request(new_url)
+ request.add_header("Authorization", auth)
+ else:
+ request = urllib2.Request(url)
+ request.add_header('User-Agent', USER_AGENT)
+ fp = urllib2.urlopen(request)
+
+ if auth:
+ # Put authentication info back into request URL if same host,
+ # so that links found on the page will work
+ s2, h2, path2, param2, query2, frag2 = \
+ urlparse.urlparse(fp.url)
+ if s2 == scheme and h2 == host:
+ fp.url = urlparse.urlunparse(
+ (s2, netloc, path2, param2, query2, frag2))
+
+ return fp
except (ValueError, httplib.InvalidURL), v:
msg = ' '.join([str(arg) for arg in v.args])
- if warning:
- self.warn(warning, msg)
- else:
- raise DistutilsError('%s %s' % (url, msg))
+ raise PyPIError('%s %s' % (url, msg))
except urllib2.HTTPError, v:
return v
except urllib2.URLError, v:
- if warning:
- self.warn(warning, v.reason)
- else:
- raise DistutilsError("Download error for %s: %s"
- % (url, v.reason))
+ raise DownloadError("Download error for %s: %s" % (url, v.reason))
except httplib.BadStatusLine, v:
- if warning:
- self.warn(warning, v.line)
- else:
- raise DistutilsError('%s returned a bad status line. '
- 'The server might be down, %s' % \
- (url, v.line))
+ raise DownloadError('%s returned a bad status line. '
+ 'The server might be down, %s' % (url, v.line))
except httplib.HTTPException, v:
- if warning:
- self.warn(warning, v)
- else:
- raise DistutilsError("Download error for %s: %s"
- % (url, v))
-
- def _download_url(self, scheme, url, tmpdir):
- # Determine download filename
- name = filter(None,urlparse.urlparse(url)[2].split('/'))
- if name:
- name = name[-1]
- while '..' in name:
- name = name.replace('..','.').replace('\\','_')
- else:
- name = "__downloaded__" # default if URL has no path contents
-
- if name.endswith('.egg.zip'):
- name = name[:-4] # strip the extra .zip before download
-
- filename = os.path.join(tmpdir,name)
-
- # Download the file
- if scheme=='svn' or scheme.startswith('svn+'):
- return self._download_svn(url, filename)
- elif scheme=='file':
- return urllib2.url2pathname(urlparse.urlparse(url)[2])
- else:
- self.url_ok(url, True) # raises error if not allowed
- return self._attempt_download(url, filename)
-
- def scan_url(self, url):
- self.process_url(url, True)
-
- def _attempt_download(self, url, filename):
- headers = self._download_to(url, filename)
- if 'html' in headers.get('content-type','').lower():
- return self._download_html(url, headers, filename)
+ raise DownloadError("Download error for %s: %s" % (url, v))
+
+ def _decode_entity(self, match):
+ what = match.group(1)
+ if what.startswith('#x'):
+ what = int(what[2:], 16)
+ elif what.startswith('#'):
+ what = int(what[1:])
else:
- return filename
-
- def _download_html(self, url, headers, filename):
- file = open(filename)
- for line in file:
- if line.strip():
- # Check for a subversion index page
- if re.search(r'<title>([^- ]+ - )?Revision \d+:', line):
- # it's a subversion index page:
- file.close()
- os.unlink(filename)
- return self._download_svn(url, filename)
- break # not an index page
- file.close()
- os.unlink(filename)
- raise DistutilsError("Unexpected HTML page found at "+url)
-
- def _download_svn(self, url, filename):
- url = url.split('#',1)[0] # remove any fragment for svn's sake
- self.info("Doing subversion checkout from %s to %s", url, filename)
- os.system("svn checkout -q %s %s" % (url, filename))
- return filename
-
- def debug(self, msg, *args):
- logging.debug(msg, *args)
-
- def info(self, msg, *args):
- logging.info(msg, *args)
-
- def warn(self, msg, *args):
- logging.warn(msg, *args)
+ from htmlentitydefs import name2codepoint
+ what = name2codepoint.get(what, match.group(0))
+ return unichr(what)
-# This pattern matches a character entity reference (a decimal numeric
-# references, a hexadecimal numeric reference, or a named reference).
-entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub
-
-def uchr(c):
- if not isinstance(c, int):
- return c
- if c>255:
- return unichr(c)
- return chr(c)
-
-def decode_entity(match):
- what = match.group(1)
- if what.startswith('#x'):
- what = int(what[2:], 16)
- elif what.startswith('#'):
- what = int(what[1:])
- else:
- from htmlentitydefs import name2codepoint
- what = name2codepoint.get(what, match.group(0))
- return uchr(what)
-
-def htmldecode(text):
- """Decode HTML entities in the given text."""
- return entity_sub(decode_entity, text)
-
-def socket_timeout(timeout=15):
- def _socket_timeout(func):
- def _socket_timeout(*args, **kwargs):
- old_timeout = socket.getdefaulttimeout()
- socket.setdefaulttimeout(timeout)
- try:
- return func(*args, **kwargs)
- finally:
- socket.setdefaulttimeout(old_timeout)
- return _socket_timeout
- return _socket_timeout
-
-def open_with_auth(url):
- """Open a urllib2 request, handling HTTP authentication"""
-
- scheme, netloc, path, params, query, frag = urlparse.urlparse(url)
-
- if scheme in ('http', 'https'):
- auth, host = urllib2.splituser(netloc)
- else:
- auth = None
-
- if auth:
- auth = "Basic " + urllib2.unquote(auth).encode('base64').strip()
- new_url = urlparse.urlunparse((scheme,host,path,params,query,frag))
- request = urllib2.Request(new_url)
- request.add_header("Authorization", auth)
- else:
- request = urllib2.Request(url)
-
- request.add_header('User-Agent', user_agent)
- fp = urllib2.urlopen(request)
-
- if auth:
- # Put authentication info back into request URL if same host,
- # so that links found on the page will work
- s2, h2, path2, param2, query2, frag2 = urlparse.urlparse(fp.url)
- if s2==scheme and h2==host:
- fp.url = urlparse.urlunparse((s2,netloc,path2,param2,query2,frag2))
-
- return fp
-
-
-# adding a timeout to avoid freezing package_index
-open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth)
-
-def fix_sf_url(url):
- return url # backward compatibility
-
-def local_open(url):
- """Read a local path, with special support for directories"""
- scheme, server, path, param, query, frag = urlparse.urlparse(url)
- filename = urllib2.url2pathname(path)
- if os.path.isfile(filename):
- return urllib2.urlopen(url)
- elif path.endswith('/') and os.path.isdir(filename):
- files = []
- for f in os.listdir(filename):
- if f=='index.html':
- fp = open(os.path.join(filename,f),'rb')
- body = fp.read()
- fp.close()
- break
- elif os.path.isdir(os.path.join(filename,f)):
- f+='/'
- files.append("<a href=%r>%s</a>" % (f,f))
- else:
- body = ("<html><head><title>%s</title>" % url) + \
- "</head><body>%s</body></html>" % '\n'.join(files)
- status, message = 200, "OK"
- else:
- status, message, body = 404, "Path not found", "Not found"
-
- return urllib2.HTTPError(url, status, message,
- {'content-type':'text/html'}, cStringIO.StringIO(body))
+ def _htmldecode(self, text):
+ """Decode HTML entities in the given text."""
+ return ENTITY_SUB(self._decode_entity, text)
diff --git a/src/distutils2/spawn.py b/src/distutils2/spawn.py
deleted file mode 100644
index a875459..0000000
--- a/src/distutils2/spawn.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""distutils.spawn
-
-Provides the 'spawn()' function, a front-end to various platform-
-specific functions for launching another program in a sub-process.
-Also provides the 'find_executable()' to search the path for a given
-executable name.
-"""
-
-__revision__ = "$Id: spawn.py 73147 2009-06-02 15:58:43Z tarek.ziade $"
-
-import sys
-import os
-
-from distutils2.errors import DistutilsPlatformError, DistutilsExecError
-from distutils2 import log
-
-def spawn(cmd, search_path=1, verbose=0, dry_run=0):
- """Run another program, specified as a command list 'cmd', in a new process.
-
- 'cmd' is just the argument list for the new process, ie.
- cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
- There is no way to run a program with a name different from that of its
- executable.
-
- If 'search_path' is true (the default), the system's executable
- search path will be used to find the program; otherwise, cmd[0]
- must be the exact path to the executable. If 'dry_run' is true,
- the command will not actually be run.
-
- Raise DistutilsExecError if running the program fails in any way; just
- return on success.
- """
- if os.name == 'posix':
- _spawn_posix(cmd, search_path, dry_run=dry_run)
- elif os.name == 'nt':
- _spawn_nt(cmd, search_path, dry_run=dry_run)
- elif os.name == 'os2':
- _spawn_os2(cmd, search_path, dry_run=dry_run)
- else:
- raise DistutilsPlatformError, \
- "don't know how to spawn programs on platform '%s'" % os.name
-
-def _nt_quote_args(args):
- """Quote command-line arguments for DOS/Windows conventions.
-
- Just wraps every argument which contains blanks in double quotes, and
- returns a new argument list.
- """
- # XXX this doesn't seem very robust to me -- but if the Windows guys
- # say it'll work, I guess I'll have to accept it. (What if an arg
- # contains quotes? What other magic characters, other than spaces,
- # have to be escaped? Is there an escaping mechanism other than
- # quoting?)
- for i, arg in enumerate(args):
- if ' ' in arg:
- args[i] = '"%s"' % arg
- return args
-
-def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0):
- executable = cmd[0]
- cmd = _nt_quote_args(cmd)
- if search_path:
- # either we find one or it stays the same
- executable = find_executable(executable) or executable
- log.info(' '.join([executable] + cmd[1:]))
- if not dry_run:
- # spawn for NT requires a full path to the .exe
- try:
- rc = os.spawnv(os.P_WAIT, executable, cmd)
- except OSError, exc:
- # this seems to happen when the command isn't found
- raise DistutilsExecError, \
- "command '%s' failed: %s" % (cmd[0], exc[-1])
- if rc != 0:
- # and this reflects the command running but failing
- raise DistutilsExecError, \
- "command '%s' failed with exit status %d" % (cmd[0], rc)
-
-def _spawn_os2(cmd, search_path=1, verbose=0, dry_run=0):
- executable = cmd[0]
- if search_path:
- # either we find one or it stays the same
- executable = find_executable(executable) or executable
- log.info(' '.join([executable] + cmd[1:]))
- if not dry_run:
- # spawnv for OS/2 EMX requires a full path to the .exe
- try:
- rc = os.spawnv(os.P_WAIT, executable, cmd)
- except OSError, exc:
- # this seems to happen when the command isn't found
- raise DistutilsExecError, \
- "command '%s' failed: %s" % (cmd[0], exc[-1])
- if rc != 0:
- # and this reflects the command running but failing
- log.debug("command '%s' failed with exit status %d" % (cmd[0], rc))
- raise DistutilsExecError, \
- "command '%s' failed with exit status %d" % (cmd[0], rc)
-
-
-def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0):
- log.info(' '.join(cmd))
- if dry_run:
- return
- exec_fn = search_path and os.execvp or os.execv
- pid = os.fork()
-
- if pid == 0: # in the child
- try:
- exec_fn(cmd[0], cmd)
- except OSError, e:
- sys.stderr.write("unable to execute %s: %s\n" %
- (cmd[0], e.strerror))
- os._exit(1)
-
- sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0])
- os._exit(1)
- else: # in the parent
- # Loop until the child either exits or is terminated by a signal
- # (ie. keep waiting if it's merely stopped)
- while 1:
- try:
- pid, status = os.waitpid(pid, 0)
- except OSError, exc:
- import errno
- if exc.errno == errno.EINTR:
- continue
- raise DistutilsExecError, \
- "command '%s' failed: %s" % (cmd[0], exc[-1])
- if os.WIFSIGNALED(status):
- raise DistutilsExecError, \
- "command '%s' terminated by signal %d" % \
- (cmd[0], os.WTERMSIG(status))
-
- elif os.WIFEXITED(status):
- exit_status = os.WEXITSTATUS(status)
- if exit_status == 0:
- return # hey, it succeeded!
- else:
- raise DistutilsExecError, \
- "command '%s' failed with exit status %d" % \
- (cmd[0], exit_status)
-
- elif os.WIFSTOPPED(status):
- continue
-
- else:
- raise DistutilsExecError, \
- "unknown error executing '%s': termination status %d" % \
- (cmd[0], status)
-
-def find_executable(executable, path=None):
- """Tries to find 'executable' in the directories listed in 'path'.
-
- A string listing directories separated by 'os.pathsep'; defaults to
- os.environ['PATH']. Returns the complete filename or None if not found.
- """
- if path is None:
- path = os.environ['PATH']
- paths = path.split(os.pathsep)
- base, ext = os.path.splitext(executable)
-
- if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
- executable = executable + '.exe'
-
- if not os.path.isfile(executable):
- for p in paths:
- f = os.path.join(p, executable)
- if os.path.isfile(f):
- # the file exists, we have a shot at spawn working
- return f
- return None
- else:
- return executable
diff --git a/src/distutils2/tests/__init__.py b/src/distutils2/tests/__init__.py
index f5b4b1e..8a8d187 100644
--- a/src/distutils2/tests/__init__.py
+++ b/src/distutils2/tests/__init__.py
@@ -45,7 +45,7 @@ class TestFailed(Error):
"""Test failed."""
-class BasicTestRunner:
+class BasicTestRunner(object):
def run(self, test):
result = unittest.TestResult()
test(result)
diff --git a/src/distutils2/tests/conversions/05_after.py b/src/distutils2/tests/conversions/05_after.py
new file mode 100644
index 0000000..2f6a7b3
--- /dev/null
+++ b/src/distutils2/tests/conversions/05_after.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2003-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+from distutils2.core import setup, find_packages
+
+extra = {}
+
+try:
+ import babel
+
+ extractors = [
+ ('**.py', 'python', None),
+ ('**/templates/**.html', 'genshi', None),
+ ('**/templates/**.txt', 'genshi',
+ {'template_class': 'genshi.template:NewTextTemplate'}),
+ ]
+ extra['message_extractors'] = {
+ 'trac': extractors,
+ 'tracopt': extractors,
+ }
+
+ from trac.util.dist import get_l10n_js_cmdclass
+ extra['cmdclass'] = get_l10n_js_cmdclass()
+
+except ImportError, e:
+ pass
+
+setup(
+ name = 'Trac',
+ version = '0.12.1',
+ summary = 'Integrated SCM, wiki, issue tracker and project environment',
+ description = """
+Trac is a minimalistic web-based software project management and bug/issue
+tracking system. It provides an interface to the Subversion revision control
+systems, an integrated wiki, flexible issue tracking and convenient report
+facilities.
+""",
+ author = 'Edgewall Software',
+ author_email = 'info@edgewall.com',
+ license = 'BSD',
+ home_page = 'http://trac.edgewall.org/',
+ download_url = 'http://trac.edgewall.org/wiki/TracDownload',
+ classifiers = [
+ 'Environment :: Web Environment',
+ 'Framework :: Trac',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Bug Tracking',
+ 'Topic :: Software Development :: Version Control',
+ ],
+
+ packages = find_packages(exclude=['*.tests']),
+ package_data = {
+ '': ['templates/*'],
+ 'trac': ['htdocs/*.*', 'htdocs/README', 'htdocs/js/*.*',
+ 'htdocs/js/messages/*.*', 'htdocs/css/*.*',
+ 'htdocs/guide/*', 'locale/*/LC_MESSAGES/messages.mo'],
+ 'trac.wiki': ['default-pages/*'],
+ 'trac.ticket': ['workflows/*.ini'],
+ },
+
+ test_suite = 'trac.test.suite',
+ zip_safe = True,
+
+ requires_dist = [
+ 'setuptools>=0.6b1',
+ 'Genshi>=0.6',
+ ],
+ extras_require = {
+ 'Babel': ['Babel>=0.9.5'],
+ 'Pygments': ['Pygments>=0.6'],
+ 'reST': ['docutils>=0.3'],
+ 'SilverCity': ['SilverCity>=0.9.4'],
+ 'Textile': ['textile>=2.0'],
+ },
+
+ entry_points = """
+ [console_scripts]
+ trac-admin = trac.admin.console:run
+ tracd = trac.web.standalone:main
+
+ [trac.plugins]
+ trac.about = trac.about
+ trac.admin.console = trac.admin.console
+ trac.admin.web_ui = trac.admin.web_ui
+ trac.attachment = trac.attachment
+ trac.db.mysql = trac.db.mysql_backend
+ trac.db.postgres = trac.db.postgres_backend
+ trac.db.sqlite = trac.db.sqlite_backend
+ trac.mimeview.patch = trac.mimeview.patch
+ trac.mimeview.pygments = trac.mimeview.pygments[Pygments]
+ trac.mimeview.rst = trac.mimeview.rst[reST]
+ trac.mimeview.silvercity = trac.mimeview.silvercity[SilverCity]
+ trac.mimeview.txtl = trac.mimeview.txtl[Textile]
+ trac.prefs = trac.prefs.web_ui
+ trac.search = trac.search.web_ui
+ trac.ticket.admin = trac.ticket.admin
+ trac.ticket.query = trac.ticket.query
+ trac.ticket.report = trac.ticket.report
+ trac.ticket.roadmap = trac.ticket.roadmap
+ trac.ticket.web_ui = trac.ticket.web_ui
+ trac.timeline = trac.timeline.web_ui
+ trac.versioncontrol.admin = trac.versioncontrol.admin
+ trac.versioncontrol.svn_authz = trac.versioncontrol.svn_authz
+ trac.versioncontrol.svn_fs = trac.versioncontrol.svn_fs
+ trac.versioncontrol.svn_prop = trac.versioncontrol.svn_prop
+ trac.versioncontrol.web_ui = trac.versioncontrol.web_ui
+ trac.web.auth = trac.web.auth
+ trac.web.session = trac.web.session
+ trac.wiki.admin = trac.wiki.admin
+ trac.wiki.interwiki = trac.wiki.interwiki
+ trac.wiki.macros = trac.wiki.macros
+ trac.wiki.web_ui = trac.wiki.web_ui
+ trac.wiki.web_api = trac.wiki.web_api
+ tracopt.mimeview.enscript = tracopt.mimeview.enscript
+ tracopt.mimeview.php = tracopt.mimeview.php
+ tracopt.perm.authz_policy = tracopt.perm.authz_policy
+ tracopt.perm.config_perm_provider = tracopt.perm.config_perm_provider
+ tracopt.ticket.commit_updater = tracopt.ticket.commit_updater
+ tracopt.ticket.deleter = tracopt.ticket.deleter
+ """,
+
+ **extra
+)
diff --git a/src/distutils2/tests/conversions/05_before.py b/src/distutils2/tests/conversions/05_before.py
new file mode 100755
index 0000000..ccce17b
--- /dev/null
+++ b/src/distutils2/tests/conversions/05_before.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2003-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+from setuptools import setup, find_packages
+
+extra = {}
+
+try:
+ import babel
+
+ extractors = [
+ ('**.py', 'python', None),
+ ('**/templates/**.html', 'genshi', None),
+ ('**/templates/**.txt', 'genshi',
+ {'template_class': 'genshi.template:NewTextTemplate'}),
+ ]
+ extra['message_extractors'] = {
+ 'trac': extractors,
+ 'tracopt': extractors,
+ }
+
+ from trac.util.dist import get_l10n_js_cmdclass
+ extra['cmdclass'] = get_l10n_js_cmdclass()
+
+except ImportError, e:
+ pass
+
+setup(
+ name = 'Trac',
+ version = '0.12.1',
+ description = 'Integrated SCM, wiki, issue tracker and project environment',
+ long_description = """
+Trac is a minimalistic web-based software project management and bug/issue
+tracking system. It provides an interface to the Subversion revision control
+systems, an integrated wiki, flexible issue tracking and convenient report
+facilities.
+""",
+ author = 'Edgewall Software',
+ author_email = 'info@edgewall.com',
+ license = 'BSD',
+ url = 'http://trac.edgewall.org/',
+ download_url = 'http://trac.edgewall.org/wiki/TracDownload',
+ classifiers = [
+ 'Environment :: Web Environment',
+ 'Framework :: Trac',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Bug Tracking',
+ 'Topic :: Software Development :: Version Control',
+ ],
+
+ packages = find_packages(exclude=['*.tests']),
+ package_data = {
+ '': ['templates/*'],
+ 'trac': ['htdocs/*.*', 'htdocs/README', 'htdocs/js/*.*',
+ 'htdocs/js/messages/*.*', 'htdocs/css/*.*',
+ 'htdocs/guide/*', 'locale/*/LC_MESSAGES/messages.mo'],
+ 'trac.wiki': ['default-pages/*'],
+ 'trac.ticket': ['workflows/*.ini'],
+ },
+
+ test_suite = 'trac.test.suite',
+ zip_safe = True,
+
+ install_requires = [
+ 'setuptools>=0.6b1',
+ 'Genshi>=0.6',
+ ],
+ extras_require = {
+ 'Babel': ['Babel>=0.9.5'],
+ 'Pygments': ['Pygments>=0.6'],
+ 'reST': ['docutils>=0.3'],
+ 'SilverCity': ['SilverCity>=0.9.4'],
+ 'Textile': ['textile>=2.0'],
+ },
+
+ entry_points = """
+ [console_scripts]
+ trac-admin = trac.admin.console:run
+ tracd = trac.web.standalone:main
+
+ [trac.plugins]
+ trac.about = trac.about
+ trac.admin.console = trac.admin.console
+ trac.admin.web_ui = trac.admin.web_ui
+ trac.attachment = trac.attachment
+ trac.db.mysql = trac.db.mysql_backend
+ trac.db.postgres = trac.db.postgres_backend
+ trac.db.sqlite = trac.db.sqlite_backend
+ trac.mimeview.patch = trac.mimeview.patch
+ trac.mimeview.pygments = trac.mimeview.pygments[Pygments]
+ trac.mimeview.rst = trac.mimeview.rst[reST]
+ trac.mimeview.silvercity = trac.mimeview.silvercity[SilverCity]
+ trac.mimeview.txtl = trac.mimeview.txtl[Textile]
+ trac.prefs = trac.prefs.web_ui
+ trac.search = trac.search.web_ui
+ trac.ticket.admin = trac.ticket.admin
+ trac.ticket.query = trac.ticket.query
+ trac.ticket.report = trac.ticket.report
+ trac.ticket.roadmap = trac.ticket.roadmap
+ trac.ticket.web_ui = trac.ticket.web_ui
+ trac.timeline = trac.timeline.web_ui
+ trac.versioncontrol.admin = trac.versioncontrol.admin
+ trac.versioncontrol.svn_authz = trac.versioncontrol.svn_authz
+ trac.versioncontrol.svn_fs = trac.versioncontrol.svn_fs
+ trac.versioncontrol.svn_prop = trac.versioncontrol.svn_prop
+ trac.versioncontrol.web_ui = trac.versioncontrol.web_ui
+ trac.web.auth = trac.web.auth
+ trac.web.session = trac.web.session
+ trac.wiki.admin = trac.wiki.admin
+ trac.wiki.interwiki = trac.wiki.interwiki
+ trac.wiki.macros = trac.wiki.macros
+ trac.wiki.web_ui = trac.wiki.web_ui
+ trac.wiki.web_api = trac.wiki.web_api
+ tracopt.mimeview.enscript = tracopt.mimeview.enscript
+ tracopt.mimeview.php = tracopt.mimeview.php
+ tracopt.perm.authz_policy = tracopt.perm.authz_policy
+ tracopt.perm.config_perm_provider = tracopt.perm.config_perm_provider
+ tracopt.ticket.commit_updater = tracopt.ticket.commit_updater
+ tracopt.ticket.deleter = tracopt.ticket.deleter
+ """,
+
+ **extra
+)
diff --git a/src/distutils2/tests/pypi_server.py b/src/distutils2/tests/pypi_server.py
index cd57ce5..3927262 100644
--- a/src/distutils2/tests/pypi_server.py
+++ b/src/distutils2/tests/pypi_server.py
@@ -26,10 +26,12 @@ def use_pypi_server(*server_args, **server_kwargs):
def wrapped(*args, **kwargs):
server = PyPIServer(*server_args, **server_kwargs)
server.start()
- func(server=server, *args, **kwargs)
- server.stop()
+ try:
+ func(server=server, *args, **kwargs)
+ finally:
+ server.stop()
return wrapped
- return wrapper
+ return wrapper
class PyPIServerTestCase(unittest.TestCase):
@@ -50,7 +52,7 @@ class PyPIServer(threading.Thread):
"""
def __init__(self, test_static_path=None,
- static_filesystem_paths=["default"], static_uri_paths=["simple"]):
+ static_filesystem_paths=["default"], static_uri_paths=["simple"]):
"""Initialize the server.
static_uri_paths and static_base_path are parameters used to provides
@@ -59,7 +61,7 @@ class PyPIServer(threading.Thread):
"""
threading.Thread.__init__(self)
self._run = True
- self.httpd = HTTPServer(('', 0), PyPIRequestHandler)
+ self.httpd = HTTPServer(('', 0), PyPIRequestHandler)
self.httpd.RequestHandlerClass.log_request = lambda *_: None
self.httpd.RequestHandlerClass.pypi_server = self
self.address = (self.httpd.server_name, self.httpd.server_port)
@@ -131,7 +133,7 @@ class PyPIRequestHandler(SimpleHTTPRequestHandler):
"""
# record the request. Read the input only on PUT or POST requests
if self.command in ("PUT", "POST"):
- if self.headers.dict.has_key("content-length"):
+ if 'content-length' in self.headers.dict:
request_data = self.rfile.read(
int(self.headers['content-length']))
else:
@@ -158,7 +160,11 @@ class PyPIRequestHandler(SimpleHTTPRequestHandler):
relative_path += "index.html"
file = open(fs_path + relative_path)
data = file.read()
- self.make_response(data)
+ if relative_path.endswith('.tar.gz'):
+ headers=[('Content-type', 'application/x-gtar')]
+ else:
+ headers=[('Content-type', 'text/html')]
+ self.make_response(data, headers=headers)
except IOError:
pass
@@ -171,8 +177,8 @@ class PyPIRequestHandler(SimpleHTTPRequestHandler):
status, headers, data = self.pypi_server.get_next_response()
self.make_response(data, status, headers)
- def make_response(self, data, status=200,
- headers=[('Content-type', 'text/html')]):
+ def make_response(self, data, status=200,
+ headers=[('Content-type', 'text/html')]):
"""Send the response to the HTTP client"""
if not isinstance(status, int):
try:
@@ -180,7 +186,7 @@ class PyPIRequestHandler(SimpleHTTPRequestHandler):
except ValueError:
# we probably got something like YYY Codename.
# Just get the first 3 digits
- status = int(status[:3])
+ status = int(status[:3])
self.send_response(status)
for header, value in headers:
diff --git a/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz
diff --git a/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html
new file mode 100644
index 0000000..b89f1bd
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html
@@ -0,0 +1,3 @@
+<html><body>
+<a href="badmd5-0.1.tar.gz#md5=3e3d86693d6564c807272b11b3069dfe" rel="download">badmd5-0.1.tar.gz</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz
diff --git a/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html
new file mode 100644
index 0000000..add0b85
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html
@@ -0,0 +1,3 @@
+<html><body>
+<a href="foobar-0.1.tar.gz#md5=d41d8cd98f00b204e9800998ecf8427e" rel="download">foobar-0.1.tar.gz</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/downloads_with_md5/simple/index.html b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/index.html
new file mode 100644
index 0000000..9baee04
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/downloads_with_md5/simple/index.html
@@ -0,0 +1,2 @@
+<a href="foobar/">foobar/</a>
+<a href="badmd5/">badmd5/</a>
diff --git a/src/distutils2/tests/pypiserver/foo_bar_baz/simple/bar/index.html b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/bar/index.html
new file mode 100644
index 0000000..c3d42c5
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/bar/index.html
@@ -0,0 +1,6 @@
+<html><head><title>Links for bar</title></head><body><h1>Links for bar</h1>
+<a rel="download" href="../../packages/source/F/bar/bar-1.0.tar.gz">bar-1.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/bar/bar-1.0.1.tar.gz">bar-1.0.1.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/bar/bar-2.0.tar.gz">bar-2.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/bar/bar-2.0.1.tar.gz">bar-2.0.1.tar.gz</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/foo_bar_baz/simple/baz/index.html b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/baz/index.html
new file mode 100644
index 0000000..4f34312
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/baz/index.html
@@ -0,0 +1,6 @@
+<html><head><title>Links for baz</title></head><body><h1>Links for baz</h1>
+<a rel="download" href="../../packages/source/F/baz/baz-1.0.tar.gz">baz-1.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/baz/baz-1.0.1.tar.gz">baz-1.0.1.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/baz/baz-2.0.tar.gz">baz-2.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/baz/baz-2.0.1.tar.gz">baz-2.0.1.tar.gz</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/foo_bar_baz/simple/foo/index.html b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/foo/index.html
new file mode 100644
index 0000000..0565e11
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/foo/index.html
@@ -0,0 +1,6 @@
+<html><head><title>Links for foo</title></head><body><h1>Links for foo</h1>
+<a rel="download" href="../../packages/source/F/foo/foo-1.0.tar.gz">foo-1.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/foo/foo-1.0.1.tar.gz">foo-1.0.1.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/foo/foo-2.0.tar.gz">foo-2.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/foo/foo-2.0.1.tar.gz">foo-2.0.1.tar.gz</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/foo_bar_baz/simple/index.html b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/index.html
new file mode 100644
index 0000000..a70cfd3
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/foo_bar_baz/simple/index.html
@@ -0,0 +1,3 @@
+<a href="foo/">foo/</a>
+<a href="bar/">bar/</a>
+<a href="baz/">baz/</a>
diff --git a/src/distutils2/tests/pypiserver/test_found_links/simple/foobar/index.html b/src/distutils2/tests/pypiserver/test_found_links/simple/foobar/index.html
index 2da4bba..a282a4e 100644
--- a/src/distutils2/tests/pypiserver/test_found_links/simple/foobar/index.html
+++ b/src/distutils2/tests/pypiserver/test_found_links/simple/foobar/index.html
@@ -1,6 +1,6 @@
<html><head><title>Links for Foobar</title></head><body><h1>Links for Foobar</h1>
-<a href="../../packages/source/F/Foobar/Foobar-1.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c174">Foobar-1.0.tar.gz</a><br/>
-<a href="../../packages/source/F/Foobar/Foobar-1.0.1.tar.gz#md5=2351efb20f6b7b5d9ce80fa4cb1bd9ca">Foobar-1.0.1.tar.gz</a><br/>
-<a href="../../packages/source/F/Foobar/Foobar-2.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c274">Foobar-2.0.tar.gz</a><br/>
-<a href="../../packages/source/F/Foobar/Foobar-2.0.1.tar.gz#md5=2352efb20f6b7b5d9ce80fa4cb2bd9ca">Foobar-2.0.1.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/Foobar/Foobar-1.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c174">Foobar-1.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/Foobar/Foobar-1.0.1.tar.gz#md5=2351efb20f6b7b5d9ce80fa4cb1bd9ca">Foobar-1.0.1.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/Foobar/Foobar-2.0.tar.gz#md5=98fa833fdabcdd78d00245aead66c274">Foobar-2.0.tar.gz</a><br/>
+<a rel="download" href="../../packages/source/F/Foobar/Foobar-2.0.1.tar.gz#md5=2352efb20f6b7b5d9ce80fa4cb2bd9ca">Foobar-2.0.1.tar.gz</a><br/>
</body></html>
diff --git a/src/distutils2/tests/pypiserver/test_link_priority/external/external.html b/src/distutils2/tests/pypiserver/with_externals/external/external.html
index 92e4702..92e4702 100644
--- a/src/distutils2/tests/pypiserver/test_link_priority/external/external.html
+++ b/src/distutils2/tests/pypiserver/with_externals/external/external.html
diff --git a/src/distutils2/tests/pypiserver/test_link_priority/simple/foobar/index.html b/src/distutils2/tests/pypiserver/with_externals/simple/foobar/index.html
index 2af83d8..b100a26 100644
--- a/src/distutils2/tests/pypiserver/test_link_priority/simple/foobar/index.html
+++ b/src/distutils2/tests/pypiserver/with_externals/simple/foobar/index.html
@@ -1,4 +1,4 @@
<html><body>
-<a href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/>
+<a rel ="download" href="/foobar-0.1.tar.gz#md5=12345678901234567">foobar-0.1.tar.gz</a><br/>
<a href="../../external/external.html" rel="homepage">external homepage</a><br/>
</body></html>
diff --git a/src/distutils2/tests/pypiserver/test_link_priority/simple/index.html b/src/distutils2/tests/pypiserver/with_externals/simple/index.html
index a1a7bb7..a1a7bb7 100644
--- a/src/distutils2/tests/pypiserver/test_link_priority/simple/index.html
+++ b/src/distutils2/tests/pypiserver/with_externals/simple/index.html
diff --git a/src/distutils2/tests/pypiserver/with_norel_links/external/homepage.html b/src/distutils2/tests/pypiserver/with_norel_links/external/homepage.html
new file mode 100644
index 0000000..1cc0c32
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_norel_links/external/homepage.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+<p>a rel=homepage HTML page</p>
+<a href="/foobar-2.0.tar.gz">foobar 2.0</a>
+</body>
+</html>
+
diff --git a/src/distutils2/tests/pypiserver/with_norel_links/external/nonrel.html b/src/distutils2/tests/pypiserver/with_norel_links/external/nonrel.html
new file mode 100644
index 0000000..f6ace22
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_norel_links/external/nonrel.html
@@ -0,0 +1 @@
+A page linked without rel="download" or rel="homepage" link.
diff --git a/src/distutils2/tests/pypiserver/with_norel_links/simple/foobar/index.html b/src/distutils2/tests/pypiserver/with_norel_links/simple/foobar/index.html
new file mode 100644
index 0000000..171df93
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_norel_links/simple/foobar/index.html
@@ -0,0 +1,6 @@
+<html><body>
+<a rel="download" href="/foobar-0.1.tar.gz" rel="download">foobar-0.1.tar.gz</a><br/>
+<a href="../../external/homepage.html" rel="homepage">external homepage</a><br/>
+<a href="../../external/nonrel.html">unrelated link</a><br/>
+<a href="/unrelated-0.2.tar.gz">unrelated download</a></br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/with_norel_links/simple/index.html b/src/distutils2/tests/pypiserver/with_norel_links/simple/index.html
new file mode 100644
index 0000000..a1a7bb7
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_norel_links/simple/index.html
@@ -0,0 +1 @@
+<a href="foobar/">foobar/</a>
diff --git a/src/distutils2/tests/pypiserver/with_real_externals/simple/foobar/index.html b/src/distutils2/tests/pypiserver/with_real_externals/simple/foobar/index.html
new file mode 100644
index 0000000..b2885ae
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_real_externals/simple/foobar/index.html
@@ -0,0 +1,4 @@
+<html><body>
+<a rel="download" href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/>
+<a href="http://a-really-external-website/external/external.html" rel="homepage">external homepage</a><br/>
+</body></html>
diff --git a/src/distutils2/tests/pypiserver/with_real_externals/simple/index.html b/src/distutils2/tests/pypiserver/with_real_externals/simple/index.html
new file mode 100644
index 0000000..a1a7bb7
--- /dev/null
+++ b/src/distutils2/tests/pypiserver/with_real_externals/simple/index.html
@@ -0,0 +1 @@
+<a href="foobar/">foobar/</a>
diff --git a/src/distutils2/tests/support.py b/src/distutils2/tests/support.py
index fd0dc2d..518cedf 100644
--- a/src/distutils2/tests/support.py
+++ b/src/distutils2/tests/support.py
@@ -14,7 +14,6 @@ import warnings
from distutils2 import log
from distutils2.log import DEBUG, INFO, WARN, ERROR, FATAL
-from distutils2.core import Distribution
if sys.version_info >= (2, 7):
# improved unittest package from 2.7's standard library
@@ -42,7 +41,7 @@ class LoggingSilencer(object):
def _log(self, level, msg, args):
if level not in (DEBUG, INFO, WARN, ERROR, FATAL):
- raise ValueError('%s wrong log level' % str(level))
+ raise ValueError('%s wrong log level' % level)
self.logs.append((level, msg, args))
def get_logs(self, *levels):
@@ -65,12 +64,22 @@ class TempdirManager(object):
def setUp(self):
super(TempdirManager, self).setUp()
self.tempdirs = []
+ self.tempfiles = []
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_)
+
+ def mktempfile(self):
+ """Create a temporary file that will be cleaned up."""
+ tempfile_ = tempfile.NamedTemporaryFile()
+ self.tempfiles.append(tempfile_.name)
+ return tempfile_
def mkdtemp(self):
"""Create a temporary directory that will be cleaned up.
@@ -105,6 +114,7 @@ class TempdirManager(object):
It returns the package directory and the distribution
instance.
"""
+ from distutils2.dist import Distribution
tmp_dir = self.mkdtemp()
pkg_dir = os.path.join(tmp_dir, pkg_name)
os.mkdir(pkg_dir)
diff --git a/src/distutils2/tests/test_Mixin2to3.py b/src/distutils2/tests/test_Mixin2to3.py
new file mode 100644
index 0000000..a455fc2
--- /dev/null
+++ b/src/distutils2/tests/test_Mixin2to3.py
@@ -0,0 +1,54 @@
+"""Tests for distutils.command.build_py."""
+import sys
+import tempfile
+
+import distutils2
+from distutils2.tests import support
+from distutils2.tests.support import unittest
+from distutils2.command.build_py import Mixin2to3
+
+
+class Mixin2to3TestCase(support.TempdirManager, unittest.TestCase):
+
+ @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+ def test_convert_code_only(self):
+ # used to check if code gets converted properly.
+ code_content = "print 'test'\n"
+ code_handle = self.mktempfile()
+ code_name = code_handle.name
+
+ code_handle.write(code_content)
+ code_handle.flush()
+
+ mixin2to3 = Mixin2to3()
+ mixin2to3._run_2to3([code_name])
+ converted_code_content = "print('test')\n"
+ new_code_content = "".join(open(code_name).readlines())
+
+ self.assertEquals(new_code_content, converted_code_content)
+
+ @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+ def test_doctests_only(self):
+ # used to check if doctests gets converted properly.
+ doctest_content = '"""\n>>> print test\ntest\n"""\nprint test\n\n'
+ doctest_handle = self.mktempfile()
+ doctest_name = doctest_handle.name
+
+ doctest_handle.write(doctest_content)
+ doctest_handle.flush()
+
+ mixin2to3 = Mixin2to3()
+ mixin2to3._run_2to3([doctest_name])
+
+ converted_doctest_content = ['"""', '>>> print(test)', 'test', '"""',
+ 'print(test)', '', '', '']
+ converted_doctest_content = '\n'.join(converted_doctest_content)
+ new_doctest_content = "".join(open(doctest_name).readlines())
+
+ self.assertEquals(new_doctest_content, converted_doctest_content)
+
+def test_suite():
+ return unittest.makeSuite(Mixin2to3TestCase)
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/distutils2/tests/test_bdist.py b/src/distutils2/tests/test_bdist.py
index ba2280b..2be01e1 100644
--- a/src/distutils2/tests/test_bdist.py
+++ b/src/distutils2/tests/test_bdist.py
@@ -1,8 +1,6 @@
"""Tests for distutils.command.bdist."""
import sys
import os
-import tempfile
-import shutil
from distutils2.tests import run_unittest
@@ -10,8 +8,7 @@ from distutils2.core import Distribution
from distutils2.command.bdist import bdist
from distutils2.tests import support
from distutils2.tests.support import unittest
-from distutils2.spawn import find_executable
-from distutils2 import spawn
+from distutils2.util import find_executable
from distutils2.errors import DistutilsExecError
class BuildTestCase(support.TempdirManager,
@@ -25,7 +22,7 @@ class BuildTestCase(support.TempdirManager,
cmd = bdist(dist)
cmd.formats = ['msi']
cmd.ensure_finalized()
- self.assertEquals(cmd.formats, ['msi'])
+ self.assertEqual(cmd.formats, ['msi'])
# what format bdist offers ?
# XXX an explicit list in bdist is
@@ -33,9 +30,9 @@ class BuildTestCase(support.TempdirManager,
# we should add a registry
formats = ['zip', 'gztar', 'bztar', 'ztar', 'tar', 'wininst', 'msi']
formats.sort()
- founded = cmd.format_command.keys()
- founded.sort()
- self.assertEquals(founded, formats)
+ found = cmd.format_command.keys()
+ found.sort()
+ self.assertEqual(found, formats)
def test_suite():
return unittest.makeSuite(BuildTestCase)
diff --git a/src/distutils2/tests/test_bdist_dumb.py b/src/distutils2/tests/test_bdist_dumb.py
index 2cdfe67..770827c 100644
--- a/src/distutils2/tests/test_bdist_dumb.py
+++ b/src/distutils2/tests/test_bdist_dumb.py
@@ -78,7 +78,7 @@ class BuildDumbTestCase(support.TempdirManager,
base = base.replace(':', '-')
wanted = ['%s.zip' % base]
- self.assertEquals(dist_created, wanted)
+ self.assertEqual(dist_created, wanted)
# now let's check what we have in the zip file
# XXX to be done
@@ -87,16 +87,16 @@ class BuildDumbTestCase(support.TempdirManager,
pkg_dir, dist = self.create_dist()
os.chdir(pkg_dir)
cmd = bdist_dumb(dist)
- self.assertEquals(cmd.bdist_dir, None)
+ self.assertEqual(cmd.bdist_dir, None)
cmd.finalize_options()
# bdist_dir is initialized to bdist_base/dumb if not set
base = cmd.get_finalized_command('bdist').bdist_base
- self.assertEquals(cmd.bdist_dir, os.path.join(base, 'dumb'))
+ self.assertEqual(cmd.bdist_dir, os.path.join(base, 'dumb'))
# the format is set to a default value depending on the os.name
default = cmd.default_format[os.name]
- self.assertEquals(cmd.format, default)
+ self.assertEqual(cmd.format, default)
def test_suite():
return unittest.makeSuite(BuildDumbTestCase)
diff --git a/src/distutils2/tests/test_build.py b/src/distutils2/tests/test_build.py
index 32be44a..abf9ef8 100644
--- a/src/distutils2/tests/test_build.py
+++ b/src/distutils2/tests/test_build.py
@@ -20,11 +20,11 @@ class BuildTestCase(support.TempdirManager,
cmd.finalize_options()
# if not specified, plat_name gets the current platform
- self.assertEquals(cmd.plat_name, get_platform())
+ self.assertEqual(cmd.plat_name, get_platform())
# build_purelib is build + lib
wanted = os.path.join(cmd.build_base, 'lib')
- self.assertEquals(cmd.build_purelib, wanted)
+ self.assertEqual(cmd.build_purelib, wanted)
# build_platlib is 'build/lib.platform-x.x[-pydebug]'
# examples:
@@ -34,21 +34,21 @@ class BuildTestCase(support.TempdirManager,
self.assertTrue(cmd.build_platlib.endswith('-pydebug'))
plat_spec += '-pydebug'
wanted = os.path.join(cmd.build_base, 'lib' + plat_spec)
- self.assertEquals(cmd.build_platlib, wanted)
+ self.assertEqual(cmd.build_platlib, wanted)
# by default, build_lib = build_purelib
- self.assertEquals(cmd.build_lib, cmd.build_purelib)
+ self.assertEqual(cmd.build_lib, cmd.build_purelib)
# build_temp is build/temp.<plat>
wanted = os.path.join(cmd.build_base, 'temp' + plat_spec)
- self.assertEquals(cmd.build_temp, wanted)
+ self.assertEqual(cmd.build_temp, wanted)
# build_scripts is build/scripts-x.x
wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3])
- self.assertEquals(cmd.build_scripts, wanted)
+ self.assertEqual(cmd.build_scripts, wanted)
# executable is os.path.normpath(sys.executable)
- self.assertEquals(cmd.executable, os.path.normpath(sys.executable))
+ self.assertEqual(cmd.executable, os.path.normpath(sys.executable))
def test_suite():
return unittest.makeSuite(BuildTestCase)
diff --git a/src/distutils2/tests/test_build_clib.py b/src/distutils2/tests/test_build_clib.py
index 452b65a..348f700 100644
--- a/src/distutils2/tests/test_build_clib.py
+++ b/src/distutils2/tests/test_build_clib.py
@@ -5,7 +5,7 @@ import sys
from distutils2.command.build_clib import build_clib
from distutils2.errors import DistutilsSetupError
from distutils2.tests import support
-from distutils2.spawn import find_executable
+from distutils2.util import find_executable
from distutils2.tests.support import unittest
class BuildCLibTestCase(support.TempdirManager,
@@ -55,14 +55,14 @@ class BuildCLibTestCase(support.TempdirManager,
self.assertRaises(DistutilsSetupError, cmd.get_source_files)
cmd.libraries = [('name', {'sources': ['a', 'b']})]
- self.assertEquals(cmd.get_source_files(), ['a', 'b'])
+ self.assertEqual(cmd.get_source_files(), ['a', 'b'])
cmd.libraries = [('name', {'sources': ('a', 'b')})]
- self.assertEquals(cmd.get_source_files(), ['a', 'b'])
+ self.assertEqual(cmd.get_source_files(), ['a', 'b'])
cmd.libraries = [('name', {'sources': ('a', 'b')}),
('name2', {'sources': ['c', 'd']})]
- self.assertEquals(cmd.get_source_files(), ['a', 'b', 'c', 'd'])
+ self.assertEqual(cmd.get_source_files(), ['a', 'b', 'c', 'd'])
def test_build_libraries(self):
@@ -91,11 +91,11 @@ class BuildCLibTestCase(support.TempdirManager,
cmd.include_dirs = 'one-dir'
cmd.finalize_options()
- self.assertEquals(cmd.include_dirs, ['one-dir'])
+ self.assertEqual(cmd.include_dirs, ['one-dir'])
cmd.include_dirs = None
cmd.finalize_options()
- self.assertEquals(cmd.include_dirs, [])
+ self.assertEqual(cmd.include_dirs, [])
cmd.distribution.libraries = 'WONTWORK'
self.assertRaises(DistutilsSetupError, cmd.finalize_options)
diff --git a/src/distutils2/tests/test_build_ext.py b/src/distutils2/tests/test_build_ext.py
index 8b5998d..f561923 100644
--- a/src/distutils2/tests/test_build_ext.py
+++ b/src/distutils2/tests/test_build_ext.py
@@ -1,6 +1,5 @@
import sys
import os
-import tempfile
import shutil
from StringIO import StringIO
import warnings
@@ -81,11 +80,11 @@ class BuildExtTestCase(support.TempdirManager,
for attr in ('error', 'foo', 'new', 'roj'):
self.assertTrue(hasattr(xx, attr))
- self.assertEquals(xx.foo(2, 5), 7)
- self.assertEquals(xx.foo(13,15), 28)
- self.assertEquals(xx.new().demo(), None)
+ self.assertEqual(xx.foo(2, 5), 7)
+ self.assertEqual(xx.foo(13,15), 28)
+ self.assertEqual(xx.new().demo(), None)
doc = 'This is a template module just for instruction.'
- self.assertEquals(xx.__doc__, doc)
+ self.assertEqual(xx.__doc__, doc)
self.assertTrue(isinstance(xx.Null(), xx.Null))
self.assertTrue(isinstance(xx.Str(), xx.Str))
@@ -195,7 +194,7 @@ class BuildExtTestCase(support.TempdirManager,
cmd = build_ext(dist)
cmd.libraries = 'my_lib'
cmd.finalize_options()
- self.assertEquals(cmd.libraries, ['my_lib'])
+ self.assertEqual(cmd.libraries, ['my_lib'])
# make sure cmd.library_dirs is turned into a list
# if it's a string
@@ -209,7 +208,7 @@ class BuildExtTestCase(support.TempdirManager,
cmd = build_ext(dist)
cmd.rpath = os.pathsep.join(['one', 'two'])
cmd.finalize_options()
- self.assertEquals(cmd.rpath, ['one', 'two'])
+ self.assertEqual(cmd.rpath, ['one', 'two'])
# XXX more tests to perform for win32
@@ -218,79 +217,32 @@ class BuildExtTestCase(support.TempdirManager,
cmd = build_ext(dist)
cmd.define = 'one,two'
cmd.finalize_options()
- self.assertEquals(cmd.define, [('one', '1'), ('two', '1')])
+ self.assertEqual(cmd.define, [('one', '1'), ('two', '1')])
# make sure undef is turned into a list of
# strings if they are ','-separated strings
cmd = build_ext(dist)
cmd.undef = 'one,two'
cmd.finalize_options()
- self.assertEquals(cmd.undef, ['one', 'two'])
+ self.assertEqual(cmd.undef, ['one', 'two'])
# make sure swig_opts is turned into a list
cmd = build_ext(dist)
cmd.swig_opts = None
cmd.finalize_options()
- self.assertEquals(cmd.swig_opts, [])
+ self.assertEqual(cmd.swig_opts, [])
cmd = build_ext(dist)
cmd.swig_opts = '1 2'
cmd.finalize_options()
- self.assertEquals(cmd.swig_opts, ['1', '2'])
-
- def test_check_extensions_list(self):
- dist = Distribution()
- cmd = build_ext(dist)
- cmd.finalize_options()
-
- #'extensions' option must be a list of Extension instances
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo')
-
- # each element of 'ext_modules' option must be an
- # Extension instance or 2-tuple
- exts = [('bar', 'foo', 'bar'), 'foo']
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
-
- # first element of each tuple in 'ext_modules'
- # must be the extension name (a string) and match
- # a python dotted-separated name
- exts = [('foo-bar', '')]
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
-
- # second element of each tuple in 'ext_modules'
- # must be a ary (build info)
- exts = [('foo.bar', '')]
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
-
- # ok this one should pass
- exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
- 'some': 'bar'})]
- cmd.check_extensions_list(exts)
- ext = exts[0]
- self.assertTrue(isinstance(ext, Extension))
-
- # check_extensions_list adds in ext the values passed
- # when they are in ('include_dirs', 'library_dirs', 'libraries'
- # 'extra_objects', 'extra_compile_args', 'extra_link_args')
- self.assertEquals(ext.libraries, 'foo')
- self.assertTrue(not hasattr(ext, 'some'))
-
- # 'macros' element of build info dict must be 1- or 2-tuple
- exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
- 'some': 'bar', 'macros': [('1', '2', '3'), 'foo']})]
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
-
- exts[0][1]['macros'] = [('1', '2'), ('3',)]
- cmd.check_extensions_list(exts)
- self.assertEquals(exts[0].undef_macros, ['3'])
- self.assertEquals(exts[0].define_macros, [('1', '2')])
+ self.assertEqual(cmd.swig_opts, ['1', '2'])
def test_get_source_files(self):
modules = [Extension('foo', ['xxx'], optional=False)]
dist = Distribution({'name': 'xx', 'ext_modules': modules})
cmd = build_ext(dist)
cmd.ensure_finalized()
- self.assertEquals(cmd.get_source_files(), ['xxx'])
+ self.assertEqual(cmd.get_source_files(), ['xxx'])
def test_compiler_option(self):
# cmd.compiler is an option and
@@ -301,7 +253,7 @@ class BuildExtTestCase(support.TempdirManager,
cmd.compiler = 'unix'
cmd.ensure_finalized()
cmd.run()
- self.assertEquals(cmd.compiler, 'unix')
+ self.assertEqual(cmd.compiler, 'unix')
def test_get_outputs(self):
tmp_dir = self.mkdtemp()
@@ -312,7 +264,7 @@ class BuildExtTestCase(support.TempdirManager,
'ext_modules': [ext]})
cmd = build_ext(dist)
cmd.ensure_finalized()
- self.assertEquals(len(cmd.get_outputs()), 1)
+ self.assertEqual(len(cmd.get_outputs()), 1)
if os.name == "nt":
cmd.debug = sys.executable.endswith("_d.exe")
@@ -332,19 +284,19 @@ class BuildExtTestCase(support.TempdirManager,
finally:
os.chdir(old_wd)
self.assertTrue(os.path.exists(so_file))
- self.assertEquals(os.path.splitext(so_file)[-1],
+ self.assertEqual(os.path.splitext(so_file)[-1],
sysconfig.get_config_var('SO'))
so_dir = os.path.dirname(so_file)
- self.assertEquals(so_dir, other_tmp_dir)
+ self.assertEqual(so_dir, other_tmp_dir)
cmd.inplace = 0
cmd.run()
so_file = cmd.get_outputs()[0]
self.assertTrue(os.path.exists(so_file))
- self.assertEquals(os.path.splitext(so_file)[-1],
+ self.assertEqual(os.path.splitext(so_file)[-1],
sysconfig.get_config_var('SO'))
so_dir = os.path.dirname(so_file)
- self.assertEquals(so_dir, cmd.build_lib)
+ self.assertEqual(so_dir, cmd.build_lib)
# inplace = 0, cmd.package = 'bar'
build_py = cmd.get_finalized_command('build_py')
@@ -352,7 +304,7 @@ class BuildExtTestCase(support.TempdirManager,
path = cmd.get_ext_fullpath('foo')
# checking that the last directory is the build_dir
path = os.path.split(path)[0]
- self.assertEquals(path, cmd.build_lib)
+ self.assertEqual(path, cmd.build_lib)
# inplace = 1, cmd.package = 'bar'
cmd.inplace = 1
@@ -366,7 +318,7 @@ class BuildExtTestCase(support.TempdirManager,
# checking that the last directory is bar
path = os.path.split(path)[0]
lastdir = os.path.split(path)[-1]
- self.assertEquals(lastdir, 'bar')
+ self.assertEqual(lastdir, 'bar')
def test_ext_fullpath(self):
ext = sysconfig.get_config_vars()['SO']
@@ -382,14 +334,14 @@ class BuildExtTestCase(support.TempdirManager,
curdir = os.getcwd()
wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
path = cmd.get_ext_fullpath('lxml.etree')
- self.assertEquals(wanted, path)
+ self.assertEqual(wanted, path)
# building lxml.etree not inplace
cmd.inplace = 0
cmd.build_lib = os.path.join(curdir, 'tmpdir')
wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext)
path = cmd.get_ext_fullpath('lxml.etree')
- self.assertEquals(wanted, path)
+ self.assertEqual(wanted, path)
# building twisted.runner.portmap not inplace
build_py = cmd.get_finalized_command('build_py')
@@ -398,13 +350,13 @@ class BuildExtTestCase(support.TempdirManager,
path = cmd.get_ext_fullpath('twisted.runner.portmap')
wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner',
'portmap' + ext)
- self.assertEquals(wanted, path)
+ self.assertEqual(wanted, path)
# building twisted.runner.portmap inplace
cmd.inplace = 1
path = cmd.get_ext_fullpath('twisted.runner.portmap')
wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext)
- self.assertEquals(wanted, path)
+ self.assertEqual(wanted, path)
def test_suite():
src = _get_source_filename()
@@ -416,4 +368,4 @@ def test_suite():
else: return unittest.makeSuite(BuildExtTestCase)
if __name__ == '__main__':
- distsutils2.tests.run_unittest(test_suite())
+ distutils2.tests.run_unittest(test_suite())
diff --git a/src/distutils2/tests/test_build_py.py b/src/distutils2/tests/test_build_py.py
index 7f7e1ec..4b46da9 100644
--- a/src/distutils2/tests/test_build_py.py
+++ b/src/distutils2/tests/test_build_py.py
@@ -19,11 +19,15 @@ class BuildPyTestCase(support.TempdirManager,
def test_package_data(self):
sources = self.mkdtemp()
f = open(os.path.join(sources, "__init__.py"), "w")
- f.write("# Pretend this is a package.")
- f.close()
+ try:
+ f.write("# Pretend this is a package.")
+ finally:
+ f.close()
f = open(os.path.join(sources, "README.txt"), "w")
- f.write("Info about this package")
- f.close()
+ try:
+ f.write("Info about this package")
+ finally:
+ f.close()
destination = self.mkdtemp()
diff --git a/src/distutils2/tests/test_build_scripts.py b/src/distutils2/tests/test_build_scripts.py
index 2167ac1..6a724bd 100644
--- a/src/distutils2/tests/test_build_scripts.py
+++ b/src/distutils2/tests/test_build_scripts.py
@@ -74,8 +74,10 @@ class BuildScriptsTestCase(support.TempdirManager,
def write_script(self, dir, name, text):
f = open(os.path.join(dir, name), "w")
- f.write(text)
- f.close()
+ try:
+ f.write(text)
+ finally:
+ f.close()
def test_version_int(self):
source = self.mkdtemp()
diff --git a/src/distutils2/tests/test_ccompiler.py b/src/distutils2/tests/test_ccompiler.py
index 85f46bf..df25253 100644
--- a/src/distutils2/tests/test_ccompiler.py
+++ b/src/distutils2/tests/test_ccompiler.py
@@ -31,7 +31,7 @@ class CCompilerTestCase(support.EnvironGuard, unittest.TestCase):
opts = gen_lib_options(compiler, libdirs, runlibdirs, libs)
wanted = ['-Llib1', '-Llib2', '-cool', '-Rrunlib1', 'found',
'-lname2']
- self.assertEquals(opts, wanted)
+ self.assertEqual(opts, wanted)
def test_customize_compiler(self):
@@ -51,7 +51,7 @@ class CCompilerTestCase(support.EnvironGuard, unittest.TestCase):
comp = compiler()
customize_compiler(comp)
- self.assertEquals(comp.exes['archiver'], 'my_ar -arflags')
+ self.assertEqual(comp.exes['archiver'], 'my_ar -arflags')
def test_suite():
return unittest.makeSuite(CCompilerTestCase)
diff --git a/src/distutils2/tests/test_check.py b/src/distutils2/tests/test_check.py
index 757e8c5..7a672e1 100644
--- a/src/distutils2/tests/test_check.py
+++ b/src/distutils2/tests/test_check.py
@@ -37,7 +37,7 @@ class CheckTestCase(support.LoggingSilencer,
'name': 'xxx', 'version': 'xxx'
}
cmd = self._run(metadata)
- self.assertEquals(len(cmd._warnings), 0)
+ self.assertEqual(len(cmd._warnings), 0)
# now with the strict mode, we should
# get an error if there are missing metadata
@@ -45,7 +45,7 @@ class CheckTestCase(support.LoggingSilencer,
# and of course, no error when all metadata are present
cmd = self._run(metadata, strict=1)
- self.assertEquals(len(cmd._warnings), 0)
+ self.assertEqual(len(cmd._warnings), 0)
def test_check_restructuredtext(self):
if not _HAS_DOCUTILS: # won't test without docutils
@@ -55,7 +55,7 @@ class CheckTestCase(support.LoggingSilencer,
pkg_info, dist = self.create_dist(description=broken_rest)
cmd = check(dist)
cmd.check_restructuredtext()
- self.assertEquals(len(cmd._warnings), 1)
+ self.assertEqual(len(cmd._warnings), 1)
# let's see if we have an error with strict=1
metadata = {'home_page': 'xxx', 'author': 'xxx',
@@ -69,7 +69,7 @@ class CheckTestCase(support.LoggingSilencer,
# and non-broken rest
metadata['description'] = 'title\n=====\n\ntest'
cmd = self._run(metadata, strict=1, restructuredtext=1)
- self.assertEquals(len(cmd._warnings), 0)
+ self.assertEqual(len(cmd._warnings), 0)
def test_check_all(self):
diff --git a/src/distutils2/tests/test_cmd.py b/src/distutils2/tests/test_cmd.py
index 09095e8..aa4a709 100644
--- a/src/distutils2/tests/test_cmd.py
+++ b/src/distutils2/tests/test_cmd.py
@@ -43,7 +43,7 @@ class CommandTestCase(unittest.TestCase):
# making sure execute gets called properly
def _execute(func, args, exec_msg, level):
- self.assertEquals(exec_msg, 'generating out from in')
+ self.assertEqual(exec_msg, 'generating out from in')
cmd.force = True
cmd.execute = _execute
cmd.make_file(infiles='in', outfile='out', func='func', args=())
@@ -62,7 +62,7 @@ class CommandTestCase(unittest.TestCase):
wanted = ["command options for 'MyCmd':", ' option1 = 1',
' option2 = 1']
- self.assertEquals(msgs, wanted)
+ self.assertEqual(msgs, wanted)
def test_ensure_string(self):
cmd = self.cmd
@@ -80,7 +80,7 @@ class CommandTestCase(unittest.TestCase):
cmd = self.cmd
cmd.option1 = 'ok,dok'
cmd.ensure_string_list('option1')
- self.assertEquals(cmd.option1, ['ok', 'dok'])
+ self.assertEqual(cmd.option1, ['ok', 'dok'])
cmd.option2 = ['xxx', 'www']
cmd.ensure_string_list('option2')
diff --git a/src/distutils2/tests/test_config.py b/src/distutils2/tests/test_config.py
index 95bdc3f..09b6d02 100644
--- a/src/distutils2/tests/test_config.py
+++ b/src/distutils2/tests/test_config.py
@@ -1,7 +1,6 @@
"""Tests for distutils.pypirc.pypirc."""
import sys
import os
-import tempfile
import shutil
from distutils2.core import PyPIRCCommand
@@ -87,20 +86,20 @@ class PyPIRCCommandTestCase(support.TempdirManager,
config = config.items()
config.sort()
- waited = [('password', 'secret'), ('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi'),
- ('server', 'server1'), ('username', 'me')]
- self.assertEquals(config, waited)
+ expected = [('password', 'secret'), ('realm', 'pypi'),
+ ('repository', 'http://pypi.python.org/pypi'),
+ ('server', 'server1'), ('username', 'me')]
+ self.assertEqual(config, expected)
# old format
self.write_file(self.rc, PYPIRC_OLD)
config = cmd._read_pypirc()
config = config.items()
config.sort()
- waited = [('password', 'secret'), ('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi'),
- ('server', 'server-login'), ('username', 'tarek')]
- self.assertEquals(config, waited)
+ expected = [('password', 'secret'), ('realm', 'pypi'),
+ ('repository', 'http://pypi.python.org/pypi'),
+ ('server', 'server-login'), ('username', 'tarek')]
+ self.assertEqual(config, expected)
def test_server_empty_registration(self):
cmd = self._cmd(self.dist)
@@ -109,7 +108,7 @@ class PyPIRCCommandTestCase(support.TempdirManager,
cmd._store_pypirc('tarek', 'xxx')
self.assertTrue(os.path.exists(rc))
content = open(rc).read()
- self.assertEquals(content, WANTED)
+ self.assertEqual(content, WANTED)
def test_suite():
return unittest.makeSuite(PyPIRCCommandTestCase)
diff --git a/src/distutils2/tests/test_config_cmd.py b/src/distutils2/tests/test_config_cmd.py
index 9e81079..1995b0c 100644
--- a/src/distutils2/tests/test_config_cmd.py
+++ b/src/distutils2/tests/test_config_cmd.py
@@ -34,7 +34,7 @@ class ConfigTestCase(support.LoggingSilencer,
f.close()
dump_file(this_file, 'I am the header')
- self.assertEquals(len(self._logs), numlines+1)
+ self.assertEqual(len(self._logs), numlines+1)
def test_search_cpp(self):
if sys.platform == 'win32':
@@ -44,10 +44,10 @@ class ConfigTestCase(support.LoggingSilencer,
# simple pattern searches
match = cmd.search_cpp(pattern='xxx', body='// xxx')
- self.assertEquals(match, 0)
+ self.assertEqual(match, 0)
match = cmd.search_cpp(pattern='_configtest', body='// xxx')
- self.assertEquals(match, 1)
+ self.assertEqual(match, 1)
def test_finalize_options(self):
# finalize_options does a bit of transformation
@@ -59,9 +59,9 @@ class ConfigTestCase(support.LoggingSilencer,
cmd.library_dirs = 'three%sfour' % os.pathsep
cmd.ensure_finalized()
- self.assertEquals(cmd.include_dirs, ['one', 'two'])
- self.assertEquals(cmd.libraries, ['one'])
- self.assertEquals(cmd.library_dirs, ['three', 'four'])
+ self.assertEqual(cmd.include_dirs, ['one', 'two'])
+ self.assertEqual(cmd.libraries, ['one'])
+ self.assertEqual(cmd.library_dirs, ['three', 'four'])
def test_clean(self):
# _clean removes files
diff --git a/src/distutils2/tests/test_converter.py b/src/distutils2/tests/test_converter.py
index 86b844a..f0717cc 100644
--- a/src/distutils2/tests/test_converter.py
+++ b/src/distutils2/tests/test_converter.py
@@ -30,7 +30,7 @@ class ConverterTestCase(unittest.TestCase):
wanted = file_.replace('before', 'after')
wanted = _read_file(os.path.join(convdir, wanted))
res = ref.refactor_string(original, 'setup.py')
- self.assertEquals(str(res), wanted)
+ self.assertEqual(str(res), wanted)
def test_suite():
return unittest.makeSuite(ConverterTestCase)
diff --git a/src/distutils2/tests/test_cygwinccompiler.py b/src/distutils2/tests/test_cygwinccompiler.py
index c94ef22..e16a309 100644
--- a/src/distutils2/tests/test_cygwinccompiler.py
+++ b/src/distutils2/tests/test_cygwinccompiler.py
@@ -44,48 +44,48 @@ class CygwinCCompilerTestCase(support.TempdirManager,
sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) \n[GCC '
'4.0.1 (Apple Computer, Inc. build 5370)]')
- self.assertEquals(check_config_h()[0], CONFIG_H_OK)
+ self.assertEqual(check_config_h()[0], CONFIG_H_OK)
# then it tries to see if it can find "__GNUC__" in pyconfig.h
sys.version = 'something without the *CC word'
# if the file doesn't exist it returns CONFIG_H_UNCERTAIN
- self.assertEquals(check_config_h()[0], CONFIG_H_UNCERTAIN)
+ self.assertEqual(check_config_h()[0], CONFIG_H_UNCERTAIN)
# if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK
self.write_file(self.python_h, 'xxx')
- self.assertEquals(check_config_h()[0], CONFIG_H_NOTOK)
+ self.assertEqual(check_config_h()[0], CONFIG_H_NOTOK)
# and CONFIG_H_OK if __GNUC__ is found
self.write_file(self.python_h, 'xxx __GNUC__ xxx')
- self.assertEquals(check_config_h()[0], CONFIG_H_OK)
+ self.assertEqual(check_config_h()[0], CONFIG_H_OK)
def test_get_msvcr(self):
# none
sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) '
'\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]')
- self.assertEquals(get_msvcr(), None)
+ self.assertEqual(get_msvcr(), None)
# MSVC 7.0
sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) '
'[MSC v.1300 32 bits (Intel)]')
- self.assertEquals(get_msvcr(), ['msvcr70'])
+ self.assertEqual(get_msvcr(), ['msvcr70'])
# MSVC 7.1
sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) '
'[MSC v.1310 32 bits (Intel)]')
- self.assertEquals(get_msvcr(), ['msvcr71'])
+ self.assertEqual(get_msvcr(), ['msvcr71'])
# VS2005 / MSVC 8.0
sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) '
'[MSC v.1400 32 bits (Intel)]')
- self.assertEquals(get_msvcr(), ['msvcr80'])
+ self.assertEqual(get_msvcr(), ['msvcr80'])
# VS2008 / MSVC 9.0
sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) '
'[MSC v.1500 32 bits (Intel)]')
- self.assertEquals(get_msvcr(), ['msvcr90'])
+ self.assertEqual(get_msvcr(), ['msvcr90'])
# unknown
sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) '
diff --git a/src/distutils2/tests/test_depgraph.py b/src/distutils2/tests/test_depgraph.py
new file mode 100644
index 0000000..6ab265a
--- /dev/null
+++ b/src/distutils2/tests/test_depgraph.py
@@ -0,0 +1,187 @@
+"""Tests for distutils.depgraph """
+
+from distutils2.tests import support
+from distutils2.tests.support import unittest
+from distutils2 import depgraph
+from distutils2._backport import pkgutil
+
+import os
+import sys
+import re
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+class DepGraphTestCase(support.LoggingSilencer,
+ unittest.TestCase):
+
+ DISTROS_DIST = ('choxie', 'grammar', 'towel-stuff')
+ DISTROS_EGG = ('bacon', 'banana', 'strawberry', 'cheese')
+ EDGE = re.compile(
+ r'"(?P<from>.*)" -> "(?P<to>.*)" \[label="(?P<label>.*)"\]'
+ )
+
+ def checkLists(self, l1, l2):
+ """ Compare two lists without taking the order into consideration """
+ self.assertListEqual(sorted(l1), sorted(l2))
+
+ def setUp(self):
+ super(DepGraphTestCase, self).setUp()
+ path = os.path.join(os.path.dirname(__file__), '..', '_backport',
+ 'tests', 'fake_dists')
+ path = os.path.abspath(path)
+ self.sys_path = sys.path[:]
+ sys.path[0:0] = [path]
+
+ def test_generate_graph(self):
+ dists = []
+ for name in self.DISTROS_DIST:
+ dist = pkgutil.get_distribution(name)
+ self.assertNotEqual(dist, None)
+ dists.append(dist)
+
+ choxie, grammar, towel = dists
+
+ graph = depgraph.generate_graph(dists)
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[choxie]]
+ self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps)
+ self.assertTrue(choxie in graph.reverse_list[towel])
+ self.checkLists(graph.missing[choxie], [])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[grammar]]
+ self.checkLists([], deps)
+ self.checkLists(graph.missing[grammar], ['truffles (>=1.2)'])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[towel]]
+ self.checkLists([], deps)
+ self.checkLists(graph.missing[towel], ['bacon (<=0.2)'])
+
+ def test_generate_graph_egg(self):
+ dists = []
+ for name in self.DISTROS_DIST + self.DISTROS_EGG:
+ dist = pkgutil.get_distribution(name, use_egg_info=True)
+ self.assertNotEqual(dist, None)
+ dists.append(dist)
+
+ choxie, grammar, towel, bacon, banana, strawberry, cheese = dists
+
+ graph = depgraph.generate_graph(dists)
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[choxie]]
+ self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps)
+ self.assertTrue(choxie in graph.reverse_list[towel])
+ self.checkLists(graph.missing[choxie], [])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[grammar]]
+ self.checkLists([('bacon', 'truffles (>=1.2)')], deps)
+ self.checkLists(graph.missing[grammar], [])
+ self.assertTrue(grammar in graph.reverse_list[bacon])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[towel]]
+ self.checkLists([('bacon', 'bacon (<=0.2)')], deps)
+ self.checkLists(graph.missing[towel], [])
+ self.assertTrue(towel in graph.reverse_list[bacon])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[bacon]]
+ self.checkLists([], deps)
+ self.checkLists(graph.missing[bacon], [])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[banana]]
+ self.checkLists([('strawberry', 'strawberry (>=0.5)')], deps)
+ self.checkLists(graph.missing[banana], [])
+ self.assertTrue(banana in graph.reverse_list[strawberry])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[strawberry]]
+ self.checkLists([], deps)
+ self.checkLists(graph.missing[strawberry], [])
+
+ deps = [(x.name, y) for (x,y) in graph.adjacency_list[cheese]]
+ self.checkLists([], deps)
+ self.checkLists(graph.missing[cheese], [])
+
+ def test_dependent_dists(self):
+ dists = []
+ for name in self.DISTROS_DIST:
+ dist = pkgutil.get_distribution(name)
+ self.assertNotEqual(dist, None)
+ dists.append(dist)
+
+ choxie, grammar, towel = dists
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, choxie)]
+ self.checkLists([], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, grammar)]
+ self.checkLists([], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, towel)]
+ self.checkLists(['choxie'], deps)
+
+
+ def test_dependent_dists_egg(self):
+ dists = []
+ for name in self.DISTROS_DIST + self.DISTROS_EGG:
+ dist = pkgutil.get_distribution(name, use_egg_info=True)
+ self.assertNotEqual(dist, None)
+ dists.append(dist)
+
+ choxie, grammar, towel, bacon, banana, strawberry, cheese = dists
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, choxie)]
+ self.checkLists([], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, grammar)]
+ self.checkLists([], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, towel)]
+ self.checkLists(['choxie'], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, bacon)]
+ self.checkLists(['choxie', 'towel-stuff', 'grammar'], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, strawberry)]
+ self.checkLists(['banana'], deps)
+
+ deps = [d.name for d in depgraph.dependent_dists(dists, cheese)]
+ self.checkLists([], deps)
+
+ def test_graph_to_dot(self):
+ expected = (
+ ('towel-stuff', 'bacon', 'bacon (<=0.2)'),
+ ('grammar', 'bacon', 'truffles (>=1.2)'),
+ ('choxie', 'towel-stuff', 'towel-stuff (0.1)'),
+ ('banana', 'strawberry', 'strawberry (>=0.5)')
+ )
+
+ dists = []
+ for name in self.DISTROS_DIST + self.DISTROS_EGG:
+ dist = pkgutil.get_distribution(name, use_egg_info=True)
+ self.assertNotEqual(dist, None)
+ dists.append(dist)
+
+ graph = depgraph.generate_graph(dists)
+ buf = StringIO.StringIO()
+ depgraph.graph_to_dot(graph, buf)
+ buf.seek(0)
+ matches = []
+ lines = buf.readlines()
+ for line in lines[1:-1]: # skip the first and the last lines
+ if line[-1] == '\n':
+ line = line[:-1]
+ match = self.EDGE.match(line.strip())
+ self.assertTrue(match is not None)
+ matches.append(match.groups())
+
+ self.checkLists(matches, expected)
+
+ def tearDown(self):
+ super(DepGraphTestCase, self).tearDown()
+ sys.path = self.sys_path
+
+def test_suite():
+ return unittest.makeSuite(DepGraphTestCase)
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/distutils2/tests/test_dist.py b/src/distutils2/tests/test_dist.py
index 825ea3a..f528684 100644
--- a/src/distutils2/tests/test_dist.py
+++ b/src/distutils2/tests/test_dist.py
@@ -71,11 +71,11 @@ class DistributionTestCase(support.TempdirManager,
sys.argv.append("build")
__, stdout = captured_stdout(self.create_distribution, files)
- self.assertEquals(stdout, '')
+ self.assertEqual(stdout, '')
distutils2.dist.DEBUG = True
try:
__, stdout = captured_stdout(self.create_distribution, files)
- self.assertEquals(stdout, '')
+ self.assertEqual(stdout, '')
finally:
distutils2.dist.DEBUG = False
@@ -129,13 +129,13 @@ class DistributionTestCase(support.TempdirManager,
# Check DistributionMetadata handling of Unicode fields
tmp_dir = self.mkdtemp()
my_file = os.path.join(tmp_dir, 'f')
- klass = Distribution
+ cls = Distribution
- dist = klass(attrs={'author': u'Mister Café',
- 'name': 'my.package',
- 'maintainer': u'Café Junior',
- 'summary': u'Café torréfié',
- 'description': u'Héhéhé'})
+ dist = cls(attrs={'author': u'Mister Café',
+ 'name': 'my.package',
+ 'maintainer': u'Café Junior',
+ 'summary': u'Café torréfié',
+ 'description': u'Héhéhé'})
# let's make sure the file can be written
@@ -144,11 +144,11 @@ class DistributionTestCase(support.TempdirManager,
dist.metadata.write_file(open(my_file, 'w'))
# regular ascii is of course always usable
- dist = klass(attrs={'author': 'Mister Cafe',
- 'name': 'my.package',
- 'maintainer': 'Cafe Junior',
- 'summary': 'Cafe torrefie',
- 'description': 'Hehehe'})
+ dist = cls(attrs={'author': 'Mister Cafe',
+ 'name': 'my.package',
+ 'maintainer': 'Cafe Junior',
+ 'summary': 'Cafe torrefie',
+ 'description': 'Hehehe'})
my_file2 = os.path.join(tmp_dir, 'f2')
dist.metadata.write_file(open(my_file, 'w'))
@@ -156,7 +156,7 @@ class DistributionTestCase(support.TempdirManager,
def test_empty_options(self):
# an empty options dictionary should not stay in the
# list of attributes
- klass = Distribution
+ cls = Distribution
# catching warnings
warns = []
@@ -166,15 +166,15 @@ class DistributionTestCase(support.TempdirManager,
old_warn = warnings.warn
warnings.warn = _warn
try:
- dist = klass(attrs={'author': 'xxx',
- 'name': 'xxx',
- 'version': 'xxx',
- 'url': 'xxxx',
- 'options': {}})
+ dist = cls(attrs={'author': 'xxx',
+ 'name': 'xxx',
+ 'version': 'xxx',
+ 'url': 'xxxx',
+ 'options': {}})
finally:
warnings.warn = old_warn
- self.assertEquals(len(warns), 0)
+ self.assertEqual(len(warns), 0)
def test_finalize_options(self):
@@ -185,20 +185,20 @@ class DistributionTestCase(support.TempdirManager,
dist.finalize_options()
# finalize_option splits platforms and keywords
- self.assertEquals(dist.metadata['platform'], ['one', 'two'])
- self.assertEquals(dist.metadata['keywords'], ['one', 'two'])
+ self.assertEqual(dist.metadata['platform'], ['one', 'two'])
+ self.assertEqual(dist.metadata['keywords'], ['one', 'two'])
def test_get_command_packages(self):
dist = Distribution()
- self.assertEquals(dist.command_packages, None)
+ self.assertEqual(dist.command_packages, None)
cmds = dist.get_command_packages()
- self.assertEquals(cmds, ['distutils2.command'])
- self.assertEquals(dist.command_packages,
+ self.assertEqual(cmds, ['distutils2.command'])
+ self.assertEqual(dist.command_packages,
['distutils2.command'])
dist.command_packages = 'one,two'
cmds = dist.get_command_packages()
- self.assertEquals(cmds, ['distutils2.command', 'one', 'two'])
+ self.assertEqual(cmds, ['distutils2.command', 'one', 'two'])
def test_announce(self):
@@ -238,7 +238,7 @@ class DistributionTestCase(support.TempdirManager,
os.path.expanduser = old_expander
# make sure --no-user-cfg disables the user cfg file
- self.assertEquals(len(all_files)-1, len(files))
+ self.assertEqual(len(all_files)-1, len(files))
class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
@@ -340,8 +340,10 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
temp_dir = self.mkdtemp()
user_filename = os.path.join(temp_dir, user_filename)
f = open(user_filename, 'w')
- f.write('.')
- f.close()
+ try:
+ f.write('.')
+ finally:
+ f.close()
try:
dist = Distribution()
@@ -365,8 +367,8 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
def test_fix_help_options(self):
help_tuples = [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
fancy_options = fix_help_options(help_tuples)
- self.assertEquals(fancy_options[0], ('a', 'b', 'c'))
- self.assertEquals(fancy_options[1], (1, 2, 3))
+ self.assertEqual(fancy_options[0], ('a', 'b', 'c'))
+ self.assertEqual(fancy_options[1], (1, 2, 3))
def test_show_help(self):
# smoke test, just makes sure some help is displayed
@@ -412,14 +414,14 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
PKG_INFO.seek(0)
metadata.read_file(PKG_INFO)
- self.assertEquals(metadata['name'], "package")
- self.assertEquals(metadata['version'], "1.0")
- self.assertEquals(metadata['summary'], "xxx")
- self.assertEquals(metadata['download_url'], 'http://example.com')
- self.assertEquals(metadata['keywords'], ['one', 'two'])
- self.assertEquals(metadata['platform'], [])
- self.assertEquals(metadata['obsoletes'], [])
- self.assertEquals(metadata['requires-dist'], ['foo'])
+ self.assertEqual(metadata['name'], "package")
+ self.assertEqual(metadata['version'], "1.0")
+ self.assertEqual(metadata['summary'], "xxx")
+ self.assertEqual(metadata['download_url'], 'http://example.com')
+ self.assertEqual(metadata['keywords'], ['one', 'two'])
+ self.assertEqual(metadata['platform'], [])
+ self.assertEqual(metadata['obsoletes'], [])
+ self.assertEqual(metadata['requires-dist'], ['foo'])
def test_suite():
suite = unittest.TestSuite()
diff --git a/src/distutils2/tests/test_install.py b/src/distutils2/tests/test_install.py
index b37efae..5cf6cd9 100644
--- a/src/distutils2/tests/test_install.py
+++ b/src/distutils2/tests/test_install.py
@@ -139,23 +139,23 @@ class InstallTestCase(support.TempdirManager,
# two elements
cmd.handle_extra_path()
- self.assertEquals(cmd.extra_path, ['path', 'dirs'])
- self.assertEquals(cmd.extra_dirs, 'dirs')
- self.assertEquals(cmd.path_file, 'path')
+ self.assertEqual(cmd.extra_path, ['path', 'dirs'])
+ self.assertEqual(cmd.extra_dirs, 'dirs')
+ self.assertEqual(cmd.path_file, 'path')
# one element
cmd.extra_path = ['path']
cmd.handle_extra_path()
- self.assertEquals(cmd.extra_path, ['path'])
- self.assertEquals(cmd.extra_dirs, 'path')
- self.assertEquals(cmd.path_file, 'path')
+ self.assertEqual(cmd.extra_path, ['path'])
+ self.assertEqual(cmd.extra_dirs, 'path')
+ self.assertEqual(cmd.path_file, 'path')
# none
dist.extra_path = cmd.extra_path = None
cmd.handle_extra_path()
- self.assertEquals(cmd.extra_path, None)
- self.assertEquals(cmd.extra_dirs, '')
- self.assertEquals(cmd.path_file, None)
+ self.assertEqual(cmd.extra_path, None)
+ self.assertEqual(cmd.extra_dirs, '')
+ self.assertEqual(cmd.path_file, None)
# three elements (no way !)
cmd.extra_path = 'path,dirs,again'
@@ -199,7 +199,7 @@ class InstallTestCase(support.TempdirManager,
# line (the egg info file)
f = open(cmd.record)
try:
- self.assertEquals(len(f.readlines()), 1)
+ self.assertEqual(len(f.readlines()), 1)
finally:
f.close()
diff --git a/src/distutils2/tests/test_install_data.py b/src/distutils2/tests/test_install_data.py
index 4d7c3cb..48cea63 100644
--- a/src/distutils2/tests/test_install_data.py
+++ b/src/distutils2/tests/test_install_data.py
@@ -27,14 +27,14 @@ class InstallDataTestCase(support.TempdirManager,
self.write_file(two, 'xxx')
cmd.data_files = [one, (inst2, [two])]
- self.assertEquals(cmd.get_inputs(), [one, (inst2, [two])])
+ self.assertEqual(cmd.get_inputs(), [one, (inst2, [two])])
# let's run the command
cmd.ensure_finalized()
cmd.run()
# let's check the result
- self.assertEquals(len(cmd.get_outputs()), 2)
+ self.assertEqual(len(cmd.get_outputs()), 2)
rtwo = os.path.split(two)[-1]
self.assertTrue(os.path.exists(os.path.join(inst2, rtwo)))
rone = os.path.split(one)[-1]
@@ -47,7 +47,7 @@ class InstallDataTestCase(support.TempdirManager,
cmd.run()
# let's check the result
- self.assertEquals(len(cmd.get_outputs()), 2)
+ self.assertEqual(len(cmd.get_outputs()), 2)
self.assertTrue(os.path.exists(os.path.join(inst2, rtwo)))
self.assertTrue(os.path.exists(os.path.join(inst, rone)))
cmd.outfiles = []
@@ -65,7 +65,7 @@ class InstallDataTestCase(support.TempdirManager,
cmd.run()
# let's check the result
- self.assertEquals(len(cmd.get_outputs()), 4)
+ self.assertEqual(len(cmd.get_outputs()), 4)
self.assertTrue(os.path.exists(os.path.join(inst2, rtwo)))
self.assertTrue(os.path.exists(os.path.join(inst, rone)))
diff --git a/src/distutils2/tests/test_install_headers.py b/src/distutils2/tests/test_install_headers.py
index 371b3d0..4e0dce7 100644
--- a/src/distutils2/tests/test_install_headers.py
+++ b/src/distutils2/tests/test_install_headers.py
@@ -23,7 +23,7 @@ class InstallHeadersTestCase(support.TempdirManager,
pkg_dir, dist = self.create_dist(headers=headers)
cmd = install_headers(dist)
- self.assertEquals(cmd.get_inputs(), headers)
+ self.assertEqual(cmd.get_inputs(), headers)
# let's run the command
cmd.install_dir = os.path.join(pkg_dir, 'inst')
@@ -31,7 +31,7 @@ class InstallHeadersTestCase(support.TempdirManager,
cmd.run()
# let's check the results
- self.assertEquals(len(cmd.get_outputs()), 2)
+ self.assertEqual(len(cmd.get_outputs()), 2)
def test_suite():
return unittest.makeSuite(InstallHeadersTestCase)
diff --git a/src/distutils2/tests/test_install_lib.py b/src/distutils2/tests/test_install_lib.py
index 71f90dd..680eaf1 100644
--- a/src/distutils2/tests/test_install_lib.py
+++ b/src/distutils2/tests/test_install_lib.py
@@ -25,8 +25,8 @@ class InstallLibTestCase(support.TempdirManager,
cmd = install_lib(dist)
cmd.finalize_options()
- self.assertEquals(cmd.compile, 1)
- self.assertEquals(cmd.optimize, 0)
+ self.assertEqual(cmd.compile, 1)
+ self.assertEqual(cmd.optimize, 0)
# optimize must be 0, 1, or 2
cmd.optimize = 'foo'
@@ -36,7 +36,7 @@ class InstallLibTestCase(support.TempdirManager,
cmd.optimize = '2'
cmd.finalize_options()
- self.assertEquals(cmd.optimize, 2)
+ self.assertEqual(cmd.optimize, 2)
@unittest.skipIf(no_bytecode, 'byte-compile not supported')
def test_byte_compile(self):
@@ -82,7 +82,7 @@ class InstallLibTestCase(support.TempdirManager,
cmd.distribution.script_name = 'setup.py'
# get_input should return 2 elements
- self.assertEquals(len(cmd.get_inputs()), 2)
+ self.assertEqual(len(cmd.get_inputs()), 2)
@unittest.skipUnless(bytecode_support, 'sys.dont_write_bytecode not supported')
def test_dont_write_bytecode(self):
diff --git a/src/distutils2/tests/test_install_scripts.py b/src/distutils2/tests/test_install_scripts.py
index 32c800d..be65d65 100644
--- a/src/distutils2/tests/test_install_scripts.py
+++ b/src/distutils2/tests/test_install_scripts.py
@@ -42,8 +42,10 @@ class InstallScriptsTestCase(support.TempdirManager,
def write_script(name, text):
expected.append(name)
f = open(os.path.join(source, name), "w")
- f.write(text)
- f.close()
+ try:
+ f.write(text)
+ finally:
+ f.close()
write_script("script1.py", ("#! /usr/bin/env python2.3\n"
"# bogus script w/ Python sh-bang\n"
diff --git a/src/distutils2/tests/test_manifest.py b/src/distutils2/tests/test_manifest.py
index f9eb9b3..13893eb 100644
--- a/src/distutils2/tests/test_manifest.py
+++ b/src/distutils2/tests/test_manifest.py
@@ -3,6 +3,7 @@ import os
import sys
import logging
+from distutils2.tests import run_unittest
from distutils2.tests import support
from distutils2.tests.support import unittest
from distutils2.manifest import Manifest
@@ -44,7 +45,7 @@ class ManifestTestCase(support.TempdirManager,
# the manifest should have been read
# and 3 warnings issued (we ddidn't provided the files)
- self.assertEquals(len(warns), 3)
+ self.assertEqual(len(warns), 3)
for warn in warns:
self.assertIn('warning: no files found matching', warn)
diff --git a/src/distutils2/tests/test_metadata.py b/src/distutils2/tests/test_metadata.py
index 16e19bb..ee763c0 100644
--- a/src/distutils2/tests/test_metadata.py
+++ b/src/distutils2/tests/test_metadata.py
@@ -5,9 +5,12 @@ from StringIO import StringIO
from distutils2.metadata import (DistributionMetadata, _interpret,
PKG_INFO_PREFERRED_VERSION)
-from distutils2.tests.support import unittest
+from distutils2.tests import run_unittest
+from distutils2.tests.support import unittest, LoggingSilencer
+from distutils2.errors import (MetadataConflictError,
+ MetadataUnrecognizedVersionError)
-class DistributionMetadataTestCase(unittest.TestCase):
+class DistributionMetadataTestCase(LoggingSilencer, unittest.TestCase):
def test_interpret(self):
@@ -15,15 +18,15 @@ class DistributionMetadataTestCase(unittest.TestCase):
version = sys.version.split()[0]
os_name = os.name
- assert _interpret("sys.platform == '%s'" % platform)
- assert _interpret("sys.platform == '%s' or python_version == '2.4'" \
- % platform)
- assert _interpret("sys.platform == '%s' and "
- "python_full_version == '%s'"\
- % (platform, version))
- assert _interpret("'%s' == sys.platform" % platform)
+ self.assertTrue(_interpret("sys.platform == '%s'" % platform))
+ self.assertTrue(_interpret(
+ "sys.platform == '%s' or python_version == '2.4'" % platform))
+ self.assertTrue(_interpret(
+ "sys.platform == '%s' and python_full_version == '%s'" %
+ (platform, version)))
+ self.assertTrue(_interpret("'%s' == sys.platform" % platform))
- assert _interpret('os.name == "%s"' % os_name)
+ self.assertTrue(_interpret('os.name == "%s"' % os_name))
# stuff that need to raise a syntax error
ops = ('os.name == os.name', 'os.name == 2', "'2' == '2'",
@@ -35,24 +38,25 @@ class DistributionMetadataTestCase(unittest.TestCase):
OP = 'os.name == "%s"' % os_name
AND = ' and '
OR = ' or '
- assert _interpret(OP+AND+OP)
- assert _interpret(OP+AND+OP+AND+OP)
- assert _interpret(OP+OR+OP)
- assert _interpret(OP+OR+OP+OR+OP)
+ self.assertTrue(_interpret(OP + AND + OP))
+ self.assertTrue(_interpret(OP + AND + OP + AND + OP))
+ self.assertTrue(_interpret(OP + OR + OP))
+ self.assertTrue(_interpret(OP + OR + OP + OR + OP))
# other operators
- assert _interpret("os.name != 'buuuu'")
- assert _interpret("python_version > '1.0'")
- assert _interpret("python_version < '5.0'")
- assert _interpret("python_version <= '5.0'")
- assert _interpret("python_version >= '1.0'")
- assert _interpret("'%s' in os.name" % os_name)
- assert _interpret("'buuuu' not in os.name")
- assert _interpret("'buuuu' not in os.name and '%s' in os.name" \
- % os_name)
+ self.assertTrue(_interpret("os.name != 'buuuu'"))
+ self.assertTrue(_interpret("python_version > '1.0'"))
+ self.assertTrue(_interpret("python_version < '5.0'"))
+ self.assertTrue(_interpret("python_version <= '5.0'"))
+ self.assertTrue(_interpret("python_version >= '1.0'"))
+ self.assertTrue(_interpret("'%s' in os.name" % os_name))
+ self.assertTrue(_interpret("'buuuu' not in os.name"))
+ self.assertTrue(_interpret(
+ "'buuuu' not in os.name and '%s' in os.name" % os_name))
# execution context
- assert _interpret('python_version == "0.1"', {'python_version': '0.1'})
+ self.assertTrue(_interpret('python_version == "0.1"',
+ {'python_version': '0.1'}))
def test_metadata_read_write(self):
@@ -63,25 +67,28 @@ class DistributionMetadataTestCase(unittest.TestCase):
res.seek(0)
res = res.read()
f = open(PKG_INFO)
- wanted = f.read()
+ try:
+ # XXX this is not used
+ wanted = f.read()
+ finally:
+ f.close()
self.assertTrue('Keywords: keyring,password,crypt' in res)
- f.close()
def test_metadata_markers(self):
# see if we can be platform-aware
PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
content = open(PKG_INFO).read()
content = content % sys.platform
- metadata = DistributionMetadata(platform_dependant=True)
+ metadata = DistributionMetadata(platform_dependent=True)
metadata.read_file(StringIO(content))
- self.assertEquals(metadata['Requires-Dist'], ['bar'])
+ self.assertEqual(metadata['Requires-Dist'], ['bar'])
# test with context
context = {'sys.platform': 'okook'}
- metadata = DistributionMetadata(platform_dependant=True,
+ metadata = DistributionMetadata(platform_dependent=True,
execution_context=context)
metadata.read_file(StringIO(content))
- self.assertEquals(metadata['Requires-Dist'], ['foo'])
+ self.assertEqual(metadata['Requires-Dist'], ['foo'])
def test_description(self):
PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
@@ -93,14 +100,14 @@ class DistributionMetadataTestCase(unittest.TestCase):
# see if we can read the description now
DESC = os.path.join(os.path.dirname(__file__), 'LONG_DESC.txt')
wanted = open(DESC).read()
- self.assertEquals(wanted, metadata['Description'])
+ self.assertEqual(wanted, metadata['Description'])
# save the file somewhere and make sure we can read it back
out = StringIO()
metadata.write_file(out)
out.seek(0)
metadata.read_file(out)
- self.assertEquals(wanted, metadata['Description'])
+ self.assertEqual(wanted, metadata['Description'])
def test_mapper_apis(self):
PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
@@ -115,25 +122,32 @@ class DistributionMetadataTestCase(unittest.TestCase):
def test_versions(self):
metadata = DistributionMetadata()
metadata['Obsoletes'] = 'ok'
- self.assertEquals(metadata['Metadata-Version'], '1.1')
+ self.assertEqual(metadata['Metadata-Version'], '1.1')
del metadata['Obsoletes']
metadata['Obsoletes-Dist'] = 'ok'
- self.assertEquals(metadata['Metadata-Version'], '1.2')
+ self.assertEqual(metadata['Metadata-Version'], '1.2')
+
+ self.assertRaises(MetadataConflictError, metadata.set,
+ 'Obsoletes', 'ok')
+ del metadata['Obsoletes']
del metadata['Obsoletes-Dist']
metadata['Version'] = '1'
- self.assertEquals(metadata['Metadata-Version'], '1.0')
+ self.assertEqual(metadata['Metadata-Version'], '1.0')
PKG_INFO = os.path.join(os.path.dirname(__file__),
'SETUPTOOLS-PKG-INFO')
metadata.read_file(StringIO(open(PKG_INFO).read()))
- self.assertEquals(metadata['Metadata-Version'], '1.0')
+ self.assertEqual(metadata['Metadata-Version'], '1.0')
PKG_INFO = os.path.join(os.path.dirname(__file__),
'SETUPTOOLS-PKG-INFO2')
metadata.read_file(StringIO(open(PKG_INFO).read()))
- self.assertEquals(metadata['Metadata-Version'], '1.1')
+ self.assertEqual(metadata['Metadata-Version'], '1.1')
+
+ metadata.version = '1.618'
+ self.assertRaises(MetadataUnrecognizedVersionError, metadata.keys)
def test_warnings(self):
metadata = DistributionMetadata()
@@ -161,7 +175,7 @@ class DistributionMetadataTestCase(unittest.TestCase):
# we should have a certain amount of warnings
num_wanted = len(values)
- self.assertEquals(num_wanted, res)
+ self.assertEqual(num_wanted, res)
def test_multiple_predicates(self):
metadata = DistributionMetadata()
@@ -184,29 +198,29 @@ class DistributionMetadataTestCase(unittest.TestCase):
res = m.warns
del m.warns
- self.assertEquals(res, 0)
+ self.assertEqual(res, 0)
def test_project_url(self):
metadata = DistributionMetadata()
metadata['Project-URL'] = [('one', 'http://ok')]
- self.assertEquals(metadata['Project-URL'],
+ self.assertEqual(metadata['Project-URL'],
[('one', 'http://ok')])
- self.assertEquals(metadata.version, '1.2')
+ self.assertEqual(metadata.version, '1.2')
def test_check(self):
metadata = DistributionMetadata()
metadata['Version'] = 'rr'
metadata['Requires-dist'] = ['Foo (a)']
missing, warnings = metadata.check()
- self.assertEquals(missing, ['Name', 'Home-page'])
- self.assertEquals(len(warnings), 2)
+ self.assertEqual(missing, ['Name', 'Home-page'])
+ self.assertEqual(len(warnings), 2)
def test_best_choice(self):
metadata = DistributionMetadata()
metadata['Version'] = '1.0'
- self.assertEquals(metadata.version, PKG_INFO_PREFERRED_VERSION)
+ self.assertEqual(metadata.version, PKG_INFO_PREFERRED_VERSION)
metadata['Classifier'] = ['ok']
- self.assertEquals(metadata.version, '1.2')
+ self.assertEqual(metadata.version, '1.2')
def test_project_urls(self):
# project-url is a bit specific, make sure we write it
@@ -214,7 +228,7 @@ class DistributionMetadataTestCase(unittest.TestCase):
metadata = DistributionMetadata()
metadata['Version'] = '1.0'
metadata['Project-Url'] = [('one', 'http://ok')]
- self.assertEquals(metadata['Project-Url'], [('one', 'http://ok')])
+ self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')])
file_ = StringIO()
metadata.write_file(file_)
file_.seek(0)
@@ -224,7 +238,7 @@ class DistributionMetadataTestCase(unittest.TestCase):
file_.seek(0)
metadata = DistributionMetadata()
metadata.read_file(file_)
- self.assertEquals(metadata['Project-Url'], [('one', 'http://ok')])
+ self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')])
def test_suite():
diff --git a/src/distutils2/tests/test_msvc9compiler.py b/src/distutils2/tests/test_msvc9compiler.py
index 346b20c..452c3f5 100644
--- a/src/distutils2/tests/test_msvc9compiler.py
+++ b/src/distutils2/tests/test_msvc9compiler.py
@@ -105,7 +105,7 @@ class msvc9compilerTestCase(support.TempdirManager,
import _winreg
HKCU = _winreg.HKEY_CURRENT_USER
keys = Reg.read_keys(HKCU, 'xxxx')
- self.assertEquals(keys, None)
+ self.assertEqual(keys, None)
keys = Reg.read_keys(HKCU, r'Control Panel')
self.assertTrue('Desktop' in keys)
@@ -116,20 +116,24 @@ class msvc9compilerTestCase(support.TempdirManager,
tempdir = self.mkdtemp()
manifest = os.path.join(tempdir, 'manifest')
f = open(manifest, 'w')
- f.write(_MANIFEST)
- f.close()
+ try:
+ f.write(_MANIFEST)
+ finally:
+ f.close()
compiler = MSVCCompiler()
compiler._remove_visual_c_ref(manifest)
# see what we got
f = open(manifest)
- # removing trailing spaces
- content = '\n'.join([line.rstrip() for line in f.readlines()])
- f.close()
+ try:
+ # removing trailing spaces
+ content = '\n'.join([line.rstrip() for line in f.readlines()])
+ finally:
+ f.close()
# makes sure the manifest was properly cleaned
- self.assertEquals(content, _CLEANED_MANIFEST)
+ self.assertEqual(content, _CLEANED_MANIFEST)
def test_suite():
diff --git a/src/distutils2/tests/test_pypi_dist.py b/src/distutils2/tests/test_pypi_dist.py
new file mode 100644
index 0000000..0267760
--- /dev/null
+++ b/src/distutils2/tests/test_pypi_dist.py
@@ -0,0 +1,247 @@
+"""Tests for the distutils2.pypi.dist module."""
+
+import os
+import shutil
+import tempfile
+
+from distutils2.tests.pypi_server import use_pypi_server
+from distutils2.tests import run_unittest
+from distutils2.tests.support import unittest, TempdirManager
+from distutils2.version import VersionPredicate
+from distutils2.pypi.errors import HashDoesNotMatch, UnsupportedHashName
+from distutils2.pypi.dist import (PyPIDistribution as Dist,
+ PyPIDistributions as Dists,
+ split_archive_name)
+
+
+class TestPyPIDistribution(TempdirManager,
+ unittest.TestCase):
+ """Tests the pypi.dist.PyPIDistribution class"""
+
+ def test_instanciation(self):
+ # Test the Distribution class provides us the good attributes when
+ # given on construction
+ dist = Dist("FooBar", "1.1")
+ self.assertEqual("FooBar", dist.name)
+ self.assertEqual("1.1", "%s" % dist.version)
+
+ def test_create_from_url(self):
+ # Test that the Distribution object can be built from a single URL
+ url_list = {
+ 'FooBar-1.1.0.tar.gz': {
+ 'name': 'foobar', # lowercase the name
+ 'version': '1.1',
+ },
+ 'Foo-Bar-1.1.0.zip': {
+ 'name': 'foo-bar', # keep the dash
+ 'version': '1.1',
+ },
+ 'foobar-1.1b2.tar.gz#md5=123123123123123': {
+ 'name': 'foobar',
+ 'version': '1.1b2',
+ 'url': {
+ 'url': 'http://test.tld/foobar-1.1b2.tar.gz', # no hash
+ 'hashval': '123123123123123',
+ 'hashname': 'md5',
+ }
+ },
+ 'foobar-1.1-rc2.tar.gz': { # use suggested name
+ 'name': 'foobar',
+ 'version': '1.1c2',
+ 'url': {
+ 'url': 'http://test.tld/foobar-1.1-rc2.tar.gz',
+ }
+ }
+ }
+
+ for url, attributes in url_list.items():
+ dist = Dist.from_url("http://test.tld/" + url)
+ for attribute, value in attributes.items():
+ if isinstance(value, dict):
+ mylist = getattr(dist, attribute)
+ for val in value.keys():
+ self.assertEqual(value[val], mylist[val])
+ else:
+ if attribute == "version":
+ self.assertEqual("%s" % getattr(dist, "version"), value)
+ else:
+ self.assertEqual(getattr(dist, attribute), value)
+
+ def test_get_url(self):
+ # Test that the url property works well
+
+ d = Dist("foobar", "1.1", url="test_url")
+ self.assertDictEqual(d.url, {
+ "url": "test_url",
+ "is_external": True,
+ "hashname": None,
+ "hashval": None,
+ })
+
+ # add a new url
+ d.add_url(url="internal_url", is_external=False)
+ self.assertEqual(d._url, None)
+ self.assertDictEqual(d.url, {
+ "url": "internal_url",
+ "is_external": False,
+ "hashname": None,
+ "hashval": None,
+ })
+ self.assertEqual(2, len(d._urls))
+
+ def test_comparaison(self):
+ # Test that we can compare PyPIDistributions
+ foo1 = Dist("foo", "1.0")
+ foo2 = Dist("foo", "2.0")
+ bar = Dist("bar", "2.0")
+ # assert we use the version to compare
+ self.assertTrue(foo1 < foo2)
+ self.assertFalse(foo1 > foo2)
+ self.assertFalse(foo1 == foo2)
+
+ # assert we can't compare dists with different names
+ self.assertRaises(TypeError, foo1.__eq__, bar)
+
+ def test_split_archive_name(self):
+ # Test we can split the archive names
+ names = {
+ 'foo-bar-baz-1.0-rc2': ('foo-bar-baz', '1.0c2'),
+ 'foo-bar-baz-1.0': ('foo-bar-baz', '1.0'),
+ 'foobarbaz-1.0': ('foobarbaz', '1.0'),
+ }
+ for name, results in names.items():
+ self.assertEqual(results, split_archive_name(name))
+
+ @use_pypi_server("downloads_with_md5")
+ 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("FooBar", "0.1", url=url,
+ url_hashname="md5", url_hashval="d41d8cd98f00b204e9800998ecf8427e")
+ add_to_tmpdirs(dist.download())
+
+ # a wrong md5 fails
+ dist2 = Dist("FooBar", "0.1", url=url,
+ url_hashname="md5", url_hashval="wrongmd5")
+
+ self.assertRaises(HashDoesNotMatch, dist2.download)
+ add_to_tmpdirs(dist2.downloaded_location)
+
+ # we can omit the md5 hash
+ dist3 = Dist("FooBar", "0.1", url=url)
+ add_to_tmpdirs(dist3.download())
+
+ # and specify a temporary location
+ # for an already downloaded dist
+ path1 = self.mkdtemp()
+ dist3.download(path=path1)
+ # and for a new one
+ path2_base = self.mkdtemp()
+ dist4 = Dist("FooBar", "0.1", url=url)
+ path2 = dist4.download(path=path2_base)
+ self.assertTrue(path2_base in path2)
+
+ def test_hashname(self):
+ # Invalid hashnames raises an exception on assignation
+ Dist("FooBar", "0.1", url_hashname="md5", url_hashval="value")
+
+ self.assertRaises(UnsupportedHashName, Dist, "FooBar", "0.1",
+ url_hashname="invalid_hashname", url_hashval="value")
+
+
+class TestPyPIDistributions(unittest.TestCase):
+
+ def test_filter(self):
+ # Test we filter the distributions the right way, using version
+ # predicate match method
+ dists = Dists((
+ Dist("FooBar", "1.1"),
+ Dist("FooBar", "1.1.1"),
+ Dist("FooBar", "1.2"),
+ Dist("FooBar", "1.2.1"),
+ ))
+ filtered = dists.filter(VersionPredicate("FooBar (<1.2)"))
+ self.assertNotIn(dists[2], filtered)
+ self.assertNotIn(dists[3], filtered)
+ self.assertIn(dists[0], filtered)
+ self.assertIn(dists[1], filtered)
+
+ def test_append(self):
+ # When adding a new item to the list, the behavior is to test if
+ # a distribution with the same name and version number already exists,
+ # and if so, to add url informations to the existing PyPIDistribution
+ # object.
+ # If no object matches, just add "normally" the object to the list.
+
+ dists = Dists([
+ Dist("FooBar", "1.1", url="external_url", type="source"),
+ ])
+ self.assertEqual(1, len(dists))
+ dists.append(Dist("FooBar", "1.1", url="internal_url",
+ url_is_external=False, type="source"))
+ self.assertEqual(1, len(dists))
+ self.assertEqual(2, len(dists[0]._urls))
+
+ dists.append(Dist("Foobar", "1.1.1", type="source"))
+ self.assertEqual(2, len(dists))
+
+ # when adding a distribution whith a different type, a new distribution
+ # has to be added.
+ dists.append(Dist("Foobar", "1.1.1", type="binary"))
+ self.assertEqual(3, len(dists))
+
+ def test_prefer_final(self):
+ # Can order the distributions using prefer_final
+
+ fb10 = Dist("FooBar", "1.0") # final distribution
+ fb11a = Dist("FooBar", "1.1a1") # alpha
+ fb12a = Dist("FooBar", "1.2a1") # alpha
+ fb12b = Dist("FooBar", "1.2b1") # beta
+ dists = Dists([fb10, fb11a, fb12a, fb12b])
+
+ dists.sort_distributions(prefer_final=True)
+ self.assertEqual(fb10, dists[0])
+
+ dists.sort_distributions(prefer_final=False)
+ self.assertEqual(fb12b, dists[0])
+
+ def test_prefer_source(self):
+ # Ordering support prefer_source
+ fb_source = Dist("FooBar", "1.0", type="source")
+ fb_binary = Dist("FooBar", "1.0", type="binary")
+ fb2_binary = Dist("FooBar", "2.0", type="binary")
+ dists = Dists([fb_binary, fb_source])
+
+ dists.sort_distributions(prefer_source=True)
+ self.assertEqual(fb_source, dists[0])
+
+ dists.sort_distributions(prefer_source=False)
+ self.assertEqual(fb_binary, dists[0])
+
+ dists.append(fb2_binary)
+ dists.sort_distributions(prefer_source=True)
+ self.assertEqual(fb2_binary, dists[0])
+
+ def test_get_same_name_and_version(self):
+ # PyPIDistributions can return a list of "duplicates"
+ fb_source = Dist("FooBar", "1.0", type="source")
+ fb_binary = Dist("FooBar", "1.0", type="binary")
+ fb2_binary = Dist("FooBar", "2.0", type="binary")
+ dists = Dists([fb_binary, fb_source, fb2_binary])
+ duplicates = dists.get_same_name_and_version()
+ self.assertTrue(1, len(duplicates))
+ self.assertIn(fb_source, duplicates[0])
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestPyPIDistribution))
+ suite.addTest(unittest.makeSuite(TestPyPIDistributions))
+ return suite
+
+if __name__ == '__main__':
+ run_unittest(test_suite())
diff --git a/src/distutils2/tests/test_pypi_server.py b/src/distutils2/tests/test_pypi_server.py
index 78174e7..65061ec 100644
--- a/src/distutils2/tests/test_pypi_server.py
+++ b/src/distutils2/tests/test_pypi_server.py
@@ -10,7 +10,7 @@ from distutils2.tests.support import unittest
class PyPIServerTest(unittest.TestCase):
def test_records_requests(self):
- """We expect that PyPIServer can log our requests"""
+ # We expect that PyPIServer can log our requests
server = PyPIServer()
server.start()
self.assertEqual(len(server.requests), 0)
@@ -23,14 +23,13 @@ class PyPIServerTest(unittest.TestCase):
self.assertEqual(len(server.requests), 1)
handler, request_data = server.requests[-1]
self.assertIn("Rock Around The Bunker", request_data)
- self.assertTrue(handler.headers.dict.has_key("x-test-header"))
- self.assertEqual(handler.headers.dict["x-test-header"],
- "Mister Iceberg")
+ self.assertIn("x-test-header", handler.headers.dict)
+ self.assertEqual(handler.headers.dict["x-test-header"],
+ "Mister Iceberg")
server.stop()
def test_serve_static_content(self):
- """PYPI Mocked server can serve static content from disk.
- """
+ # PYPI Mocked server can serve static content from disk.
def uses_local_files_for(server, url_path):
"""Test that files are served statically (eg. the output from the
diff --git a/src/distutils2/tests/test_pypi_simple.py b/src/distutils2/tests/test_pypi_simple.py
index 6429ab8..85d8b77 100644
--- a/src/distutils2/tests/test_pypi_simple.py
+++ b/src/distutils2/tests/test_pypi_simple.py
@@ -5,20 +5,34 @@ import sys
import os
import shutil
import tempfile
-import unittest2
import urllib2
-from distutils2.tests.pypi_server import use_pypi_server
from distutils2.pypi import simple
+from distutils2.tests import support, run_unittest
+from distutils2.tests.support import unittest
+from distutils2.tests.pypi_server import (use_pypi_server, PyPIServer,
+ PYPI_DEFAULT_STATIC_PATH)
-class PyPISimpleTestCase(unittest2.TestCase):
+class PyPISimpleTestCase(support.TempdirManager,
+ unittest.TestCase):
+
+ def _get_simple_index(self, server, base_url="/simple/", hosts=None,
+ *args, **kwargs):
+ """Build and return a SimpleSimpleIndex instance, with the test server
+ urls
+ """
+ if hosts is None:
+ hosts = (server.full_address.strip("http://"),)
+ kwargs['hosts'] = hosts
+ return simple.SimpleIndex(server.full_address + base_url, *args,
+ **kwargs)
def test_bad_urls(self):
- index = simple.PackageIndex()
+ index = simple.SimpleIndex()
url = 'http://127.0.0.1:0/nonesuch/test_simple'
try:
- v = index.open_url(url)
+ v = index._open_url(url)
except Exception, v:
self.assertTrue(url in str(v))
else:
@@ -27,12 +41,10 @@ class PyPISimpleTestCase(unittest2.TestCase):
# issue 16
# easy_install inquant.contentmirror.plone breaks because of a typo
# in its home URL
- index = simple.PackageIndex(
- hosts=('www.example.com',))
-
+ index = simple.SimpleIndex(hosts=('www.example.com',))
url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk'
try:
- v = index.open_url(url)
+ v = index._open_url(url)
except Exception, v:
self.assertTrue(url in str(v))
else:
@@ -47,7 +59,7 @@ class PyPISimpleTestCase(unittest2.TestCase):
url = 'http://example.com'
try:
try:
- v = index.open_url(url)
+ v = index._open_url(url)
except Exception, v:
self.assertTrue('line' in str(v))
else:
@@ -58,7 +70,7 @@ class PyPISimpleTestCase(unittest2.TestCase):
# issue 20
url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk'
try:
- index.open_url(url)
+ index._open_url(url)
except Exception, v:
self.assertTrue('nonnumeric port' in str(v))
@@ -68,103 +80,198 @@ class PyPISimpleTestCase(unittest2.TestCase):
url = 'http://example.com'
page = ('<a href="http://www.famfamfam.com]('
'http://www.famfamfam.com/">')
- index.process_index(url, page)
-
- def test_url_ok(self):
- index = simple.PackageIndex(
- hosts=('www.example.com',))
- url = 'file:///tmp/test_simple'
- self.assertTrue(index.url_ok(url, True))
-
+ index._process_url(url, page)
+
@use_pypi_server("test_found_links")
def test_found_links(self, server):
- """Browse the index, asking for a specified distribution version
- """
+ # Browse the index, asking for a specified distribution version
# The PyPI index contains links for version 1.0, 1.1, 2.0 and 2.0.1
- # We query only for the version 1.1, so all distributions must me
- # filled in the package_index (as the url has been scanned), but
- # obtain must only return the one we want.
- pi = simple.PackageIndex(server.full_address + "/simple/")
- last_distribution = pi.obtain(simple.Requirement.parse("foobar"))
+ index = self._get_simple_index(server)
+ last_distribution = index.get("foobar")
# we have scanned the index page
- self.assertTrue(pi.package_pages.has_key('foobar'))
self.assertIn(server.full_address + "/simple/foobar/",
- pi.package_pages['foobar'])
-
+ index._processed_urls)
+
# we have found 4 distributions in this page
- self.assertEqual(len(pi["foobar"]), 4)
+ self.assertEqual(len(index._distributions["foobar"]), 4)
# and returned the most recent one
- self.assertEqual(last_distribution.version, '2.0.1')
+ self.assertEqual("%s" % last_distribution.version, '2.0.1')
- @use_pypi_server("with_external_pages")
- def test_process_external_pages(self, server):
- """If the index provides links external to the pypi index,
- they need to be processed, in order to discover new distributions.
- """
+ def test_is_browsable(self):
+ index = simple.SimpleIndex(follow_externals=False)
+ self.assertTrue(index._is_browsable(index.index_url + "test"))
- @use_pypi_server()
- def test_scan_all(self, server):
- """Test that we can process the whole index and discover all related
- links.
+ # Now, when following externals, we can have a list of hosts to trust.
+ # and don't follow other external links than the one described here.
+ index = simple.SimpleIndex(hosts=["pypi.python.org", "test.org"],
+ follow_externals=True)
+ good_urls = (
+ "http://pypi.python.org/foo/bar",
+ "http://pypi.python.org/simple/foobar",
+ "http://test.org",
+ "http://test.org/",
+ "http://test.org/simple/",
+ )
+ bad_urls = (
+ "http://python.org",
+ "http://test.tld",
+ )
- """
- # FIXME remove?
-
- @use_pypi_server("with_external_pages")
- def test_disable_external_pages(self, server):
- """Test that when we tell the simple client to not retreive external
- urls, it does well."""
-
- @use_pypi_server()
- def test_process_index(self, server):
- """Test that we can process a simple given string and find related links
- to distributions.
- """
-
- @use_pypi_server()
- def test_process_url(self, server):
- """Test that we can process an alternative url (not pypi related) to
- find links in it.
- """
-
- @use_pypi_server(static_filesystem_paths=["test_link_priority"],
+ for url in good_urls:
+ self.assertTrue(index._is_browsable(url))
+
+ for url in bad_urls:
+ self.assertFalse(index._is_browsable(url))
+
+ # allow all hosts
+ index = simple.SimpleIndex(follow_externals=True, hosts=("*",))
+ self.assertTrue(index._is_browsable("http://an-external.link/path"))
+ self.assertTrue(index._is_browsable("pypi.test.tld/a/path"))
+
+ # specify a list of hosts we want to allow
+ index = simple.SimpleIndex(follow_externals=True,
+ hosts=("*.test.tld",))
+ self.assertFalse(index._is_browsable("http://an-external.link/path"))
+ self.assertTrue(index._is_browsable("http://pypi.test.tld/a/path"))
+
+ @use_pypi_server("with_externals")
+ def test_restrict_hosts(self, server):
+ # Include external pages
+ # Try to request the package index, wich contains links to "externals"
+ # resources. They have to be scanned too.
+ index = self._get_simple_index(server, follow_externals=True)
+ index.get("foobar")
+ self.assertIn(server.full_address + "/external/external.html",
+ index._processed_urls)
+
+ @use_pypi_server("with_real_externals")
+ def test_restrict_hosts(self, server):
+ # Only use a list of allowed hosts is possible
+ # Test that telling the simple pyPI client to not retrieve external
+ # works
+ index = self._get_simple_index(server, follow_externals=False)
+ index.get("foobar")
+ self.assertNotIn(server.full_address + "/external/external.html",
+ index._processed_urls)
+
+ @use_pypi_server(static_filesystem_paths=["with_externals"],
static_uri_paths=["simple", "external"])
def test_links_priority(self, server):
- """
- Download links from the pypi simple index should be used before
- external download links.
- http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error
-
- Usecase :
- - someone uploads a package on pypi, a md5 is generated
- - someone manually copies this link (with the md5 in the url) onto an
- external page accessible from the package page.
- - someone reuploads the package (with a different md5)
- - while easy_installing, an MD5 error occurs because the external link
- is used
- -> Distribute should use the link from pypi, not the external one.
- """
+ # Download links from the pypi simple index should be used before
+ # external download links.
+ # http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error
+ #
+ # Usecase :
+ # - someone uploads a package on pypi, a md5 is generated
+ # - someone manually coindexes this link (with the md5 in the url) onto
+ # an external page accessible from the package page.
+ # - someone reuploads the package (with a different md5)
+ # - while easy_installing, an MD5 error occurs because the external link
+ # is used
+ # -> The index should use the link from pypi, not the external one.
+
# start an index server
index_url = server.full_address + '/simple/'
# scan a test index
- pi = simple.PackageIndex(index_url)
- requirement = simple.Requirement.parse('foobar')
- pi.find_packages(requirement)
+ index = simple.SimpleIndex(index_url, follow_externals=True)
+ dists = index.find("foobar")
server.stop()
- # the distribution has been found
- self.assertTrue('foobar' in pi)
# we have only one link, because links are compared without md5
- self.assertEqual(len(pi['foobar']), 1)
+ self.assertEqual(len(dists), 1)
# the link should be from the index
- self.assertTrue('correct_md5' in pi['foobar'][0].location)
+ self.assertEqual('12345678901234567', dists[0].url['hashval'])
+ self.assertEqual('md5', dists[0].url['hashname'])
+
+ @use_pypi_server(static_filesystem_paths=["with_norel_links"],
+ static_uri_paths=["simple", "external"])
+ def test_not_scan_all_links(self, server):
+ # Do not follow all index page links.
+ # The links not tagged with rel="download" and rel="homepage" have
+ # to not be processed by the package index, while processing "pages".
+
+ # process the pages
+ index = self._get_simple_index(server, follow_externals=True)
+ index.find("foobar")
+ # now it should have processed only pages with links rel="download"
+ # and rel="homepage"
+ self.assertIn("%s/simple/foobar/" % server.full_address,
+ index._processed_urls) # it's the simple index page
+ self.assertIn("%s/external/homepage.html" % server.full_address,
+ index._processed_urls) # the external homepage is rel="homepage"
+ self.assertNotIn("%s/external/nonrel.html" % server.full_address,
+ index._processed_urls) # this link contains no rel=*
+ self.assertNotIn("%s/unrelated-0.2.tar.gz" % server.full_address,
+ index._processed_urls) # linked from simple index (no rel)
+ self.assertIn("%s/foobar-0.1.tar.gz" % server.full_address,
+ index._processed_urls) # linked from simple index (rel)
+ self.assertIn("%s/foobar-2.0.tar.gz" % server.full_address,
+ index._processed_urls) # linked from external homepage (rel)
+
+ def test_uses_mirrors(self):
+ # When the main repository seems down, try using the given mirrors"""
+ server = PyPIServer("foo_bar_baz")
+ mirror = PyPIServer("foo_bar_baz")
+ mirror.start() # we dont start the server here
+
+ try:
+ # create the index using both servers
+ index = simple.SimpleIndex(server.full_address + "/simple/",
+ hosts=('*',), timeout=1, # set the timeout to 1s for the tests
+ mirrors=[mirror.full_address + "/simple/",])
+
+ # this should not raise a timeout
+ self.assertEqual(4, len(index.find("foo")))
+ finally:
+ mirror.stop()
+
+ def test_simple_link_matcher(self):
+ # Test that the simple link matcher yields the right links"""
+ index = simple.SimpleIndex(follow_externals=False)
+
+ # Here, we define:
+ # 1. one link that must be followed, cause it's a download one
+ # 2. one link that must *not* be followed, cause the is_browsable
+ # returns false for it.
+ # 3. one link that must be followed cause it's a homepage that is
+ # browsable
+ self.assertTrue(index._is_browsable("%stest" % index.index_url))
+ self.assertFalse(index._is_browsable("http://dl-link2"))
+ content = """
+ <a href="http://dl-link1" rel="download">download_link1</a>
+ <a href="http://dl-link2" rel="homepage">homepage_link1</a>
+ <a href="%stest" rel="homepage">homepage_link2</a>
+ """ % index.index_url
+
+ # Test that the simple link matcher yield the good links.
+ generator = index._simple_link_matcher(content, index.index_url)
+ self.assertEqual(('http://dl-link1', True), generator.next())
+ self.assertEqual(('%stest' % index.index_url, False),
+ generator.next())
+ self.assertRaises(StopIteration, generator.next)
+
+ # Follow the external links is possible
+ index.follow_externals = True
+ generator = index._simple_link_matcher(content, index.index_url)
+ self.assertEqual(('http://dl-link1', True), generator.next())
+ self.assertEqual(('http://dl-link2', False), generator.next())
+ self.assertEqual(('%stest' % index.index_url, False),
+ generator.next())
+ self.assertRaises(StopIteration, generator.next)
+ def test_browse_local_files(self):
+ # Test that we can browse local files"""
+ index_path = os.sep.join(["file://" + PYPI_DEFAULT_STATIC_PATH,
+ "test_found_links", "simple"])
+ index = simple.SimpleIndex(index_path)
+ dists = index.find("foobar")
+ self.assertEqual(4, len(dists))
def test_suite():
- return unittest2.makeSuite(PyPISimpleTestCase)
+ return unittest.makeSuite(PyPISimpleTestCase)
if __name__ == '__main__':
- unittest2.main(defaultTest="test_suite")
+ unittest.main(defaultTest="test_suite")
diff --git a/src/distutils2/tests/test_pypi_versions.py b/src/distutils2/tests/test_pypi_versions.py
index 4e97147..7781982 100644
--- a/src/distutils2/tests/test_pypi_versions.py
+++ b/src/distutils2/tests/test_pypi_versions.py
@@ -19,6 +19,7 @@ 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():
@@ -52,7 +53,7 @@ def test_pypi():
print "Saving package info..."
f = open(INDEX_PICKLE_FILE, 'wb')
try:
- pickle.dump(package_info, o)
+ pickle.dump(package_info, f)
finally:
f.close()
diff --git a/src/distutils2/tests/test_register.py b/src/distutils2/tests/test_register.py
index 3261610..be782d1 100644
--- a/src/distutils2/tests/test_register.py
+++ b/src/distutils2/tests/test_register.py
@@ -124,7 +124,7 @@ class RegisterTestCase(PyPIRCCommandTestCase):
# with the content similar to WANTED_PYPIRC
content = open(self.rc).read()
- self.assertEquals(content, WANTED_PYPIRC)
+ self.assertEqual(content, WANTED_PYPIRC)
# now let's make sure the .pypirc file generated
# really works : we shouldn't be asked anything
@@ -141,7 +141,7 @@ class RegisterTestCase(PyPIRCCommandTestCase):
self.assertTrue(self.conn.reqs, 2)
req1 = dict(self.conn.reqs[0].headers)
req2 = dict(self.conn.reqs[1].headers)
- self.assertEquals(req2['Content-length'], req1['Content-length'])
+ self.assertEqual(req2['Content-length'], req1['Content-length'])
self.assertTrue('xxx' in self.conn.reqs[1].data)
def test_password_not_in_file(self):
@@ -154,7 +154,7 @@ class RegisterTestCase(PyPIRCCommandTestCase):
# dist.password should be set
# therefore used afterwards by other commands
- self.assertEquals(cmd.distribution.password, 'password')
+ self.assertEqual(cmd.distribution.password, 'password')
def test_registering(self):
# this test runs choice 2
@@ -171,7 +171,7 @@ class RegisterTestCase(PyPIRCCommandTestCase):
self.assertTrue(self.conn.reqs, 1)
req = self.conn.reqs[0]
headers = dict(req.headers)
- self.assertEquals(headers['Content-length'], '608')
+ self.assertEqual(headers['Content-length'], '608')
self.assertTrue('tarek' in req.data)
def test_password_reset(self):
@@ -189,7 +189,7 @@ class RegisterTestCase(PyPIRCCommandTestCase):
self.assertTrue(self.conn.reqs, 1)
req = self.conn.reqs[0]
headers = dict(req.headers)
- self.assertEquals(headers['Content-length'], '290')
+ self.assertEqual(headers['Content-length'], '290')
self.assertTrue('tarek' in req.data)
@unittest.skipUnless(DOCUTILS_SUPPORT, 'needs docutils')
@@ -246,8 +246,8 @@ class RegisterTestCase(PyPIRCCommandTestCase):
cmd.ensure_finalized()
cmd.distribution.metadata['Requires-Dist'] = ['lxml']
data = cmd.build_post_data('submit')
- self.assertEquals(data['metadata_version'], '1.2')
- self.assertEquals(data['requires_dist'], ['lxml'])
+ self.assertEqual(data['metadata_version'], '1.2')
+ self.assertEqual(data['requires_dist'], ['lxml'])
def test_suite():
return unittest.makeSuite(RegisterTestCase)
diff --git a/src/distutils2/tests/test_sdist.py b/src/distutils2/tests/test_sdist.py
index 964857b..8859a3d 100644
--- a/src/distutils2/tests/test_sdist.py
+++ b/src/distutils2/tests/test_sdist.py
@@ -20,7 +20,6 @@ except ImportError:
from os.path import join
import sys
-import tempfile
import warnings
from distutils2.tests import captured_stdout
@@ -31,7 +30,7 @@ from distutils2.core import Distribution
from distutils2.tests.support import unittest
from distutils2.tests.test_config import PyPIRCCommandTestCase
from distutils2.errors import DistutilsExecError, DistutilsOptionError
-from distutils2.spawn import find_executable
+from distutils2.util import find_executable
from distutils2.tests import support
from distutils2.log import WARN
try:
@@ -128,7 +127,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
# now let's check what we have
dist_folder = join(self.tmp_dir, 'dist')
files = os.listdir(dist_folder)
- self.assertEquals(files, ['fake-1.0.zip'])
+ self.assertEqual(files, ['fake-1.0.zip'])
zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip'))
try:
@@ -137,7 +136,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
zip_file.close()
# making sure everything has been pruned correctly
- self.assertEquals(len(content), 4)
+ self.assertEqual(len(content), 4)
@unittest.skipUnless(zlib, "requires zlib")
def test_make_distribution(self):
@@ -159,7 +158,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
dist_folder = join(self.tmp_dir, 'dist')
result = os.listdir(dist_folder)
result.sort()
- self.assertEquals(result,
+ self.assertEqual(result,
['fake-1.0.tar', 'fake-1.0.tar.gz'] )
os.remove(join(dist_folder, 'fake-1.0.tar'))
@@ -173,7 +172,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
result = os.listdir(dist_folder)
result.sort()
- self.assertEquals(result,
+ self.assertEqual(result,
['fake-1.0.tar', 'fake-1.0.tar.gz'])
@unittest.skipUnless(zlib, "requires zlib")
@@ -223,7 +222,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
# now let's check what we have
dist_folder = join(self.tmp_dir, 'dist')
files = os.listdir(dist_folder)
- self.assertEquals(files, ['fake-1.0.zip'])
+ self.assertEqual(files, ['fake-1.0.zip'])
zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip'))
try:
@@ -232,11 +231,11 @@ class SDistTestCase(PyPIRCCommandTestCase):
zip_file.close()
# making sure everything was added
- self.assertEquals(len(content), 11)
+ self.assertEqual(len(content), 11)
# checking the MANIFEST
manifest = open(join(self.tmp_dir, 'MANIFEST')).read()
- self.assertEquals(manifest, MANIFEST % {'sep': os.sep})
+ self.assertEqual(manifest, MANIFEST % {'sep': os.sep})
@unittest.skipUnless(zlib, "requires zlib")
def test_metadata_check_option(self):
@@ -248,7 +247,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
cmd.ensure_finalized()
cmd.run()
warnings = self.get_logs(WARN)
- self.assertEquals(len(warnings), 1)
+ self.assertEqual(len(warnings), 1)
# trying with a complete set of metadata
self.clear_logs()
@@ -260,7 +259,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
# removing manifest generated warnings
warnings = [warn for warn in warnings if
not warn.endswith('-- skipping')]
- self.assertEquals(len(warnings), 0)
+ self.assertEqual(len(warnings), 0)
def test_show_formats(self):
@@ -270,7 +269,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
num_formats = len(get_archive_formats())
output = [line for line in stdout.split('\n')
if line.strip().startswith('--formats=')]
- self.assertEquals(len(output), num_formats)
+ self.assertEqual(len(output), num_formats)
def test_finalize_options(self):
@@ -278,9 +277,9 @@ class SDistTestCase(PyPIRCCommandTestCase):
cmd.finalize_options()
# default options set by finalize
- self.assertEquals(cmd.manifest, 'MANIFEST')
- self.assertEquals(cmd.template, 'MANIFEST.in')
- self.assertEquals(cmd.dist_dir, 'dist')
+ self.assertEqual(cmd.manifest, 'MANIFEST')
+ self.assertEqual(cmd.template, 'MANIFEST.in')
+ self.assertEqual(cmd.dist_dir, 'dist')
# formats has to be a string splitable on (' ', ',') or
# a stringlist
@@ -317,8 +316,8 @@ class SDistTestCase(PyPIRCCommandTestCase):
archive = tarfile.open(archive_name)
try:
for member in archive.getmembers():
- self.assertEquals(member.uid, 0)
- self.assertEquals(member.gid, 0)
+ self.assertEqual(member.uid, 0)
+ self.assertEqual(member.gid, 0)
finally:
archive.close()
@@ -339,7 +338,7 @@ class SDistTestCase(PyPIRCCommandTestCase):
# rights (see #7408)
try:
for member in archive.getmembers():
- self.assertEquals(member.uid, os.getuid())
+ self.assertEqual(member.uid, os.getuid())
finally:
archive.close()
diff --git a/src/distutils2/tests/test_spawn.py b/src/distutils2/tests/test_spawn.py
deleted file mode 100644
index 17c7430..0000000
--- a/src/distutils2/tests/test_spawn.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""Tests for distutils.spawn."""
-import os
-import time
-from distutils2.tests import captured_stdout
-
-from distutils2.spawn import _nt_quote_args
-from distutils2.spawn import spawn, find_executable
-from distutils2.errors import DistutilsExecError
-from distutils2.tests import support
-from distutils2.tests.support import unittest
-
-class SpawnTestCase(support.TempdirManager,
- support.LoggingSilencer,
- unittest.TestCase):
-
- def test_nt_quote_args(self):
-
- for (args, wanted) in ((['with space', 'nospace'],
- ['"with space"', 'nospace']),
- (['nochange', 'nospace'],
- ['nochange', 'nospace'])):
- res = _nt_quote_args(args)
- self.assertEquals(res, wanted)
-
-
- @unittest.skipUnless(os.name in ('nt', 'posix'),
- 'Runs only under posix or nt')
- def test_spawn(self):
- tmpdir = self.mkdtemp()
-
- # creating something executable
- # through the shell that returns 1
- if os.name == 'posix':
- exe = os.path.join(tmpdir, 'foo.sh')
- self.write_file(exe, '#!/bin/sh\nexit 1')
- os.chmod(exe, 0777)
- else:
- exe = os.path.join(tmpdir, 'foo.bat')
- self.write_file(exe, 'exit 1')
-
- os.chmod(exe, 0777)
- self.assertRaises(DistutilsExecError, spawn, [exe])
-
- # now something that works
- if os.name == 'posix':
- exe = os.path.join(tmpdir, 'foo.sh')
- self.write_file(exe, '#!/bin/sh\nexit 0')
- os.chmod(exe, 0777)
- else:
- exe = os.path.join(tmpdir, 'foo.bat')
- self.write_file(exe, 'exit 0')
-
- os.chmod(exe, 0777)
- spawn([exe]) # should work without any error
-
-def test_suite():
- return unittest.makeSuite(SpawnTestCase)
-
-if __name__ == "__main__":
- unittest.main(defaultTest="test_suite")
diff --git a/src/distutils2/tests/test_upload.py b/src/distutils2/tests/test_upload.py
index a0a7330..5ca88a1 100644
--- a/src/distutils2/tests/test_upload.py
+++ b/src/distutils2/tests/test_upload.py
@@ -1,12 +1,13 @@
"""Tests for distutils.command.upload."""
# -*- encoding: utf8 -*-
-import os, sys
+import os
+import sys
from distutils2.command.upload import upload
from distutils2.core import Distribution
-from distutils2.tests.pypi_server import PyPIServer, PyPIServerTestCase
from distutils2.tests import support
+from distutils2.tests.pypi_server import PyPIServer, PyPIServerTestCase
from distutils2.tests.support import unittest
from distutils2.tests.test_config import PYPIRC, PyPIRCCommandTestCase
@@ -29,10 +30,10 @@ class UploadTestCase(PyPIServerTestCase, PyPIRCCommandTestCase):
dist = Distribution()
cmd = upload(dist)
cmd.finalize_options()
- for attr, waited in (('username', 'me'), ('password', 'secret'),
- ('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi')):
- self.assertEqual(getattr(cmd, attr), waited)
+ for attr, expected in (('username', 'me'), ('password', 'secret'),
+ ('realm', 'pypi'),
+ ('repository', 'http://pypi.python.org/pypi')):
+ self.assertEqual(getattr(cmd, attr), expected)
def test_saved_password(self):
# file with no password
diff --git a/src/distutils2/tests/test_util.py b/src/distutils2/tests/test_util.py
index b9b8f4a..51097a7 100644
--- a/src/distutils2/tests/test_util.py
+++ b/src/distutils2/tests/test_util.py
@@ -4,16 +4,21 @@ import sys
from copy import copy
from StringIO import StringIO
import subprocess
+import tempfile
+import time
+from distutils2.tests import captured_stdout
+from distutils2.tests.support import unittest
from distutils2.errors import (DistutilsPlatformError,
DistutilsByteCompileError,
- DistutilsFileError)
-
+ DistutilsFileError,
+ DistutilsExecError)
from distutils2.util import (convert_path, change_root,
- check_environ, split_quoted, strtobool,
- rfc822_escape, get_compiler_versions,
- _find_exe_version, _MAC_OS_X_LD_VERSION,
- byte_compile, find_packages)
+ check_environ, split_quoted, strtobool,
+ rfc822_escape, get_compiler_versions,
+ _find_exe_version, _MAC_OS_X_LD_VERSION,
+ byte_compile, find_packages, spawn, find_executable,
+ _nt_quote_args)
from distutils2 import util
from distutils2.tests import support
from distutils2.tests.support import unittest
@@ -100,7 +105,7 @@ class UtilTestCase(support.EnvironGuard,
return '/'.join(path)
os.path.join = _join
- self.assertEquals(convert_path('/home/to/my/stuff'),
+ self.assertEqual(convert_path('/home/to/my/stuff'),
'/home/to/my/stuff')
# win
@@ -112,9 +117,9 @@ class UtilTestCase(support.EnvironGuard,
self.assertRaises(ValueError, convert_path, '/home/to/my/stuff')
self.assertRaises(ValueError, convert_path, 'home/to/my/stuff/')
- self.assertEquals(convert_path('home/to/my/stuff'),
+ self.assertEqual(convert_path('home/to/my/stuff'),
'home\\to\\my\\stuff')
- self.assertEquals(convert_path('.'),
+ self.assertEqual(convert_path('.'),
os.curdir)
def test_change_root(self):
@@ -127,9 +132,9 @@ class UtilTestCase(support.EnvironGuard,
return '/'.join(path)
os.path.join = _join
- self.assertEquals(change_root('/root', '/old/its/here'),
+ self.assertEqual(change_root('/root', '/old/its/here'),
'/root/old/its/here')
- self.assertEquals(change_root('/root', 'its/here'),
+ self.assertEqual(change_root('/root', 'its/here'),
'/root/its/here')
# windows
@@ -146,9 +151,9 @@ class UtilTestCase(support.EnvironGuard,
return '\\'.join(path)
os.path.join = _join
- self.assertEquals(change_root('c:\\root', 'c:\\old\\its\\here'),
+ self.assertEqual(change_root('c:\\root', 'c:\\old\\its\\here'),
'c:\\root\\old\\its\\here')
- self.assertEquals(change_root('c:\\root', 'its\\here'),
+ self.assertEqual(change_root('c:\\root', 'its\\here'),
'c:\\root\\its\\here')
# BugsBunny os (it's a great os)
@@ -159,7 +164,7 @@ class UtilTestCase(support.EnvironGuard,
# XXX platforms to be covered: os2, mac
def test_split_quoted(self):
- self.assertEquals(split_quoted('""one"" "two" \'three\' \\four'),
+ self.assertEqual(split_quoted('""one"" "two" \'three\' \\four'),
['one', 'two', 'three', 'four'])
def test_strtobool(self):
@@ -177,7 +182,7 @@ class UtilTestCase(support.EnvironGuard,
res = rfc822_escape(header)
wanted = ('I am a%(8s)spoor%(8s)slonesome%(8s)s'
'header%(8s)s') % {'8s': '\n'+8*' '}
- self.assertEquals(res, wanted)
+ self.assertEqual(res, wanted)
def test_find_exe_version(self):
# the ld version scheme under MAC OS is:
@@ -195,7 +200,7 @@ class UtilTestCase(support.EnvironGuard,
('@(#)PROGRAM:ld PROJECT:ld64-95.2.12',
'95.2.12')):
result = _MAC_OS_X_LD_VERSION.search(output)
- self.assertEquals(result.group(1), version)
+ self.assertEqual(result.group(1), version)
def _find_executable(self, name):
if name in self._exes:
@@ -205,43 +210,43 @@ class UtilTestCase(support.EnvironGuard,
def test_get_compiler_versions(self):
# get_versions calls distutils.spawn.find_executable on
# 'gcc', 'ld' and 'dllwrap'
- self.assertEquals(get_compiler_versions(), (None, None, None))
+ self.assertEqual(get_compiler_versions(), (None, None, None))
# Let's fake we have 'gcc' and it returns '3.4.5'
self._exes['gcc'] = 'gcc (GCC) 3.4.5 (mingw special)\nFSF'
res = get_compiler_versions()
- self.assertEquals(str(res[0]), '3.4.5')
+ self.assertEqual(str(res[0]), '3.4.5')
# and let's see what happens when the version
# doesn't match the regular expression
# (\d+\.\d+(\.\d+)*)
self._exes['gcc'] = 'very strange output'
res = get_compiler_versions()
- self.assertEquals(res[0], None)
+ self.assertEqual(res[0], None)
# same thing for ld
if sys.platform != 'darwin':
self._exes['ld'] = 'GNU ld version 2.17.50 20060824'
res = get_compiler_versions()
- self.assertEquals(str(res[1]), '2.17.50')
+ self.assertEqual(str(res[1]), '2.17.50')
self._exes['ld'] = '@(#)PROGRAM:ld PROJECT:ld64-77'
res = get_compiler_versions()
- self.assertEquals(res[1], None)
+ self.assertEqual(res[1], None)
else:
self._exes['ld'] = 'GNU ld version 2.17.50 20060824'
res = get_compiler_versions()
- self.assertEquals(res[1], None)
+ self.assertEqual(res[1], None)
self._exes['ld'] = '@(#)PROGRAM:ld PROJECT:ld64-77'
res = get_compiler_versions()
- self.assertEquals(str(res[1]), '77')
+ self.assertEqual(str(res[1]), '77')
# and dllwrap
self._exes['dllwrap'] = 'GNU dllwrap 2.17.50 20060824\nFSF'
res = get_compiler_versions()
- self.assertEquals(str(res[2]), '2.17.50')
+ self.assertEqual(str(res[2]), '2.17.50')
self._exes['dllwrap'] = 'Cheese Wrap'
res = get_compiler_versions()
- self.assertEquals(res[2], None)
+ self.assertEqual(res[2], None)
@unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
'no dont_write_bytecode support')
@@ -257,7 +262,10 @@ class UtilTestCase(support.EnvironGuard,
def test_newer(self):
self.assertRaises(DistutilsFileError, util.newer, 'xxx', 'xxx')
-
+ self.newer_f1 = tempfile.NamedTemporaryFile()
+ time.sleep(1)
+ self.newer_f2 = tempfile.NamedTemporaryFile()
+ self.assertTrue(util.newer(self.newer_f2.name, self.newer_f1.name))
def test_find_packages(self):
# let's create a structure we want to scan:
@@ -294,7 +302,78 @@ class UtilTestCase(support.EnvironGuard,
self.write_file(os.path.join(pkg5, '__init__.py'))
res = find_packages([root], ['pkg1.pkg2'])
- self.assertEquals(set(res), set(['pkg1', 'pkg5', 'pkg1.pkg3', 'pkg1.pkg3.pkg6']))
+ 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_run_2to3_on_code(self):
+ content = "print 'test'"
+ converted_content = "print('test')"
+ file_handle = self.mktempfile()
+ file_name = file_handle.name
+ file_handle.write(content)
+ file_handle.flush()
+ file_handle.seek(0)
+ from distutils2.util import run_2to3
+ run_2to3([file_name])
+ new_content = "".join(file_handle.read())
+ file_handle.close()
+ self.assertEquals(new_content, converted_content)
+
+ @unittest.skipUnless(sys.version > '2.6', 'Need Python 2.6 or more')
+ def test_run_2to3_on_doctests(self):
+ # to check if text files containing doctests only get converted.
+ content = ">>> print 'test'\ntest\n"
+ converted_content = ">>> print('test')\ntest\n\n"
+ file_handle = self.mktempfile()
+ file_name = file_handle.name
+ file_handle.write(content)
+ file_handle.flush()
+ file_handle.seek(0)
+ from distutils2.util import run_2to3
+ run_2to3([file_name], doctests_only=True)
+ new_content = "".join(file_handle.readlines())
+ file_handle.close()
+ self.assertEquals(new_content, converted_content)
+
+ def test_nt_quote_args(self):
+
+ for (args, wanted) in ((['with space', 'nospace'],
+ ['"with space"', 'nospace']),
+ (['nochange', 'nospace'],
+ ['nochange', 'nospace'])):
+ res = _nt_quote_args(args)
+ self.assertEqual(res, wanted)
+
+
+ @unittest.skipUnless(os.name in ('nt', 'posix'),
+ 'Runs only under posix or nt')
+ def test_spawn(self):
+ tmpdir = self.mkdtemp()
+
+ # creating something executable
+ # through the shell that returns 1
+ if os.name == 'posix':
+ exe = os.path.join(tmpdir, 'foo.sh')
+ self.write_file(exe, '#!/bin/sh\nexit 1')
+ os.chmod(exe, 0777)
+ else:
+ exe = os.path.join(tmpdir, 'foo.bat')
+ self.write_file(exe, 'exit 1')
+
+ os.chmod(exe, 0777)
+ self.assertRaises(DistutilsExecError, spawn, [exe])
+
+ # now something that works
+ if os.name == 'posix':
+ exe = os.path.join(tmpdir, 'foo.sh')
+ self.write_file(exe, '#!/bin/sh\nexit 0')
+ os.chmod(exe, 0777)
+ else:
+ exe = os.path.join(tmpdir, 'foo.bat')
+ self.write_file(exe, 'exit 0')
+
+ os.chmod(exe, 0777)
+ spawn([exe]) # should work without any error
def test_suite():
diff --git a/src/distutils2/tests/test_version.py b/src/distutils2/tests/test_version.py
index cc725cf..343b397 100644
--- a/src/distutils2/tests/test_version.py
+++ b/src/distutils2/tests/test_version.py
@@ -3,7 +3,7 @@ import doctest
import os
from distutils2.version import NormalizedVersion as V
-from distutils2.version import IrrationalVersionError
+from distutils2.version import HugeMajorVersionNumError, IrrationalVersionError
from distutils2.version import suggest_normalized_version as suggest
from distutils2.version import VersionPredicate
from distutils2.tests.support import unittest
@@ -22,18 +22,22 @@ class VersionTestCase(unittest.TestCase):
(V('1.0.dev345'), '1.0.dev345'),
(V('1.0.post456.dev623'), '1.0.post456.dev623'))
+ def test_repr(self):
+
+ self.assertEqual(repr(V('1.0')), "NormalizedVersion('1.0')")
+
def test_basic_versions(self):
for v, s in self.versions:
- self.assertEquals(str(v), s)
+ self.assertEqual(str(v), s)
def test_from_parts(self):
for v, s in self.versions:
parts = v.parts
v2 = V.from_parts(*v.parts)
- self.assertEquals(v, v2)
- self.assertEquals(str(v), str(v2))
+ self.assertEqual(v, v2)
+ self.assertEqual(str(v), str(v2))
def test_irrational_versions(self):
@@ -44,6 +48,12 @@ class VersionTestCase(unittest.TestCase):
for s in irrational:
self.assertRaises(IrrationalVersionError, V, s)
+ def test_huge_version(self):
+
+ self.assertEquals(str(V('1980.0')), '1980.0')
+ self.assertRaises(HugeMajorVersionNumError, V, '1981.0')
+ self.assertEquals(str(V('1981.0', error_on_huge_major_num=False)), '1981.0')
+
def test_comparison(self):
r"""
>>> V('1.2.0') == '1.2'
@@ -51,12 +61,33 @@ class VersionTestCase(unittest.TestCase):
...
TypeError: cannot compare NormalizedVersion and str
+ >>> V('1.2') < '1.3'
+ Traceback (most recent call last):
+ ...
+ TypeError: cannot compare NormalizedVersion and str
+
>>> V('1.2.0') == V('1.2')
True
>>> V('1.2.0') == V('1.2.3')
False
+ >>> V('1.2.0') != V('1.2.3')
+ True
>>> V('1.2.0') < V('1.2.3')
True
+ >>> V('1.2.0') < V('1.2.0')
+ False
+ >>> V('1.2.0') <= V('1.2.0')
+ True
+ >>> V('1.2.0') <= V('1.2.3')
+ True
+ >>> V('1.2.3') <= V('1.2.0')
+ False
+ >>> V('1.2.0') >= V('1.2.0')
+ True
+ >>> V('1.2.3') >= V('1.2.0')
+ True
+ >>> V('1.2.0') >= V('1.2.3')
+ False
>>> (V('1.0') > V('1.0b2'))
True
>>> (V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1')
@@ -96,34 +127,35 @@ class VersionTestCase(unittest.TestCase):
def test_suggest_normalized_version(self):
- self.assertEquals(suggest('1.0'), '1.0')
- self.assertEquals(suggest('1.0-alpha1'), '1.0a1')
- self.assertEquals(suggest('1.0c2'), '1.0c2')
- self.assertEquals(suggest('walla walla washington'), None)
- self.assertEquals(suggest('2.4c1'), '2.4c1')
+ self.assertEqual(suggest('1.0'), '1.0')
+ self.assertEqual(suggest('1.0-alpha1'), '1.0a1')
+ self.assertEqual(suggest('1.0c2'), '1.0c2')
+ self.assertEqual(suggest('walla walla washington'), None)
+ self.assertEqual(suggest('2.4c1'), '2.4c1')
+ self.assertEqual(suggest('v1.0'), '1.0')
# from setuptools
- self.assertEquals(suggest('0.4a1.r10'), '0.4a1.post10')
- self.assertEquals(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
- self.assertEquals(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
- self.assertEquals(suggest('2.4preview1'), '2.4c1')
- self.assertEquals(suggest('2.4pre1') , '2.4c1')
- self.assertEquals(suggest('2.1-rc2'), '2.1c2')
+ self.assertEqual(suggest('0.4a1.r10'), '0.4a1.post10')
+ self.assertEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
+ self.assertEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
+ self.assertEqual(suggest('2.4preview1'), '2.4c1')
+ self.assertEqual(suggest('2.4pre1') , '2.4c1')
+ self.assertEqual(suggest('2.1-rc2'), '2.1c2')
# from pypi
- self.assertEquals(suggest('0.1dev'), '0.1.dev0')
- self.assertEquals(suggest('0.1.dev'), '0.1.dev0')
+ self.assertEqual(suggest('0.1dev'), '0.1.dev0')
+ self.assertEqual(suggest('0.1.dev'), '0.1.dev0')
# we want to be able to parse Twisted
# development versions are like post releases in Twisted
- self.assertEquals(suggest('9.0.0+r2363'), '9.0.0.post2363')
+ self.assertEqual(suggest('9.0.0+r2363'), '9.0.0.post2363')
# pre-releases are using markers like "pre1"
- self.assertEquals(suggest('9.0.0pre1'), '9.0.0c1')
+ self.assertEqual(suggest('9.0.0pre1'), '9.0.0c1')
# we want to be able to parse Tcl-TK
# they us "p1" "p2" for post releases
- self.assertEquals(suggest('1.4p1'), '1.4.post1')
+ self.assertEqual(suggest('1.4p1'), '1.4.post1')
def test_predicate(self):
# VersionPredicate knows how to parse stuff like:
@@ -151,15 +183,37 @@ class VersionTestCase(unittest.TestCase):
self.assertFalse(VersionPredicate('Hey (<=2.5)').match('2.6.0'))
self.assertTrue(VersionPredicate('Hey (>=2.5)').match('2.5.1'))
+ self.assertRaises(ValueError, VersionPredicate, '')
+
# XXX need to silent the micro version in this case
#assert not VersionPredicate('Ho (<3.0,!=2.6)').match('2.6.3')
+ def test_is_final(self):
+ # VersionPredicate knows is a distribution is a final one or not.
+ final_versions = ('1.0', '1.0.post456')
+ other_versions = ('1.0.dev1', '1.0a2', '1.0c3')
+
+ for version in final_versions:
+ self.assertTrue(V(version).is_final)
+ for version in other_versions:
+ self.assertFalse(V(version).is_final)
+
+class VersionWhiteBoxTestCase(unittest.TestCase):
+
+ def test_parse_numdots(self):
+ # For code coverage completeness, as pad_zeros_length can't be set or
+ # influenced from the public interface
+ self.assertEquals(V('1.0')._parse_numdots('1.0', '1.0',
+ pad_zeros_length=3),
+ [1, 0, 0])
+
+
def test_suite():
#README = os.path.join(os.path.dirname(__file__), 'README.txt')
#suite = [doctest.DocFileSuite(README), unittest.makeSuite(VersionTestCase)]
- suite = [unittest.makeSuite(VersionTestCase)]
+ suite = [unittest.makeSuite(VersionTestCase),
+ unittest.makeSuite(VersionWhiteBoxTestCase)]
return unittest.TestSuite(suite)
if __name__ == "__main__":
unittest.main(defaultTest="test_suite")
-
diff --git a/src/distutils2/util.py b/src/distutils2/util.py
index 0ab2e3e..8f9f81d 100644
--- a/src/distutils2/util.py
+++ b/src/distutils2/util.py
@@ -1,22 +1,25 @@
"""distutils.util
-Miscellaneous utility functions -- anything that doesn't fit into
-one of the other *util.py modules.
+Miscellaneous utility functions.
"""
__revision__ = "$Id: util.py 77761 2010-01-26 22:46:15Z tarek.ziade $"
-import sys, os, string, re
+import sys
+import os
+import string
+import re
+from copy import copy
from fnmatch import fnmatchcase
from distutils2.errors import (DistutilsPlatformError, DistutilsFileError,
- DistutilsByteCompileError)
-from distutils2.spawn import spawn, find_executable
+ DistutilsByteCompileError, DistutilsExecError)
from distutils2 import log
from distutils2._backport import sysconfig as _sysconfig
_PLATFORM = None
+
def newer(source, target):
"""Tells if the target is newer than the source.
@@ -37,6 +40,7 @@ def newer(source, target):
return os.stat(source).st_mtime > os.stat(target).st_mtime
+
def get_platform():
"""Return a string that identifies the current platform.
@@ -48,6 +52,7 @@ def get_platform():
_PLATFORM = _sysconfig.get_platform()
return _PLATFORM
+
def set_platform(identifier):
"""Sets the platform string identifier returned by get_platform().
@@ -57,6 +62,7 @@ def set_platform(identifier):
global _PLATFORM
_PLATFORM = identifier
+
def convert_path(pathname):
"""Return 'pathname' as a name that will work on the native filesystem.
@@ -125,6 +131,7 @@ def change_root(new_root, pathname):
_environ_checked = 0
+
def check_environ():
"""Ensure that 'os.environ' has all the environment variables needed.
@@ -147,6 +154,7 @@ def check_environ():
_environ_checked = 1
+
def subst_vars(s, local_vars):
"""Perform shell/Perl-style variable substitution on 'string'.
@@ -158,7 +166,8 @@ def subst_vars(s, local_vars):
variables not found in either 'local_vars' or 'os.environ'.
"""
check_environ()
- def _subst (match, local_vars=local_vars):
+
+ def _subst(match, local_vars=local_vars):
var_name = match.group(1)
if var_name in local_vars:
return str(local_vars[var_name])
@@ -170,6 +179,7 @@ def subst_vars(s, local_vars):
except KeyError, var:
raise ValueError("invalid variable '$%s'" % var)
+
def grok_environment_error(exc, prefix="error: "):
"""Generate a useful error message from an EnvironmentError.
@@ -196,12 +206,14 @@ def grok_environment_error(exc, prefix="error: "):
# Needed by 'split_quoted()'
_wordchars_re = _squote_re = _dquote_re = None
+
def _init_regex():
global _wordchars_re, _squote_re, _dquote_re
_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
+
def split_quoted(s):
"""Split a string up according to Unix shell-like rules for quotes and
backslashes.
@@ -217,7 +229,8 @@ def split_quoted(s):
# This is a nice algorithm for splitting up a single string, since it
# doesn't require character-by-character examination. It was a little
# bit of a brain-bender to get it working right, though...
- if _wordchars_re is None: _init_regex()
+ if _wordchars_re is None:
+ _init_regex()
s = s.strip()
words = []
@@ -237,8 +250,8 @@ def split_quoted(s):
elif s[end] == '\\': # preserve whatever is being escaped;
# will become part of the current word
- s = s[:end] + s[end+1:]
- pos = end+1
+ s = s[:end] + s[end + 1:]
+ pos = end + 1
else:
if s[end] == "'": # slurp singly-quoted string
@@ -253,7 +266,7 @@ def split_quoted(s):
raise ValueError("bad string (mismatched %s quotes?)" % s[end])
(beg, end) = m.span()
- s = s[:beg] + s[beg+1:end-1] + s[end:]
+ s = s[:beg] + s[beg + 1:end - 1] + s[end:]
pos = m.end() - 2
if pos >= len(s):
@@ -296,7 +309,7 @@ def strtobool(val):
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
return 0
else:
- raise ValueError, "invalid truth value %r" % (val,)
+ raise ValueError("invalid truth value %r" % (val,))
def byte_compile(py_files, optimize=0, force=0, prefix=None, base_dir=None,
@@ -350,12 +363,8 @@ def byte_compile(py_files, optimize=0, force=0, prefix=None, base_dir=None,
# "Indirect" byte-compilation: write a temporary script and then
# run it with the appropriate flags.
if not direct:
- try:
- from tempfile import mkstemp
- (script_fd, script_name) = mkstemp(".py")
- except ImportError:
- from tempfile import mktemp
- (script_fd, script_name) = None, mktemp(".py")
+ from tempfile import mkstemp
+ script_fd, script_name = mkstemp(".py")
log.info("writing byte-compilation script '%s'", script_name)
if not dry_run:
if script_fd is not None:
@@ -363,43 +372,50 @@ def byte_compile(py_files, optimize=0, force=0, prefix=None, base_dir=None,
else:
script = open(script_name, "w")
- script.write("""\
-from distutils.util import byte_compile
+ try:
+ script.write("""\
+from distutils2.util import byte_compile
files = [
""")
- # XXX would be nice to write absolute filenames, just for
- # safety's sake (script should be more robust in the face of
- # chdir'ing before running it). But this requires abspath'ing
- # 'prefix' as well, and that breaks the hack in build_lib's
- # 'byte_compile()' method that carefully tacks on a trailing
- # slash (os.sep really) to make sure the prefix here is "just
- # right". This whole prefix business is rather delicate -- the
- # problem is that it's really a directory, but I'm treating it
- # as a dumb string, so trailing slashes and so forth matter.
-
- #py_files = map(os.path.abspath, py_files)
- #if prefix:
- # prefix = os.path.abspath(prefix)
-
- script.write(",\n".join(map(repr, py_files)) + "]\n")
- script.write("""
+ # XXX would be nice to write absolute filenames, just for
+ # safety's sake (script should be more robust in the face of
+ # chdir'ing before running it). But this requires abspath'ing
+ # 'prefix' as well, and that breaks the hack in build_lib's
+ # 'byte_compile()' method that carefully tacks on a trailing
+ # slash (os.sep really) to make sure the prefix here is "just
+ # right". This whole prefix business is rather delicate -- the
+ # problem is that it's really a directory, but I'm treating it
+ # as a dumb string, so trailing slashes and so forth matter.
+
+ #py_files = map(os.path.abspath, py_files)
+ #if prefix:
+ # prefix = os.path.abspath(prefix)
+
+ script.write(",\n".join(map(repr, py_files)) + "]\n")
+ script.write("""
byte_compile(files, optimize=%r, force=%r,
prefix=%r, base_dir=%r,
verbose=%r, dry_run=0,
direct=1)
""" % (optimize, force, prefix, base_dir, verbose))
- script.close()
+ finally:
+ script.close()
cmd = [sys.executable, script_name]
if optimize == 1:
cmd.insert(1, "-O")
elif optimize == 2:
cmd.insert(1, "-OO")
- spawn(cmd, dry_run=dry_run)
- execute(os.remove, (script_name,), "removing %s" % script_name,
- dry_run=dry_run)
+
+ env = copy(os.environ)
+ env['PYTHONPATH'] = ':'.join(sys.path)
+ try:
+ spawn(cmd, dry_run=dry_run, env=env)
+ finally:
+ execute(os.remove, (script_name,), "removing %s" % script_name,
+ dry_run=dry_run)
# "Direct" byte-compilation: use the py_compile module to compile
# right here, right now. Note that the script generated in indirect
@@ -447,7 +463,9 @@ def rfc822_escape(header):
return sep.join(lines)
_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
-_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld PROJECT:ld64-((\d+)(\.\d+)*)')
+_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
+ 'PROJECT:ld64-((\d+)(\.\d+)*)')
+
def _find_ld_version():
"""Finds the ld version. The version scheme differs under Mac OSX."""
@@ -456,6 +474,7 @@ def _find_ld_version():
else:
return _find_exe_version('ld -v')
+
def _find_exe_version(cmd, pattern=_RE_VERSION):
"""Find the version of an executable by running `cmd` in the shell.
@@ -485,6 +504,7 @@ def _find_exe_version(cmd, pattern=_RE_VERSION):
return None
return result.group(1)
+
def get_compiler_versions():
"""Returns a tuple providing the versions of gcc, ld and dllwrap
@@ -496,6 +516,7 @@ def get_compiler_versions():
dllwrap = _find_exe_version('dllwrap --version')
return gcc, ld, dllwrap
+
def newer_group(sources, target, missing='error'):
"""Return true if 'target' is out-of-date with respect to any file
listed in 'sources'.
@@ -535,14 +556,18 @@ def newer_group(sources, target, missing='error'):
return False
+
def write_file(filename, contents):
"""Create a file with the specified name and write 'contents' (a
sequence of strings without line terminators) to it.
"""
- f = open(filename, "w")
- for line in contents:
- f.write(line + "\n")
- f.close()
+ try:
+ f = open(filename, "w")
+ for line in contents:
+ f.write(line + "\n")
+ finally:
+ f.close()
+
def _is_package(path):
"""Returns True if path is a package (a dir with an __init__ file."""
@@ -550,6 +575,7 @@ def _is_package(path):
return False
return os.path.isfile(os.path.join(path, '__init__.py'))
+
def _under(path, root):
path = path.split(os.sep)
root = root.split(os.sep)
@@ -560,12 +586,14 @@ def _under(path, root):
return False
return True
+
def _package_name(root_path, path):
"""Returns a dotted package name, given a subpath."""
if not _under(path, root_path):
raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
return path[len(root_path) + 1:].replace(os.sep, '.')
+
def find_packages(paths=('.',), exclude=()):
"""Return a list all Python packages found recursively within
directories 'paths'
@@ -580,6 +608,7 @@ def find_packages(paths=('.',), exclude=()):
"""
packages = []
discarded = []
+
def _discarded(path):
for discard in discarded:
if _under(path, discard):
@@ -611,3 +640,232 @@ def find_packages(paths=('.',), exclude=()):
packages.append(package_name)
return packages
+
+# utility functions for 2to3 support
+
+def run_2to3(files, doctests_only=False, fixer_names=None, options=None,
+ explicit=None):
+ """ Wrapper function around the refactor() class which
+ performs the conversions on a list of python files.
+ Invoke 2to3 on a list of Python files. The files should all come
+ from the build area, as the modification is done in-place."""
+
+ if not files:
+ return
+
+ # Make this class local, to delay import of 2to3
+ from lib2to3.refactor import get_fixers_from_package
+ from distutils2.converter.refactor import DistutilsRefactoringTool
+
+ if fixer_names is None:
+ fixer_names = get_fixers_from_package('lib2to3.fixes')
+
+ r = DistutilsRefactoringTool(fixer_names, options=options)
+ if doctests_only:
+ r.refactor(files, doctests_only=True, write=True)
+ else:
+ r.refactor(files, write=True)
+
+
+class Mixin2to3:
+ """ Wrapper class for commands that run 2to3.
+ To configure 2to3, setup scripts may either change
+ the class variables, or inherit from this class
+ to override how 2to3 is invoked.
+ """
+ # provide list of fixers to run.
+ # defaults to all from lib2to3.fixers
+ fixer_names = None
+
+ # options dictionary
+ options = None
+
+ # list of fixers to invoke even though they are marked as explicit
+ explicit = None
+
+ def run_2to3(self, files, doctests_only=False):
+ """ Issues a call to util.run_2to3. """
+ return run_2to3(files, doctests_only, self.fixer_names,
+ self.options, self.explicit)
+
+
+def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
+ """Run another program specified as a command list 'cmd' in a new process.
+
+ 'cmd' is just the argument list for the new process, ie.
+ cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
+ There is no way to run a program with a name different from that of its
+ executable.
+
+ If 'search_path' is true (the default), the system's executable
+ search path will be used to find the program; otherwise, cmd[0]
+ must be the exact path to the executable. If 'dry_run' is true,
+ the command will not actually be run.
+
+ If 'env' is given, it's a environment dictionary used for the execution
+ environment.
+
+ Raise DistutilsExecError if running the program fails in any way; just
+ return on success.
+ """
+ if os.name == 'posix':
+ _spawn_posix(cmd, search_path, dry_run=dry_run, env=env)
+ elif os.name == 'nt':
+ _spawn_nt(cmd, search_path, dry_run=dry_run, env=env)
+ elif os.name == 'os2':
+ _spawn_os2(cmd, search_path, dry_run=dry_run, env=env)
+ else:
+ raise DistutilsPlatformError(
+ "don't know how to spawn programs on platform '%s'" % os.name)
+
+
+def _nt_quote_args(args):
+ """Quote command-line arguments for DOS/Windows conventions.
+
+ Just wraps every argument which contains blanks in double quotes, and
+ returns a new argument list.
+ """
+ # XXX this doesn't seem very robust to me -- but if the Windows guys
+ # say it'll work, I guess I'll have to accept it. (What if an arg
+ # contains quotes? What other magic characters, other than spaces,
+ # have to be escaped? Is there an escaping mechanism other than
+ # quoting?)
+ for i, arg in enumerate(args):
+ if ' ' in arg:
+ args[i] = '"%s"' % arg
+ return args
+
+
+def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0, env=None):
+ executable = cmd[0]
+ cmd = _nt_quote_args(cmd)
+ if search_path:
+ # either we find one or it stays the same
+ executable = find_executable(executable) or executable
+ log.info(' '.join([executable] + cmd[1:]))
+ if not dry_run:
+ # spawn for NT requires a full path to the .exe
+ try:
+ if env is None:
+ rc = os.spawnv(os.P_WAIT, executable, cmd)
+ else:
+ rc = os.spawnve(os.P_WAIT, executable, cmd, env)
+
+ except OSError, exc:
+ # this seems to happen when the command isn't found
+ raise DistutilsExecError(
+ "command '%s' failed: %s" % (cmd[0], exc[-1]))
+ if rc != 0:
+ # and this reflects the command running but failing
+ raise DistutilsExecError(
+ "command '%s' failed with exit status %d" % (cmd[0], rc))
+
+
+def _spawn_os2(cmd, search_path=1, verbose=0, dry_run=0, env=None):
+ executable = cmd[0]
+ if search_path:
+ # either we find one or it stays the same
+ executable = find_executable(executable) or executable
+ log.info(' '.join([executable] + cmd[1:]))
+ if not dry_run:
+ # spawnv for OS/2 EMX requires a full path to the .exe
+ try:
+ if env is None:
+ rc = os.spawnv(os.P_WAIT, executable, cmd)
+ else:
+ rc = os.spawnve(os.P_WAIT, executable, cmd, env)
+
+ except OSError, exc:
+ # this seems to happen when the command isn't found
+ raise DistutilsExecError(
+ "command '%s' failed: %s" % (cmd[0], exc[-1]))
+ if rc != 0:
+ # and this reflects the command running but failing
+ log.debug("command '%s' failed with exit status %d" % (cmd[0], rc))
+ raise DistutilsExecError(
+ "command '%s' failed with exit status %d" % (cmd[0], rc))
+
+
+def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0, env=None):
+ log.info(' '.join(cmd))
+ if dry_run:
+ return
+
+ if env is None:
+ exec_fn = search_path and os.execvp or os.execv
+ else:
+ exec_fn = search_path and os.execvpe or os.execve
+
+ pid = os.fork()
+
+ if pid == 0: # in the child
+ try:
+ if env is None:
+ exec_fn(cmd[0], cmd)
+ else:
+ exec_fn(cmd[0], cmd, env)
+ except OSError, e:
+ sys.stderr.write("unable to execute %s: %s\n" %
+ (cmd[0], e.strerror))
+ os._exit(1)
+
+ sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0])
+ os._exit(1)
+ else: # in the parent
+ # Loop until the child either exits or is terminated by a signal
+ # (ie. keep waiting if it's merely stopped)
+ while 1:
+ try:
+ pid, status = os.waitpid(pid, 0)
+ except OSError, exc:
+ import errno
+ if exc.errno == errno.EINTR:
+ continue
+ raise DistutilsExecError(
+ "command '%s' failed: %s" % (cmd[0], exc[-1]))
+ if os.WIFSIGNALED(status):
+ raise DistutilsExecError(
+ "command '%s' terminated by signal %d" % \
+ (cmd[0], os.WTERMSIG(status)))
+
+ elif os.WIFEXITED(status):
+ exit_status = os.WEXITSTATUS(status)
+ if exit_status == 0:
+ return # hey, it succeeded!
+ else:
+ raise DistutilsExecError(
+ "command '%s' failed with exit status %d" % \
+ (cmd[0], exit_status))
+
+ elif os.WIFSTOPPED(status):
+ continue
+
+ else:
+ raise DistutilsExecError(
+ "unknown error executing '%s': termination status %d" % \
+ (cmd[0], status))
+
+
+def find_executable(executable, path=None):
+ """Tries to find 'executable' in the directories listed in 'path'.
+
+ A string listing directories separated by 'os.pathsep'; defaults to
+ os.environ['PATH']. Returns the complete filename or None if not found.
+ """
+ if path is None:
+ path = os.environ['PATH']
+ paths = path.split(os.pathsep)
+ base, ext = os.path.splitext(executable)
+
+ if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
+ executable = executable + '.exe'
+
+ if not os.path.isfile(executable):
+ for p in paths:
+ f = os.path.join(p, executable)
+ if os.path.isfile(f):
+ # the file exists, we have a shot at spawn working
+ return f
+ return None
+ else:
+ return executable
diff --git a/src/distutils2/version.py b/src/distutils2/version.py
index dcf568b..e61c60e 100644
--- a/src/distutils2/version.py
+++ b/src/distutils2/version.py
@@ -1,8 +1,11 @@
-import sys
import re
from distutils2.errors import IrrationalVersionError, HugeMajorVersionNumError
+__all__ = ['NormalizedVersion', 'suggest_normalized_version',
+ 'VersionPredicate', 'is_valid_version', 'is_valid_versions',
+ 'is_valid_predicate']
+
# A marker used in the second and third parts of the `parts` tuple, for
# versions that don't have those segments, to sort properly. An example
# of versions in sort order ('highest' last):
@@ -18,9 +21,9 @@ from distutils2.errors import IrrationalVersionError, HugeMajorVersionNumError
# |
# 'dev' < 'f' ----------------------------------------------/
# Other letters would do, but 'f' for 'final' is kind of nice.
-FINAL_MARKER = ('f',)
+_FINAL_MARKER = ('f',)
-VERSION_RE = re.compile(r'''
+_VERSION_RE = re.compile(r'''
^
(?P<version>\d+\.\d+) # minimum 'N.N'
(?P<extraversion>(?:\.\d+)*) # any number of extra '.N' segments
@@ -32,6 +35,7 @@ VERSION_RE = re.compile(r'''
(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
$''', re.VERBOSE)
+
class NormalizedVersion(object):
"""A rational version.
@@ -57,7 +61,8 @@ class NormalizedVersion(object):
@param error_on_huge_major_num {bool} Whether to consider an
apparent use of a year or full date as the major version number
an error. Default True. One of the observed patterns on PyPI before
- the introduction of `NormalizedVersion` was version numbers like this:
+ the introduction of `NormalizedVersion` was version numbers like
+ this:
2009.01.03
20040603
2005.01
@@ -67,16 +72,17 @@ class NormalizedVersion(object):
the possibility of using a version number like "1.0" (i.e.
where the major number is less than that huge major number).
"""
+ self.is_final = True # by default, consider a version as final.
self._parse(s, error_on_huge_major_num)
@classmethod
- def from_parts(cls, version, prerelease=FINAL_MARKER,
- devpost=FINAL_MARKER):
+ def from_parts(cls, version, prerelease=_FINAL_MARKER,
+ devpost=_FINAL_MARKER):
return cls(cls.parts_to_str((version, prerelease, devpost)))
def _parse(self, s, error_on_huge_major_num=True):
"""Parses a string version into parts."""
- match = VERSION_RE.search(s)
+ match = _VERSION_RE.search(s)
if not match:
raise IrrationalVersionError(s)
@@ -97,8 +103,9 @@ class NormalizedVersion(object):
block += self._parse_numdots(groups.get('prerelversion'), s,
pad_zeros_length=1)
parts.append(tuple(block))
+ self.is_final = False
else:
- parts.append(FINAL_MARKER)
+ parts.append(_FINAL_MARKER)
# postdev
if groups.get('postdev'):
@@ -106,14 +113,15 @@ class NormalizedVersion(object):
dev = groups.get('dev')
postdev = []
if post is not None:
- postdev.extend([FINAL_MARKER[0], 'post', int(post)])
+ postdev.extend([_FINAL_MARKER[0], 'post', int(post)])
if dev is None:
- postdev.append(FINAL_MARKER[0])
+ postdev.append(_FINAL_MARKER[0])
if dev is not None:
postdev.extend(['dev', int(dev)])
+ self.is_final = False
parts.append(tuple(postdev))
else:
- parts.append(FINAL_MARKER)
+ parts.append(_FINAL_MARKER)
self.parts = tuple(parts)
if error_on_huge_major_num and self.parts[0][0] > 1980:
raise HugeMajorVersionNumError("huge major version number, %r, "
@@ -154,10 +162,10 @@ class NormalizedVersion(object):
# XXX This doesn't check for invalid tuples
main, prerel, postdev = parts
s = '.'.join(str(v) for v in main)
- if prerel is not FINAL_MARKER:
+ if prerel is not _FINAL_MARKER:
s += prerel[0]
s += '.'.join(str(v) for v in prerel[1:])
- if postdev and postdev is not FINAL_MARKER:
+ if postdev and postdev is not _FINAL_MARKER:
if postdev[0] == 'f':
postdev = postdev[1:]
i = 0
@@ -200,6 +208,7 @@ class NormalizedVersion(object):
# See http://docs.python.org/reference/datamodel#object.__hash__
__hash__ = object.__hash__
+
def suggest_normalized_version(s):
"""Suggest a normalized version close to the given version string.
@@ -211,7 +220,7 @@ def suggest_normalized_version(s):
on observation of versions currently in use on PyPI. Given a dump of
those version during PyCon 2009, 4287 of them:
- 2312 (53.93%) match NormalizedVersion without change
- - with the automatic suggestion
+ with the automatic suggestion
- 3474 (81.04%) match when using this suggestion method
@param s {str} An irrational version string.
@@ -301,7 +310,6 @@ def suggest_normalized_version(s):
# PyPI stats: ~21 (0.62%) better
rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
-
# Tcl/Tk uses "px" for their post release markers
rs = re.sub(r"p(\d+)$", r".post\1", rs)
@@ -318,6 +326,7 @@ _VERSIONS = re.compile(r"^\s*\((.*)\)\s*$")
_PLAIN_VERSIONS = re.compile(r"^\s*(.*)\s*$")
_SPLIT_CMP = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$")
+
def _split_predicate(predicate):
match = _SPLIT_CMP.match(predicate)
if match is None:
@@ -364,26 +373,25 @@ class VersionPredicate(object):
return False
return True
-class Versions(VersionPredicate):
+
+class _Versions(VersionPredicate):
def __init__(self, predicate):
predicate = predicate.strip()
match = _PLAIN_VERSIONS.match(predicate)
- if match is None:
- raise ValueError('Bad predicate "%s"' % predicate)
self.name = None
predicates = match.groups()[0]
self.predicates = [_split_predicate(pred.strip())
for pred in predicates.split(',')]
-class Version(VersionPredicate):
+
+class _Version(VersionPredicate):
def __init__(self, predicate):
predicate = predicate.strip()
match = _PLAIN_VERSIONS.match(predicate)
- if match is None:
- raise ValueError('Bad predicate "%s"' % predicate)
self.name = None
self.predicates = _split_predicate(match.groups()[0])
+
def is_valid_predicate(predicate):
try:
VersionPredicate(predicate)
@@ -392,19 +400,20 @@ def is_valid_predicate(predicate):
else:
return True
+
def is_valid_versions(predicate):
try:
- Versions(predicate)
+ _Versions(predicate)
except (ValueError, IrrationalVersionError):
return False
else:
return True
+
def is_valid_version(predicate):
try:
- Version(predicate)
+ _Version(predicate)
except (ValueError, IrrationalVersionError):
return False
else:
return True
-