Here’s an overview of the main improvements of webapp2 compared to webapp.
Table of Contents
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():
run_wsgi_app(app)
if __name__ == '__main__':
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.
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.
webapp2 uses a full-featured response object from WebOb
. If offers several
conveniences to set headers, like easy cookies and other goodies:
class MyHandler(webapp2.RequestHandler):
def get(self):
self.response.set_cookie('key', 'value', max_age=360, path='/')
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.
abort(404)
# Raise a 'Forbidden' exception and let the 403 error handler do its job.
self.abort(403)
HTTP exceptions can also be handled by the WSGI application:
# ...
import logging
def handle_404(request, response, exception):
logging.exception(exception)
response.write('Oops! I could swear this page was here!')
response.set_status(404)
app = webapp2.WSGIApplication([
('/', MyHandler),
])
app.error_handlers[404] = handle_404
Lazy handlers can be defined as a string to be imported only when needed:
app = webapp2.WSGIApplication([
('/', 'my.module.MyHandler'),
])
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 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),
])
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'),
])
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'),
])
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'),
])
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.
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'),
])
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.
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')
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}),
])
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.
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.
Because it works on threaded environments, webapp2 is ready for when App Engine introduces threading support in the Python 2.7 runtime.
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: