Access file from nested directory under PYTHONPATH - python

I have checked many SO questions, but didn't help me to solve my issue.
I have a folder structure:
|--test/foo.py
|--library/ #This is set as my PYTHONPATH
|--|--file1.py
|--|--folder1
|--|--|--util.py
I am trying to access util.py from foo.py.
Note: At this point i am able to access all the files under library from test folder. But whenever i try to access library/folder1/util.py, it gives an error saying "ImportError: No module named util"
I have tried this so far:
foo.py
import os
import sys
import file1
sys.path.insert(0, '/folder1/')
import util
util.function_name
#do something
This approach works but then i am not able to use "library/file1.py".
Is there any cleaner way to avoid this?
Note: These are only folder structure (I am maintaining to differentiate files), not modules, (so i believe i can not use __init__.py and something like import utils.functionname)

If you want to brute force it, run this from your top-level directory (in your case, the unnamed directory that contains test/ and library/):
export PYTHONPATH=$(myarray=($(find "$(pwd -P)" -type d)) \
&& printf '%s\n' "$(IFS=:; printf '%s' "${myarray[*]}")")
This will add your top-level directory and every subdirectory to the python path. No more nonsense.

Related

How do I run a Python script from a subdirectory without breaking upper-level imports?

I have a very simple scenario like this:
example/
common.py
folder1/
script.py
where script.py file should import common.py module.
If I cd into folder1/ and run the script (i.e. by calling python3 script.py on the command line), the import breaks with the usual error ModuleNotFoundError: No module named 'common'.
I know I could turn the whole parent folder into a package by adding __init__.py files in each subdirectory, however this solution still prevents me to run the script directly from inside folder1/.
How can I fix this?
If you turn both directories into python packages, and the top directory is in your PYTHONPATH, you can run script.py like so:
python3 -m example.folder1.script
If you need to add a parent directory to the search path, you can always do:
import os
import sys
DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, DIR)
os.path.realpath(__file__) gets the realpath to the file, and os.path.dirname gets the directory name of this file. Doing it twice gets the example directory here. Now, since this is added to the search path, you can import common.
However, you should really consider having a dedicated scripts or bin directory, rather than try to run scripts from subdirectories. Think about writing a library, and import this library rather than individual files.

Basic Python import mechanics

I have the following directory tree:
project/
A/
__init__.py
foo.py
TestA/
__init__.py
testFoo.py
the content of testFoo is:
import unittest
from A import foo
from the project directory I run python testA/testFoo.py
I get a ModuleNotFoundError No module named A
I have two question: how to improt and run A.foo from TestA.testFoo and why is it so difficult to grasp the import logic in Python? Isn't there any debug trick to solve this kind of issues rapidly, I'm sorry I have to bother you with such basics questions?
When your are executing a file an environment variable called python path is generated, python import work with this variable to find your file to import, this path is generated with the path of the file you are executing and it will search in the current directory and sub directories containing an __init__.py file, if you want to import from a directory on the same level you need to modify your python path or change the architecture of your project so the file executed is always on top level.
you can include path to your python path like this :
import sys
sys.path.insert(0, "/path/to/file.py")
You can read more on import system : https://docs.python.org/3/reference/import.html
The best way in my opinion is to not touch the python path and include your test directoy into the directory where tested files are:
project/
A/
__init__.py
foo.py
TestA/
__init__.py
testFoo.py
Then run the python -m unittest command into your A or project directory, it will search into your current and sub directories for test and execute it.
More on unittest here : https://docs.python.org/3/library/unittest.html
Add the folder project/testA to the system pythonpath first:
import sys
sys.path.insert(0, "/path/to/pythonfile")
and try the import again.
Can you try this ?
Create an empty file __init__.py in subdirectory TestA. And add at the begin of main code
from __future__ import absolute_import
Then import as below :
import A.foo as testfoo
The recommended way in py3 may be like below
echo $pwd
$ /home/user/project
python -m testA.testFoo
The way of execute module python -m in python is a good way to replace relative references。
You definitely cannot find A because python need look from sys.path, PYTHONPATH to find the module.
And python will automatically add current top level script to sys.path not currently directory to sys.path. So if you add print(sys.path) in testFoo.py, you will see it only add project/TestA to the sys.path.
Another word, the project did not be included in sys.path, then how python can find the module A?
So you had to add the project folder to sys.path by yourself, and, this just needed in top script, something like follows:
import unittest
import sys
import os
file_path = os.path.abspath(os.path.dirname(__file__)).replace('\\', '/')
lib_path = os.path.abspath(os.path.join(file_path, '..')).replace('\\', '/')
sys.path.append(lib_path)

