monkeypatch a function that needs to be imported in conftest - python

I'm trying to use pytest.monkeypatch to patch a function I've defined in another file. I then need to patch a function from another that relies on this first monkeypatch. Here's a simple example
# class_def.py
class C:
def __init__(self):
# Normally, there is something that makes self.p
# that will use a file that will exist on production
raise FileNotFoundError
def factory():
print('in factory')
return C()
----
# function_def.py
from .class_def import factory
foo = factory()
def bar():
return 0
----
# conftest.py
from unittest.mock import MagicMock
import pytest
import playground.class_def
#pytest.fixture(autouse=True)
def patch_c(monkeypatch):
fake_c = MagicMock()
def factory():
print('in monkey factory')
return fake_c
monkeypatch.setattr('playground.class_def.factory', factory)
from .function_def import bar
# Then I would patch bar
And running pytest . will fail with FileNotFoundError. I believe this happens because I am calling foo = factory() at the top level of function_def.py. I expected this not to happen because I am patching factory before doing this import, but that doesn't seem to be happening. Is there a way to ensure this monkeypatch.setattr will go into effect before from .function_def import bar in conftest.py?
Also, the file structure looks like
playground
|--- __init__.py
|--- conftest.py
|--- class_def.py
|--- function_def

You have direct access to the attribute you want to change. You don't need monkeypatch at all.
Here's my tree :
$ tree .
.
├── a.py
├── b.py
├── __init__.py
└── test_a.py
0 directories, 4 files
a.py
class A:
def __init__(self):
raise Exception
def factory():
return A()
b.py
import a
print(a.factory())
test_a.py
import a
def test_a():
def fake_factory():
return 'A'
a.factory = fake_factory
import b
And it works:
$ pytest
=============================================================================================== test session starts ===============================================================================================
platform linux -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /home/ahorgnies/test/monkeypatch, inifile:
plugins: remotedata-0.2.1, openfiles-0.3.0, doctestplus-0.1.3, arraydiff-0.2
collected 1 item
test_a.py . [100%]
============================================================================================ 1 passed in 0.01 seconds =============================================================================================

Related

How do I specify inheritence behavior on initialization of an object in Python?

I'm trying to write a class with a method that behaves differently based on data given on the initialization of the object. I want to pass this object code to run stored in a different file. The behavior should be as follows
foo = Myclass(config="return_a.py")
bar = Myclass(config="return_b.py")
foo.baz() # returns a
bar.baz() # returns b
# where return_a.py and return_b.py are files in the directory
The closest I've come to fixing it so far is using exec and having my configured python write to a file which I then read from. I don't know how I'd do this in memory
You can use importlib to import the files dynamically.
Let's say your project has the structure:
.
├── main.py
├── return_a.py
└── return_b.py
you can put in main.py your code:
import importlib
class Myclass:
def __init__(self, config) -> None:
config = importlib.import_module(
config)
self.baz = config.baz
foo = Myclass(config="return_a")
bar = Myclass(config="return_b")
foo.baz()
bar.baz()
This is assuming you have the function baz your return_a and return_b files. For example:
#return_a.py
def baz():
print("I am A")
#return_b.py
def baz():
print("I am B")
Now if you execute main.py you will get:
I am A
I am B

Pytest path of mock.patch() for third party package

