I have written a class that locates the relative root project directory path from where I'm calling that class.
E.g., I have this structure:
rootProjectDir
|_sub
|__subsub
|___subsubfile.txt
|_another_sub
|__another_sub_file.py
and what I'm doing is when I'm working within another_sub_file.py, I'm calling my class GetRootPath to get /home/user/dev/rootProjectDir, and then I can construct the path to the ./rootProjectDir/sub/subsub/subsubfile.txt to get that subsubfile.txt. I didn't find any better solution. I hope there is, but I wrote this class helper for me to achieve this:
import os
class GetRootPath:
def __init__(self):
self.rootFolder = '/rootProjectDir'
def get_root(self):
working_file_dir = os.path.dirname(os.path.abspath(__file__))
i = len(working_file_dir.split('/'))
root = list(working_file_dir.split('/'))
new_root = []
for item in root[1:]:
new_root.append('/' + str(item))
while True:
if self.rootFolder == new_root[-1]:
builded = ''.join(new_root) + '/'
return builded
else:
new_root.pop()
i -= 1
Is there any better way to achieve my goal and also, what could be done better with my class to improve performance and stability of creating relative path to root directory of my project? Or is there a better Pythonic way of navigating your project folder to get files, etc., as you need? My GetRootPath class looks like a non-Pythonic way of doing this.
I'm working in PyCharm, and my project is created with it, so it's not just folders, I guess.
2 Answers 2
As written, your code assumes it will be running on a filesystem which uses / as a directory separator, such as Unix, Linux, and MacOS. It will never work when run on a filesystem with \ as the separator, such as Windows. While you could try to fix this using os.sep, there is an easier way...
pathlib.Path is an object-oriented interface to the filesystem. Using it frees you from needing to worry about which separator needs to be used, and from issues with the file system root which necessitated the unexplained root[1:] in your code, which "seems to" ignore the first component of the filesystem path.
Once you've converted the filename to a Path object, Path.parts will split it into the individual components. Count backwards until you find the required rootProjectDir, and then use Path.parents to retrieve full path to that directory. Eg)
from pathlib import Path
def get_root_path() -> Path:
path = Path(__file__).absolute()
idx = path.parts[::-1].index("rootProjectDir")
return path.parents[idx - 1]
I've replaced the class containing a single method with a simple function. Still, the above still feels wrong. The project root directory doesn't ever change. Moreover, the file itself likely knows its position in the project structure, so it doesn't really need the name of the project directory.
Your project is laid out like ...
rootProjectDir
|_sub
| |_subsub
| |_subsubfile.txt
|_another_sub
|_another_sub_file.py
Is another_sub a Python package? If so, when inside the another_sub_file.py module, __name__ will be another_sub.another_sub_file. Since the module name has two components, you can immediately determine the project root directory is the grandparent of this __file__.
from pathlib import Path
ROOT_PATH = Path(__file__).absolute().parents[__name__.count('.')]
Now the project root is computed once when the file is imported, not repeatedly every time you want to access a different file.
DRY
This expression appears on 2 consecutive lines:
working_file_dir.split('/')
You could set it to a variable so that split is only called once.
However, as noted in the comments on the question, the i variable
is not needed, which means these 2 lines can be deleted:
i = len(working_file_dir.split('/'))
i -= 1
Simpler
These lines:
builded = ''.join(new_root) + '/'
return builded
can be combined into a single line:
return ''.join(new_root) + '/'
This eliminates the poorly-named builded variable.
Also, there is no need for the else line after the return. here is
the simplified while loop:
while True:
if self.rootFolder == new_root[-1]:
return ''.join(new_root) + '/'
new_root.pop()
The code might be simpler without the class since there is really only one function.
Documentation
The PEP 8 style guide recommends adding docstrings for classes and functions.
Naming
The PEP 8 style guide recommends snake_case for function and variable names.
For example, rootFolder would be root_folder.
Array variables are commonly pluralized; root would be better as roots.
i? \$\endgroup\$GetRootPathis supposed to solve? \$\endgroup\$