I stumbled across a problem today with running a python package from a different directory than where the python files are. The package looked like this (in principle):


The foobar.py script contains this code:

def foo():
   tmpl = open('template.html').read()
   return tmpl.replace('X','Y')

That's fine and works when you run the file directly. The problem comes when you run the foobar.py script from somewhere completely different (e.g. /home/mrbloggs/otherpackage/). The solution to this comes from Zope's package_home.

The problem is that if your script does the following:

import sys
sys.path.insert(0, '/home/someone/mypackage')
import foobar

Then the file template.html can not be found. That's because upon executing the importing script it holds a different execution path.

So what I did was that I added the following code which I pinched from Zope's package_home:

def package_home(gdict):
   filename = gdict["__file__"]
   return os.path.dirname(filename)

def foo():
   tmpl = open(os.path.join(package_home(globals()), 'template.html')).read()
   return tmpl.replace('X','Y')

Good to know for the future!

adam smith - 16 June 2006 [«« Reply to this]
Thanks for this post--I have been perplexed by this same issue. I did the following to make it a little more convenient to call:

I put this code into a package_home.py module:

import os

def get_package_home(context):
return os.path.dirname(context["__file__"])

def get_execution_path(context, filename):
return os.path.join(get_package_home(context), filename)

Then in my code, I just need to import it and call it like this:

from package_home import get_execution_path
get_execution_path(globals(), 'template.html')

(It doesn't look like indentation is preserved in these posts.)

Python is not my primary language, so I am not sure if this is possible, but I wonder if we can't get rid of the repetitive globals() in the call by having the get_execution_path method somehow (through introspection) get this information for the calling object.

Also, is there a way to include these methods in an upper level package's __init__.py file so they can be available to all the code in an application without having to explicitly import it into every module we want to use it in?
adam smith - 14 July 2006 [«« Reply to this]
OK, I finally returned to this, and here is the latest version using introspection to make finding the package home as convenient as possible:

# IN package_home.py
import os, sys, inspect

def _package_home(filename):
return os.path.dirname(filename)

def package_home(context):
""" this preserves the functionality of the original package_home function
within this new approach"""
return _package_home(context["__file__"])

def execution_path(filename):
return os.path.join(_package_home(inspect.getfile(sys._getframe(1))), filename)

def execution_path2(filename):
"""alternately, you can just use this method to hide a lot of complexity
without needing any of the other functions above"""
return os.path.join(os.path.dirname(inspect.getfile(sys._getframe(1))), filename)

from package_home import execution_path

Again, sorry that I don't know how to preserve indentation in these posts.

The sys._getframe(1) call examines the call stack and retrieves information about the calling function, so no matter how it is executed, it should work.
Justin - 21 October 2009 [«« Reply to this]
Very useful, thanks.

Your email will never ever be published