backport stable branch

authorSylvain Thénault <sylvain.thenault@logilab.fr>
changeset9f16b91a2776
branchdefault
phasepublic
hiddenno
parent revision#e29e63763456 remove obsolete lenny packaging, #03abf06e88d7 update Changelog
child revision#85c903003289 0.60, #f8a1cc4ceb74 [deprecation] stacklevel tweaks
files modified by this revision
ChangeLog
graph.py
modutils.py
python-logilab-common.spec
registry.py
setup.py
test/unittest_cache.py
test/unittest_modutils.py
testlib.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1374827287 -7200
# Fri Jul 26 10:28:07 2013 +0200
# Node ID 9f16b91a2776fcda13943019b885ccad5d51bd36
# Parent e29e6376345623b29042cdfbe23373b7684bed5c
# Parent 03abf06e88d7a880a65653b53a0e7b704e42976e
backport stable branch

diff --git a/ChangeLog b/ChangeLog
@@ -1,12 +1,27 @@
1  ChangeLog for logilab.common
2  ============================
3 
4  --
5      * configuration: rename option_name method into option_attrname (#140667)
6 +
7      * deprecation: new DeprecationManager class (closes #108205)
8 
9 +    * modutils:
10 +
11 +      * fix typo causing name error in python3 / bad message in python2
12 +        (#136037)
13 +
14 +      * fix python3.3 crash in file_from_modpath due to implementation
15 +        change of imp.find_module wrt builtin modules (#137244)
16 +
17 +    * testlib: use assertCountEqual instead of assertSameElements/assertItemsEqual 
18 +      (deprecated), fixing crash with python 3.3 (#144526)
19 +
20 +    * graph: use codecs.open avoid crash when writing utf-8 data under python3
21 +      (#155138)
22 +
23 
24  2013-04-16  --  0.59.1
25      * graph: added pruning of the recursive search tree for detecting cycles in
26        graphs (closes #2469)
27 
diff --git a/graph.py b/graph.py
@@ -26,11 +26,11 @@
28 
29  import os.path as osp
30  import os
31  import sys
32  import tempfile
33 -from logilab.common.compat import str_encode
34 +import codecs
35 
36  def escape(value):
37      """Make <value> usable in a dot file."""
38      lines = [line.replace('"', '\\"') for line in value.split('\n')]
39      data = '\\l'.join(lines)
@@ -104,12 +104,12 @@
40              target = 'png'
41              pdot, dot_sourcepath = tempfile.mkstemp(".dot", name)
42              ppng, outputfile = tempfile.mkstemp(".png", name)
43              os.close(pdot)
44              os.close(ppng)
45 -        pdot = open(dot_sourcepath, 'w')
46 -        pdot.write(str_encode(self.source, 'utf8'))
47 +        pdot = codecs.open(dot_sourcepath, 'w', encoding='utf8')
48 +        pdot.write(self.source)
49          pdot.close()
50          if target != 'dot':
51              if sys.platform == 'win32':
52                  use_shell = True
53              else:
diff --git a/modutils.py b/modutils.py
@@ -1,7 +1,7 @@
54  # -*- coding: utf-8 -*-
55 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
56 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
57  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
58  #
59  # This file is part of logilab-common.
60  #
61  # logilab-common is free software: you can redistribute it and/or modify it under
@@ -568,14 +568,19 @@
62      for filepath, importer in pic.items():
63          if importer is not None:
64              if importer.find_module(modpath[0]):
65                  if not importer.find_module('/'.join(modpath)):
66                      raise ImportError('No module named %s in %s/%s' % (
67 -                        '.'.join(modpath[1:]), file, modpath))
68 +                        '.'.join(modpath[1:]), filepath, modpath))
69                  return ZIPFILE, abspath(filepath) + '/' + '/'.join(modpath), filepath
70      raise ImportError('No module named %s' % '.'.join(modpath))
71 
72 +try:
73 +    import pkg_resources
74 +except ImportError:
75 +    pkg_resources = None
76 +
77  def _module_file(modpath, path=None):
78      """get a module type / file path
79 
80      :type modpath: list or tuple
81      :param modpath:
@@ -602,20 +607,36 @@
82                  except zipimport.ZipImportError:
83                      pic[__path] = None
84          checkeggs = True
85      except AttributeError:
86          checkeggs = False
87 +    # pkg_resources support (aka setuptools namespace packages)
88 +    if pkg_resources is not None and modpath[0] in pkg_resources._namespace_packages and len(modpath) > 1:
89 +        # setuptools has added into sys.modules a module object with proper
90 +        # __path__, get back information from there
91 +        module = sys.modules[modpath.pop(0)]
92 +        path = module.__path__
93      imported = []
94      while modpath:
95 +        modname = modpath[0]
96 +        # take care to changes in find_module implementation wrt builtin modules
97 +        #
98 +        # Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23)
99 +        # >>> imp.find_module('posix')
100 +        # (None, 'posix', ('', '', 6))
101 +        #
102 +        # Python 3.3.1 (default, Apr 26 2013, 12:08:46)
103 +        # >>> imp.find_module('posix')
104 +        # (None, None, ('', '', 6))
105          try:
106 -            _, mp_filename, mp_desc = find_module(modpath[0], path)
107 +            _, mp_filename, mp_desc = find_module(modname, path)
108          except ImportError:
109              if checkeggs:
110                  return _search_zip(modpath, pic)[:2]
111              raise
112          else:
113 -            if checkeggs:
114 +            if checkeggs and mp_filename:
115                  fullabspath = [abspath(x) for x in _path]
116                  try:
117                      pathindex = fullabspath.index(dirname(abspath(mp_filename)))
118                      emtype, emp_filename, zippath = _search_zip(modpath, pic)
119                      if pathindex > _path.index(zippath):
@@ -631,11 +652,20 @@
120          mtype = mp_desc[2]
121          if modpath:
122              if mtype != PKG_DIRECTORY:
123                  raise ImportError('No module %s in %s' % ('.'.join(modpath),
124                                                            '.'.join(imported)))
125 -            path = [mp_filename]
126 +            # XXX guess if package is using pkgutil.extend_path by looking for
127 +            # those keywords in the first four Kbytes
128 +            data = open(join(mp_filename, '__init__.py')).read(4096)
129 +            if 'pkgutil' in data and 'extend_path' in data:
130 +                # extend_path is called, search sys.path for module/packages of this name
131 +                # see pkgutil.extend_path documentation
132 +                path = [join(p, modname) for p in sys.path
133 +                        if isdir(join(p, modname))]
134 +            else:
135 +                path = [mp_filename]
136      return mtype, mp_filename
137 
138  def _is_python_file(filename):
139      """return true if the given filename should be considered as a python file
140 
diff --git a/python-logilab-common.spec b/python-logilab-common.spec
@@ -8,11 +8,11 @@
141  %define __python /usr/bin/python
142  %endif
143  %{!?_python_sitelib: %define _python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
144 
145  Name:           %{python}-logilab-common
146 -Version:        0.59.0
147 +Version:        0.59.1
148  Release:        logilab.1%{?dist}
149  Summary:        Common libraries for Logilab projects
150 
151  Group:          Development/Libraries
152  License:        LGPLv2.1+
diff --git a/registry.py b/registry.py
@@ -931,13 +931,13 @@
153              continue
154          predicate._decorators.add(decorator)
155          predicate.__call__ = decorator(predicate.__call__)
156 
157  class PredicateMetaClass(type):
158 -    def __new__(cls, *args, **kwargs):
159 +    def __new__(mcs, *args, **kwargs):
160          # use __new__ so subclasses doesn't have to call Predicate.__init__
161 -        inst = type.__new__(cls, *args, **kwargs)
162 +        inst = type.__new__(mcs, *args, **kwargs)
163          proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p)))
164          _PREDICATES[id(proxy)] = proxy
165          return inst
166 
167  class Predicate(object):
diff --git a/setup.py b/setup.py
@@ -138,12 +138,14 @@
168                  shutil.rmtree(dest, ignore_errors=True)
169                  shutil.copytree(directory, dest)
170                  if sys.version_info >= (3, 0):
171                      # process manually python file in include_dirs (test data)
172                      from subprocess import check_call
173 -                    print('running 2to3 on', dest) # brackets are NOT optional here for py3k compat
174 -                    check_call(['2to3', '-wn', dest])
175 +                    # brackets are NOT optional here for py3k compat
176 +                    print('running 2to3 on', dest)
177 +                    # Needs `shell=True` to run on Windows.
178 +                    check_call(['2to3', '-wn', dest], shell=True)
179 
180 
181  def install(**kwargs):
182      """setup entry point"""
183      if USE_SETUPTOOLS:
diff --git a/test/unittest_cache.py b/test/unittest_cache.py
@@ -31,11 +31,11 @@
184          self.cache[1] = 'foo'
185          self.assertEqual(self.cache[1], 'foo', "1:foo is not in cache")
186          self.assertEqual(len(self.cache._usage), 1)
187          self.assertEqual(self.cache._usage[-1], 1,
188                           '1 is not the most recently used key')
189 -        self.assertItemsEqual(self.cache._usage,
190 +        self.assertCountEqual(self.cache._usage,
191                                self.cache.keys(),
192                                "usage list and data keys are different")
193 
194      def test_setitem2(self):
195          """Checks that the setitem method works for multiple items"""
@@ -45,21 +45,21 @@
196                           "2 : 'bar' is not in cache.data")
197          self.assertEqual(len(self.cache._usage), 2,
198                           "lenght of usage list is not 2")
199          self.assertEqual(self.cache._usage[-1], 2,
200                       '1 is not the most recently used key')
201 -        self.assertItemsEqual(self.cache._usage,
202 +        self.assertCountEqual(self.cache._usage,
203                                self.cache.keys())# usage list and data keys are different
204 
205      def test_setitem3(self):
206          """Checks that the setitem method works when replacing an element in the cache"""
207          self.cache[1] = 'foo'
208          self.cache[1] = 'bar'
209          self.assertEqual(self.cache[1], 'bar', "1 : 'bar' is not in cache.data")
210          self.assertEqual(len(self.cache._usage), 1, "lenght of usage list is not 1")
211          self.assertEqual(self.cache._usage[-1], 1, '1 is not the most recently used key')
212 -        self.assertItemsEqual(self.cache._usage,
213 +        self.assertCountEqual(self.cache._usage,
214                                self.cache.keys())# usage list and data keys are different
215 
216      def test_recycling1(self):
217          """Checks the removal of old elements"""
218          self.cache[1] = 'foo'
@@ -72,11 +72,11 @@
219                       'key 1 has not been suppressed from the cache dictionnary')
220          self.assertTrue(1 not in self.cache._usage,
221                       'key 1 has not been suppressed from the cache LRU list')
222          self.assertEqual(len(self.cache._usage), 5, "lenght of usage list is not 5")
223          self.assertEqual(self.cache._usage[-1], 6, '6 is not the most recently used key')
224 -        self.assertItemsEqual(self.cache._usage,
225 +        self.assertCountEqual(self.cache._usage,
226                                self.cache.keys())# usage list and data keys are different
227 
228      def test_recycling2(self):
229          """Checks that accessed elements get in the front of the list"""
230          self.cache[1] = 'foo'
@@ -84,22 +84,22 @@
231          self.cache[3] = 'baz'
232          self.cache[4] = 'foz'
233          a = self.cache[1]
234          self.assertEqual(a, 'foo')
235          self.assertEqual(self.cache._usage[-1], 1, '1 is not the most recently used key')
236 -        self.assertItemsEqual(self.cache._usage,
237 +        self.assertCountEqual(self.cache._usage,
238                                self.cache.keys())# usage list and data keys are different
239 
240      def test_delitem(self):
241          """Checks that elements are removed from both element dict and element
242          list.
243          """
244          self.cache['foo'] = 'bar'
245          del self.cache['foo']
246          self.assertTrue('foo' not in self.cache.keys(), "Element 'foo' was not removed cache dictionnary")
247          self.assertTrue('foo' not in self.cache._usage, "Element 'foo' was not removed usage list")
248 -        self.assertItemsEqual(self.cache._usage,
249 +        self.assertCountEqual(self.cache._usage,
250                                self.cache.keys())# usage list and data keys are different
251 
252 
253      def test_nullsize(self):
254          """Checks that a 'NULL' size cache doesn't store anything
diff --git a/test/unittest_modutils.py b/test/unittest_modutils.py
@@ -1,6 +1,6 @@
255 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
256 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
257  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
258  #
259  # This file is part of logilab-common.
260  #
261  # logilab-common is free software: you can redistribute it and/or modify it under
@@ -141,36 +141,38 @@
262  class file_from_modpath_tc(ModutilsTestCase):
263      """given a mod path (i.e. splited module / package name), return the
264      corresponding file, giving priority to source file over precompiled file
265      if it exists"""
266 
267 -    def test_knownValues_file_from_modpath_1(self):
268 +    def test_site_packages(self):
269          self.assertEqual(path.realpath(modutils.file_from_modpath(['logilab', 'common', 'modutils'])),
270                           path.realpath(modutils.__file__.replace('.pyc', '.py')))
271 
272 -    def test_knownValues_file_from_modpath_2(self):
273 +    def test_std_lib(self):
274          from os import path
275          self.assertEqual(path.realpath(modutils.file_from_modpath(['os', 'path']).replace('.pyc', '.py')),
276                           path.realpath(path.__file__.replace('.pyc', '.py')))
277 
278 -    def test_knownValues_file_from_modpath_3(self):
279 +    def test_xmlplus(self):
280          try:
281              # don't fail if pyxml isn't installed
282              from xml.dom import ext
283          except ImportError:
284              pass
285          else:
286              self.assertEqual(path.realpath(modutils.file_from_modpath(['xml', 'dom', 'ext']).replace('.pyc', '.py')),
287                               path.realpath(ext.__file__.replace('.pyc', '.py')))
288 
289 -    def test_knownValues_file_from_modpath_4(self):
290 +    def test_builtin(self):
291          self.assertEqual(modutils.file_from_modpath(['sys']),
292                           None)
293 
294 -    def test_raise_file_from_modpath_Exception(self):
295 +
296 +    def test_unexisting(self):
297          self.assertRaises(ImportError, modutils.file_from_modpath, ['turlututu'])
298 
299 +
300  class get_source_file_tc(ModutilsTestCase):
301 
302      def test(self):
303          from os import path
304          self.assertEqual(modutils.get_source_file(path.__file__),
diff --git a/testlib.py b/testlib.py
@@ -719,11 +719,11 @@
305                  base = '%s\n' % context
306              else:
307                  base = ''
308              self.fail(base + '\n'.join(msgs))
309 
310 -    @deprecated('Please use assertItemsEqual instead.')
311 +    @deprecated('Please use assertCountEqual instead.')
312      def assertUnorderedIterableEquals(self, got, expected, msg=None):
313          """compares two iterable and shows difference between both
314 
315          :param got: the unordered Iterable that we found
316          :param expected: the expected unordered Iterable
@@ -1181,14 +1181,17 @@
317                  excName = str(excClass)
318              raise self.failureException("%s not raised" % excName)
319 
320      assertRaises = failUnlessRaises
321 
322 -    if not hasattr(unittest.TestCase, 'assertItemsEqual'):
323 -        # python 3.2 has deprecated assertSameElements and is missing
324 -        # assertItemsEqual
325 -        assertItemsEqual = unittest.TestCase.assertSameElements
326 +    if sys.version_info >= (3,2):
327 +        assertItemsEqual = unittest.TestCase.assertCountEqual
328 +    else:
329 +        assertCountEqual = unittest.TestCase.assertItemsEqual
330 +
331 +TestCase.assertItemsEqual = deprecated('assertItemsEqual is deprecated, use assertCountEqual')(
332 +    TestCase.assertItemsEqual)
333 
334  import doctest
335 
336  class SkippedSuite(unittest.TestSuite):
337      def test(self):