Run python project without relative imports - python-3.x

There is a lot written about how to structure the files and folders in a Python project. It is also written that relative imports should avoided. But I do not see how.
Here is an example project structure (imagine this as an upstream code repository).
Foo
├── foo
│   ├── bar.py
│   ├── __init__.py
│   └── __main__.py
└── run.sh
The __init__.py is empty and just indicates that foo is a package.
I run the project with the run.sh script.
#!/usr/bin/env bash
python3 -m foo
This is my __main__.py showing the two approaches to import bar.py.
#!/usr/bin/env python3
# works
#from . import bar
# do not work
import bar
if __name__ == '__main__':
bar.do()
The point is that only the first (currently out commented) works. When I use the second one I got this error message when running run.sh.
ImportError: No module named 'bar'

Well, that's because there is no bar.
Rather, it is foo.bar.
When run in the context of foo, the . dot indicates foo,
so .bar is foo.bar.
The solution in your MWE would be to modify the import line this way
from foo import bar

Related

import both python 2 and 3 modules in a single python file for conditional execution

I am trying to conditionally execute two python scripts in a single python script like so.
# main.py
import sys
if sys.version_info[0] > 2:
from scripts.script3 import script3
script3()
else:
from scripts.script2 import script2
script2()
This version works with a Python3 interpreter but if I switch to Python 2 it breaks. For Python2 I created a work around using an __init__.py in the scripts folder but when I switched back to Python3 it broke that version.
main.py
/scripts/
├── script2.py (Python2)
└── script3.py (Python3)
Is there a way to achieve this?
Thanks all.
With the following directory layout, I think I accomplished what you want to do:
.
├── app
│ ├── __init__.py
│ └── a.py
└── scripts
├── __init__.py
├── b.py
└── c.py
% python3 -m app.a
Hello, Python 3!
% python -m app.a
Good day, Python 2.
app/a.py looks like this:
import sys
if sys.version_info[0] > 2:
from scripts.b import fun
else:
from scripts.c import fun
fun()
Edit: if you want to go full minimal, the following appears to work as well:
.
├── a.py
└── scripts
├── __init__.py
├── b.py
└── c.py
Then you can just run a.py with python a.py (or python3 a.py).

Module not recognising root directory for Python imports

