4

I've got this function:

def relative_path(*paths):
    return os.path.join(os.path.dirname(__file__), *paths)

How would I change it to return the path relative to the caller?

For example, if I called relative_path('index.html') from another script, is it possible to get the path relative the script from where it was called implicitly or would I need to modify relative_path to pass __file__ across as well like this?

def relative_path(__file__, *paths):
    return os.path.join(os.path.dirname(__file__), *paths)
fny
  • 31,255
  • 16
  • 96
  • 127
l0b0
  • 55,365
  • 30
  • 138
  • 223
  • do these paths strictly begin with the directory of the caller? – dmg Jan 19 '15 at 09:41
  • Are you asking how to determine the path of the script the caller resides within? If not, what is the path of the caller? – martineau Jan 19 '15 at 11:18
  • I don't know how to make it more clear than "Get relative path of caller". Could you clarify why that is not clear? – l0b0 Jan 19 '15 at 13:02
  • Why the need to magically use the parent file location? Why not be more explicit about what to base the path on? Flask requires you to create the `Flask()` object with the current module name (`app = Flask(__name__)`) so that it can look up the path to load templates (`sys.modules[name_passed_in].__file__`). That makes it explicit and also lets you override the path. No magic caller lookups required. – Martijn Pieters Oct 13 '17 at 15:43

2 Answers2

3

adapting the solution in Get __name__ of calling function's module in Python

file1.py

import os
import inspect

def relative_path(*paths):
    return os.path.join(os.path.dirname(__file__), *paths)

def relative_to_caller(*paths):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    return os.path.join(os.path.dirname(mod.__file__), *paths)

if __name__ == '__main__':
    print(relative_path('index.html'))

sub/sub_file.py

import sys
sys.path.append(r'/Users/xx/PythonScripts/!scratch')

import file1

if __name__ == '__main__':
    print(file1.relative_path('index.html'))
    print(file1.relative_to_caller('index.html'))

Running sub_file.py gives the following output:

/Users/xx/PythonScripts/!scratch/index.html
/Users/xx/PythonScripts/!scratch/sub/index.html

There are some caveats in the comments to the question in the link above...

Rob Buckley
  • 708
  • 4
  • 16
0

Note that tracing stack would be a possibility here, but it could cause some serious trouble ( like confusing 'garbage collector', or may not even work in eggs )

I believe the cleanest way would be to pass the caller to the rel_path function.

However, as you may know, there is usually an ugly way of doing things in python. You can do for example something like this:

consider following two scripts:

# relpath.py

import os


def rel_path(path):
    if os.path.isfile(__name__):
        return os.path.relpath(path, start=__name__)

    print("Warning: %s is not a file: returning path relative to the current working dir" % __name__, file=sys.stderr)
    return os.path.relpath(path)


# caller.py

import importlib.util


spec = importlib.util.spec_from_file_location(name=__file__, location="/workspace/relpath.py")

rel =  importlib.util.module_from_spec(spec)

spec.loader.exec_module(rel)
print(rel.rel_path("/tmp"))

What we did here: when loading the module using importlib.util, we passed the name=__file__, which gave our module the name consisting of the caller script path. Hence we needn't pass it as an argument to the relpath.py.

Note that this is not clean solution and might not be readable for future developers reading your code. I just wanted to demonstrate the possibilities of python.

CermakM
  • 1,642
  • 1
  • 16
  • 15