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
[email protected]
http://mail.python.org/mailman/listinfo/pytest-commit