summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <sylvain.thenault@logilab.fr>2013-04-11 11:04:54 +0200
committerSylvain Th?nault <sylvain.thenault@logilab.fr>2013-04-11 11:04:54 +0200
commit201edf589f93be9ed66916874b3e704a127f2007 (patch)
treeedc58cd4d36b4ebb950e9b94f66938d7ad53e86f
parent20180f3add21b4dc93d728754a1a491793837709 (diff)
parentee3a7c45f3bf5e515f355f5d90a4c53753a6cf1f (diff)
downloadlogilab-common-201edf589f93be9ed66916874b3e704a127f2007.tar.gz
backport stable branch
-rw-r--r--ChangeLog19
-rw-r--r--decorators.py14
-rw-r--r--logging_ext.py6
-rw-r--r--python-logilab-common.spec4
-rw-r--r--registry.py10
-rw-r--r--test/unittest_decorators.py42
-rw-r--r--test/unittest_testlib.py11
-rw-r--r--test/unittest_umessage.py10
-rw-r--r--testlib.py15
9 files changed, 96 insertions, 35 deletions
diff --git a/ChangeLog b/ChangeLog
index c579d02..a216b5b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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):
diff --git a/testlib.py b/testlib.py
index fe519ec..0517032 100644
--- a/testlib.py
+++ b/testlib.py
@@ -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):