Using Python's os.path, how do I go up one directory?
Question:
I recently upgrade Django from v1.3.1 to v1.4.
In my old settings.py
I have
TEMPLATE_DIRS = (
os.path.join(os.path.dirname( __file__ ), 'templates').replace('\', '/'),
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
This will point to /Users/hobbes3/Sites/mysite/templates
, but because Django v1.4 moved the project folder to the same level as the app folders, my settings.py
file is now in /Users/hobbes3/Sites/mysite/mysite/
instead of /Users/hobbes3/Sites/mysite/
.
So actually my question is now twofold:
- How do I use
os.path
to look at a directory one level above from __file__
. In other words, I want /Users/hobbes3/Sites/mysite/mysite/settings.py
to find /Users/hobbes3/Sites/mysite/templates
using relative paths.
- Should I be keeping the
template
folder (which has cross-app templates, like admin
, registration
, etc.) at the project /User/hobbes3/Sites/mysite
level or at /User/hobbes3/Sites/mysite/mysite
?
Answers:
os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'templates'))
As far as where the templates folder should go, I don’t know since Django 1.4 just came out and I haven’t looked at it yet. You should probably ask another question on SE to solve that issue.
You can also use normpath
to clean up the path, rather than abspath
. However, in this situation, Django expects an absolute path rather than a relative path.
For cross platform compatability, use os.pardir
instead of '..'
.
To get the folder of a file just use:
os.path.dirname(path)
To get a folder up just use os.path.dirname
again
os.path.dirname(os.path.dirname(path))
You might want to check if __file__
is a symlink:
if os.path.islink(__file__): path = os.readlink (__file__)
You want exactly this:
BASE_DIR = os.path.join( os.path.dirname( __file__ ), '..' )
from os.path import dirname, realpath, join
join(dirname(realpath(dirname(__file__))), 'templates')
Update:
If you happen to “copy” settings.py
through symlinking, @forivall’s answer is better:
~user/
project1/
mysite/
settings.py
templates/
wrong.html
project2/
mysite/
settings.py -> ~user/project1/settings.py
templates/
right.html
The method above will ‘see’ wrong.html
while @forivall’s method will see right.html
In the absense of symlinks the two answers are identical.
For a paranoid like me, I’d prefer this one
TEMPLATE_DIRS = (
__file__.rsplit('/', 2)[0] + '/templates',
)
Personally, I’d go for the function approach
def get_parent_dir(directory):
import os
return os.path.dirname(directory)
current_dirs_parent = get_parent_dir(os.getcwd())
I think the easiest thing to do is just to reuse dirname()
So you can call
os.path.dirname(os.path.dirname( __file__ ))
if you file is at /Users/hobbes3/Sites/mysite/templates/method.py
This will return “/Users/hobbes3/Sites/mysite”
This might be useful for other cases where you want to go x folders up. Just run walk_up_folder(path, 6)
to go up 6 folders.
def walk_up_folder(path, depth=1):
_cur_depth = 1
while _cur_depth < depth:
path = os.path.dirname(path)
_cur_depth += 1
return path
If you are using Python 3.4 or newer, a convenient way to move up multiple directories is pathlib
:
from pathlib import Path
full_path = "path/to/directory"
str(Path(full_path).parents[0]) # "path/to"
str(Path(full_path).parents[1]) # "path"
str(Path(full_path).parents[2]) # "."
To go n
folders up… run up(n)
import os
def up(n, nth_dir=os.getcwd()):
while n != 0:
nth_dir = os.path.dirname(nth_dir)
n -= 1
return nth_dir
Of course: simply use os.chdir(..)
.
With using os.path
we can go one directory up like that
one_directory_up_path = os.path.dirname('.')
also after finding the directory you want you can join with other file/directory path
other_image_path = os.path.join(one_directory_up_path, 'other.jpg')
Go up a level from the work directory
import os
os.path.dirname(os.getcwd())
or from the current directory
import os
os.path.dirname('current path')
From the current file path you could use:
os.path.join(os.path.dirname(__file__),'..','img','banner.png')
I’m surprised handling for an arbitrary number of ".." parent directory tokens in a path string isn’t directly handled for by the os library. Here’s a quick and dirty function that’ll give you an absolute path string from a relative one:
def get_abs_from_relpath(relpath:str) -> str:
ap = os.path.abspath(__file__).split("/")[:-1]
sp = relpath.split("/")
sp_start_index = 0
for slug in sp:
if slug == "..":
ap.pop(-1)
sp_start_index += 1
else:
return "/".join(ap+sp[sp_start_index:])
You can call it with open() like this:
with open(get_abs_from_relpath('../../../somedir/myfile.txt')) as f:
foo = f.read()
If you prefer a one-liner for getting the parent directory, I’d suggest this:
import os
parent_dir = os.path.split(os.getcwd())[0]
os.path.split()
method returns a tuple (head, tail) where tail is everything after the final slash. So the first index is the parent of your absolute path.
Not an answer but a long tangential comment that probably should be made as someone may be led astray…
The syntax os.path.join( os.path.dirname( __file__ ), 'foo.txt')
to get a file within the same folder as the python file getting run is not the "advised" solution for packages, instead package data is preferred for a couple of reasons, for example in a zip packaged package or a more complicated filesystem.
pkg_resources.read_text(__package__, 'foo.txt')
was the formerly recommended solution, but will be removed at some point and importlib.resources.read_text(__package__, 'foo.txt')
is the recommended way —see https://docs.python.org/3/library/importlib.html#module-importlib.resources for the many options.
However, this
- requires
include_package_data=True
and package_data
with a Dict[str, List[str]
in the setup.py
file
- requires a
MANIFEST.in
if pip distributed as sdist (but not a built wheel)
- will not work for relative imports (i.e. not installed)
- is wisely and generally ignored for sake of sanity in webapps due to the way they run
I recently upgrade Django from v1.3.1 to v1.4.
In my old settings.py
I have
TEMPLATE_DIRS = (
os.path.join(os.path.dirname( __file__ ), 'templates').replace('\', '/'),
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
This will point to /Users/hobbes3/Sites/mysite/templates
, but because Django v1.4 moved the project folder to the same level as the app folders, my settings.py
file is now in /Users/hobbes3/Sites/mysite/mysite/
instead of /Users/hobbes3/Sites/mysite/
.
So actually my question is now twofold:
- How do I use
os.path
to look at a directory one level above from__file__
. In other words, I want/Users/hobbes3/Sites/mysite/mysite/settings.py
to find/Users/hobbes3/Sites/mysite/templates
using relative paths. - Should I be keeping the
template
folder (which has cross-app templates, likeadmin
,registration
, etc.) at the project/User/hobbes3/Sites/mysite
level or at/User/hobbes3/Sites/mysite/mysite
?
os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'templates'))
As far as where the templates folder should go, I don’t know since Django 1.4 just came out and I haven’t looked at it yet. You should probably ask another question on SE to solve that issue.
You can also use normpath
to clean up the path, rather than abspath
. However, in this situation, Django expects an absolute path rather than a relative path.
For cross platform compatability, use os.pardir
instead of '..'
.
To get the folder of a file just use:
os.path.dirname(path)
To get a folder up just use os.path.dirname
again
os.path.dirname(os.path.dirname(path))
You might want to check if __file__
is a symlink:
if os.path.islink(__file__): path = os.readlink (__file__)
You want exactly this:
BASE_DIR = os.path.join( os.path.dirname( __file__ ), '..' )
from os.path import dirname, realpath, join
join(dirname(realpath(dirname(__file__))), 'templates')
Update:
If you happen to “copy” settings.py
through symlinking, @forivall’s answer is better:
~user/
project1/
mysite/
settings.py
templates/
wrong.html
project2/
mysite/
settings.py -> ~user/project1/settings.py
templates/
right.html
The method above will ‘see’ wrong.html
while @forivall’s method will see right.html
In the absense of symlinks the two answers are identical.
For a paranoid like me, I’d prefer this one
TEMPLATE_DIRS = (
__file__.rsplit('/', 2)[0] + '/templates',
)
Personally, I’d go for the function approach
def get_parent_dir(directory):
import os
return os.path.dirname(directory)
current_dirs_parent = get_parent_dir(os.getcwd())
I think the easiest thing to do is just to reuse dirname()
So you can call
os.path.dirname(os.path.dirname( __file__ ))
if you file is at /Users/hobbes3/Sites/mysite/templates/method.py
This will return “/Users/hobbes3/Sites/mysite”
This might be useful for other cases where you want to go x folders up. Just run walk_up_folder(path, 6)
to go up 6 folders.
def walk_up_folder(path, depth=1):
_cur_depth = 1
while _cur_depth < depth:
path = os.path.dirname(path)
_cur_depth += 1
return path
If you are using Python 3.4 or newer, a convenient way to move up multiple directories is pathlib
:
from pathlib import Path
full_path = "path/to/directory"
str(Path(full_path).parents[0]) # "path/to"
str(Path(full_path).parents[1]) # "path"
str(Path(full_path).parents[2]) # "."
To go n
folders up… run up(n)
import os
def up(n, nth_dir=os.getcwd()):
while n != 0:
nth_dir = os.path.dirname(nth_dir)
n -= 1
return nth_dir
Of course: simply use os.chdir(..)
.
With using os.path
we can go one directory up like that
one_directory_up_path = os.path.dirname('.')
also after finding the directory you want you can join with other file/directory path
other_image_path = os.path.join(one_directory_up_path, 'other.jpg')
Go up a level from the work directory
import os
os.path.dirname(os.getcwd())
or from the current directory
import os
os.path.dirname('current path')
From the current file path you could use:
os.path.join(os.path.dirname(__file__),'..','img','banner.png')
I’m surprised handling for an arbitrary number of ".." parent directory tokens in a path string isn’t directly handled for by the os library. Here’s a quick and dirty function that’ll give you an absolute path string from a relative one:
def get_abs_from_relpath(relpath:str) -> str:
ap = os.path.abspath(__file__).split("/")[:-1]
sp = relpath.split("/")
sp_start_index = 0
for slug in sp:
if slug == "..":
ap.pop(-1)
sp_start_index += 1
else:
return "/".join(ap+sp[sp_start_index:])
You can call it with open() like this:
with open(get_abs_from_relpath('../../../somedir/myfile.txt')) as f:
foo = f.read()
If you prefer a one-liner for getting the parent directory, I’d suggest this:
import os
parent_dir = os.path.split(os.getcwd())[0]
os.path.split()
method returns a tuple (head, tail) where tail is everything after the final slash. So the first index is the parent of your absolute path.
Not an answer but a long tangential comment that probably should be made as someone may be led astray…
The syntax os.path.join( os.path.dirname( __file__ ), 'foo.txt')
to get a file within the same folder as the python file getting run is not the "advised" solution for packages, instead package data is preferred for a couple of reasons, for example in a zip packaged package or a more complicated filesystem.
pkg_resources.read_text(__package__, 'foo.txt')
was the formerly recommended solution, but will be removed at some point and importlib.resources.read_text(__package__, 'foo.txt')
is the recommended way —see https://docs.python.org/3/library/importlib.html#module-importlib.resources for the many options.
However, this
- requires
include_package_data=True
andpackage_data
with aDict[str, List[str]
in thesetup.py
file - requires a
MANIFEST.in
if pip distributed as sdist (but not a built wheel) - will not work for relative imports (i.e. not installed)
- is wisely and generally ignored for sake of sanity in webapps due to the way they run