Why the 1st case succeed. But the 2nd case failed AssertionError: Expected 'Jenkins' to have been called.
util.py
from jenkinsapi.jenkins import Jenkins
import os
class Util:
#staticmethod
def rm(filename):
os.remove(filename)
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_remove(mocker):
m = mocker.patch('os.remove')
Util.rm('file')
m.assert_called()
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins')
Util.get_jenkins_instance()
m.assert_called()
Two files are in the same root folder.
Not very clear what's the differences between Python's import and from ... import ....
But if you use from ... import ..., the mock looks as following:
util.py
from jenkinsapi.jenkins import Jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('util.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
If you use import directly, the mock looks as following:
util.py
import jenkinsapi.jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
jenkinsapi.jenkins.Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
========== Edit (Aug 5, 2022) ==========
Here's the reason why patched like this.
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
Now we want to test some_function but we want to mock out SomeClass using patch(). The problem is that when we import module b, which we will have to do then it imports SomeClass from module a. If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.
The key is to patch out SomeClass where it is used (or where it is looked up ). In this case some_function will actually look up SomeClass in module b, where we have imported it.

Override function in a module with complex file tree

I have a module module containing 2 functions a and b splited into 2 different files m1.py and m2.py.
The module's file tree:
module/
__init__.py
m1.py
m2.py
__init__.py contains:
from .m1 import a
from .m2 import b
m1.py contains:
def a():
print('a')
m2.py contains:
from . import a
def b():
a()
Now, I want to override the function a in a main.py file, such that the function b uses the new function a. I tried the following:
import module
module.a = lambda: print('c')
module.b()
But it doesn't work, module.b() still print a.
I found a solution, that consists in not importing with from . import a but with import module.
m2.py becomes:
import module
def b():
module.a()

Python3 relative imports

I'm tired of reading one-off use cases of relative imports so I figured I'd as a question to get an example of how to do a relative import from a directory above and bellow, for both importing module functions and class objects.
directory structure:
.
├── lib
│   ├── __init__.py
│   └── bar.py
└── src
├── main.py
└── srclib
├── __init__.py
└── foo.py
bar.py
def BarFunc():
print("src Bar function")
class BarClass():
def __inti__(self):
print("src Bar Class")
def test(self):
print("BarClass working")
foo.py
def FooFunction():
print("srclib Foo function")
class FooClass():
def __init__(self):
print("srclib Foo Class")
def test(self):
print("FooClass working")
Question: What is the syntax in python 3 to import for these use cases?
main.py
# What is the syntax to import in python 3?
# I want to be able to call FooFunc()
foo.FooFunc()
# I want to be able to create a FooClass() object
foo_class = foo.FooClass()
foo_class.test()
# I want to be able to call FooFunc()
bar.BarFunc()
# I want to be able to create a BarClass() object
bar_class = bar.BarClass()
bar_class.test()
It all depends on where you start your python interpreter from. In your case, I would suggest you to start the interpreter from your project's root directory while making the following changes:
In file src/srclib/__init__.py add:
from . import foo
The reason for doing this is to explicitly state in your __init__.py file what to import from your module.
In your main.py file, add the following:
from lib import bar
from src.srclib import foo
Hope this helps!

Package python codes which uses __import__ function

My directory structure is:
./
├── foo
│   ├── bar.py
│   ├── foo.py
│   └── __init__.py
└── main.py
with:
bar.py:
def get_data():
return 'ha'
foo.py:
class foo:
def __init__(self):
self.lib = __import__('bar', fromlist=['bar'])
self.data = self.lib.get_data()
def print_data(self):
print(self.data)
if __name__=='__main__':
f = foo()
f.print_data()
__init__.py:
from foo import foo
and main.py:
from foo import foo
a = foo()
a.print_data()
Running python foo.py I get ha correctly, but running python main.py I get the following message:
Traceback (most recent call last):
File "main.py", line 3, in <module>
a = foo()
File ".../foo/foo.py", line 3, in __init__
self.lib = __import__('bar')
ImportError: No module named bar
My requirements are 1) making foo to work like a package, 2) using __import__ in foo.py's __init__ function instead of import in the first line of foo.py.
I changed line 3 of foo.py to self.lib = __import__('foo.bar', fromlist=['bar']), and then got the correct answer. But that is not I want, since running python foo.py will lead to a failure and that is not a package solution when the whole directory ./ become another package. It seems an import path problem that I cannot figure out.
Changing foo.py to that makes it work correctly:
import importlib
class foo:
def __init__(self):
self.lib = importlib.import_module('foo.bar')
self.data = self.lib.get_data()
def print_data(self):
print(self.data)
if __name__=='__main__':
f = foo()
f.print_data()

Categories

Resources