summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <sylvain.thenault@logilab.fr>2013-07-26 10:28:07 +0200
committerSylvain Th?nault <sylvain.thenault@logilab.fr>2013-07-26 10:28:07 +0200
commitcfe1107d4f5d8d44e2cf4f9733be145254e6c8e8 (patch)
treeaa80cc1624345afa637caa9d72d306e9e160c20e
parent8c9024ac95f410199372806816437488ff3f9194 (diff)
parent07f9b4ac057f646c5232781d01fc34765c1c04c2 (diff)
downloadlogilab-common-cfe1107d4f5d8d44e2cf4f9733be145254e6c8e8.tar.gz
backport stable branch
-rw-r--r--ChangeLog15
-rw-r--r--graph.py6
-rw-r--r--modutils.py40
-rw-r--r--python-logilab-common.spec2
-rw-r--r--registry.py4
-rw-r--r--setup.py6
-rw-r--r--test/unittest_cache.py12
-rw-r--r--test/unittest_modutils.py14
-rw-r--r--testlib.py13
9 files changed, 82 insertions, 30 deletions
diff --git a/ChangeLog b/ChangeLog
index 4b0d1aa..888cc7e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/graph.py b/graph.py
index 5cae530..94a71b6 100644
--- a/graph.py
+++ b/graph.py
@@ -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
diff --git a/setup.py b/setup.py
index cc54251..8f27b3b 100644
--- a/setup.py
+++ b/setup.py
@@ -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):
diff --git a/testlib.py b/testlib.py
index 0517032..27326b8 100644
--- a/testlib.py
+++ b/testlib.py
@@ -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