May 9, 2013

Debugging CherryPy applications with Werkzeug

When you are working on web application within your development machine it is very handy to have two things, code reloading and in-place debugger, code reloading is provided by CherryPy itself you just have to play nice with the cherrypy.engine and the debugger feature will enable you to inspect the frames of the stack right from your browser and see what's going on when you application explode, the Werkzeug DebuggedApplication provides that functionality.

So how do you integrate the wsgi middlewares with you CherryPy application?, the answer is wsgi.pipeline, from my point of view is cleaner and simpler than actually wrap you application in the other object.

I have found a few different ways of doing this on the Internet but none of them seems to actually use the appropriate configuration parameters, which is a shame given that I consider CherryPy as an extremely parametrizable library with the configuration system that have all over the place.

The code below it's flexible with the configuration, just remove the DemoApplication and set the app argument to your application, it would be a nice feature to inspect the config file and look for tree.APP and use that, but I will leave that for another post.

So hopefully this will help you to debug your CherryPy applications and by the way this is also python 3 compatible I have tried the Werkzeug fork for python 3 and is working fine.

import sys

import cherrypy
from cherrypy.lib import reprconf
from cherrypy.wsgiserver import CherryPyWSGIServer
from cherrypy.process.plugins import Autoreloader
from cherrypy.process.servers import ServerAdapter
from werkzeug.debug import DebuggedApplication


def _merge_configs(config, section_name, newconfig):
    """Merge a configuration section inside
    a previous config (file, dict, reprconf.Config).
    """
    if config is None:
        config = reprconf.Config()
    else:
        config = reprconf.Config(config)
    section = config.get(section_name, {})
    section.update(newconfig)
    config[section_name] = section
    return config


def run_with_debugger(app, config=None, autoreload=True, def_host='0.0.0.0',
                      def_port=8080):
    """Run the cherrypy application with the CherryPyWSGIServer and with the
    werkzeug debugging middleware.

    The WSGI server is wrapped inside a ServerAdapter and subscribed to the
    cherrypy.engine, so any cherrypy plugin is going to work with this.

    By default the Autoreloader plugin is enabled, this obeys the
    *autoreload* parameter.

    **The wrapped applicantion cannot use the  InternalRedirect exception.**
    """
    dbgconfig = {
          'request.throw_errors': True,
          'wsgi.pipeline': [('debugger', DebuggedApplication),],
          'wsgi.debugger.evalex': True
    }
    config = _merge_configs(config, '/', dbgconfig)
    cherrypy.config.update(config)
    if 'global' in config:
        host = config['global'].get('server.socket_host', def_host)
        port = config['global'].get('server.socket_port', def_port)
    else:
        host, port = def_host, def_port
    bind_addr = (host, port)
    app = cherrypy.Application(app, None, config=config)
    wserver = CherryPyWSGIServer(bind_addr, app)
    cherrypy.server.unsubscribe()
    # bind_addr is not really required in ServerAdapter, but it
    # does improve the messages that generates the adapter.
    ServerAdapter(cherrypy.engine, wserver, bind_addr).subscribe()
    if autoreload:
        Autoreloader(cherrypy.engine).subscribe()
    cherrypy.engine.start()
    cherrypy.engine.block()


class DemoApplication(object):

    @cherrypy.expose
    def index(self):
        return ('Hi!, how about <a href="/OMG">this</a> or'
                ' a quick hack at the <a href="/console">console</a>.')

    @cherrypy.expose
    def OMG(self, arg=None, **kwargs):
        if arg is None:
            raise Exception('OMG you did not pass any argument! (which is good)')
        else:
            return ('Ehem... the point of this was to demostrate when it fails,'
                    ' please do not pass any argument, like '
                    '<a href="/OMG">/OMG</a>, thanks :).')


if __name__ == '__main__':
    try:
       config_file = sys.argv[1]
    except IndexError:
       config_file = None
    run_with_debugger(DemoApplication(), config_file)
Tags: cherrypy python