ImportError: No module named even after I appended to system path

I have the following files:
./ElementExtractor.py
./test/ElementExtractorTest.py
In ElementExtractorTest.py, I am trying to import ElementExtractor.py like this:
import sys
sys.path.append('../')
import ElementExtractor
However, I am getting:
ImportError: No module named 'ElementExtractor'
How come it's not seen?
Is there a simple way to import another class with a relative reference?
The general answer to this question should be don't, I guess. Messing with relative paths means that the path is relative to the place from where you're calling it. That's why PYTHONPATH is worth embracing instead.
Let's assume, your directory structure looks like this:
./projects/myproject/ElementExtractor.py
./projects/myproject/test/ElementExtractorTest.py
Now, you're calling your script like this:
[./projects/myproject]$ python3.5 ./test/ElementExtractorTest.py
Your current directory is myproject and in ElementExtractorTest.py you're adding ../ directory to sys.path. This means, that ./projects/myproject/../ (i.e.: ./projects) is effectively added to your PYTHONPATH. That' why Python is unable to locate your module.
Your code would work from test directory though:
[./projects/myproject/test]$ python3.5 ./ElementExtractorTest.py
Now, by adding .. to sys.path you are effectively adding ./projects/myproject/test/../ (i.e. ./projects/myproject) to sys.path, so the module can be found and imported.
my folder structure
./test.py
./Data.py
from test.py
import Data
obj = Data.Myclass()
update
in your case
from ..ElementExtractor import MyClass
Hope this helps :)

Path appended but python does not find module

I have the following structure:
~/git/
~/git/folder1
~/git/folder2
in ~/git/folder1 I have main.py, which imports doing the following:
import folder2.future_data as future_data
which throws the following error:
import folder2.future_data as f_d
ImportError: No module named folder2.future_data
Despite my $PATH containing
user#mac-upload:~$ echo $PATH
/home/user/anaconda2/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/user/git/folder2
Why am I unable to import from folder2 despite it being in my path?
Am I missing something?
Try putting an empty __init__.py file in each directory (~/git, ~/git/folder1, and ~/git/folder2). Then do export PYTHONPATH=${HOME}/git:$PYTHONPATH (assuming bash shell).
This will also allow you to just set your PYTHONPATH once at the top level and be done with it. If you add more directories (modules) that you need to import, you can just keep adding __init__.py files to your structure (instead of having to constantly modify your PYTHONPATH every time your file/directory structure changes).
You can explicitly added the path inside the main.py script before you doing import
import sys
sys.path.append(r'~/git/folder2')
import future_data

Importing correctly with pytest

