1 new commit in pytest:

https://bitbucket.org/hpk42/pytest/commits/b4cd6235587f/
Changeset:   b4cd6235587f
User:        hpk42
Date:        2013-08-07 15:35:27
Summary:     a new monkeypatch.replace(target, value) call which derives the
monkeypatch location from target (can be class/module/function or
string which is taken as importable python path)
examples:

    monkeypatch.replace(os.path.abspath, lambda x: "")
    monkeypatch.replace("requests.get", ...)
Affected #:  4 files

diff -r 9c9044347a5642b97bca9fba52d0dd14027dc287 -r 
b4cd6235587f2259678b618c1c5a429babe2a2c5 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,16 @@
 Changes between 2.3.5 and 2.4.DEV
 -----------------------------------
 
+- new monkeypatch.replace() to allow for more direct patching::
+
+     monkeypatch.replace(os.path.abspath, lambda x: "mocked")
+
+  instead of: monkeypatch.setattr(os.path, "abspath", lambda x: "mocked")
+
+  You can also avoid imports by specifying a python path string::
+
+     monkeypatch.replace("requests.get", ...)
+  
 - fix issue322: tearDownClass is not run if setUpClass failed. Thanks
   Mathieu Agopian for the initial fix.  Also make all of pytest/nose finalizer 
   mimick the same generic behaviour: if a setupX exists and fails, 
@@ -98,6 +108,7 @@
 
 - better parametrize error messages, thanks Brianna Laugher
 
+
 known incompatibilities:
 
 - if calling --genscript from python2.7 or above, you only get a

diff -r 9c9044347a5642b97bca9fba52d0dd14027dc287 -r 
b4cd6235587f2259678b618c1c5a429babe2a2c5 _pytest/monkeypatch.py
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -26,6 +26,47 @@
 
 notset = object()
 
+if sys.version_info < (3,0):
+    def derive_obj_and_name(obj):
+        name = obj.__name__
+        real_obj = getattr(obj, "im_self", None)
+        if real_obj is None:
+            real_obj = getattr(obj, "im_class", None)
+            if real_obj is None:
+                real_obj = sys.modules[obj.__module__]
+        assert getattr(real_obj, name) == obj, \
+                "could not derive object/name pair"
+        return name, real_obj
+
+else:
+    def derive_obj_and_name(obj):
+        name = obj.__name__
+        real_obj = getattr(obj, "__self__", None)
+        if real_obj is None:
+            current = sys.modules[obj.__module__]
+            for name in obj.__qualname__.split("."):
+                real_obj = current
+                current = getattr(current, name)
+        assert getattr(real_obj, name) == obj, \
+               "could not derive object/name pair"
+        return name, real_obj
+
+def derive_from_string(target):
+    rest = []
+    while target:
+        try:
+            obj = __import__(target, None, None, "__doc__")
+        except ImportError:
+            if "." not in target:
+                raise
+            target, name = target.rsplit(".", 1)
+            rest.append(name)
+        else:
+            assert len(rest) >= 1
+            while len(rest) != 1:
+                obj = getattr(obj, rest.pop())
+            return rest[0], obj
+
 class monkeypatch:
     """ object keeping a record of setattr/item/env/syspath changes. """
     def __init__(self):
@@ -33,9 +74,28 @@
         self._setitem = []
         self._cwd = None
 
+    def replace(self, target, value):
+        """ derive monkeypatching location from ``target`` and call
+        setattr(derived_obj, derived_name, value).
+
+        This function can usually derive monkeypatch locations
+        for function, method or class targets.  It also accepts
+        a string which is taken as a python path which is then
+        tried to be imported.  For example the target "os.path.abspath"
+        will lead to a call to setattr(os.path, "abspath", value)
+        without the need to import "os.path" yourself.
+        """
+        if isinstance(target, str):
+            name, obj = derive_from_string(target)
+        else:
+            name, obj = derive_obj_and_name(target)
+        return self.setattr(obj, name, value)
+
     def setattr(self, obj, name, value, raising=True):
         """ set attribute ``name`` on ``obj`` to ``value``, by default
-        raise AttributeEror if the attribute did not exist. """
+        raise AttributeEror if the attribute did not exist.
+
+        """
         oldval = getattr(obj, name, notset)
         if raising and oldval is notset:
             raise AttributeError("%r has no attribute %r" %(obj, name))

diff -r 9c9044347a5642b97bca9fba52d0dd14027dc287 -r 
b4cd6235587f2259678b618c1c5a429babe2a2c5 doc/en/monkeypatch.txt
--- a/doc/en/monkeypatch.txt
+++ b/doc/en/monkeypatch.txt
@@ -29,7 +29,7 @@
     def test_mytest(monkeypatch):
         def mockreturn(path):
             return '/abc'
-        monkeypatch.setattr(os.path, 'expanduser', mockreturn)
+        monkeypatch.setattr(os.path., 'expanduser', mockreturn)
         x = getssh()
         assert x == '/abc/.ssh'
 
@@ -41,7 +41,7 @@
 -----------------------------------------------------
 
 .. autoclass:: monkeypatch
-    :members: setattr, delattr, setitem, delitem, setenv, delenv, 
syspath_prepend, chdir, undo
+    :members: setattr, replace, delattr, setitem, delitem, setenv, delenv, 
syspath_prepend, chdir, undo
 
 ``monkeypatch.setattr/delattr/delitem/delenv()`` all
 by default raise an Exception if the target does not exist.

diff -r 9c9044347a5642b97bca9fba52d0dd14027dc287 -r 
b4cd6235587f2259678b618c1c5a429babe2a2c5 testing/test_monkeypatch.py
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -35,6 +35,38 @@
     monkeypatch.undo() # double-undo makes no modification
     assert A.x == 5
 
+class TestDerived:
+    def f(self):
+        pass
+
+    def test_class_function(self, monkeypatch):
+        monkeypatch.replace(TestDerived.f, lambda x: 42)
+        assert TestDerived().f() == 42
+
+    def test_instance_function(self, monkeypatch):
+        t = TestDerived()
+        monkeypatch.replace(t.f, lambda: 42)
+        assert t.f() == 42
+
+    def test_module_class(self, monkeypatch):
+        class New:
+            pass
+        monkeypatch.replace(TestDerived, New)
+        assert TestDerived == New
+
+    def test_nested_module(self, monkeypatch):
+        monkeypatch.replace(os.path.abspath, lambda x: "hello")
+        assert os.path.abspath("123") == "hello"
+
+    def test_string_expression(self, monkeypatch):
+        monkeypatch.replace("os.path.abspath", lambda x: "hello2")
+        assert os.path.abspath("123") == "hello2"
+
+    def test_string_expression_class(self, monkeypatch):
+        monkeypatch.replace("_pytest.config.Config", 42)
+        import _pytest
+        assert _pytest.config.Config == 42
+
 def test_delattr():
     class A:
         x = 1

Repository URL: https://bitbucket.org/hpk42/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
pytest-commit@python.org
http://mail.python.org/mailman/listinfo/pytest-commit

Reply via email to