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__"):
|