« Sorry about the recent outagesURL-rewriting in CherryPy 2.1 »

WSGI wrapper for mod_python


Permalink 01:18:00 am, by fumanchu Email , 879 words   English (US)
Categories: Python, WSGI

WSGI wrapper for mod_python

Update Nov 6, 2005: Finally got it to work with Apache2-prefork on Unix (it only worked on mpm_winnt until now).

Update Oct 25, 2005: I was having a problem setting up a new install of my CherryPy application, using this recipe. It turned out that I didn't have the right interpreter_name in my PythonImport directive:

PythonImport module interpreter_name

Therefore, the CherryPy server started in a different intepreter than the one being used for the requests. It must exactly match the value of req.interpreter, and is case-sensitive. I've updated the code with comments to that effect (just to have it all in one place).

Update Aug 11, 2005: I was having a problem serving .css and .js pages. CherryPy's standalone WSGI server did fine, but mod_python did not. I finally tracked it down to the fact that I was both setting apache's req.status and returning req.status from the handler. Funky. It worked when I chose to simply return the value, and not set it.

Update June 5, 2005:

  1. Pages take forever to terminate when returning a status of 200--apache.OK must be returned instead in that case.

  2. Added code for hotshot profiling.

  3. Added code for using paste.lint

As I mentioned I was doing last week, I wrote a more complete WSGI wrapper for modpython. Here it is. Feedback welcome. Phil Eby told me he'd like a mod_python wrapper for inclusion in wsgiref; he should feel free to use this one however he sees fit. ;)

WSGI wrapper for mod_python. Requires Python 2.2 or greater.

Example httpd.conf section for a CherryPy app called "mcontrol":

<Directory D:\htdocs\mcontrol>
    SetHandler python-program
    PythonHandler wsgiref.modpy_wrapper::handler
    PythonOption application cherrypy.wsgiapp::wsgiApp
    PythonOption import mcontrol.cherry::startup


import sys
from mod_python import apache
from wsgiref.handlers import BaseCGIHandler

class InputWrapper(object):
    def __init__(self, req):
        self.req = req
    def close(self):
    def read(self, size=-1):
        return self.req.read(size)
    def readline(self):
        return self.req.readline()
    def readlines(self, hint=-1):
        return self.req.readlines(hint)
    def __iter__(self):
        line = self.readline()
        while line:
            yield line
            # Notice this won't prefetch the next line; it only
            # gets called if the generator is resumed.
            line = self.readline()

class ErrorWrapper(object):
    def __init__(self, req):
        self.req = req
    def flush(self):
    def write(self, msg):
    def writelines(self, seq):

bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
             "when running a version of mod_python < 3.1")

class Handler(BaseCGIHandler):
    def __init__(self, req):
        options = req.get_options()
        # Threading and forking
            q = apache.mpm_query
        except AttributeError:
            threaded = options.get('multithread', '').lower()
            if threaded == 'on':
                threaded = True
            elif threaded == 'off':
                threaded = False
                raise ValueError(bad_value % "multithread")
            forked = options.get('multiprocess', '').lower()
            if forked == 'on':
                forked = True
            elif forked == 'off':
                forked = False
                raise ValueError(bad_value % "multiprocess")
            threaded = q(apache.AP_MPMQ_IS_THREADED)
            forked = q(apache.AP_MPMQ_IS_FORKED)
        env = dict(apache.build_cgi_env(req))
        if req.headers_in.has_key("authorization"):
            env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"]
        self.request = req
        self._write = req.write
    def _flush(self):
    def send_headers(self):
        self.headers_sent = True
        # Can't just return 200 or the page will hang until timeout
        s = int(self.status[:3])
        if s == 200:
            self.finalstatus = apache.OK
            self.finalstatus = s
        # the headers.Headers class doesn't have an iteritems method...
        for key, val in self.headers.items():
            if key.lower() == 'content-length':
                if val is not None:
            elif key.lower() == 'content-type':
                self.request.content_type = val
                self.request.headers_out[key] = val

