289

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.

matanc1
  • 6,525
  • 6
  • 37
  • 57
  • does `/__init__.py` exist? – mgilson Aug 19 '14 at 17:08
  • Either your configuration file is a python module, and you can easily access it just with an import statement, either it's not a python module and you should put it in a well known location. For example $HOME/.my_project/my_project.conf. – John Smith Optional Aug 19 '14 at 17:09
  • @JohnSmithOptional - It's a JSON file. I need to be able to access it using the path. Yes. All of the folders include it. – matanc1 Aug 19 '14 at 17:09
  • _ It's okay to assume that we know the project root's name._ Does that mean you know the path to the project? Isn't it just os.path.join(known_root_name, "configuration.conf") then? – tdelaney Aug 19 '14 at 17:19
  • If it's a user configuration I'd generally use something like `os.path.expanduser('~/.myproject/myproject.conf')`. It works on Unix and Windows. – John Smith Optional Aug 19 '14 at 17:22
  • @tdelaney - I know the project's name (i.e the project root's name) and I know the config file's name and that it's in the root directory. I want to extract the path to the root using the project's name. – matanc1 Aug 20 '14 at 06:48
  • @shookie, I have no idea what "project name" means. Can you give an example? – tdelaney Aug 20 '14 at 15:56

25 Answers25

297

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.

jrd1
  • 10,358
  • 4
  • 34
  • 51
  • 1
    To include the definitions.py file like that, will it be required to add a `__init__.py` file to the root project directory as well ? Should that be correct ? I've just started with python and not sure on the best practices. Thanks. – akskap Aug 30 '16 at 08:20
  • 3
    @akskap: No, an `__init__.py` will not be required, as that file is only required when defining packages: *The `__init__.py` files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, `__init__.py` can just be an empty file, but it can also execute initialization code for the package or set the `__all__` variable, described later.* See: https://docs.python.org/3/tutorial/modules.html#packages – jrd1 Aug 31 '16 at 06:02
  • I am curious, style wise, whether it is acceptable or frowned upon to add these definitions to the `__init.py__` of the root package. It would save creating another file, as well as allow the nicer syntax of `from root_pack import ROOT_DIR, CONFIG_PATH`. – Johndt Jan 05 '17 at 02:30
  • @Johndt6: the convention is to keep `__init__.py` empty, but that isn't strictly true (it's a convention after all). See this for more: http://stackoverflow.com/questions/2361124/using-init-py – jrd1 Jan 05 '17 at 05:58
  • Here `os` is not by default available. Need to import `os`. So adding the line `import os` would make the answer more complete. – Md. Abu Nafee Ibna Zahid May 03 '18 at 11:47
  • @akskap I'm not sure if you are correct when saying `__init__.py` is not required. Without it, running a .py file in a sub-directory (i.e `$ python src/foo.py`) returns a ModuleNotFoundError – Joshua Zastrow Jan 24 '19 at 01:05
  • Shouldn't it be `ROOT_DIR = os.path.dirname(os.path.abspath('__file__'))`? I'm just pointing out the quote symbols. – JavNoor Feb 17 '19 at 00:24
  • 3
    @JavNoor: no - in the example you cited, `os.path.abspath` is calling a string, `'__file__'`. Recall that `__file__` is actually an import attribute that's defined for Python modules. In this case, `__file__` will return the pathname from which the module is loaded. Read more here (see the modules section): https://docs.python.org/3/reference/datamodel.html – jrd1 Feb 17 '19 at 08:18
  • Thanks @jrd1. I had tried it in jupyter notebook and there it wasn't accepting the input without quotations. But after reading the link you sent it makes sense that quotations shouldn't be there. I suppose these aren't really meant for jupyter. – JavNoor Feb 17 '19 at 22:41
  • @JavNoor: Indeed. Jupyter's behavior is correct - `__file__` applies to modules and python scripts. Jupyter notebooks do not meet this criterion as they're web applications run via a server. – jrd1 Feb 18 '19 at 07:33
  • I get a `ModuleNotFoundError: No module named 'definitions'` error when trying this. I have my project structured like this: `project/src/...` contains all the code, this is where I'm importing `definitions`. The `definitions.py` is next to it in `project/definitions.py`. In my `setup.py `, I define `src` as `package_dir`. Not sure if this has anything to do with it – stefanbschneider Aug 21 '19 at 15:25
  • @CGFoX: that behavior makes sense - you'll likely need to do a relative import. Also, your question is distinct from the one the OP posed, and can (and _should_) stand on its own. I strongly encourage you to consider posting your own question and solicit feedback from the community in the future. – jrd1 Aug 21 '19 at 21:32
  • `os.path.dirname(os.path.abspath(__file__))` is not the root directory. It is the path the the file the code is called from. – pookie Apr 03 '21 at 14:23
  • @pookie: Thank you for your comment. I've never said that was the root directory - it's the _project root_. Yes, ROOT_DIR is perhaps a misnomer for the variable name, but given the context of its use and the associated explanation as the project root, the variable's purpose and meaning is clear. – jrd1 Apr 03 '21 at 16:51
  • Wouldn't this fail when run from `project/subdir/newfile.py`? – AstroFloyd May 18 '21 at 13:30
  • 1
    @AstroFloyd: It will only fail if subdir is not a sub-package (i.e. it's missing `__init__.py` in the subdir). Without package structuring, Python won't know where ROOT_DIR should be referenced from – jrd1 May 18 '21 at 15:26
  • So how would the line `from definitions import CONFIG_PATH` change in my `project/subdir/newfile.py`? How does Python know to look in `../definitions.py` instead of `definitions.py` from my `subdir/`? If I do `print(os.path.dirname(os.path.abspath(__file__)))` in my `newfile.py`, it reports `project/subdir/`, not `project/`. Do I need `__init__.py` somewhere to change that? – AstroFloyd May 18 '21 at 17:32
  • 1
    @AstroFloyd: The example shown is for a project root in which `definitions.py` is in the top level. The example you've shown is for a different case within a sub-directory, as calling the statements provided within a file under that heading will only print out the sub-directory root. If your project is structured like a package and the sub-directories are done in a similar manner, then you can call the ROOT_DIR from `definitions.py` in the top-level using an absolute or relative import statement. See this link for more: https://realpython.com/absolute-vs-relative-python-imports/ – jrd1 May 18 '21 at 18:45
  • 2
    I understood that to allow relative imports, you need to "turn your code into a package", but it took me a while to figure out that that means **all of** 1) putting an `__init__.py` in the top dir of your code **and** 2) adding the parent directory of the top dir to your `PYTHONPATH` **and** 3) setting the `__package__` variable in your Python program to the name of the directory that contains `__init__.py`. All is working now. Thanks! – AstroFloyd May 20 '21 at 11:44
