1 new commit in pytest:

https://bitbucket.org/hpk42/pytest/commits/7f5dba9095f4/
Changeset:   7f5dba9095f4
User:        hpk42
Date:        2013-08-07 16:49:29
Summary:     monkeypatch.replace() now only accepts a string.  Improved error 
handling and
docs thanks to suggestions from flub, pelme, schmir, ronny.
Affected #:  4 files

diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r 
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,15 +1,12 @@
 Changes between 2.3.5 and 2.4.DEV
 -----------------------------------
 
-- new monkeypatch.replace() to allow for more direct patching::
+- new monkeypatch.replace() to avoid imports and provide a shorter
+  invocation for patching out classes/functions from modules:
 
-     monkeypatch.replace(os.path.abspath, lambda x: "mocked")
+     monkeypatch.replace("requests.get", myfunc
 
-  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", ...)
+  will replace the "get" function of the "requests" module with ``myfunc``.
   
 - fix issue322: tearDownClass is not run if setUpClass failed. Thanks
   Mathieu Agopian for the initial fix.  Also make all of pytest/nose finalizer 

diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r 
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd _pytest/monkeypatch.py
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -1,6 +1,7 @@
 """ monkeypatching and mocking functionality.  """
 
 import os, sys, inspect
+import pytest
 
 def pytest_funcarg__monkeypatch(request):
     """The returned ``monkeypatch`` funcarg provides these
@@ -26,47 +27,6 @@
 
 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):
@@ -74,22 +34,45 @@
         self._setitem = []
         self._cwd = None
 
-    def replace(self, target, value):
-        """ derive monkeypatching location from ``target`` and call
-        setattr(derived_obj, derived_name, value).
+    def replace(self, import_path, value):
+        """ replace the object specified by a dotted ``import_path``
+        with the given ``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.
+        For example ``replace("os.path.abspath", value)`` will
+        trigger an ``import os.path`` and a subsequent
+        setattr(os.path, "abspath", value).  Or to prevent
+        the requests library from performing requests you can call
+        ``replace("requests.sessions.Session.request", None)``
+        which will lead to an import of ``requests.sessions`` and a call
+        to ``setattr(requests.sessions.Session, "request", None)``.
         """
-        if isinstance(target, str):
-            name, obj = derive_from_string(target)
-        else:
-            name, obj = derive_obj_and_name(target)
-        return self.setattr(obj, name, value)
+        if not isinstance(import_path, str) or "." not in import_path:
+            raise TypeError("must be absolute import path string, not %r" %
+                            (import_path,))
+        rest = []
+        target = import_path
+        while target:
+            try:
+                obj = __import__(target, None, None, "__doc__")
+            except ImportError:
+                if "." not in target:
+                    __tracebackhide__ = True
+                    pytest.fail("could not import any sub part: %s" %
+                                import_path)
+                target, name = target.rsplit(".", 1)
+                rest.append(name)
+            else:
+                assert rest
+                try:
+                    while len(rest) > 1:
+                        attr = rest.pop()
+                        obj = getattr(obj, attr)
+                    attr = rest[0]
+                    getattr(obj, attr)
+                except AttributeError:
+                    __tracebackhide__ = True
+                    pytest.fail("object %r has no attribute %r" % (obj, attr))
+                return self.setattr(obj, attr, value)
 
     def setattr(self, obj, name, value, raising=True):
         """ set attribute ``name`` on ``obj`` to ``value``, by default

diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r 
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd 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'
 
@@ -37,6 +37,23 @@
 then calls into an function that calls it.  After the test function 
 finishes the ``os.path.expanduser`` modification will be undone.
 
+example: preventing "requests" from remote operations
+------------------------------------------------------
+
+If you want to prevent the "requests" library from performing http
+requests in all your tests, you can do::
+
+    # content of conftest.py
+
+    import pytest
+    @pytest.fixture(autouse=True)
+    def no_requests(monkeypatch):
+        monkeypatch.replace("requests.session.Session.request", None)
+
+This autouse fixture will be executed for all test functions and it
+will replace the method ``request.session.Session.request`` with the
+value None so that any attempts to create http requests will fail.
+
 Method reference of the monkeypatch function argument
 -----------------------------------------------------
 

diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r 
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd testing/test_monkeypatch.py
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -35,29 +35,7 @@
     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"
-
+class TestReplace:
     def test_string_expression(self, monkeypatch):
         monkeypatch.replace("os.path.abspath", lambda x: "hello2")
         assert os.path.abspath("123") == "hello2"
@@ -67,6 +45,17 @@
         import _pytest
         assert _pytest.config.Config == 42
 
+    def test_wrong_target(self, monkeypatch):
+        pytest.raises(TypeError, lambda: monkeypatch.replace(None, None))
+
+    def test_unknown_import(self, monkeypatch):
+        pytest.raises(pytest.fail.Exception,
+                      lambda: monkeypatch.replace("unkn123.classx", None))
+
+    def test_unknown_attr(self, monkeypatch):
+        pytest.raises(pytest.fail.Exception,
+                      lambda: monkeypatch.replace("os.path.qweqwe", None))
+
 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