I have a Python project that uses the MicroKernel pattern where I want each of the modules to be completely independent. I import each of the modules into the kernel and that works fine. However, when I am in a module I want the root of the module to be the module dir. This is the part that is not working.
Project structure;
.
├── requirements.txt
├── ...
├── kernel
│   ├── config.py
│   ├── main.py
│   ├── src
│   │   ├── __init__.py
│   │   ├── ...
│   └── test
│   ├── __init__.py
│   ├── ...
├── modules
│   └── img_select
│   ├── __init__.py
│   ├── config.py
│   ├── main.py
│   └── test
│   ├── __init__.py
│   └── test_main.py
If I import from main import somefunction in modules/img_select/test/test_main.py I get the following error:
ImportError: cannot import name 'somefunction' from 'main' (./kernel/main.py)
So it clearly does not see the modules/img_select as the root of the module, which leads to the following question:
How can I set the root for imports in a module?
Some additional info, I did add the paths with sys.path in the config files;
kernel/config.py;
import os
import sys
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
MODULES_DIR = os.path.join(ROOT_DIR, '../modules')
sys.path.insert(0, os.path.abspath(MODULES_DIR))
modules/img_select/config.py;
import os
import sys
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(ROOT_DIR))
And my python version is 3.7.3
I do realise that there are a lot of excellent resources out there, but I have tried most approaches and can't seem to get it to work.
I'm not sure what main you are trying to import from. I think python is confused from the pathing as well. How does test_main.py choose which main to run? Typically when you have a package (directory with __init__.py) you import from the package and not individual modules.
# test_main.py
# If img_select is in the path and has __init__.py
from img_select.main import somefunction
If img_select does not have __init__.py and you have img_select in the path then you can import from main.
# test_main.py
# If img_select is in the path without __init__.py
from main import somefunction
In your case I do not know how you are trying to indicate which main.py to import from. How are you importing and calling the proper config.py?
You might be able to get away with changing the current directory with os.chdir. I think your main problem is that img_select is a package with __init__.py. Python doesn't like to use from main import ... when main is in a package. Python is expecting from img_select.main import ....
Working Directory
If you are in the directory modules/img_select/test/ and call python test_main.py then this directory is known as your working directory. Your working directory is wherever you call python. If you are in the top level directory (where requirements.txt lives) and call python modules/img_select/test/test_main.py then the top level directory is the working directory. Python uses this working directory as path.
If kernel has an __init__.py then python will find kernel from the top level directory. If kernel is not a package then you need add the kernel directory to the path in order for python to see kernel/main.py. One way is to modify sys.path or PYTHONPATH like you suggested. However, if your working directory is modules/img_select/test/ then you have to go up several directories to find the correct path.
# test_main.py
import sys
TEST_DIR = os.path.dirname(__file__) # modules/img_select/test/
IMG_DIR = os.path.dirname(TEST_DIR)
MOD_DIR = os.path.dirname(IMG_DIR)
KERNEL_DIR = os.path.join(os.path.dirname(MOD_DIR), 'kernel')
sys.path.append(KERNEL_DIR)
from main import somefunction
If your top level directory (where requirements.txt lives) is your working directory then you still need to add kernel to the path.
# modules/img_select/test/test_main.py
import sys
sys.path.append('kernel')
As you can see this can change depending on your working directory, and you would have to modify every running file manually. You can get around this with abspath like you are doing. However, every file needs the path modified. I do not recommend manually changing the path.
Libraries
Python pathing can be a pain. I suggest making a library.
You just make a setup.py file to install the kernel or other packages as a library. The setup.py file should be at the same level as requirements.txt
# setup.py
"""
setup.py - Setup file to distribute the library
See Also:
* https://github.com/pypa/sampleproject
* https://packaging.python.org/en/latest/distributing.html
* https://pythonhosted.org/an_example_pypi_project/setuptools.html
"""
from setuptools import setup, Extension, find_packages
setup(name='kernel',
version='0.0.1',
# Specify packages (directories with __init__.py) to install.
# You could use find_packages(exclude=['modules']) as well
packages=['kernel'], # kernel needs to have __init__.py
include_package_data=True,
)
The kernel directory needs an __init__.py. Install the library as editable if you are still working on it. Call pip install -e . in the top level directory that has the setup.py file.
After you install the library python will have copied or linked the kernel directory into its site-packages path. Now your test_main.py file just needs to import kernel correctly
# test_main.py
from kernel.main import somefunction
somefunction()
Customizing init.py
Since kernel now has an __init__.py you can control the functions available from importing kernel
# __init__.py
# The "." indicates a relative import
from .main import somefunction
from .config import ...
try:
from .src.mymodule import myfunc
except (ImportError, Exception):
def myfunc(*args, **kwargs):
raise EnvironmentError('Function not available. Missing dependency "X".')
After changing the __init__.py you can import from kernel instead of kernel.main
# test_main.py
from kernel import somefunction
somefunction()
If you delete the NumPy (any library) from the site manager and save that folder in another location then use:
import sys
sys.path.append("/home/shubhangi/numpy") # path of numpy dir (which is removed from site manager and paste into another directory)
from numpy import __init__ as np
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr)
print(type(arr))

Python cannot import py files in the same folder

VSCode Version: 1.41.1
OS Version:Ubuntu 18.04
Steps to Reproduce:
# tree:
.
├── demo1
│ ├── __init__.py
│ └── test.py
├── __init__.py
├── auto.py
# auto.py
def func():
print("1")
# test.py
from auto import func
func()
Use examples to solve problems that arise in a project
Run the test.py file, and I get "ModuleNotFoundError: No module named 'func'"
I used 'CTRL '+ left mouse button in test.py to jump to func
The same code can be run in pycharm
If you run test.py directly then you need to add the parent folder to PYTHONPATH. Try:
import sys
sys.path.append("..\<parent_folder>")
from auto import func
Otherwise, if you merely want to import test.py in another .py file, you can use relative import of python
from . import auto #another dot '.' to go up two packages
auto.func()
Reference
Add this in test.py, before import:
import sys
sys.path.insert(0, "/path/to/project/root/directory")
For me it's not a good file organization. A better practice might be as below:
Let your project file tree be like:
.
├── __init__.py
├── lib
│ ├── auto.py
│ └── __init__.py
└── test.py
And write test.py like:
from lib.auto import func
func()
Simple one-line solution
from ... import auto
and call the function by using auto.func().

