I'm fairly new to Python. I'm working on a small Python project, structured as so:
artwork_grabber/
|
|-- artwork_grabber/
| |-- __init__.py
| |-- helpers.py
|
|-- tests/
| |-- __init__.py
| |-- test_module.py
|
|-- README
Both __init__.py files are empty of any content.
The helpers.py file contains a few functions, one of which is as follows:
from os import path
from tinytag import TinyTag
def create_search_term(file_path_to_song):
"""
Takes in a file path to a song and returns a phrase that will be used to search for the song's corresponding album artwork.
:param file_path_to_song: a file path to an .mp3 or .m4a file.
:type file_path_to_song: `string`, required.
:return: an object of type string that represents the search term to be used when finding album artwork for song file passed into the function.
:rtype: `string`.
"""
if str(path.isfile(file_path_to_song)):
tag = TinyTag.get(file_path_to_song)
album = tag.get_album()
artist = tag.get_artist()
term = f"{artist} {album} Album Cover"
return term
else:
return False
I would like to write a test for create_search_term() using the Mock Object Library. In the test_module.py function, I have the following:
from unittest import TestCase
from mock import patch
import unittest
from artwork_grabber.helpers import create_search_term
class UnitTests(TestCase):
mock_song_info = {
"album": "A Deeper Understanding",
"artist": "The War On Drugs"
}
# patch where the function is USED, not where it is DEFINED
#mock.patch('artwork_grabber.helpers.create_search_term', return_value=mock_song_info)
def test_create_search_term(self, mock_song):
actual_result = create_search_term(mock_song_info)
expected_result = "A Deeper Understanding The War On Drugs Album Cover"
self.assertEqual(actual_result, expected_result,
"Expected the search terms to match.")
if __name__ == "__main__":
unittest.main()
The problem is that when I run python test_module.py from the terminal (where pwd outputs /path/to/artwork_grabber/tests), I get the following error:
Traceback (most recent call last):
File "test_module.py", line 5, in <module>
from artwork_grabber.artwork_grabber import create_search_term
ModuleNotFoundError: No module named 'artwork_grabber'
Any idea what I might be missing? I've viewed several tutorials on using Mock, but they haven't seemed to help.
As documented in The Module Search Path:
When a module named spam is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path. sys.path is initialized from these locations:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
...
So in your case, the path that python would look for your modules is at /path/to/artwork_grabber/tests which obviously doesn't contain any artwork_grabber/helpers.py since it only contains an __init__.py and test_module.py. To satisfy either of the 2 ways above as documented, do either of the following:
Go to the parent folder cd /path/to/artwork_grabber and then execute the test python tests/test_module.py. This will satisfy the 1st above which will include the current path thus including artwork_grabber/helpers.py and the subdirectories and files in it.
Define it in the variable export PYTHONPATH=${PYTHONPATH}:/path/to/artwork_grabber to satisfy the 2nd above.
Note that as pointed out by #MatiasCicero it wouldn't make sense to mock function-x to return y and assert that function-x returned y, the point of testing is to test source code, not to test the test if it correctly did the mock. Ideally, you should run the actual create_search_term and pass it a test file file_path_to_song.
Related
I have a Python project in which I have the following folder structure:
> root
> download_module
> __init__.py
> downloadProcess.py
> sharedFunctions.py
> someHelper.py
> useSharedFunction.py
The download_module/__init__.py has the following code:
from .sharedFunctions import stringArgumentToDate
from .downloadProcess import downloadProcessMethod
The sharedFunctions.py file contains the following function:
def stringArgumentToDate(arg):
dateformat = "%m/%d/%Y"
date = None
if arg.isnumeric():
date = datetime.fromtimestamp(int(arg))
if date == None:
date = datetime.strptime(arg, dateformat)
return date
Then on the useSharedFunction.py I try to import the shared function and use it like this.
from download_module import stringArgumentToDate
from download_module import downloadProcessMethod
def main():
arg = '03/14/2022'
dateArg = stringArgumentToDate(arg)
if __name__ == '__main__':
main()
When I try to run this by using python3 useSharedFunction.py I got the following error:
Traceback (most recent call last):
File "useSharedFunction.py", line 4, in <module>
from download_module import stringArgumentToDate
File "/Users/jacobo/Documents/project/download_module/__init__.py", line 2, in <module>
from .download_module import downloadAndProcessMethod
File "/Users/jacobo/Documents/project/download_module/downloadProcess.py", line 10, in <module>
from sharedFunctions import stringArgumentToDate, otherFunction
ModuleNotFoundError: No module named 'sharedFunctions'
I do believe the error is in downloadProcess since at the beggining of the file we got this import:
from sharedFunctions import stringArgumentToDate, otherFunction
from someHelper import Helper
Which refers to sibling files.
However I'm unsure what will be a proper fix to allow to run the downloadProcess.py main independently but also, being able to call it one of its method from a root or any other file out of the module.
Consider this structure:
┬ module
| ├ __init__.py
| ├ importing_submodule.py
| └ some_submodule.py
├ __main__.py
├ some_submodule.py
└ module_in_parent_dir.py
with content:
__main__.py
import module
/module/__init__.py
from . import importing_submodule
/module/importing_submodule.py
from some_submodule import SomeClass
/module/some_submodule.py
print("you imported from module")
class SomeClass:
pass
/some_submodule.py
print("you imported from root")
class SomeClass:
pass
/module_in_parent_dir.py
class SomeOtherClass:
pass
How sibling import works
(skip this section if you know already)
Now lets run __main__.py and it will say "you imported from root".
But if we change code a bit..
/module/importing_submodule.py
from module.some_submodule import SomeClass
It now says "You imported from module" as we wanted, probably with scary red line in IDE saying "Unresolved reference" if you didn't config working directory in IDE.
How this happen is simple: script root(Current working directory) is decided by main script(first script that's running), and python uses namespaces.
Python's import system uses 2 import method, and for convenience let's call it absolute import and relative import.
Absolute import: Import from dir listed in sys.path and current working directory
Relative import: Import relative to the very script that called import
And what decide the behavior is whether we use . at start of module name or not.
Since we imported by from some_submodule without preceeding dot, python take it as 'Absolute import'(the term we decided earlier).
And then when we also specified module name like from module.some_submodule python looks for module in path list or in current working directory.
Of course, this is never a good idea; script root can change via calls like os.chdir() then submodules inside module folder may get lost.
Therefore, the best practices for sibling import is using relative import inside module folder.
/module/importing_submodule.py
from .some_submodule import SomeClass
Making script that work in both way
To make submodule import it's siblings when running as main script, yet still work as submodule when imported by other script, then use try - except and look for ImportError.
For importing_submodule.py as an example:
/module/importing_submodule.py
try:
from .some_submodule import SomeClass
except ImportError:
# attempted relative import with no known parent package
# because this is running as main script, there's no parent package.
from some_submodule import SomeClass
Importing modules from parent directory is a bit more tricky.
Since submodule is now main script, relative import to parent level directory doesn't work.
So we need to add the parent directory to sys.path, when the script is running as main script.
/module/importing_submodule.py
try:
from .some_submodule import SomeClass
except ImportError:
# attempted relative import with no known parent package
# because this is running as main script, there's no parent package.
from some_submodule import SomeClass
# now since we don't have parent package, we just append the path.
from sys import path
import pathlib
path.append(pathlib.Path(__file__).parent.parent.as_posix())
print("Importing module_in_parent_dir from sys.path")
else:
print("Importing module_in_parent_dir from working directory")
# Now either case we have parent directory of `module_in_parent_dir`
# in working dir or path, we can import it
# might need to suppress false IDE warning this case.
# noinspection PyUnresolvedReferences
from module_in_parent_dir import SomeOtherClass
Output:
"C:\Program Files\Python310\python.exe" .../module/importing_module.py
you imported from module
Importing module_in_parent_dir from sys.path
Process finished with exit code 0
"C:\Program Files\Python310\python.exe" .../__main__.py
you imported from module
Importing module_in_parent_dir from working directory
Process finished with exit code 0
Im new to python and have looked at various stack overflow posts. i feel like this should work but it doesnt. How do you import a class from another file in python?
This folder structure
src/example/ClassExample
src/test/ClassExampleTest
I have this class
class ClassExample:
def __init__(self):
pass
def helloWorld(self):
return "Hello World!"
I have this test class
import unittest
from example import ClassExample
class ClassExampleTest(unittest.TestCase):
def test_HelloWorld(self):
hello = ClassExample()
self.assertEqual("Hello World!", hello.helloWorld())
if __name__ == '__main__':
unittest.main()
When the unit test runs the object is None:
AttributeError: 'NoneType' object has no attribute 'helloWorld'
What's wrong with this? How do you import a class in python?
If you're using Python 3, then imports are absolute by default. This means that import example will look for an absolute package named example, somewhere in the module search path.
So instead, you probably want a relative import. This is useful when you want to import a module that is relative the module doing the importing. In this case:
from ..example.ClassExample import ClassExample
I'm assuming that your folders are Python packages, meaning that they contain __init__.py files. So your directory structure would look like this.
src
|-- __init__.py
|-- example
| |-- __init__.py
| |-- ClassExample.py
|-- test
| |-- __init__.py
| |-- ClassExampleTest.py
I know how to import a package or module, but I meet a quite strange problem.
If I run swmm5_extend_function/example.py, everything is fine. However, when I run example.py, errors occur:
Traceback (most recent call last):
File "example.py", line 2, in <module>
from swmm5_extend_function.Swmm5Extend import SWMM5ReadInp
File "C:\project\swmm5_extend_function\Swmm5Extend.py", line 1, in <module>
import swig.SWMM5ReadInpFile as swmm
ModuleNotFoundError: No module named 'swig'
Here is my project structure:
project/
-- example.py
-- ......
-- swmm5_extend_function/
-- __init__.py
-- example.py
-- Swmm5Extend.py
-- swig/
-- __init__.py
-- SWMM5ReadInpFile.py
-- ....
Here is code of each .py file:
swmm5_extend_function/Swmm5Extend.py
import swig.SWMM5ReadInpFile as swmm
class SWMM5ReadInp(object):
pass
swmm5_extend_function/example.py
from Swmm5Extend import SWMM5ReadInp
example.py
from swmm5_extend_function.Swmm5Extend import SWMM5ReadInp
I want to know why this strange error happens.
For a better explanation, I've created the following folder structure:
test/
-- __init__.py
-- greeter.py # Greets in German
-- subfolder/
-- __init__.py
-- greeter.py # Greets in Italian
-- test.py
-- deepfolder/
-- __init__.py
-- greeter.py # Greets in France
As you may notice, we have 3 files with the same name, each one greets in a different language using a function with the same name. The only function in a greeter.py file is:
def says():
print("Hello World!")
IMPORT FROM THE SAME FOLDER
If from test.py file we import greeter and run the says function, we'll have:
import greeter as greeter
greeter.says()
Output:
Buongiorno Mondo! # Italian output
IMPORT FROM A SUBFOLDER
But what if we want to import from a subfolder?
To import from a subfolder (i.e., deepfolder/), we simply add an empty __init__.py file into the folder, then we can specify the path in the import:
import deepfolder.greeter as greeter
greeter.says()
Output:
Bonjour le Monde! # France output
IMPORT FROM A PARENT FOLDER
At last, you may want to import from a parent folder.
You should try to have your main running file at the top of the folder tree, but things happens and you find yourself looking to import a module from a parent folder.
For doing this, you need to add the parent folder to the sys.path:
import sys
sys.path.append("/path/to/dir")
from test import greeter as greeter
greeter.says()
Output:
Guten Morgen Welt! # German output
Importing scripts and modules isn't really the most pythonic way to solve things, you may want to have a look on Python's documentation about packages.
TL;DR
In your project/example.py use
import swmm5_extend_function.swig.SWMM5ReadInpFile as swmm
instead of
import swig.SWMM5ReadInpFile as swmm
I have a python script saved to file.
test1.py
import maya.cmds as cmds
import sys
def process():
print 'working'
I need to run the function from this script inside another python script, inside maya. I have:
import sys
sys.path.append('J:\scripts\src\maya')
from test1 import process
test1.process()
but it gives me:
from test1 import process
# Error: ImportError: file <maya console> line 4: cannot import name process #
What am I doing wrong here?
('import test1' gives no error, so the path is correct).
Solution:
Reload your test1 module, my guess is that you created and imported test1 without the process method inside. To effectively reload a module, you can't just re-import it, you have to use the reload.
reload(test1)
from test1 import process
Other observations:
Use raw string when using paths:
Add r before your path string:
sys.path.append(r'J:\scripts\src\maya')
Python Doc
The backslash () character is used to escape characters that
otherwise have a special meaning, such as newline, backslash itself,
or the quote character. String literals may optionally be prefixed
with a letter 'r' or 'R'; such strings are called raw strings and use
different rules for interpreting backslash escape sequences.
Check the way you import your modules:
You wrote, which is not valid:
from test1 import process
test1.process()
But you can have either way:
import test1
test1.process()
or:
from test1 import process
process()
To sum-up these are the ways to import a module or package:
>>> import test_imports
>>> from test_imports import top_package
>>> from test_imports import top_module
test_imports.top_module
>>> from test_imports.top_package import sub_module
test_imports.top_package.sub_module
assuming you have the following hierarchy:
J:\scripts\src\maya # <-- you are here
.
`-- test_imports
|-- __init__.py
|-- top_package
| |-- __init__.py
| |-- sub_package
| | |-- __init__.py
| | `-- other_module.py
| |-- sub_module.py
`-- top_module.py
Credits goes to Sam & Max blog (French)
First you need to add the script location path in system path.
and if you are making this as a python package than do not forget to add
a __init__.py file in the package directory.
Than you can execute following code.
import sys
path = r'J:\scripts\src\maya'
if path not in sys.path:
sys.path.append(path)
import test1
test1.process()
It's my first post here, please understand that I'm a beginner and that I'm learning "on-the-job".
Can someone explain how can I import files from a different module in a Maya python script? I'm getting the following error:
Error: ImportError: file E:/.../bin/mainScript.py line 17: No module named tools
Here are my directories and codes:
Main_folder\
|-- install.mel
|-- ReadMeFirst.txt
`-- bin\
|-- __init__.py
|-- mainScript.py
|-- tools.py
`-- presets\
|-- bipedPreset.txt
|-- quadrupedPreset.txt
`-- [...] .txt
I'm trying to import tools.py in mainScript.py
EDIT:
Ok, as it won't fit in a comment I edit this post to add precisions. I moved the 'Main_folder' on my Desktop and ran the script once again in Maya. It still doesn't work but I have a more complete error traceback. Here it is:
# Error: Error in maya.utils._guiExceptHook:
# File "C:\Program Files\Autodesk\Maya2014\Python\lib\site-packages\maya\utils.py", line 332, in formatGuiException
# result = u'%s: file %s line %s: %s' % (exceptionType.__name__, file, line, exceptionMsg)
# UnicodeDecodeError: 'ascii' codec can't decode byte 0xfc in position 11: ordinal not in range(128)
#
# Original exception was:
# Traceback (most recent call last):
# File "<maya console>", line 3, in <module>
# File "C:/Users/UKDP/Desktop/Main_folder/bin/mainScript.py", line 17, in <module>
# from tools import ClassTest
# ImportError: No module named tools #
You need to make sure any importable modules are on you python path.
If your files are in E:/main_folder you'll need to do
import sys
sys.path.append("E:/main_folder")
import bin.tools
import bin.mainScript
and so on. The way you have it set up (with 'bin/__init__.py') you're telling python that the module is named bin and it has submodules named 'mainScript' and 'tools'. Long discussion here
Try importing like:
>>>import san.libs.stringops
Where the san is dir(in san create __init__.py)
libs is a dir(in libs create __init__.py)
and stringops.py is imported
Try this
I have a folder that is on my desktop and the folder is called Combo and has a script called combo.py and here is how I access it:
import sys #imports system's directory module
sys.path.insert(0,"C:/Users/dharmin.doshi/Desktop") #Add your directory, do not add your folder in the path unless the script is inside of folder inside of folder. Path needs to be in quotes and 0 is added as needed by the argument.
from Combo(Folder name) import combo (my python script)
reload (combo) #Optional to reload it every time you make changes to the script.
combo.run() #Call the function that runs the code.
In your case if you need to access tools.py then your path will be like:
sys.path.insert(0, "MainFolder/bin")
import tools
Hope this helps :)
exe1.py
import san.libs.stringops
import san.libs.maths
import san.libs.utils.ran
import san.printing
newstr = san.libs.stringops.add2string("hey","hi")
san.printing.myprint(newstr)
result = san.libs.maths.add(2,3)
san.printing.myprint(result)
random_number = san.libs.utils.ran.getnumber()
san.printing.myprint(random_number)