diff options
author | Andrew Laski <andrew@lascii.com> | 2016-03-22 15:29:46 -0400 |
---|---|---|
committer | Robert Collins <robertc@robertcollins.net> | 2016-04-07 12:33:27 +1200 |
commit | 8c43eb9e04a35dc0cb19e4ed362da22bec6e3f0d (patch) | |
tree | bc30e8a8e2329b14463b0e7c00a15bf2a2fc7359 /fixtures | |
parent | bdf14556313ef1cfb7c6b806463defe9201caccc (diff) | |
download | fixtures-git-8c43eb9e04a35dc0cb19e4ed362da22bec6e3f0d.tar.gz |
MonkeyPatch staticmethod
In Python setattr(Class, name, func) automatically converts a function
into an instancemethod. To keep type(Class.func) as function,
staticmethod(func) must be applied explicitly.
This was previously fixed for Python 2 when cleaning up the patched
function but Python 3 needs the same handling.
When patching a function it was being converted to an instancemethod for
both Python 2 and 3 and this has now been fixed. This is a breaking
change as it was previously acceptable to patch a staticmethod with an
instancemethod.
The test for this case was updated to correctly check both cases. The
patched function is called as both Class.function() and
Class().function(), and then called again after the cleanup has occurred
resetting the function to its original state. The Class().function()
check is important because the method does not become bound until the
class it is defined on is instantiated.
Sem-Ver: api-break
Diffstat (limited to 'fixtures')
-rw-r--r-- | fixtures/_fixtures/monkeypatch.py | 26 | ||||
-rw-r--r-- | fixtures/tests/_fixtures/test_monkeypatch.py | 4 |
2 files changed, 22 insertions, 8 deletions
diff --git a/fixtures/_fixtures/monkeypatch.py b/fixtures/_fixtures/monkeypatch.py index 7db3671..4198b4f 100644 --- a/fixtures/_fixtures/monkeypatch.py +++ b/fixtures/_fixtures/monkeypatch.py @@ -23,6 +23,23 @@ import types from fixtures import Fixture +def _setattr(obj, name, value): + """Handle some corner cases when calling setattr. + + setattr transforms a function into instancemethod, so where appropriate + value needs to be wrapped with staticmethod(). + """ + if sys.version_info[0] == 2: + class_types = (type, types.ClassType) + else: + # All classes are <class 'type'> in Python 3 + class_types = type + if (isinstance(obj, class_types) and + isinstance(value, types.FunctionType)): + value = staticmethod(value) + setattr(obj, name, value) + + class MonkeyPatch(Fixture): """Replace or delete an attribute.""" @@ -60,16 +77,11 @@ class MonkeyPatch(Fixture): if old_value is not sentinel: delattr(current, attribute) else: - setattr(current, attribute, self.new_value) + _setattr(current, attribute, self.new_value) if old_value is sentinel: self.addCleanup(self._safe_delete, current, attribute) else: - # Python 2's setattr transforms function into instancemethod - if (sys.version_info[0] == 2 and - isinstance(current, (type, types.ClassType)) and - isinstance(old_value, types.FunctionType)): - old_value = staticmethod(old_value) - self.addCleanup(setattr, current, attribute, old_value) + self.addCleanup(_setattr, current, attribute, old_value) def _safe_delete(self, obj, attribute): """Delete obj.attribute handling the case where its missing.""" diff --git a/fixtures/tests/_fixtures/test_monkeypatch.py b/fixtures/tests/_fixtures/test_monkeypatch.py index 1a84d7f..81927c8 100644 --- a/fixtures/tests/_fixtures/test_monkeypatch.py +++ b/fixtures/tests/_fixtures/test_monkeypatch.py @@ -78,6 +78,8 @@ class TestMonkeyPatch(testtools.TestCase, TestWithFixtures): 'fixtures.tests._fixtures.test_monkeypatch.C.foo', bar) with fixture: - pass + C.foo() + C().foo() self.assertEqual(oldfoo, C.foo) + self.assertEqual(oldfoo, C().foo) |