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