184

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).

RikH
  • 2,994
  • 1
  • 16
  • 15
  • 8
    Any module that's in the root. Calling src.utils from outside of the root shouldn't work. Am I wrong? – aerijman Oct 30 '19 at 16:15
  • name '__file__' is not defined , why? – Luk Aron Mar 25 '20 at 07:38
  • @LukAron: Be sure to use `__file__` (note underscores) which is a module attribute containing absolute path of module, otherwise it will not work. – Nerxis Feb 08 '21 at 09:48
  • For me, this still doesn't help because you still have to know where src/utils.py is located if you're not in src – JSON K. Feb 20 '21 at 00:28
  • 2
    In my case (due to Linux OS?), `Path()` returns the *relative* path. Hence, I need `Path(__file__).absolute().parent.parent` for this example. – AstroFloyd May 18 '21 at 14:07
  • 1
    Awesome, this actually works in my case where I was using python datasets library and on load_dataset, my custom dataset class was being called inside huggingface path (a symlink was being generated of my dataclass file inside the huggingface cache dir). So, I was not able to load other files from cache because files were not in cache but inside project root directory. This helps me there. – Syed Muhammad Asad Sep 29 '22 at 09:11
  • this is the real deal – LeYAUable Sep 30 '22 at 14:54
59

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)
Martim
  • 931
  • 9
  • 9
  • 3
    Put that in config.py, at the root of the directory, .. bamn! Yout got yourself a singleton. – swdev Mar 12 '19 at 15:35
  • 14
    This method presumes you run the application from within the path that it exists. Many "users" have an icon they click from a desktop or can run the app from another directory entirely. – DevPlayer Oct 22 '19 at 20:12
53

Below Code Returns the path until your project root

import sys
print(sys.path[1])
Arpan Saini
  • 4,623
  • 1
  • 42
  • 50
29

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()
VCD
  • 889
  • 10
  • 27
