Welcome, guest | Sign In | My Account | Store | Cart

I ran into a dilemma when writing a PyUnit test case that read in sample data from a text file in the same package as the test. How I could successfully reference the relative location of the file changed depending on how I executed the code. So for example, when I ran the unit test in isolation, it passed, but when I ran the test as part of a suite, it failed, because the code was being executed from a different location. I needed to find a way to determine the relative location of the file at run-time no matter how it was executed.

Python, 9 lines
1
2
3
4
5
6
7
8
9
import os, sys, inspect
def execution_path(filename):
  return os.path.join(os.path.dirname(inspect.getfile(sys._getframe(1))), filename)

# open a file in a test
open(execution_path('sample.txt')).read()

# get the absolute path of the file
os.path.abspath(execution_path('sample.txt'))

The key is to use introspection to examine the call stack and get information about the calling function--the same function that wants to reliably reference the file within the package.

And of course, I eventaully refactored the function into a separate package so I could import it into multiple tests.

To derive this solution, I found the following resources very useful, and you might too: http://www.peterbe.com/plog/python-package-execution-path http://www.faqs.org/docs/diveintopython/regression_path.html http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062 http://docs.python.org/lib/module-sys.html

5 comments

Trent Mick 17 years, 9 months ago  # | flag

use "__file__" Unless I am misunderstanding you should be able to use the __file__ attribute of a module for that.

print os.path.dirname(__file__)
adam smith (author) 17 years, 9 months ago  # | flag

I think that what you are describing was basically the way that Peter B. was doing it in the link above, which was in turn based on something done in Zope, except for it to work, you have to pass __file__ to the function in some way (unless I am the one who is misunderstanding...). I wanted to be able to just have the function correctly guess the caller's package, which is why I ended up using the introspection code. It was all for the sake of making the calling code simpler.

Let me know if I've missed your point.

Stephen McDonald 17 years, 8 months ago  # | flag

Patch. This is a great solution however when I imported a module that references the function by importing another module, sys._getframe(1) does not return a frame object and instead returns an object with a _frame attribute that contains the frame object. I don't understand the inner workings of Python enough to explain this however this patch fixes the function:

import os, sys, inspect
def execution_path(filename):
    frame = sys._getframe(1)
    if hasattr(frame, "_frame"):
        frame = frame._frame
    return os.path.join(os.path.dirname(inspect.getfile(frame)), filename)
Stephen McDonald 17 years, 8 months ago  # | flag

Patched again. I've since come across scenarios where inspect.getfile() still fails. A code object however seems to always be available and its file property can be used without needing the insepct module at all:

import os, sys
def execution_path(filename):
    return os.path.join(os.path.dirname(sys._getframe(1).f_code.co_filename), filename)
adam smith (author) 17 years, 7 months ago  # | flag

Thank you. Thank you for improving this approach!