[mod_python] Raising Exceptions

Graham Dumpleton grahamd at dscpl.com.au
Sat Sep 30 03:13:38 EDT 2006


What you have uncovered is a symptom of some of the problems that
exist in the module importer in mod_python. The main thing this example
touches on is that the module importer tries to support itself the  
loading
of Python packages. This causes various problems and for that reason,
mod_python will not do that in mod_python 3.3 and will always defer to
the standard Python importer for importing of packages.

The consequences of this change and other changes in mod_python 3.3
though are that the way you are trying to structure all of your code  
in the
document tree as one big package will not necessarily work by default in
mod_python 3.3. To get it to work you will need to override module  
search
paths with the PythonPath directive and will have to be very careful not
to mix Python packages and file based modules in the top directory where
your PythonHandler directive is used.

Finally, these changes in mod_python 3.3 will mean that there will be no
automatic module reloading on Python code structured as Python
packages. Thus, you will need to restart Apache every time you make a
change to your code due to the way you use packages. Automatic
module reloading still works with file based Python modules though,
but your code isn't configured that way.

Anyway, the key problem arises from the fact that you have put the
definition of the MyError exception class in test/__init__.py. This  
combines
with problems in the module importer whereby it performs redundant  
reloads
of __init__.py files within a package. Turns out that this is a  
particular
issue I haven't noted before and thus it isn't documented in the list of
importer problems described at:

   http://www.dscpl.com.au/wiki/ModPython/Articles/ 
ModuleImportingIsBroken

That you are mixing import and apache.import_module() could still have
resulted in redundant reloads at other times though and thus similar
problems. See ISSUE 9 and 10 in that document.

Stepping through this, the first thing that happens when the request  
arrives
is that mod_python uses apache.import_module() to load your top level
handler module test.dispatcher and executes the handler in it.  
Because it
is part of a package, it first loads test/__init__.py and then the  
sub module
test/dispatcher.py to simulate true package loading. In test/ 
dispatcher.py it
uses import to get the class MyError. This comes from the test/ 
__init__.py
just loaded and already stored in sys.modules.

The next thing is that apache.import_module() is asked to import the
module test.handlers.testhandler. The apache.import_module() detects  
that
the module hasn't been loaded before and so needs to load it. The  
problem
is the importer doesn't check whether it has already loaded the  
__init__.py
files already for that directory or any parent directories of the  
package and
thus proceeds to load the test/__init__.py file (a second time) and  
then the
test/handlers/__init__.py file.

That test/__init__.py is loaded a second time is now the trigger for the
problem. This is because in reloading the file the definition of  
MyError is
replaced with a new instances loaded from the file. At this point,  
because
you imported MyError as:

   from test import MyError

the test/dispatcher.py file holds a reference to the version of  
MyError from
before the reload. Ie., not the same as that now in test/__init__.py.  
Now
when test.handlers.testhandler gets loaded, it will find the newer  
version
of MyError in test/__init__.py. When the testhandler handler raises  
MyError
it is using the new MyError whereas as the top level handler is  
expecting
to see the old MyError. Because they are different they will not  
match and
so it will get caught as an Exception instead.

How do you solve this with the current module importer? The quick answer
is to never put anything in the __init__.py files of packages being  
imported
using apache.import_module().

Graham

On 29/09/2006, at 8:09 PM, John Keyes wrote:

> Hi guys,
>
> I've included some test code (see below) for some weird
> behaviour I've noticed.
>
> I have dispatcher.py set up as a handler which dynamically
> imports another handler (testhandler.py) and then calls
> it's handler function.
>
> The handler function in testhandler throws a user defined
> exception (see __init__.py) which the dispatcher is
> explicity set up to catch (see except MyError).
>
> If I import testhandler using apache.import_module I
> get the following output:
>
>  test
>  testhandler - b4 raise
>  exception - MyError - jk
>
> Yet if I use __import__ and getattr I get the following
> output:
>
>  test
>  testhandler - b4 raise
>  myerror
>
> As you can see using import_module the incorrect except
> block is executed, but the class name of the exception
> is correct.
>
> Can anyone explain to me why this is the case?
>
> I've the code included inline but if you want to run the
> code I've attached a zip file as well.
>
> Apache     - 2.0.58
> mod_python - 3.2.8
> Python     - 2.4.3
>
> Cheers,
> -John K
>
> == test.__init__.py ==
> class MyError(Exception):
>    def __init__(self, msg):
>        self.msg = msg
>
>    def __str__(self):
>        return self.msg
>
> == test.dispatcher.py ==
> from mod_python import apache
>
> from test import MyError
>
> def handler(req):
>    req.write("\ntest")
>
>    try:
>        #test_handler = apache.import_module 
> ('test.handlers.testhandler')
>        test_handler = my_import('test.handlers.testhandler')
>        val = test_handler.handler(req)
>    except MyError, me:
>        req.write("\nmyerror")
>    except Exception, e:
>        req.write("\nexception - %s - %s" % (e.__class__.__name__, e))
>
>    return apache.OK
>
> def my_import(name):
>    mod = __import__(name)
>    components = name.split('.')
>    for comp in components[1:]:
>        mod = getattr(mod, comp)
>    return mod
>
> == test.handlers.testhandler.py ==
> from mod_python import apache
>
> from test import MyError
>
> def handler(req):
>    req.write("\ntesthandler - b4 raise")
>
>    raise MyError('jk')
>
>    req.write("\ntesthandler - af raise")
>    return apache.OK
> <import_test.zip>
> _______________________________________________
> Mod_python mailing list
> Mod_python at modpython.org
> http://mailman.modpython.org/mailman/listinfo/mod_python


More information about the Mod_python mailing list