I just got set up to use pytest with Python 2.6. It has worked well so far with the exception of handling "import" statements: I can't seem to get pytest to respond to imports in the same way that my program does.
My directory structure is as follows:
src/
main.py
util.py
test/
test_util.py
geom/
vector.py
region.py
test/
test_vector.py
test_region.py
To run, I call python main.py from src/.
In main.py, I import both vector and region with
from geom.region import Region
from geom.vector import Vector
In vector.py, I import region with
from geom.region import Region
These all work fine when I run the code in a standard run. However, when I call "py.test" from src/, it consistently exits with import errors.
Some Problems and My Solution Attempts
My first problem was that, when running "test/test_foo.py", py.test could not "import foo.py" directly. I solved this by using the "imp" tool. In "test_util.py":
import imp
util = imp.load_source("util", "util.py")
This works great for many files. It also seems to imply that when pytest is running "path/test/test_foo.py" to test "path/foo.py", it is based in the directory "path".
However, this fails for "test_vector.py". Pytest can find and import the vector module, but it cannot locate any of vector's imports. The following imports (from "vector.py") both fail when using pytest:
from geom.region import *
from region import *
These both give errors of the form
ImportError: No module named [geom.region / region]
I don't know what to do next to solve this problem; my understanding of imports in Python is limited.
What is the proper way to handle imports when using pytest?
Edit: Extremely Hacky Solution
In vector.py, I changed the import statement from
from geom.region import Region
to simply
from region import Region
This makes the import relative to the directory of "vector.py".
Next, in "test/test_vector.py", I add the directory of "vector.py" to the path as follows:
import sys, os
sys.path.append(os.path.realpath(os.path.dirname(__file__)+"/.."))
This enables Python to find "../region.py" from "geom/test/test_vector.py".
This works, but it seems extremely problematic because I am adding a ton of new directories to the path. What I'm looking for is either
1) An import strategy that is compatible with pytest, or
2) An option in pytest that makes it compatible with my import strategy
So I am leaving this question open for answers of these kinds.
The issue here is that Pytest walks the filesystem to discover files that contain tests, but then needs to generate a module name that will cause import to load that file. (Remember, files are not modules.)
Pytest comes up with this test package name by finding the first directory at or above the level of the file that does not include an __init__.py file and declaring that the "basedir" for the module tree containing a module generated from this file. It then adds the basedir to sys.path and imports using the module name that will find that file relative to the basedir.
There are some implications of this of which you should beware:
The basepath may not match your intended basepath in which case the module will have a name that doesn't match what you would normally use. E.g., what you think of as geom.test.test_vector will actually be named just test_vector during the Pytest run because it found no __init__.py in src/geom/test/ and so added that directory to sys.path.
You may run into module naming collisions if two files in different directories have the same name. For example, lacking __init__.py files anywhere, adding geom/test/test_util.py will conflict with test/test_util.py because both are loaded as import test_util.py, with both test/ and geom/test/ in the path.
The system you're using here, without explicit __init__.py modules, is having Python create implicit namespace packages for your directories. (A package is a module with submodules.) Ideally we'd configure Pytest with a path from which it would also generate this, but it doesn't seem to know how to do that.
The easiest solution here is simply to add empty __init__.py files to all of the subdirectories under src/; this will cause Pytest to import everything using package/module names that start with directory names under src/.
The question How do I Pytest a project using PEP 420 namespace packages? discusses other solutions to this.
import looks in the following directories to find a module:
The home directory of the program. This is the directory of your root script. When you are running pytest your home directory is where it is installed (/usr/local/bin probably). No matter that you are running it from your src directory because the location of your pytest determines your home directory. That is the reason why it doesn't find the modules.
PYTHONPATH. This is an environment variable. You can set it from the command line of your operating system. In Linux/Unix systems you can do this by executing: 'export PYTHONPATH=/your/custom/path' If you wanted Python to find your modules from the test directory you should include the src path in this variable.
The standard libraries directory. This is the directory where all your libraries are installed.
There is a less common option using a pth file.
sys.path is the result of combining the home directory, PYTHONPATH and the standard libraries directory. What you are doing, modifying sys.path is correct. It is something I do regularly. You could try using PYTHONPATH if you don't like messing with sys.path
If you include an __init__.py file inside your tests directory, then when the program is looking to set a home directory it will walk 'upwards' until it finds one that does not contain an init file. In this case src/.
From here you can import by saying :
from geom.region import *
you must also make sure that you have an init file in any other subdirectories, such as the other nested test directory
I was wondering what to do about this problem too. After reading this post, and playing around a bit, I figured out an elegant solution. I created a file called "test_setup.py" and put the following code in it:
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
I put this file in the top-level directory (such as src). When pytest is run from the top-level directory, it will run all test files including this one since the file is prefixed with "test". There are no tests in the file, but it is still run since it begins with "test".
The code will append the current directory name of the test_setup.py file to the system path within the test environment. This will be done only once, so there are not a bunch of things added to the path.
Then, from within any test function, you can import modules relative to that top-level folder (such as import geom.region) and it knows where to find it since the src directory was added to the path.
If you want to run a single test file (such as test_util.py) instead of all the files, you would use:
pytest test_setup.py test\test_util.py
This runs both the test_setup and test_util code so that the test_setup code can still be used.
Are so late to answer that question but usining python 3.9 or 3.10 u just need to add __init__.py folder in tests folders.
When u add this file python interprets this folders as a module.
Wold be like this
src/
main.py
util.py
test/
__init__.py
test_util.py
geom/
vector.py
region.py
test/
__init__.py
test_vector.py
test_region.py
so u just run pytest.
Sorry my poor english
Not the best solution, but maybe the fastest one:
cd path/python_folder
python -m pytest python_file.py

Categories

Resources