Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python monkey patch private function

I have a module with a function (call it a()) that calls another function defined in the same module (call it __b()). __b() is a function which speaks to a website via urllib2 and gets some data back. Now I'm trying to test a(), but of course would rather not have my unit tests speak to the public internet. Thus, I'm thinking if I can monkey patch __b() with a function which returns canned data, then I can write the tests for a().

To be more concrete, my module looks kinda like:

def a():
    return __b("someval")

def __b(args):
    return something_complex_with_args

So now I want to test a(), but I need to monkey patch out __b. The problem is that A) the vast majority of information on monkey patching applies to methods of a class, not to functions in a module, and B) the function I want to monkey patch is private. I am willing to change __b to be non-private if it makes the process more feasible, but would rather not.

Suggestions?

Edit: as it stands the test class looks like:

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule._b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

And when I run this, I see the output if the monkey patching had not been done at all, rather than seeing {'a': 'b'} get printed out.

like image 546
Adam Parkin Avatar asked Dec 26 '12 21:12

Adam Parkin


People also ask

What is a monkey patch in Python?

In Python, the term monkey patch refers to dynamic (or run-time) modifications of a class or module. In Python, we can actually change the behavior of code at run-time. We use above module (monk) in below code and change behavior of func () at run-time by assigning different value.

Is there a way to monkeypatch a function?

To really "monkeypatch" that you would need to change the function itself (you are only changing what names refer to); this is possible, but you do not actually want to do that. In the first attempt, you make do_something_expensive a global name in your module.

Is there a PyCon 2010 equivalent for monkeypatching?

There's also the dectools package, which I saw an PyCon 2010, which may be able to be used in this context too, but that might actually go the other way (monkeypatching at the method declarative level... where you're not) Show activity on this post.

What is the best way to patch a Python program?

2 I would recommend to use the most simple and shortest approach, which is in Python probably option A, monkey patching. Everything else makes the code more complicated, with no substantial benefit.


1 Answers

I can't seem to reproduce your issue (I've tweaked your example a bit, since it doesn't run at all as-is). Did you just mistype something (like mymodule._b instead of mymodule.__b)?

mymodule.py:

def a(x):
    return __b("someval")

def __b(args):
    return "complex_thingy: {}".format(args)

mytest.py:

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

Output:

C:\TEMP>python -m unittest mytest
{'a': 'b'}
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

C:\TEMP>

Seems to work fine.


Or outside of unittest:

mytest2.py:

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

print(mymodule.a('somearg'))

Output:

C:\TEMP>python mytest2.py
{'a': 'b'}

C:\TEMP>
like image 121
Gerrat Avatar answered Oct 18 '22 12:10

Gerrat