[mod_python] mod_python doing weird things to import

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


More information about the Mod_python mailing list