DevPlayer
  • 5,393
  • 1
  • 25
  • 20
  • 1
    Here `os` is not by default available. Need to import `os`. So adding the line `import os` would make the answer more complete. – Md. Abu Nafee Ibna Zahid May 03 '18 at 11:50
  • 8
    This gives the directory that contains the script that was executed. For example, when running `python3 -m topmodule.submodule.script` it will give `/path/to/topmodule/submodule` instead of `/path/to/topmodule`. – danijar Mar 31 '19 at 18:18
  • In context of tests invoked by `pytest` I have got `/home/user/.local/lib/python3.9/site-packages/pytest/__main__.py`. RikH's answer is most robust for me (`Path(__file__).parent.parent` in fix-placed module). – x'ES Apr 11 '22 at 00:42
20

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.

shrewmouse
  • 5,338
  • 3
  • 38
  • 43
18

Try:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
Yeonghun
  • 400
  • 5
  • 13
14

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]

Alirezak
  • 455
  • 5
  • 14
  • 4
    An alternative to the list comprehension is a generator, and using `Path.name`: `project_dir = next(p for p in current_dir.parents if p.name == "my_project")`. – Alex Povel Dec 22 '21 at 12:13
9

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've tried your "Location independent" code snippet, and I see that it returns a folder where a Python file is executed, not the project root. – Nairum Sep 28 '22 at 09:37
7

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.

Aleksey Voko
  • 207
  • 3
  • 8
7

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

ekon
  • 443
  • 3
  • 12
3

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])
GWed
  • 15,167
  • 5
  • 62
  • 99
  • 3
    This won't work with setups like anaconda or pipenv, since the virtual environment isn't contained within the project in those cases. – Gripp Jul 06 '18 at 11:17
  • Clearly the best solution when working with venv. Thank you, it was worth scrolling down. – jMike Dec 14 '22 at 16:03
3

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')
Guy
  • 491
  • 4
  • 13
  • Using a virtual environment and installing the package with `python3 setup.py install` it was not pointing to the source code folder anymore, but to the egg inside `~./virtualenv/..../app.egg`. So I had to include the configuration file into the package install. – loxosceles May 13 '19 at 23:11
2

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)
Alex Granovsky
  • 2,996
  • 1
  • 15
  • 10
2

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
daveoncode
  • 18,900
  • 15
  • 104
  • 159
1

I used the ../ method to fetch the current project path.

Example: Project1 -- D:\projects

src

ConfigurationFiles

Configuration.cfg

Path="../src/ConfigurationFiles/Configuration.cfg"

Adarsh
  • 73
  • 10
1

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

Gergely M
  • 583
  • 4
  • 11
1

To do this, you can add the root directory of your code repository to the Python path. You can do this by adding the following lines of code at the beginning of your script:

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

This code adds the parent directory of the current file (which is assumed to be in a subfolder of the root directory) to the Python path.

cconsta1
  • 737
  • 1
  • 6
  • 20
0

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

0

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/

Charles
  • 405
  • 4
  • 13
0

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
Shamshirsaz.Navid
  • 2,224
  • 3
  • 22
  • 36
0

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.

  1. Install the groo package: pip install groo-ozika

  2. Place a hidden file in your root directory, e.g. .my_hidden_root_file.

  3. 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")

  1. 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
  • This is over-complicated for most of time, but would work for `pyproject.toml` instead of `.my_hidden_root_file` . – Kazuya Gosho Jul 20 '22 at 12:46
  • @KazuyaGosho I disagree on this point - there might be multiple `pyproject.toml` files in the hierarchy, same for `.git` or similar. This is very simple to reproduce and it works from all levels and on all platforms :) – user2116617 Aug 12 '22 at 09:22
0

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)
okamoto
  • 31
  • 1
-1

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
alonhzn
  • 150
  • 1
  • 7
  • I do not think we know our projects are in the PYTHONPATH path. I can create a shortcut icon for my desktop (Microsoft Windows) that has a "starting directory" that is outside my PYTHONPATH, and I can just open a console, cd to the directory with the Python module and just type it in: python.exe my_root_module.py which would not be in my PYTHONPATH environment variable. – DevPlayer Jun 23 '21 at 20:46
-1

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().

Pyprohly
  • 1,128
  • 1
  • 11
  • 10
  • 1
    Could you add a short description about your solution and how they can use it as their solution? – LuRsT Apr 10 '20 at 09:56
  • `root_pkg_dirname` is the directory where a file is called. It is not always a project root. – Nairum Sep 27 '22 at 11:13
  • @Nairum The script has to be run as `python -m` for this method to work. I’ve added a note now explaining this. – Pyprohly Sep 28 '22 at 03:13