_counter = 0

def profile(req):
    # Call this function instead of handler
    # to get profiling data for each call.
    import hotshot, os.path
    ppath = os.path.dirname(__file__)
    if not os.path.exists(ppath):
    global _counter
    _counter += 1
    ppath = os.path.join(ppath, "cp_%s.prof" % _counter)
    prof = hotshot.Profile(ppath)
    result = prof.runcall(handler, req)
    return result

def handler(req):
    config = req.get_config()
    debug = int(config.get("PythonDebug", 0))
    options = req.get_options()
    # Because PythonImport cannot be specified per Directory or Location,
    # take any 'import' PythonOption's and import them. If a function name
    # in that module is provided (after the "::"), it will be called with
    # the request as an argument. The module and function, if any, should
    # be re-entrant (i.e., handle multiple threads), and, since they will
    # be called per request, must be designed to run setup code only on the
    # first request (a global 'first_request' flag is usually enough).
    import_opt = options.get('import')
    if import_opt:
        atoms = import_opt.split('::', 1)
        modname = atoms.pop(0)
        module = __import__(modname, globals(), locals(), [''])
        if atoms:
            func = getattr(module, atoms[0])
    # Import the wsgi 'application' callable and pass it to Handler.run
    modname, objname = options['application'].split('::', 1)
    module = __import__(modname, globals(), locals(), [''])
    app = getattr(module, objname)
    h = Handler(req)
##    from paste import lint
##    app = lint.middleware(app)
    # finalstatus was set in Handler.send_headers()
    return h.finalstatus


Comment from: Adrian Holovaty [Visitor] · http://www.holovaty.com/

Thanks very much for this. Are you interested in working with me to get this to work with Paste? Please contact me to chat about it. (I couldn't find your e-mail address on this site.)

05/31/05 @ 15:33
Comment from: alikat [Member] Email

Bob - it's really great to understand now - what all this means...not that I really understand any of it - but now I understand why you post it. Thanks.

06/06/05 @ 13:52
Comment from: Nicolas Lehuen [Visitor] · http://nicolas.lehuen.com/

Hi, I'm on the developers team at mod_python, and I've found your implementation of the WSGI spec for mod_python. I've seen that there are requests to integrate your code in application frameworks, but would not it be more logical to integrate it into mod_python ? I'm just speaking on my behalf for now, but would you consider donating this code to the mod_python community (in exchange of due reference and huge thanks) ? Contact me if you're interested. Thanks !

06/08/05 @ 15:43
Comment from: Leandro Lucarella [Visitor] · http://www.mazziblog.com.ar/blog/

It would be really great to include a wsgi handler in mod_python distribution!

07/02/05 @ 13:25
Comment from: fumanchu [Member] Email


Yes, it would be great! I was contacted a few weeks ago by the mod_python team, and have given them full permission to use my handler in the standard distribution. I'm just waiting now for it to show up on their site. ;)

07/03/05 @ 13:36
Comment from: Lethalman [Visitor] · http://www.lethalman.net

I get Segmantation fault in apache logs. I'm using apache 2.0.49, Python 2.4.2, CherryPy 2.1.0, ModPython 3.1.4... it happens when cherrypy is imported.
In fact i tried to do a simple "import cherrypy" in modpython_handler.handler function and i get segfault. Imports work with other modules, what about?

11/09/05 @ 02:59
Comment from: fumanchu [Member] Email


If you could make a ticket on the CherryPy Trac site, it would be a big help. http://www.cherrypy.org/newticket Please include the full text of the log when the segfault occurs.

11/09/05 @ 09:04

Leave a comment

Your email address will not be revealed on this site.

Your URL will be displayed.

Please enter the phrase "I am a real human." in the textbox above.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
October 2016
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          


The requested Blog doesn't exist any more!

XML Feeds

open source blog tool