Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

it is possible to monkeypatch a local variable introduced in a function body?

In py.test monkeypatching/mocking documentation this is not mentioned, but it is possible to monkeypatch a local variable introduced in a function body?

my experiment:

def my_method():
    my_var = 'foo'
    return my_var[:2]

test is:

def test_my_method(monkeypatch):
    monkeypatch.setattr(my_module.MyClass.my_method.my_var, lambda: 'bar')
    assert my_method() == 'ba'

AttributeError: 'function' object at MyClass.my_method has no attribute 'my_var'

like image 478
Vitaly Zdanevich Avatar asked Dec 06 '16 14:12

Vitaly Zdanevich


2 Answers

This is not possible as the variable does not exist ahead of time and py.test cannot hook into the creation of a local variable as far as i know.

like image 74
RedX Avatar answered Sep 18 '22 16:09

RedX


With a little care, it would be possible to patch the consts in the function code object using ctypes.

import ctypes
from contextlib import contextmanager

def tuple_setitem(tup, index, item):
    obj = ctypes.py_object(tup)
    item = ctypes.py_object(item)
    ref_count = ctypes.c_long.from_address(id(tup))
    original_count = ref_count.value
    if original_count != 1:
        ref_count.value = 1
    ctypes.pythonapi.Py_IncRef(item)
    ctypes.pythonapi.PyTuple_SetItem(obj, ctypes.c_ssize_t(index), item)
    ref_count.value = original_count

@contextmanager
def patch_tuple_item(tup, index, item):
    old = tup[index]
    try:
        tuple_setitem(tup, index, item)
        yield
    finally:
        tuple_setitem(tup, index, old)

Demo:

>>> def my_method():
...     my_var = "foo"
...     return my_var[:2]
...
>>> consts = my_method.__code__.co_consts
>>> consts
(None, 'foo', 2)
>>> with patch_tuple_item(consts, index=1, item="bar"):
...     print(my_method())
...
ba
>>> print(my_method())
fo
like image 21
wim Avatar answered Sep 21 '22 16:09

wim