diff options
author | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2013-04-11 11:04:54 +0200 |
---|---|---|
committer | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2013-04-11 11:04:54 +0200 |
commit | 201edf589f93be9ed66916874b3e704a127f2007 (patch) | |
tree | edc58cd4d36b4ebb950e9b94f66938d7ad53e86f | |
parent | 20180f3add21b4dc93d728754a1a491793837709 (diff) | |
parent | ee3a7c45f3bf5e515f355f5d90a4c53753a6cf1f (diff) | |
download | logilab-common-201edf589f93be9ed66916874b3e704a127f2007.tar.gz |
backport stable branch
-rw-r--r-- | ChangeLog | 19 | ||||
-rw-r--r-- | decorators.py | 14 | ||||
-rw-r--r-- | logging_ext.py | 6 | ||||
-rw-r--r-- | python-logilab-common.spec | 4 | ||||
-rw-r--r-- | registry.py | 10 | ||||
-rw-r--r-- | test/unittest_decorators.py | 42 | ||||
-rw-r--r-- | test/unittest_testlib.py | 11 | ||||
-rw-r--r-- | test/unittest_umessage.py | 10 | ||||
-rw-r--r-- | testlib.py | 15 |
9 files changed, 96 insertions, 35 deletions
@@ -1,6 +1,18 @@ ChangeLog for logilab.common ============================ +-- + * testlib: check for generators in with_tempdir (closes #117533) + + * registry: + * select_or_none should not silent ObjectNotFound exception + (closes #119819) + * remove 2 accidentally introduced tabs breaking python 3 compat + (closes #117580) + + * fix umessages test w/ python 3 and LC_ALL=C (closes #119967, report and + patch by Ian Delaney) + 2013-01-21 -- 0.59.0 @@ -21,7 +33,7 @@ ChangeLog for logilab.common - use register_all when no registration callback defined (closes #111011) - * loggin_ext: on windows, use colorama to display colored logs, if available (closes #107436) + * logging_ext: on windows, use colorama to display colored logs, if available (closes #107436) * packaging: remove references to ftp at logilab @@ -35,7 +47,10 @@ ChangeLog for logilab.common * configuration: enhance merge_options function (closes #113458) - + * decorators: fix @monkeypatch decorator contract for dark corner + cases such as monkeypatching of a callable instance: no more + turned into an unbound method, which was broken in python 3 and + probably not used anywhere (actually closes #104047). 2012-11-14 -- 0.58.3 * date: fix ustrftime() impl. for python3 (closes #82161, patch by Arfrever diff --git a/decorators.py b/decorators.py index 43c3652..34bbd3a 100644 --- a/decorators.py +++ b/decorators.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 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. @@ -19,6 +19,7 @@ __docformat__ = "restructuredtext en" import sys +import types from time import clock, time from logilab.common.compat import callable, method_type @@ -249,7 +250,9 @@ def locked(acquire, release): def monkeypatch(klass, methodname=None): - """Decorator extending class with the decorated callable + """Decorator extending class with the decorated callable. This is basically + a syntactic sugar vs class assignment. + >>> class A: ... pass >>> @monkeypatch(A) @@ -273,11 +276,6 @@ def monkeypatch(klass, methodname=None): raise AttributeError('%s has no __name__ attribute: ' 'you should provide an explicit `methodname`' % func) - if callable(func) and sys.version_info < (3, 0): - setattr(klass, name, method_type(func, None, klass)) - else: - # likely a property - # this is quite borderline but usage already in the wild ... - setattr(klass, name, func) + setattr(klass, name, func) return func return decorator diff --git a/logging_ext.py b/logging_ext.py index e4d2490..54cc48f 100644 --- a/logging_ext.py +++ b/logging_ext.py @@ -112,7 +112,11 @@ def get_handler(debug=False, syslog=False, logfile=None, rotation_parameters=Non else: try: if rotation_parameters is None: - handler = logging.FileHandler(logfile) + if os.name == 'posix' and sys.version_info >= (2, 6): + from logging.handlers import WatchedFileHandler + handler = WatchedFileHandler(logfile) + else: + handler = logging.FileHandler(logfile) else: from logging.handlers import TimedRotatingFileHandler handler = TimedRotatingFileHandler( diff --git a/python-logilab-common.spec b/python-logilab-common.spec index 0ee1282..d960177 100644 --- a/python-logilab-common.spec +++ b/python-logilab-common.spec @@ -10,12 +10,12 @@ %{!?_python_sitelib: %define _python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: %{python}-logilab-common -Version: 0.58.2 +Version: 0.59.0 Release: logilab.1%{?dist} Summary: Common libraries for Logilab projects Group: Development/Libraries -License: GPLv2+ +License: LGPLv2.1+ URL: http://www.logilab.org/projects/logilab-common Source0: http://download.logilab.org/pub/common/logilab-common-%{version}.tar.gz BuildArch: noarch diff --git a/registry.py b/registry.py index fec35ad..4afdfb9 100644 --- a/registry.py +++ b/registry.py @@ -332,7 +332,8 @@ class Registry(dict): """return object with the `oid` identifier. Only one object is expected to be found. - raise :exc:`ObjectNotFound` if not object with id <oid> in <registry> + raise :exc:`ObjectNotFound` if there are no object with id `oid` in this + registry raise :exc:`AssertionError` if there is more than one object there """ @@ -344,9 +345,10 @@ class Registry(dict): """return the most specific object among those with the given oid according to the given context. - raise :exc:`ObjectNotFound` if not object with id <oid> in <registry> + raise :exc:`ObjectNotFound` if there are no object with id `oid` in this + registry - raise :exc:`NoSelectableObject` if not object apply + raise :exc:`NoSelectableObject` if no object can be selected """ obj = self._select_best(self[__oid], *args, **kwargs) if obj is None: @@ -803,7 +805,7 @@ class RegistryStore(dict): and getattr(obj, '__select__', None)): return False elif issubclass(obj, RegistrableInstance): - return False + return False elif not isinstance(obj, RegistrableInstance): return False if not obj.__regid__: diff --git a/test/unittest_decorators.py b/test/unittest_decorators.py index 72d8b07..688d837 100644 --- a/test/unittest_decorators.py +++ b/test/unittest_decorators.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. @@ -17,6 +17,7 @@ # with logilab-common. If not, see <http://www.gnu.org/licenses/>. """unit tests for the decorators module """ +import sys import types from logilab.common.testlib import TestCase, unittest_main @@ -34,28 +35,37 @@ class DecoratorsTC(TestCase): @monkeypatch(MyClass) def meth2(self): return 12 - self.assertTrue(isinstance(MyClass.meth1, types.MethodType)) - self.assertTrue(isinstance(MyClass.meth2, types.MethodType)) - - def test_monkeypatch_callable_non_callable(self): - tester = self + if sys.version_info < (3, 0): + self.assertIsInstance(MyClass.meth1, types.MethodType) + self.assertIsInstance(MyClass.meth2, types.MethodType) + else: + # with python3, unbound method are functions + self.assertIsInstance(MyClass.meth1, types.FunctionType) + self.assertIsInstance(MyClass.meth2, types.FunctionType) + self.assertEqual(MyClass().meth1(), 12) + self.assertEqual(MyClass().meth2(), 12) + + def test_monkeypatch_property(self): class MyClass: pass @monkeypatch(MyClass, methodname='prop1') @property def meth1(self): return 12 - class XXX(object): - def __call__(self, other): - tester.assertIsInstance(other, MyClass) - return 12 - try: - monkeypatch(MyClass)(XXX()) - except AttributeError, err: - self.assertTrue(str(err).endswith('has no __name__ attribute: you should provide an explicit `methodname`')) - monkeypatch(MyClass, 'foo')(XXX()) self.assertIsInstance(MyClass.prop1, property) - self.assertTrue(callable(MyClass.foo)) self.assertEqual(MyClass().prop1, 12) + + def test_monkeypatch_arbitrary_callable(self): + class MyClass: pass + class ArbitraryCallable(object): + def __call__(self): + return 12 + # ensure it complains about missing __name__ + with self.assertRaises(AttributeError) as cm: + monkeypatch(MyClass)(ArbitraryCallable()) + self.assertTrue(str(cm.exception).endswith('has no __name__ attribute: you should provide an explicit `methodname`')) + # ensure no black magic under the hood + monkeypatch(MyClass, 'foo')(ArbitraryCallable()) + self.assertTrue(callable(MyClass.foo)) self.assertEqual(MyClass().foo(), 12) def test_monkeypatch_with_same_name(self): diff --git a/test/unittest_testlib.py b/test/unittest_testlib.py index 4ea8242..f83e1f2 100644 --- a/test/unittest_testlib.py +++ b/test/unittest_testlib.py @@ -628,6 +628,17 @@ class DecoratorTC(TestCase): self.assertListEqual(list(os.walk(tempdir)), [(tempdir, [], [])]) + def test_tmpdir_generator(self): + orig_tempdir = tempfile.gettempdir() + + @with_tempdir + def gen(): + yield tempfile.gettempdir() + + for tempdir in gen(): + self.assertNotEqual(orig_tempdir, tempdir) + self.assertEqual(orig_tempdir, tempfile.gettempdir()) + def setUp(self): self.pyversion = sys.version_info diff --git a/test/unittest_umessage.py b/test/unittest_umessage.py index 6bf56c6..edd7633 100644 --- a/test/unittest_umessage.py +++ b/test/unittest_umessage.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-common. If not, see <http://www.gnu.org/licenses/>. +import sys import email from os.path import join, dirname, abspath @@ -27,9 +28,14 @@ DATA = join(dirname(abspath(__file__)), 'data') class UMessageTC(TestCase): def setUp(self): - msg1 = email.message_from_file(open(join(DATA, 'test1.msg'))) + if sys.version_info >= (3, 2): + import io + msg1 = email.message_from_file(io.open(join(DATA, 'test1.msg'), encoding='utf8')) + msg2 = email.message_from_file(io.open(join(DATA, 'test2.msg'), encoding='utf8')) + else: + msg1 = email.message_from_file(open(join(DATA, 'test1.msg'))) + msg2 = email.message_from_file(open(join(DATA, 'test2.msg'))) self.umessage1 = UMessage(msg1) - msg2 = email.message_from_file(open(join(DATA, 'test2.msg'))) self.umessage2 = UMessage(msg2) def test_get_subject(self): @@ -124,6 +124,21 @@ __unittest = 1 def with_tempdir(callable): """A decorator ensuring no temporary file left when the function return Work only for temporary file create with the tempfile module""" + if is_generator(callable): + def proxy(*args, **kwargs): + old_tmpdir = tempfile.gettempdir() + new_tmpdir = tempfile.mkdtemp(prefix="temp-lgc-") + tempfile.tempdir = new_tmpdir + try: + for x in callable(*args, **kwargs): + yield x + finally: + try: + rmtree(new_tmpdir, ignore_errors=True) + finally: + tempfile.tempdir = old_tmpdir + return proxy + @wraps(callable) def proxy(*args, **kargs): |