l0b0
l0b0

Reputation: 58768

Get relative path of caller in Python

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)

Upvotes: 4

Views: 2585

Answers (2)

CermakM
CermakM

Reputation: 2022

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.

Upvotes: 0

Rob Buckley
Rob Buckley

Reputation: 758

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

Upvotes: 3

Related Questions