I am trying to dynamically import modules and get it as global variable.
I am using maya 2020 python interpreter (Python 2.7)
I have a test module called "trigger_test_script.py" under "/home/arda.kutlu/Downloads/" folder.
When I dont import any custom class and run this:
###########################################################################[START]
import sys
import imp
class TestClass(object):
def __init__(self):
self.filePath = None
self.asName = None
def action(self):
exec("global %s" % self.asName, globals())
foo = "imp.load_source('%s', '/home/arda.kutlu/Downloads/trigger_test_script.py')" %self.asName
cmd = "{0}={1}".format(self.asName, foo)
exec(cmd, globals())
###########################################################################[END]
test = TestClass()
test.filePath = "/home/arda.kutlu/Downloads/trigger_test_script.py"
test.asName = "supposed_to_be_global"
test.action()
print(supposed_to_be_global)
I get the exact result that I want:
<module 'trigger_test_script' from '/home/arda.kutlu/Downloads/trigger_test_script.pyc'>
However, when I save TestClass (the part between hashes) into a file and import it like this:
import testClass
test = testClass.TestClass()
test.filePath = "/home/arda.kutlu/Downloads/trigger_test_script.py"
test.asName = "supposed_to_be_global"
test.action()
print(supposed_to_be_global)
the variable which 'supposed_to_be_global' is not becoming global and I get the NameError.
I always assumed that these two usages should return the same result but clearly I am missing something.
I appreciate any help, thanks.
I don't quite understand your last comment about having several modules with different action() methods is a problem. So ignoring that, here's how to make what's in your question work, The part in the hashes will work both in-line or if put in a separate module and imported.
###########################################################################[START]
import imp
class TestClass(object):
def __init__(self):
self.filePath = None
self.asName = None
def action(self):
foo = imp.load_source(self.asName, self.filePath)
return foo
###########################################################################[END]
#from testclass import TestClass
test = TestClass()
test.filePath = "/home/arda.kutlu/Downloads/trigger_test_script.py"
test.asName = "supposed_to_be_global"
supposed_to_be_global = test.action()
print(supposed_to_be_global)
Related
so i've got a problem with my code.
File 1:
class Abc(object):
...
def function1(self):
#do something
def function2(self):
x = input()
return x+1
and now i'm trying to test function 2 so i wrote a test for it and i don't know what i am doing wrong:
from unittest.mock import patch
import unittest
from file1 import *
class TestBackend(unittest.TestCase):
def test_mode_first(self):
self.assertEqual(Abc().funcion1(), 30)
#patch('funcion2.input', create=True)
def test_mode_second(self, mocked_input):
mocked_input.side_effect = ["QWE"]
result = Abc().funcion2()
self.assertEqual(result, 10)
if __name__ == '__main__':
unittest.main()
i get ModuleNotFoundError: No module named 'function2'
so what i am doing wrong in here?
thanks for your help :)
You get ModuleNotFoundError because funcion2 is not a module. patch doc is clear about this:
target should be a string in the form 'package.module.ClassName'. The
target is imported and the specified object replaced with the new
object, so the target must be importable from the environment you are
calling patch() from. The target is imported when the decorated
function is executed, not at decoration time.
This works for me when executed with python3 -m unittest discover from the directory the files are in.
BTW you have a couple of typos in your example, e.g. Abc().funcion2(), note the missing t in funcion2.
Also, try not to use from … import *: https://docs.quantifiedcode.com/python-anti-patterns/maintainability/from_module_import_all_used.html#using-wildcard-imports-from-import
# file1.py
class Abc(object):
def function1(self):
return 30
def function2(self):
x = input()
return x + "1"
# test_file1.py
import unittest
from unittest.mock import patch
from file1 import Abc
class TestBackend(unittest.TestCase):
def test_mode_first(self):
self.assertEqual(Abc().function1(), 30)
#patch('builtins.input')
def test_mode_second(self, mocked_input):
mocked_input.return_value = "QWE"
result = Abc().function2()
self.assertEqual(result, "QWE1")
I am trying to test the module:
package/module.py
DATA_PATH = os.path.join(os.path.dirname(__file__), "data")
class SomeClass:
def __init__(self):
self.filename = os.path.join(DATA_PATH, "ABC.txt")
in tests/module_test.py
I am trying to do
from package import module
#patch("package.module.DATA_PATH", "data_path_here") # doesn't work
class TestSomeClass(unittest.TestCase):
def setUp(self):
module.DATA_PATH = "data_path_here" # doesn't work
self.obj= SomeClass()
#patch("package.module.DATA_PATH", "data_path_here") # doesn't work either
def test_constructor(self):
self.assertEqual(r"data_path_here\ABC.txt", self.obj.filename)
but the DATA_PATH is still not mocked out. I think that I tried all the possible options to mock it out but it still returns original path instead of "data_path_here"
What am I doing wrong?
EDIT:
It is not a duplicate of Modifying global variables in Python unittest framework
Because that solution does not work
You don't need patching since you are using a global variable from another module:
#module.py
DATA_PATH = 1
def getData():
return DATA_PATH
#tests.py
from package import module
print(module.DATA_PATH, module.getData())
module.DATA_PATH = 2
print(module.DATA_PATH, module.getData())
Output:
1 1
2 2
For me, using mock/patch is a painful exercise. On the other hand, it was trivial to accomplish by setting (and restoring) the global for the tests:
import mock_module
class TestSomeClass(unittest2.TestCase):
def setUp(self):
self._original = mock_module.DATA_PATH
mock_module.DATA_PATH = 'data_path_here'
def tearDown(self):
mock_module.DATA_PATH = self._original
def test_constructor(self):
obj = mock_module.SomeClass()
self.assertEqual(r"data_path_here\ABC.txt", obj.filename)
Note that for my os path join, the separator is \ but your usage may differ.
Ran 1 test in 0.005s
OK
I am currently attempting to write unit tests for my Main.py's main() function
Here is a simplified version of my Main.py:
from Configuration import Configuration # Configuration.py is a file in the same dir
def main():
try:
Configuration('settings.ini')
except:
sys.exit(1) # Test path1
sys.exit(0) # Test path2
if __name__ == '__main__':
main()
In my Unit Tests\MainUnitTests.py I want to import ..\Main.py and fake the Configuration class in such a way that I can hit Test path1 and Test path2
I found that i can assert sys.exit() with the following:
with self.assertRaises(SystemExit) as cm:
main()
self.assertEqual(cm.exception.code, 1)
but I am having trouble overriding the from Configuration import Configuration
Thoughts?
So far I have tried the following within Unit Tests\MainUnitTests.py:
class FakeFactory(object):
def __init__(self, *a):
pass
sys.modules['Configuration'] = __import__('FakeFactory')
class Configuration(FakeFactory):
pass
Another example for demonstration:
foo.py:
from bar import a,b
x = a()
class a(object):
def __init__(self):
self.q = 2
y = a()
print x.q, y.q # prints '1 2' as intended
b() # I want this to print 2 without modifying bar.py
bar.py:
class a(object):
def __init__(self):
self.q = 1
def b():
t = a()
print t.q
when you use the import
from bar import a
it import the name directly into the module, so monkeypatching bar won't help, you need to override a directly in the main file:
def fake_a():
print("OVERRIDEN!")
main.a = fake_a
Do know that unittest has helper functions for this in the mock subpackage, I believe you could do something like:
from unittest.mock import patch
...
with patch("main.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
do_stuff()
This would work in your first example with configuration since the class that needs to be patched is not used in the global scope although foo uses bar.a as soon as it is loaded so you would need to patch it before even loading foo:
from unittest.mock import patch
...
with patch("bar.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
import foo #now when it loads it will be loaded with the patched name
However in this case foo.a would not be reverted at the end of the with block because it can't be caught by unittest... I really hope your actual use case doesn't use the stuff to be patched at module level.
I'm curious if it's possible to access the name of the parent module that some other module is imported into.
For instance, if I have a module (moduleA) and a parent is module, foo.py, into which it will be imported into, is it possible for moduleA to know where foo is located ?
ModuleA
def print_parent_module():
os.path.asbpath(#somehow access filename of parent module)
foo.py
import moduleA
print moduleA.print_parent_module()
>>> "foo.py"
I ran into a similar issue. You could make use of __name__:
parent_name = '.'.join(__name__.split('.')[:-1])
Or if you try to access the module directly (not the OP's question but related), see my answer in Is there a way to access parent modules in Python
No. Imported modules do not hold any form of state which stores data related to how they are imported.
I think the better question is, why would you even want to do it this way? You're aware that if foo.py is indeed your __main__ then you can easily get the name foo.py out of it (by using sys.argv)? And if foo.py is an imported module instead, then you obviously already know the name of foo.py and its location, etc. at the time that you import it.
Here is something I came with to store a parent's method name and variables to be recalled later in the class with a decorator.
import inspect
from functools import wraps
def set_reuse_vars(method):
#wraps(method)
def _impl(self, *method_args, **method_kwargs):
func_current = inspect.currentframe()
self.recall_func = dict()
self.recall_func['method_kwargs'] = func_current.f_locals['method_kwargs']
self.recall_func['method_name'] = func_current.f_locals['method'].__name__
return method(self, *method_args, **method_kwargs)
return _impl
class APIData(object):
def __init__(self):
self.client = None
self.response = None
self.recall_func = None
def get_next_page(self):
# Takes a pageToken to return the next result
get_func = getattr(self, self.recall_func['method_name'])
self.recall_func['method_kwargs']['pageToken'] = self.response['nextPageToken']
return get_func(**self.recall_func['method_kwargs'])
#set_reuse_vars
def search_list_by_keyword(self, **kwargs):
self.response = self.client.search().list(
**kwargs
).execute()
return self.response
script to run it.
api_data = APIData()
results = api_data.search_list_by_keyword(
part='snippet',
maxResults=25,
order='date')
print(results)
resultsTwo = api_data.get_next_page()
print(resultsTwo)
Figured it out!
A little import statement inside of the function can give access to the module name.
moduleA
def print_module_name():
import sys
return sys.argv[0]
Then, inside the "parent" module
# foo.py
import os
import moduleA
if __name__ == '__main__':
print os.path.split(moduleA.print_module_name())[-1]
Gives:
>>> 'foo.py'
I seem to have run into a really confusing error. Despite importing the .py file containing my class, Python is insistent that the class doesn't actually exist.
Class definition in testmodule.py:
class Greeter:
def __init__(self, arg1=None):
self.text = arg1
def say_hi(self):
return self.text
main.py:
#!/usr/bin/python
import testmodule
sayinghi = Greeter("hello world!")
print(sayinghi.say_hi())
I have a theory that the import is not working as it should. How do I do this correctly?
Use the fully-qualified name:
sayinghi = testmodule.Greeter("hello world!")
There is an alternative form of import that would bring Greeter into your namespace:
from testmodule import Greeter
import testmodule
# change to
from testmodule import Greeter
or
import testmodule
sayinghi = Greeter("hello world!")
# change to
import testmodule
sayinghi = testmodule.Greeter("hello world!")
You imported the module/package, but you need to reference the class inside it.
You could also do this instead
from testmodule import *
but then beware of namespace pollution