How to import from a sibling directory in python3?

I have the following file structure:
bot
├── LICENSE.md
├── README.md
├── bot.py # <-- file that is executed from command line
├── plugins
│   ├── __init__.py
│   ├── debug.py
│   └── parsemessages.py
├── helpers
│   ├── __init__.py
│   ├── parse.py
│   └── greetings.py
└── commands
   ├── __init__.py
   └── search.py
bot.py, when executed from the command line, will load in everything in the plugins directory.
I want plugins/parsemessages.py to import parse from the helpers directory, so I do that:
# parsemessages.py
from ..helpers import parse
parse.execute("string to be parsed")
I run python3 bot.py from the command line.
I get the following error:
File "/home/bot/plugins/parsemessages.py", line 2, in <module>
from ..helpers import parse
ValueError: attempted relative import beyond top-level package
So I change two dots to one:
# parsemessages.py
from .helpers import parse
parse.execute("string to be parsed")
...but I get another error:
File "/home/bot/plugins/parsemessages.py", line 2, in <module>
from .helpers import parse
ImportError: No module named 'plugins.helpers'
How can I get this import to work?
It's worth noting that I'm not attempting to make a package here, this is just a normal script. That being said, I'm not willing to mess around with sys.path - I want this to be clean to use.
Additionally, I want parse to be imported as parse - so for the example above, I should be typing parse.execute() and not execute().
I found this post and this post, but they start with a file that's quite deep in the file structure (mine is right at the top). I also found this post, but it seems to be talking about a package rather than just a regular .py.
What's the solution here?
You could remove the dots, and it should work:
# parsemessages.py
from helpers import parse
parse.execute("string to be parsed")
That's probably your best solution if you really don't want to make it a package. You could also nest the entire project one directory deeper, and call it like python3 foo/bot.py.
Explanation:
When you're not working with an actual installed package and just importing stuff relative to your current working directory, everything in that directory is considered a top-level package. In your case, bot, plugins, helpers, and commands are all top-level packages/modules. Your current working directory itself is not a package.
So when you do ...
from ..helpers import parse
... helpers is considered a top-level package, because it's in your current working directory, and you're trying to import from one level higher than that (from your current working directory itself, which is not a package).
When you do ...
from .helpers import parse
... you're importing relative to plugins. So .helpers resolves to plugins.helpers.
When you do ...
from helpers import parse
... it finds helpers as a top-level package because it's in your current working directory.
If you want to execute your code from the root, my best answer to this is adding to the Path your root folder with os.getcwd().
Be sure your sibling folder has a init.py file.
import os
os.sys.path.insert(0, os.getcwd())
from sibling import module

How to import module so that it can be accessed from any directory?

I have the following project structure:
x/
a.py
b.py
main.py
a.py:
from b import *
class A:
.....
main.py
from x.a import A
.....
I want to be able to run a.py independently as well as access its functionality through main.py
I'm able to run a.py but when I try to import it as shown in main.py, the module is unable to be found. I can fix this problem by adding the following line to a.py:
sys.path.append(os.path.join(os.path.dirname(__file__)))
but this feels hacky. Is there a better way to achieve the desired behavior?
You need to mark the directory "x" as a package to be able to load anything off it.
As stated in the official documentation of Python, you have to create an empty "__init__.py" file in the root of "x" to mark it off as a package.
Then your directory structure should look something like this:
.
└── x
├── __init__.py
├── a.py
└── b.py
└── main.py
You may want to edit "a.py" to load the modules relative to the package it is in using a period to represent the current package:
# x/a.py
from .b import *
class A:
# rest of your code

Resources