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):
/home/someone/mypackage/ __init__.py template.html foobar.py
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
The problem is that if your script does the following:
import sys sys.path.insert(0, '/home/someone/mypackage') import foobar foobar.foo()
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!
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:
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
(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?
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
""" this preserves the functionality of the original package_home function
within this new approach"""
return os.path.join(_package_home(inspect.getfile(sys._getframe(1))), 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.
Very useful, thanks.