|
Byron Ellacott
bje at apnic.net
Wed Jun 9 22:14:24 EDT 2004
(Apache/2.0.47 mod_python/3.1.3 Python/2.3.3)
I've been beating my head against a bug for a few days now. It looks
like there's something strange happening when mod_python imports
modules that are parts of packages. To sum up:
I keep the modules I develop under a namespace of "bje.<foo>" - just
because I prefer to keep them distinct in some respect. I have a
module called "bje.pframe" that is my web framework; right now I've
pared it right down to just print some debug as text/plain and return
apache.OK. I have a module called "bje.budgetweb" that is the web
application proper; it uses bje.pframe for certain things. And
finally, there's bje.budget, which is a library used by bje.budgetweb
(and bje.budgetcli, down the road).
So, trimmed down, bje.budgetweb is:
------
import bje.budget
import bje.pframe
def handler(req):
app = bje.pframe.PFrameApp({}, {})
app.handler(req)
------
And bje.pframe is:
------
from mod_python import apache
from mod_python import util
class PFrameApp:
def __init__(self, views, actions):
self.views = views
self.actions = actions
def handler(self, req):
req.content_type = 'text/plain'
req.write('Filename: %s\n' % req.filename)
return apache.OK
def authenhandler(req):
if req.user == 'test' and req.get_basic_auth_pw() == 'test':
return apache.OK
else:
return apache.HTTP_UNAUTHORIZED
------
There's a bit more in them, but none of it is called right now. The
trouble is, I get an AttributeError exception: 'module' object has no
attribute 'pframe', in bje.budgetweb, where it tries to call
bje.pframe.PFrameApp().
Adding debugging to output dir(bje) shows:
__*__
budget
No pframe! Despite the "import bje.pframe" ! Checking
sys.modules.has_key('bje.pframe') shows True.
These modules are configured via a <Location /budget> block. There's
a PythonHandler of bje.budgetweb, and a PythonAuthenHandler of
bje.pframe.
If I disable the PythonAuthenHandler, the problem goes away: dir(bje)
includes "pframe" and the bje.pframe.PFrameApp() call succeeds.
For my particular case, this isn't a big deal: I'm not going to do
authentication the way I had originally intended anyway. But it seems
to point out a bug in mod_python's import code, when it's importing
from packages. It's not compatible with python's import statement!
Looking at the source, it seems the trouble is in apache.py's
import_module(). When it uses imp.load_module(), it winds up loading
modules 'bje' and 'bje.pframe,' creating sys.modules[] entries for
both. Subsequent loads of bje.pframe (such as, via "import
bje.pframe") will then of course return the sys.modules[] entry
instead of reloading it. This is all correct behaviour, /except/
import_module() has not set "bje.pframe" == sys.modules['bje.pframe'].
I would consider this a bug in mod_python. You may not agree, though,
so I can at best suggest that a fix for it would be to setattr() each
subsequently loaded module into its parent. I've attached a patch
against 3.1.3 that does just that.
The other solution would be for me to add "bje.pframe =
sys.modules['bje.pframe']" after the import, or to use mod_python's
import_module() method instead of the import keyword, but I don't
really want to do either of those. :)
--
bje
-------------- next part --------------
diff -c old/apache.py new/apache.py
*** old/apache.py Wed Jun 9 21:10:05 2004
--- new/apache.py Wed Jun 9 21:12:35 2004
***************
*** 449,460 ****
--- 449,464 ----
s = "mod_python: (Re)importing module '%s'" % module_name
_apache.log_error(s, APLOG_NOERRNO|APLOG_NOTICE)
+ parent = None
parts = module_name.split('.')
for i in range(len(parts)):
f, p, d = imp.find_module(parts[i], path)
try:
mname = ".".join(parts[:i+1])
module = imp.load_module(mname, f, p, d)
+ if parent:
+ setattr(parent, parts[i], module)
+ parent = module
finally:
if f: f.close()
if hasattr(module, "__path__"):
|