Using common constants module leads to circular import - python

I want to import constants from a constants module from two different modules, but I get the following error:
Traceback (most recent call last):
File "C:\Temp\tmp\pycircular\pycircular\pycircular.py", line 2, in <module>
from my_classes.foo import Foo
File "C:\Temp\tmp\pycircular\pycircular\my_classes\foo.py", line 1, in <module>
from pycircular.constants import ANOTHER_CONSTANT
File "C:\Temp\tmp\pycircular\pycircular\pycircular.py", line 2, in <module>
from my_classes.foo import Foo
ImportError: cannot import name 'Foo' from partially initialized module 'my_classes.foo' (most likely due to a circular import) (C:\Temp\tmp\pycircular\pycircular\my_classes\foo.py)
My project structure is the following:
|-constants.py
|-my_classes
| |-foo.py
| |-__init__.py
|-pycircular.py
|-__init__.py
# =============
# pycircular.py
# =============
from constants import SOME_CONSTANT
from my_classes.foo import Foo
def main():
print(SOME_CONSTANT)
my_foo = Foo()
my_foo.do_something()
if __name__ == "__main__":
main()
# =============
# foo.py
# =============
from pycircular.constants import ANOTHER_CONSTANT
class Foo:
def do_something(self):
print(ANOTHER_CONSTANT)
# =============
# constants.py
# =============
ANOTHER_CONSTANT = "ANOTHER"
SOME_CONSTANT = "CONSTANT"
I assume that it is the same problem as solved here https://stackoverflow.com/a/62303448/2021763.
But I really do not get why from my_classes.foo import Foo in pycircular.py is called a second time.
Update:
After renaming the package pycircular to pycircular_pack it worked in PyCharm.
But it only works because in Pycharm the option Add content roots to to PYTHONPATH is automatically set.
The output of sys.path is ['C:\\Temp\\tmp\\pycircular\\pycircular_pack', 'C:\\Temp\\tmp\\pycircular', 'C:\\Tools\\miniconda\\envs\\my_env\\python39.zip', 'C:\\Tools\\miniconda\\envs\\my_env\\DLLs', 'C:\\Tools\\miniconda\\envs\\my_env\\lib', 'C:\\Tools\\miniconda\\envs\\my_env', 'C:\\Tools\\miniconda\\envs\\my_env\\lib\\site-packages']
Without the option the output is ['C:\\Temp\\tmp\\pycircular\\pycircular_pack', 'C:\\Tools\\miniconda\\envs\\my_env\\python39.zip', 'C:\\Tools\\miniconda\\envs\\my_env\\DLLs', 'C:\\Tools\\miniconda\\envs\\my_env\\lib', 'C:\\Tools\\miniconda\\envs\\my_env', 'C:\\Tools\\miniconda\\envs\\my_env\\lib\\site-packages']
And without the option I only get it to work with absolute imports.
# pycircular.py
from constants import SOME_CONSTANT
from my_classes.foo import Foo
...
# foo.py
from constants import ANOTHER_CONSTANT

