webapp2 features

Here’s an overview of the main improvements of webapp2 compared to webapp.

Compatible with webapp

webapp2 is designed to work with existing webapp apps without any changes. See how this looks familiar:

import webapp2 as webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class HelloWorldHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write('Hello, World!')

app = webapp.WSGIApplication([
    ('/', HelloWorldHandler),
], debug=True)

def main():

if __name__ == '__main__':

Everybody starting with App Engine must know a bit of webapp. And you you can use webapp2 exactly like webapp, following the official tutorials, and learn the new features later, as you go. This makes webapp2 insanely easy to learn.

Also, the SDK libraries that use webapp can be used with webapp2 as they are or with minimal adaptations.

Compatible with latest WebOb

The WebOb version included in the App Engine SDK was released in 2008. Since then many bugs were fixed and the source code became cleaner and better documented. webapp2 is compatible with the WebOb version included in the SDK, but for those that prefer the latest version can be used as well. This avoids the bugs #170, #719 and #2788, at least.

Status code exceptions

abort() (or self.abort() inside handlers) raises a proper HTTPException (from WebOb) and stops processing:

# Raise a 'Not Found' exception and let the 404 error handler do its job.
# Raise a 'Forbidden' exception and let the 403 error handler do its job.

Improved exception handling

HTTP exceptions can also be handled by the WSGI application:

# ...
import logging

def handle_404(request, response, exception):
    response.write('Oops! I could swear this page was here!')

app = webapp2.WSGIApplication([
    ('/', MyHandler),
app.error_handlers[404] = handle_404

Lazy handlers

Lazy handlers can be defined as a string to be imported only when needed:

app = webapp2.WSGIApplication([
    ('/', 'my.module.MyHandler'),

Keyword arguments from URI

RequestHandler methods can also receive keyword arguments, which are easier to maintain than positional ones. Simply use the Route class to define URIs (and you can also create custom route classes, examples here):

class BlogArchiveHandler(webapp2.RequestHandler):
    def get(self, year=None, month=None):
        self.response.write('Hello, keyword arguments world!')

app = webapp2.WSGIApplication([
    webapp2.Route('/<year:\d{4}>/<month:\d{2}>', handler=BlogArchiveHandler, name='blog-archive'),

Positional arguments from URI

Positional arguments are also supported, as URI routing is fully compatible with webapp:

class BlogArchiveHandler(webapp2.RequestHandler):
    def get(self, year, month):
        self.response.write('Hello, webapp routing world!')

app = webapp2.WSGIApplication([
    ('/(\d{4})/(\d{2})', BlogArchiveHandler),

Returned responses

Several Python frameworks adopt the pattern on returning a response object, instead of writing to an existing response object like webapp. For those that prefer, webapp2 supports this: simply return a response object from a handler and it will be used instead of the one created by the application:

class BlogArchiveHandler(webapp2.RequestHandler):
    def get(self):
        return webapp2.Response('Hello, returned response world!')

app = webapp2.WSGIApplication([
    webapp2.Route('/', handler=HomeHandler, name='home'),

Custom handler methods

webapp2 routing and dispatching system can do a lot more than webapp. For example, handlers can also use custom methods:

class MyHandler(webapp2.RequestHandler):
    def my_custom_method(self):
        self.response.write('Hello, custom method world!')

    def my_other_method(self):
        self.response.write('Hello, another custom method world!')

app = webapp2.WSGIApplication([
    webapp2.Route('/', handler=MyHandler, name='custom-1', handler_method='my_custom_method'),
    webapp2.Route('/other', handler=MyHandler, name='custom-2', handler_method='my_other_method'),

View functions

In webapp2 handlers don’t need necessarily to be classes. For those that prefer, functions can be used as well:

def my_sweet_function(request, *args, **kwargs):
    return webapp2.Response('Hello, function world!')

app = webapp2.WSGIApplication([
    webapp2.Route('/', handler=my_sweet_function, name='home'),

More flexible dispatching mechanism

The WSGIApplication in webapp is hard to modify. It dispatches the handler giving little chance to define how it is done, or to pre-process requests before a handler method is actually called. In webapp2 the handlers dispatch themselves, making it easy to implement before and after dispatch hooks.

webapp2 is thought to be lightweight but flexible. It basically provides an easy to customize URI routing and dispatching mechanisms: you can even extend how URIs are matched or built or how handlers are adapted or dispatched without subclassing.

For an example of webapp2’s flexibility, see A micro-framework based on webapp2.

Domain and subdomain routing

webapp2 supports domain and subdomain routing to restrict URI matches based on the server name:

routes.DomainRoute('www.mydomain.com', [
    webapp2.Route('/', handler=HomeHandler, name='home'),

Match HTTP methods or URI schemes

webapp2 routing system allows routes to be restricted to the HTTP method or a specific URI scheme. You can set routes that will only match requests using ‘https’, for example.

URI builder

URIs defined in the aplication can be built. This is more maintanable than hardcoding them in the code or templates. Simply use the uri_for() function:

uri = uri_for('blog-archive', year='2010', month='07')

And a handler helper for redirects builds the URI to redirect to. redirect_to = redirect + uri_for:

self.redirect_to('blog-archive', year='2010', month='07')

Redirection for legacy URIs

Old URIs can be conveniently redirected using a simple route:

def get_redirect_uri(handler, *args, **kwargs):
    return handler.uri_for('view', item=kwargs.get('item'))

app = webapp2.WSGIApplication([
    webapp2.Route('/view/<item>', ViewHandler, 'view'),
    webapp2.Route('/old-page', RedirectHandler, defaults={'uri': '/view/i-came-from-a-redirect'}),
    webapp2.Route('/old-view/<item>', RedirectHandler, defaults={'uri': get_redirect_uri}),

Simple, well-tested and documented

webapp2 is simple, extensively documented and has almost 100% test coverage. The source code is explicit, magic-free and made to be extended. We like less.

Independent of the App Engine SDK

webapp2 doesn’t depend on the Google App Engine SDK and can be used outside of App Engine. If the SDK is not found, it has fallbacks to be used in any server as a general purpose web framework.

Future proof

Because it works on threaded environments, webapp2 is ready for when App Engine introduces threading support in the Python 2.7 runtime.

Same performance

Best of all is that with all these features, there is no loss of performance: cold start times are the same as webapp. Here are some logs of a ‘Hello World’ cold start:

100ms 77cpu_ms
143ms 58cpu_ms
155ms 77cpu_ms
197ms 96cpu_ms
106ms 77cpu_ms


The webapp2_extras package provides common utilities that integrate well with webapp2:

  • Localization and internationalization support
  • Sessions using secure cookies, memcache or datastore
  • Extra route classes – to match subdomains and other conveniences
  • Support for third party libraries: Jinja2 and Mako
  • Support for threaded environments, so that you can use webapp2 outside of App Engine or in the upcoming App Engine Python 2.7 runtime