I'm trying to learn some advanced decorator usage. Specifically, I'm trying to to monkey patch a class's method via a decorator within a function.
This is a basic example to illustrate what I'm trying to do. I have a function something
that does some stuff; and within that function there's an instance of a class. That instance I would like to monkey patch.
from functools import update_wrapper
class Foobar:
def get_something(self):
return "apple"
class FakeFoobar:
def get_something(self):
return "orange"
class my_decorator:
def __init__(self, original_function):
self._original_function = original_function
update_wrapper(self, original_function)
def __call__(self, *args, **kwargs):
# some magic here?
return self._original_function(*args, **kwargs)
@my_decorator
def something():
f = Foobar()
return f.get_something()
if __name__ == '__main__':
print(something())
I'm trying either trying to do a 1 to 1 replacement with Foobar
to FakeFoobar
or, monkey patch Foobar
's get_something
method to FakeFoobar
's get_something
method.
When I run the code above, I get the following:
>>> something()
'apple'
>>>
I would like to find some way augment the Foobar
's get_something
method so that we get the following output:
>>> something()
'orange'
>>>
There's a mock
module within the unittests
library, however, it's not clear to be how I could leverage that for my use case. I'm fairly married to the idea of not passing an argument into the decorator or an extra argument into the something
function as many of the examples of the mock library show.
I also notice that the moto library is accomplishing something similar to what I'm trying to do. I tried digging into the source code, but it seems fairly complex for what I'm trying to do.
How about updating the global variables dict of the function?
from functools import update_wrapper
class Foobar:
def get_something(self):
return "apple"
class FakeFoobar:
def get_something(self):
return "orange"
class my_decorator:
def __init__(self, original_function):
self._original_function = original_function
update_wrapper(self, original_function)
def __call__(self, *args, **kwargs):
f = self._original_function
restore_val = f.func_globals['Foobar']
f.func_globals['Foobar'] = f.func_globals['FakeFoobar']
# ^^^^^ This is your magic-line.
try:
return f(*args, **kwargs)
except:
raise
finally:
f.func_globals['Foobar'] = restore_val
@my_decorator
def something():
f = Foobar()
return f.get_something()
if __name__ == '__main__':
print(something()) #Prints orange
print(Foobar().get_something()) #Prints apple