To elaborate based on the comments and edit:
After renaming the package pycircular to pycircular_pack it worked in PyCharm. But it only works because in Pycharm the option Add content roots to to PYTHONPATH is automatically set.
You should make sure the package directory is not set as a content root or source root. The directory hosting the package directory should be set as source root.
C:\Temp\tmp\pycircular # <- source root
|- pycircular_pack # <- not set as anything
| |- constants.py
| |- my_classes
| | |- foo.py
| | |- __init__.py
| |- pycircular.py
| |- __init__.py
|- other_file.py # <- for illustration's sake
Now your sys.path will be set to include C:\Temp\tmp\pycircular only and there will be exactly one way to import things from your module.
Namely,
other_file.py (outside the package) will be able to use the package as pycircular_pack
pycircular_pack/*.py can refer to modules in the pycircular_pack package by either
(e.g.) from .constants import ... (relative import from current package), or
(e.g.) from pycircular_pack.constants import ... (absolute import)
pycircular_pack/my_classes/*.py can refer to modules in the pycircular_pack package by either
(e.g.) from ..constants import ... (relative import from parent package), or
(e.g.) from pycircular_pack.constants import ... (absolute import)
If your pycircular_pack package would contain a runnable script, e.g. a CLI as pycircular_pack/cli.py, then the correct way to run that script on the command line would be to use python -m pycircular_pack.cli; this has Python set up the path just like we want here, where python pycircular_pack/cli.py would not do the right thing.

Related

How to get parent module/package functions in namespace of child package/module in Python?

I have an extremely complicated module and I want to break out the subpackages into individual packages. My first attempt will be for the "utilities" submodule. I want to be able to import everything from the parent package example_utils.py into example_module.utils but I also want example_module.utils to have it's own functions as well.
In the end I want to be able to do the following:
import example_module as em
x = 10
y1 = em.utils.f_parent1(x)
y2 = em.utils.f_child1(x)
# and do this
from example_module.utils import f_parent1, f_child1
# and use the parent module as a standalone
from example_utils import f_parent1, f_parent2
How can I structure my child module example_module to have this functionality?**
Module utilities saved as separate standalone module example_utils.py
def f_parent1(x):
return x
def f_parent2(x,y):
return x+y
This module will be installed in my environment:
pip install path/to/example_module
Larger module (example_module) using example_utils as a dependency
# Directory structure for larger Module
example_module
|++++| __init__.py
|++++| utils
|++++| ++++ | __init__.py
|++++| ++++ | utils.py
Contents of |++++| ++++ | __init__.py
from .utils import *
Contents of |++++| ++++ | utils.py
from example_utils import *
def f_child1(x):
return x**2
Contents of |++++| __init__.py
__version__= "0.1"
__developmental__ = True
# Utilities
from .utils import utils
# =======
# Direct Exports
# =======
_submodules = ["utils"]
__all__ = sorted(__all__)
Apologies in advance if namespace is not the correct term. I get confused with namespace, scope, etc.
With suggestions from #r-ook I found that I could use getattr to grab the function by string name from the parent module. After that, I could add the function into the namespace?scope? of the child module.
example.py
from example_utils import as emu
functions_from_parent = ["f_parent1", "f_parent2"]
__all__ = {"f_child1"}
for function_name in functions_from_parent:
globals()[function_name] = getattr(emu, function_name)
__all__.add(function_name)
__all__ = sorted(__all__)
def f_child1(x):
return x**2

ModuleNotFoundError using "classic" import on module imported from importlib

TL,DR
I've :
mod = import_module('path.module')
After that, what i want/need to do :
from mod.script import func
But that give me :
ModuleNotFoundError: No module named 'mod.script'
Warning : call it using "mod.script.func()" or something like that doesn't respond to my need (project constraint), i search how to have a syntax like "from [module_imported_from_importlib] import XXX"
Introduction :
I need to split an existing code in different folder with multiple version. The goal is to have different part in the app, with each part using a specified version of an other part.
Example tree :
ref.py
block1
__init__.py
- v1
| __init__.py
|- __init__.py
|- script1.py
block2
__init__.py
- v1
| __init__.py
|-- __init__.py
|-- script2.py
- v2
|- __init__.py
|-- __init__.py
|-- script2.py
With this, i need to run :
/block2/v1/script2.py functions in /block1/v1/script1.py
Goal
what i try to do is to specify where script1 should take the "script2" (in v1 or v2 in block2) using the same syntax but just specifying the block without the version (that will change) :
old script1.py :
from script2 import <func>
new script1.py
from block2.script2 import <func>
Code
I've tryed a lot of thing without success, now i'm here that seems to be close to solution but i can't find it (maybe it's not possible ?) :
in block1/v1/init.py :
from importlib import import_module, reload
MODULE = import_module('block2.v1') # With 'block2.v1' defined as a variable somewhere else (eg in ref.py)
reload(MODULE)
in block1/v1/script1.py :
from block1.v1 import MODULE as block2
print(block2)
print(f'block2 : {dir(block2)}')
from block2.script2 import test
in block2/v1/init.py :
from block2.v1 import script2
print(script2)
in block2/v1/script2.py :
def test():
print("hello")
Result of # python block1/v1/script1.py :
<module 'block2.v1.script2' from 'xxx/block2/v1/script2.py'>
<module 'block2.v1.script2' from 'xxx/block2/v1/script2.py'>
<module 'block2.v1' from 'xxx/block1/v1/__init__.py'>
block2 : ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'script2']
Traceback (most recent call last):
File "block1/v1/script1.py", line 17, in <module>
from block2.script2 import test
ModuleNotFoundError: No module named 'block2.script2'
Expected result
Shortly, I expect to have this call syntax in script 1 :
from block2.script2 import test
test()
to be able to run my test function
Thanks a lot for help, I don't know it's really clear !
After lot of test, I finally found something nice.
If you want to use a custom module with a custom name, the best to do is to create a module and giving him the name you want :D
Tree :
/GLOBALCONFIG
|- /module_manager
|- block1.py
|- block2.py
|- blocX..
- module_version_manager
/block1
|- /v1
|- script1.py
|- /v2
|- script1.py
/block2
|- /v1
|- script2.py
|- /v2
|- script2.py
For that I've made a dir named "GLOBALCONFIG" at the project root that contains a .py manager with this :
from importlib import import_module
import sys
import os
block1 = sys.modules['block1'] = import_module('block1.v1')
block2 = sys.modules['block2'] = import_module('block2.v1')
Using this, i can keep all my import, specifying the version only on the "manager" file, eg :
block1/v1/script1.py :
from GLOBALCONFIG.module_manager.block1 import block2
from block2 import script2
if I put script2 in block2/v2 or /v3 or any, i just have to change with the right version to use for block2 in block1.py manager and code will still work
Moreover, if you want :
block1/v1 using block2/v1
block1/v2 using block2/v2
You can create a dict that refer the module / version in your global config :
module_version_manager.py :
block1_modules_versions = {
'block1.v1': {
'block2':'block2.v1',
}
'block1.v2': {
'block2':'block2.v2',
}
}
block2_modules_versions = {
'block2.v2': {
'block1':'block1.v1',
}
'block2.v2': {
'block1':'block1.v2',
}
}
then, in your block1.py manager :
from GLOBALCONFIG.module_version_manager import block1_modules_versions
from importlib import import_module
import sys
import os
block1 = sys.modules['u1'] = import_module('.'.join(str.rsplit(sys.argv[0], "/")[3:5])) # give me the current version of the current block : block1.vX
block2 = sys.modules['block2'] = import_module(block1_modules_versions['.'.join(str.rsplit(sys.argv[0], "/")[3:5])]['block2']) # give me the block2 to use for the right block1.vX
in block1/v1/script1.py :
import sys
sys.argv[0] = __file__ # [path]/block1/v1/script1 => current path of executed script giving the right folder and import the right modules
from GLOBALCONFIG.module_manager.block1 import block1, block2
block1 => block1/v1
block2 => block2/v1
But here is the magical trick :
in block1/v2/script1.py :
import sys
sys.argv[0] = __file__
from GLOBALCONFIG.module_manager.block1 import block1, block2
block1 => block1/v2
block2 => block2/v2
Different version but exactly the same code
FYI : The sys.argv[0] = file that force the current file path is mandatory for me cause my service start using gunicon wsgi:app that made the default path at [venv]/bin/gunicorn but without this constraint, the GLOBALCONFIG import should nicely work
Regards,

How to access symbols in __init__.py from __main__.py?

I have a module - let's call it foo - and I want to make it usable via a python -m foo call. My program look like this:
my_project
├── foo
│   └── __init__.py
└── my_program.py
In __init__.py I have some code which I run when calling python -m foo:
def bar(name):
print(name)
# -- code used to 'run' the module
def main(name):
bar("fritz")
if __name__ == "__main__":
main()
Since I have a fair amount of execution code in __init__.py now (argparse stuff and some logic) I want to separate it into a __main__.py:
my_project
├── foo
│   ├── __init__.py
│   └── __main__.py
└── my_program.py
Despite that looks very simple to me I didn't manage to import stuff located in __init__.py from __main__.py yet.
I know - if foo is located in site-packages or accessible via PYTHONPATH I can just import foo..
But in case I want to execute __main__.py directly (e.g. from some IDE) with foo located anywhere (i.e. not a folder where Python looks for packages) - is there a way to import foo (__init__.py from the same directory)?
I tried import . and import foo - but both approaches fail (because they just mean something else of course)
What I can do - at least to explain my goal - is something like this:
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import foo
Works, but is ugly and a bit dangerous since I don't even know if I really import foo from the same directory..
You can manually set the module import state as if __main__.py were executed with -m:
# foo/__main__.py
import os
import sys
if __package__ is None and __name__ == "__main__": # executed without -m
# set special attributes as if part of the package
__file__ = os.path.abspath(__file__)
__package__ = os.path.basename(os.path.dirname(__file__))
# replace import path for __main__ with path for package
main_path = os.path.dirname(__file__)
try:
index = sys.path.index(dir_path)
if index != 0 or index != 1:
raise ValueError('expected script directory after current directory or matching it')
except ValueError:
raise RuntimeError('sys.path does not include script directory as expected')
else:
sys.path[index] = main_path
# import regularly
from . import bar
This exploits that python3 path/to/foo/__main__.py executes __main__ as a standalone script: __package__ is None and the __name__ does not include the package either. The search path in this case is <current directory>, <__main__ directory>, ..., though it gets collapsed if the two are the same: the index is either 0 or 1.
As with all trickery on internals, there is some transient state where invariants are violated. Do not perform any imports before the module is patched!

How to create a library from multiple sub-modules?

I'm trying to build a library with multiple sub-modules. The general structure looks like so:
package_test/
|___ setup.py
|___ README
|___ package_name/
|___|___ __init__.py
|___|___ submodule/
|___|___|___ __init__.py
|___|___|___ superawesome.py
|___|___ submodule2/
|___|___|___ __init__.py
|___|___|___ prettyokay.py
The goal is to allow a user to do something like:
from package_name.submodule import superawesome
from package_name.submodule2 import prettyokay
The __init__.py for each submodule currently contains (or the appropriate name for submodule2):
from .superawesome import superawesome
__all__ = ['superawesome']
The __init__.py for inside package_name contains:
__version__ = 'v0.0.alpha'
__all__ = ['submodule','submodule2']
When I attempt to use this, iPython shows this error:
From package_name.submodule import superawesome
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-1-03be4241edd5> in <module>()
----> 1 from package_name.submodule import superawesome
ModuleNotFoundError: No module named 'package_name.submodule'
My setup.py currently contains:
from distutils.core import setup
setup(
name = 'package_name',
packages = ['package_name'], # this must be the same as the name above
version = '0.1.1',
long_description=open('README').read(),
)
I need help figuring out how to do the layered importing structure in order to properly import the submodules as part of the whole module. This is clearly a contrived naming set, but in the real problem, each submodule contains a set of modules that belong together... but all of the submodules together are necessary for the library; so I really want to maintain this structure.

Python - Get path of root project structure

I've got a python project with a configuration file in the project root.
The configuration file needs to be accessed in a few different files throughout the project.
So it looks something like: <ROOT>/configuration.conf
<ROOT>/A/a.py, <ROOT>/A/B/b.py (when b,a.py access the configuration file).
What's the best / easiest way to get the path to the project root and the configuration file without depending on which file inside the project I'm in? i.e without using ../../? It's okay to assume that we know the project root's name.
You can do this how Django does it: define a variable to the Project Root from a file that is in the top-level of the project. For example, if this is what your project structure looks like:
project/
configuration.conf
definitions.py
main.py
utils.py
In definitions.py you can define (this requires import os):
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # This is your Project Root
Thus, with the Project Root known, you can create a variable that points to the location of the configuration (this can be defined anywhere, but a logical place would be to put it in a location where constants are defined - e.g. definitions.py):
CONFIG_PATH = os.path.join(ROOT_DIR, 'configuration.conf') # requires `import os`
Then, you can easily access the constant (in any of the other files) with the import statement (e.g. in utils.py): from definitions import CONFIG_PATH.
Other answers advice to use a file in the top-level of the project. This is not necessary if you use pathlib.Path and parent (Python 3.4 and up). Consider the following directory structure where all files except README.md and utils.py have been omitted.
project
│ README.md
|
└───src
│ │ utils.py
| | ...
| ...
In utils.py we define the following function.
from pathlib import Path
def get_project_root() -> Path:
return Path(__file__).parent.parent
In any module in the project we can now get the project root as follows.
from src.utils import get_project_root
root = get_project_root()
Benefits: Any module which calls get_project_root can be moved without changing program behavior. Only when the module utils.py is moved we have to update get_project_root and the imports (refactoring tools can be used to automate this).
All the previous solutions seem to be overly complicated for what I think you need, and often didn't work for me. The following one-line command does what you want:
import os
ROOT_DIR = os.path.abspath(os.curdir)
Below Code Returns the path until your project root
import sys
print(sys.path[1])
To get the path of the "root" module, you can use:
import os
import sys
os.path.dirname(sys.modules['__main__'].__file__)
But more interestingly if you have an config "object" in your top-most module you could -read- from it like so:
app = sys.modules['__main__']
stuff = app.config.somefunc()
Try:
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
A standard way to achieve this would be to use the pkg_resources module which is part of the setuptools package. setuptools is used to create an install-able python package.
You can use pkg_resources to return the contents of your desired file as a string and you can use pkg_resources to get the actual path of the desired file on your system.
Let's say that you have a package called stackoverflow.
stackoverflow/
|-- app
| `-- __init__.py
`-- resources
|-- bands
| |-- Dream\ Theater
| |-- __init__.py
| |-- King's\ X
| |-- Megadeth
| `-- Rush
`-- __init__.py
3 directories, 7 files
Now let's say that you want to access the file Rush from a module app.run. Use pkg_resources.resouces_filename to get the path to Rush and pkg_resources.resource_string to get the contents of Rush; thusly:
import pkg_resources
if __name__ == "__main__":
print pkg_resources.resource_filename('resources.bands', 'Rush')
print pkg_resources.resource_string('resources.bands', 'Rush')
The output:
/home/sri/workspace/stackoverflow/resources/bands/Rush
Base: Geddy Lee
Vocals: Geddy Lee
Guitar: Alex Lifeson
Drums: Neil Peart
This works for all packages in your python path. So if you want to know where lxml.etree exists on your system:
import pkg_resources
if __name__ == "__main__":
print pkg_resources.resource_filename('lxml', 'etree')
output:
/usr/lib64/python2.7/site-packages/lxml/etree
The point is that you can use this standard method to access files that are installed on your system (e.g pip install xxx or yum -y install python-xxx) and files that are within the module that you're currently working on.
Simple and Dynamic!
this solution works on any OS and in any level of directory:
Assuming your project folder name is my_project
from pathlib import Path
current_dir = Path(__file__)
project_dir = [p for p in current_dir.parents if p.parts[-1]=='my_project'][0]
I've recently been trying to do something similar and I have found these answers inadequate for my use cases (a distributed library that needs to detect project root). Mainly I've been battling different environments and platforms, and still haven't found something perfectly universal.
Code local to project
I've seen this example mentioned and used in a few places, Django, etc.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Simple as this is, it only works when the file that the snippet is in is actually part of the project. We do not retrieve the project directory, but instead the snippet's directory
Similarly, the sys.modules approach breaks down when called from outside the entrypoint of the application, specifically I've observed a child thread cannot determine this without relation back to the 'main' module. I've explicitly put the import inside a function to demonstrate an import from a child thread, moving it to top level of app.py would fix it.
app/
|-- config
| `-- __init__.py
| `-- settings.py
`-- app.py
app.py
#!/usr/bin/env python
import threading
def background_setup():
# Explicitly importing this from the context of the child thread
from config import settings
print(settings.ROOT_DIR)
# Spawn a thread to background preparation tasks
t = threading.Thread(target=background_setup)
t.start()
# Do other things during initialization
t.join()
# Ready to take traffic
settings.py
import os
import sys
ROOT_DIR = None
def setup():
global ROOT_DIR
ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__)
# Do something slow
Running this program produces an attribute error:
>>> import main
>>> Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python2714\lib\threading.py", line 801, in __bootstrap_inner
self.run()
File "C:\Python2714\lib\threading.py", line 754, in run
self.__target(*self.__args, **self.__kwargs)
File "main.py", line 6, in background_setup
from config import settings
File "config\settings.py", line 34, in <module>
ROOT_DIR = get_root()
File "config\settings.py", line 31, in get_root
return os.path.dirname(sys.modules['__main__'].__file__)
AttributeError: 'module' object has no attribute '__file__'
...hence a threading-based solution
Location independent
Using the same application structure as before but modifying settings.py
import os
import sys
import inspect
import platform
import threading
ROOT_DIR = None
def setup():
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
current_main_frame = sys._current_frames()[main_id]
base_frame = inspect.getouterframes(current_main_frame)[-1]
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
Breaking this down:
First we want to accurately find the thread ID of the main thread. In Python3.4+ the threading library has threading.main_thread() however, everybody doesn't use 3.4+ so we search through all threads looking for the main thread save it's ID. If the main thread has already exited, it won't be listed in the threading.enumerate(). We raise a RuntimeError() in this case until I find a better solution.
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
Next we find the very first stack frame of the main thread. Using the cPython specific function sys._current_frames() we get a dictionary of every thread's current stack frame. Then utilizing inspect.getouterframes() we can retrieve the entire stack for the main thread and the very first frame.
current_main_frame = sys._current_frames()[main_id]
base_frame = inspect.getouterframes(current_main_frame)[-1]
Finally, the differences between Windows and Linux implementations of inspect.getouterframes() need to be handled. Using the cleaned up filename, os.path.abspath() and os.path.dirname() clean things up.
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
So far I've tested this on Python2.7 and 3.6 on Windows as well as Python3.4 on WSL
I decided for myself as follows.
Need to get the path to 'MyProject/drivers' from the main file.
MyProject/
├─── RootPackge/
│ ├── __init__.py
│ ├── main.py
│ └── definitions.py
│
├─── drivers/
│ └── geckodriver.exe
│
├── requirements.txt
└── setup.py
definitions.py
Put not in the root of the project, but in the root of the main package
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
Use ROOT_DIR:
main.py
# imports must be relative,
# not from the root of the project,
# but from the root of the main package.
# Not this way:
# from RootPackge.definitions import ROOT_DIR
# But like this:
from definitions import ROOT_DIR
# Here we use ROOT_DIR
# get path to MyProject/drivers
drivers_dir = ROOT_DIR / 'drivers'
# Thus, you can get the path to any directory
# or file from the project root
driver = webdriver.Firefox(drivers_dir)
driver.get('http://www.google.com')
Then PYTHON_PATH will not be used to access the 'definitions.py' file.
Works in PyCharm:
run file 'main.py' (ctrl + shift + F10 in Windows)
Works in CLI from project root:
$ py RootPackge/main.py
Works in CLI from RootPackge:
$ cd RootPackge
$ py main.py
Works from directories above project:
$ cd ../../../../
$ py MyWork/PythoProjects/MyProject/RootPackge/main.py
Works from anywhere if you give an absolute path to the main file.
Doesn't depend on venv.
Here is a package that solves that problem: from-root
pip install from-root
from from_root import from_root, from_here
# path to config file at the root of your project
# (no matter from what file of the project the function is called!)
config_path = from_root('config.json')
# path to the data.csv file at the same directory where the callee script is located
# (has nothing to do with the current working directory)
data_path = from_here('data.csv')
Check out the link above and read the readme to see more use cases
I struggled with this problem too until I came to this solution.
This is the cleanest solution in my opinion.
In your setup.py add "packages"
setup(
name='package_name'
version='0.0.1'
.
.
.
packages=['package_name']
.
.
.
)
In your python_script.py
import pkg_resources
import os
resource_package = pkg_resources.get_distribution(
'package_name').location
config_path = os.path.join(resource_package,'configuration.conf')
This worked for me using a standard PyCharm project with my virtual environment (venv) under the project root directory.
Code below isnt the prettiest, but consistently gets the project root. It returns the full directory path to venv from the VIRTUAL_ENV environment variable e.g. /Users/NAME/documents/PROJECT/venv
It then splits the path at the last /, giving an array with two elements. The first element will be the project path e.g. /Users/NAME/documents/PROJECT
import os
print(os.path.split(os.environ['VIRTUAL_ENV'])[0])
Just an example: I want to run runio.py from within helper1.py
Project tree example:
myproject_root
- modules_dir/helpers_dir/helper1.py
- tools_dir/runio.py
Get project root:
import os
rootdir = os.path.dirname(os.path.realpath(__file__)).rsplit(os.sep, 2)[0]
Build path to script:
runme = os.path.join(rootdir, "tools_dir", "runio.py")
execfile(runme)
I used the ../ method to fetch the current project path.
Example:
Project1 -- D:\projects
src
ConfigurationFiles
Configuration.cfg
Path="../src/ConfigurationFiles/Configuration.cfg"
I had to implement a custom solution because it's not as simple as you might think.
My solution is based on stack trace inspection (inspect.stack()) + sys.path and is working fine no matter the location of the python module in which the function is invoked nor the interpreter (I tried by running it in PyCharm, in a poetry shell and other...). This is the full implementation with comments:
def get_project_root_dir() -> str:
"""
Returns the name of the project root directory.
:return: Project root directory name
"""
# stack trace history related to the call of this function
frame_stack: [FrameInfo] = inspect.stack()
# get info about the module that has invoked this function
# (index=0 is always this very module, index=1 is fine as long this function is not called by some other
# function in this module)
frame_info: FrameInfo = frame_stack[1]
# if there are multiple calls in the stacktrace of this very module, we have to skip those and take the first
# one which comes from another module
if frame_info.filename == __file__:
for frame in frame_stack:
if frame.filename != __file__:
frame_info = frame
break
# path of the module that has invoked this function
caller_path: str = frame_info.filename
# absolute path of the of the module that has invoked this function
caller_absolute_path: str = os.path.abspath(caller_path)
# get the top most directory path which contains the invoker module
paths: [str] = [p for p in sys.path if p in caller_absolute_path]
paths.sort(key=lambda p: len(p))
caller_root_path: str = paths[0]
if not os.path.isabs(caller_path):
# file name of the invoker module (eg: "mymodule.py")
caller_module_name: str = Path(caller_path).name
# this piece represents a subpath in the project directory
# (eg. if the root folder is "myproject" and this function has ben called from myproject/foo/bar/mymodule.py
# this will be "foo/bar")
project_related_folders: str = caller_path.replace(os.sep + caller_module_name, '')
# fix root path by removing the undesired subpath
caller_root_path = caller_root_path.replace(project_related_folders, '')
dir_name: str = Path(caller_root_path).name
return dir_name
Here's my take on this issue.
I have a simple use-case that bugged me for a while. Tried a few solutions, but I didn't like either of them flexible enough.
So here's what I figured out.
create a blank python file in the root dir -> I call this beacon.py
(assuming that the project root is in the PYTHONPATH so it can be imported)
add a few lines to my module/class which I call here not_in_root.py.
This will import the beacon.py module and get the path to that
module
Here's an example project structure
this_project
├── beacon.py
├── lv1
│   ├── __init__.py
│   └── lv2
│   ├── __init__.py
│   └── not_in_root.py
...
The content of the not_in_root.py
import os
from pathlib import Path
class Config:
try:
import beacon
print(f"'import beacon' -> {os.path.dirname(os.path.abspath(beacon.__file__))}") # only for demo purposes
print(f"'import beacon' -> {Path(beacon.__file__).parent.resolve()}") # only for demo purposes
except ModuleNotFoundError as e:
print(f"ModuleNotFoundError: import beacon failed with {e}. "
f"Please. create a file called beacon.py and place it to the project root directory.")
project_root = Path(beacon.__file__).parent.resolve()
input_dir = project_root / 'input'
output_dir = project_root / 'output'
if __name__ == '__main__':
c = Config()
print(f"Config.project_root: {c.project_root}")
print(f"Config.input_dir: {c.input_dir}")
print(f"Config.output_dir: {c.output_dir}")
The output would be
/home/xyz/projects/this_project/venv/bin/python /home/xyz/projects/this_project/lv1/lv2/not_in_root.py
'import beacon' -> /home/xyz/projects/this_project
'import beacon' -> /home/xyz/projects/this_project
Config.project_root: /home/xyz/projects/this_project
Config.input_dir: /home/xyz/projects/this_project/input
Config.output_dir: /home/xyz/projects/this_project/output
Of course, it doesn't need to be called beacon.py nor need to be empty, essentially any python file (importable) file would do as long as it's in the root directory.
Using an empty .py file sort of guarantees that it will not be moved elsewhere due to some future refactoring.
Cheers
If you are working with anaconda-project, you can query the PROJECT_ROOT from the environment variable --> os.getenv('PROJECT_ROOT'). This works only if the script is executed via anaconda-project run .
If you do not want your script run by anaconda-project, you can query the absolute path of the executable binary of the Python interpreter you are using and extract the path string up to the envs directory exclusiv. For example: The python interpreter of my conda env is located at:
/home/user/project_root/envs/default/bin/python
# You can first retrieve the env variable PROJECT_DIR.
# If not set, get the python interpreter location and strip off the string till envs inclusiv...
if os.getenv('PROJECT_DIR'):
PROJECT_DIR = os.getenv('PROJECT_DIR')
else:
PYTHON_PATH = sys.executable
path_rem = os.path.join('envs', 'default', 'bin', 'python')
PROJECT_DIR = py_path.split(path_rem)[0]
This works only with conda-project with fixed project structure of a anaconda-project
I ended up needing to do this in various different situations where different answers worked correctly, others didn't, or either with various modifications, so I made this package to work for most situations
pip install get-project-root
from get_project_root import root_path
project_root = root_path(ignore_cwd=False)
# >> "C:/Users/person/source/some_project/"
https://pypi.org/project/get-project-root/
This is not exactly the answer to this question; But it might help someone. In fact, if you know the names of the folders, you can do this.
import os
import sys
TMP_DEL = '×'
PTH_DEL = '\\'
def cleanPath(pth):
pth = pth.replace('/', TMP_DEL)
pth = pth.replace('\\', TMP_DEL)
return pth
def listPath():
return sys.path
def getPath(__file__):
return os.path.abspath(os.path.dirname(__file__))
def getRootByName(__file__, dirName):
return getSpecificParentDir(__file__, dirName)
def getSpecificParentDir(__file__, dirName):
pth = cleanPath(getPath(__file__))
dirName = cleanPath(dirName)
candidate = f'{TMP_DEL}{dirName}{TMP_DEL}'
if candidate in pth:
pth = (pth.split(candidate)[0]+TMP_DEL +
dirName).replace(TMP_DEL*2, TMP_DEL)
return pth.replace(TMP_DEL, PTH_DEL)
return None
def getSpecificChildDir(__file__, dirName):
for x in [x[0] for x in os.walk(getPath(__file__))]:
dirName = cleanPath(dirName)
x = cleanPath(x)
if TMP_DEL in x:
if x.split(TMP_DEL)[-1] == dirName:
return x.replace(TMP_DEL, PTH_DEL)
return None
List available folders:
print(listPath())
Usage:
#Directories
#ProjectRootFolder/.../CurrentFolder/.../SubFolder
print(getPath(__file__))
# c:\ProjectRootFolder\...\CurrentFolder
print(getRootByName(__file__, 'ProjectRootFolder'))
# c:\ProjectRootFolder
print(getSpecificParentDir(__file__, 'ProjectRootFolder'))
# c:\ProjectRootFolder
print(getSpecificParentDir(__file__, 'CurrentFolder'))
# None
print(getSpecificChildDir(__file__, 'SubFolder'))
# c:\ProjectRootFolder\...\CurrentFolder\...\SubFolder
One-line solution
Hi all! I have been having this issue for ever as well and none of the solutions worked for me, so I used a similar approach that here::here() uses in R.
Install the groo package: pip install groo-ozika
Place a hidden file in your root directory, e.g. .my_hidden_root_file.
Then from anywhere lower in the directory hierarchy (i.e. within
the root) run the following:
from groo.groo import get_root
root_folder = get_root(".my_hidden_root_file")
That's it!
It just executes the following function:
def get_root(rootfile):
import os
from pathlib import Path
d = Path(os.getcwd())
found = 0
while found == 0:
if os.path.isfile(os.path.join(d, rootfile)):
found = 1
else:
d=d.parent
return d
The project root directory does not have __init__.py.
I solved this problem by looking for an ancestor directory that does not have __init__.py.
from functools import lru_cache
from pathlib import Path
#lru_cache()
def get_root_dir() -> str:
path = Path().cwd()
while Path(path, "__init__.py").exists():
path = path.parent
return str(path)
There are many answers here but I couldn't find something simple that covers all cases so allow me to suggest my solution too:
import pathlib
import os
def get_project_root():
"""
There is no way in python to get project root. This function uses a trick.
We know that the function that is currently running is in the project.
We know that the root project path is in the list of PYTHONPATH
look for any path in PYTHONPATH list that is contained in this function's path
Lastly we filter and take the shortest path because we are looking for the root.
:return: path to project root
"""
apth = str(pathlib.Path().absolute())
ppth = os.environ['PYTHONPATH'].split(':')
matches = [x for x in ppth if x in apth]
project_root = min(matches, key=len)
return project_root
Important: This solution requires you to run the file as a module with python -m pkg.file and not as a script like python file.py.
import sys
import os.path as op
root_pkg_dirname = op.dirname(sys.modules[__name__.partition('.')[0]].__file__)
Other answers have requirements like depending on an environment variable or the position of another module in the package structure.
As long as you run the script as python -m pkg.file (with the -m), this approach is self-contained and will work in any module of the package, including in the top-level __init__.py file.
import sys
import os.path as op
root_pkg_name, _, _ = __name__.partition('.')
root_pkg_module = sys.modules[root_pkg_name]
root_pkg_dirname = op.dirname(root_pkg_module.__file__)
config_path = os.path.join(root_pkg_dirname, 'configuration.conf')
It works by taking the first component in the dotted string contained in __name__ and using it as a key in sys.modules which returns the module object of the top-level package. Its __file__ attribute contains the path we want after trimming off /__init__.py using os.path.dirname().

Categories