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

adam smith

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

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.

Justin

Very useful, thanks.

Your email will never ever be published.

Related posts