diff options
author | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2013-07-26 10:28:07 +0200 |
---|---|---|
committer | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2013-07-26 10:28:07 +0200 |
commit | cfe1107d4f5d8d44e2cf4f9733be145254e6c8e8 (patch) | |
tree | aa80cc1624345afa637caa9d72d306e9e160c20e | |
parent | 8c9024ac95f410199372806816437488ff3f9194 (diff) | |
parent | 07f9b4ac057f646c5232781d01fc34765c1c04c2 (diff) | |
download | logilab-common-cfe1107d4f5d8d44e2cf4f9733be145254e6c8e8.tar.gz |
backport stable branch
-rw-r--r-- | ChangeLog | 15 | ||||
-rw-r--r-- | graph.py | 6 | ||||
-rw-r--r-- | modutils.py | 40 | ||||
-rw-r--r-- | python-logilab-common.spec | 2 | ||||
-rw-r--r-- | registry.py | 4 | ||||
-rw-r--r-- | setup.py | 6 | ||||
-rw-r--r-- | test/unittest_cache.py | 12 | ||||
-rw-r--r-- | test/unittest_modutils.py | 14 | ||||
-rw-r--r-- | testlib.py | 13 |
9 files changed, 82 insertions, 30 deletions
@@ -3,8 +3,23 @@ ChangeLog for logilab.common -- * configuration: rename option_name method into option_attrname (#140667) + * deprecation: new DeprecationManager class (closes #108205) + * modutils: + + * fix typo causing name error in python3 / bad message in python2 + (#136037) + + * fix python3.3 crash in file_from_modpath due to implementation + change of imp.find_module wrt builtin modules (#137244) + + * testlib: use assertCountEqual instead of assertSameElements/assertItemsEqual + (deprecated), fixing crash with python 3.3 (#144526) + + * graph: use codecs.open avoid crash when writing utf-8 data under python3 + (#155138) + 2013-04-16 -- 0.59.1 * graph: added pruning of the recursive search tree for detecting cycles in @@ -28,7 +28,7 @@ import os.path as osp import os import sys import tempfile -from logilab.common.compat import str_encode +import codecs def escape(value): """Make <value> usable in a dot file.""" @@ -106,8 +106,8 @@ class DotBackend: ppng, outputfile = tempfile.mkstemp(".png", name) os.close(pdot) os.close(ppng) - pdot = open(dot_sourcepath, 'w') - pdot.write(str_encode(self.source, 'utf8')) + pdot = codecs.open(dot_sourcepath, 'w', encoding='utf8') + pdot.write(self.source) pdot.close() if target != 'dot': if sys.platform == 'win32': diff --git a/modutils.py b/modutils.py index 2cae005..9d0bb49 100644 --- a/modutils.py +++ b/modutils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. @@ -570,10 +570,15 @@ def _search_zip(modpath, pic): if importer.find_module(modpath[0]): if not importer.find_module('/'.join(modpath)): raise ImportError('No module named %s in %s/%s' % ( - '.'.join(modpath[1:]), file, modpath)) + '.'.join(modpath[1:]), filepath, modpath)) return ZIPFILE, abspath(filepath) + '/' + '/'.join(modpath), filepath raise ImportError('No module named %s' % '.'.join(modpath)) +try: + import pkg_resources +except ImportError: + pkg_resources = None + def _module_file(modpath, path=None): """get a module type / file path @@ -604,16 +609,32 @@ def _module_file(modpath, path=None): checkeggs = True except AttributeError: checkeggs = False + # pkg_resources support (aka setuptools namespace packages) + if pkg_resources is not None and modpath[0] in pkg_resources._namespace_packages and len(modpath) > 1: + # setuptools has added into sys.modules a module object with proper + # __path__, get back information from there + module = sys.modules[modpath.pop(0)] + path = module.__path__ imported = [] while modpath: + modname = modpath[0] + # take care to changes in find_module implementation wrt builtin modules + # + # Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23) + # >>> imp.find_module('posix') + # (None, 'posix', ('', '', 6)) + # + # Python 3.3.1 (default, Apr 26 2013, 12:08:46) + # >>> imp.find_module('posix') + # (None, None, ('', '', 6)) try: - _, mp_filename, mp_desc = find_module(modpath[0], path) + _, mp_filename, mp_desc = find_module(modname, path) except ImportError: if checkeggs: return _search_zip(modpath, pic)[:2] raise else: - if checkeggs: + if checkeggs and mp_filename: fullabspath = [abspath(x) for x in _path] try: pathindex = fullabspath.index(dirname(abspath(mp_filename))) @@ -633,7 +654,16 @@ def _module_file(modpath, path=None): if mtype != PKG_DIRECTORY: raise ImportError('No module %s in %s' % ('.'.join(modpath), '.'.join(imported))) - path = [mp_filename] + # XXX guess if package is using pkgutil.extend_path by looking for + # those keywords in the first four Kbytes + data = open(join(mp_filename, '__init__.py')).read(4096) + if 'pkgutil' in data and 'extend_path' in data: + # extend_path is called, search sys.path for module/packages of this name + # see pkgutil.extend_path documentation + path = [join(p, modname) for p in sys.path + if isdir(join(p, modname))] + else: + path = [mp_filename] return mtype, mp_filename def _is_python_file(filename): diff --git a/python-logilab-common.spec b/python-logilab-common.spec index d960177..0544013 100644 --- a/python-logilab-common.spec +++ b/python-logilab-common.spec @@ -10,7 +10,7 @@ %{!?_python_sitelib: %define _python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: %{python}-logilab-common -Version: 0.59.0 +Version: 0.59.1 Release: logilab.1%{?dist} Summary: Common libraries for Logilab projects diff --git a/registry.py b/registry.py index 0dc7a21..7721d76 100644 --- a/registry.py +++ b/registry.py @@ -933,9 +933,9 @@ def wrap_predicates(decorator): predicate.__call__ = decorator(predicate.__call__) class PredicateMetaClass(type): - def __new__(cls, *args, **kwargs): + def __new__(mcs, *args, **kwargs): # use __new__ so subclasses doesn't have to call Predicate.__init__ - inst = type.__new__(cls, *args, **kwargs) + inst = type.__new__(mcs, *args, **kwargs) proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p))) _PREDICATES[id(proxy)] = proxy return inst @@ -140,8 +140,10 @@ class MyBuildPy(build_py): if sys.version_info >= (3, 0): # process manually python file in include_dirs (test data) from subprocess import check_call - print('running 2to3 on', dest) # brackets are NOT optional here for py3k compat - check_call(['2to3', '-wn', dest]) + # brackets are NOT optional here for py3k compat + print('running 2to3 on', dest) + # Needs `shell=True` to run on Windows. + check_call(['2to3', '-wn', dest], shell=True) def install(**kwargs): diff --git a/test/unittest_cache.py b/test/unittest_cache.py index 9b02b39..459f172 100644 --- a/test/unittest_cache.py +++ b/test/unittest_cache.py @@ -33,7 +33,7 @@ class CacheTestCase(TestCase): self.assertEqual(len(self.cache._usage), 1) self.assertEqual(self.cache._usage[-1], 1, '1 is not the most recently used key') - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys(), "usage list and data keys are different") @@ -47,7 +47,7 @@ class CacheTestCase(TestCase): "lenght of usage list is not 2") self.assertEqual(self.cache._usage[-1], 2, '1 is not the most recently used key') - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys())# usage list and data keys are different def test_setitem3(self): @@ -57,7 +57,7 @@ class CacheTestCase(TestCase): self.assertEqual(self.cache[1], 'bar', "1 : 'bar' is not in cache.data") self.assertEqual(len(self.cache._usage), 1, "lenght of usage list is not 1") self.assertEqual(self.cache._usage[-1], 1, '1 is not the most recently used key') - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys())# usage list and data keys are different def test_recycling1(self): @@ -74,7 +74,7 @@ class CacheTestCase(TestCase): 'key 1 has not been suppressed from the cache LRU list') self.assertEqual(len(self.cache._usage), 5, "lenght of usage list is not 5") self.assertEqual(self.cache._usage[-1], 6, '6 is not the most recently used key') - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys())# usage list and data keys are different def test_recycling2(self): @@ -86,7 +86,7 @@ class CacheTestCase(TestCase): a = self.cache[1] self.assertEqual(a, 'foo') self.assertEqual(self.cache._usage[-1], 1, '1 is not the most recently used key') - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys())# usage list and data keys are different def test_delitem(self): @@ -97,7 +97,7 @@ class CacheTestCase(TestCase): del self.cache['foo'] self.assertTrue('foo' not in self.cache.keys(), "Element 'foo' was not removed cache dictionnary") self.assertTrue('foo' not in self.cache._usage, "Element 'foo' was not removed usage list") - self.assertItemsEqual(self.cache._usage, + self.assertCountEqual(self.cache._usage, self.cache.keys())# usage list and data keys are different diff --git a/test/unittest_modutils.py b/test/unittest_modutils.py index 3e9a74a..dfbcf14 100644 --- a/test/unittest_modutils.py +++ b/test/unittest_modutils.py @@ -1,4 +1,4 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. @@ -143,16 +143,16 @@ class file_from_modpath_tc(ModutilsTestCase): corresponding file, giving priority to source file over precompiled file if it exists""" - def test_knownValues_file_from_modpath_1(self): + def test_site_packages(self): self.assertEqual(path.realpath(modutils.file_from_modpath(['logilab', 'common', 'modutils'])), path.realpath(modutils.__file__.replace('.pyc', '.py'))) - def test_knownValues_file_from_modpath_2(self): + def test_std_lib(self): from os import path self.assertEqual(path.realpath(modutils.file_from_modpath(['os', 'path']).replace('.pyc', '.py')), path.realpath(path.__file__.replace('.pyc', '.py'))) - def test_knownValues_file_from_modpath_3(self): + def test_xmlplus(self): try: # don't fail if pyxml isn't installed from xml.dom import ext @@ -162,13 +162,15 @@ class file_from_modpath_tc(ModutilsTestCase): self.assertEqual(path.realpath(modutils.file_from_modpath(['xml', 'dom', 'ext']).replace('.pyc', '.py')), path.realpath(ext.__file__.replace('.pyc', '.py'))) - def test_knownValues_file_from_modpath_4(self): + def test_builtin(self): self.assertEqual(modutils.file_from_modpath(['sys']), None) - def test_raise_file_from_modpath_Exception(self): + + def test_unexisting(self): self.assertRaises(ImportError, modutils.file_from_modpath, ['turlututu']) + class get_source_file_tc(ModutilsTestCase): def test(self): @@ -721,7 +721,7 @@ succeeded test into", osp.join(os.getcwd(), FILE_RESTART) base = '' self.fail(base + '\n'.join(msgs)) - @deprecated('Please use assertItemsEqual instead.') + @deprecated('Please use assertCountEqual instead.') def assertUnorderedIterableEquals(self, got, expected, msg=None): """compares two iterable and shows difference between both @@ -1183,10 +1183,13 @@ succeeded test into", osp.join(os.getcwd(), FILE_RESTART) assertRaises = failUnlessRaises - if not hasattr(unittest.TestCase, 'assertItemsEqual'): - # python 3.2 has deprecated assertSameElements and is missing - # assertItemsEqual - assertItemsEqual = unittest.TestCase.assertSameElements + if sys.version_info >= (3,2): + assertItemsEqual = unittest.TestCase.assertCountEqual + else: + assertCountEqual = unittest.TestCase.assertItemsEqual + +TestCase.assertItemsEqual = deprecated('assertItemsEqual is deprecated, use assertCountEqual')( + TestCase.assertItemsEqual) import doctest |