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
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
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!
Comments
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?
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)
#USAGE
from package_home import execution_path
execution_path('template.html')
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.