summaryrefslogtreecommitdiff
path: root/fixtures
diff options
context:
space:
mode:
authorAndrew Laski <andrew@lascii.com>2016-03-22 15:29:46 -0400
committerRobert Collins <robertc@robertcollins.net>2016-04-07 12:33:27 +1200
commit8c43eb9e04a35dc0cb19e4ed362da22bec6e3f0d (patch)
treebc30e8a8e2329b14463b0e7c00a15bf2a2fc7359 /fixtures
parentbdf14556313ef1cfb7c6b806463defe9201caccc (diff)
downloadfixtures-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.py26
-rw-r--r--fixtures/tests/_fixtures/test_monkeypatch.py4
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)