Migrating to Python 2.7, part 2: Webapp and templates

In part 1 of the migration series, we discussed changes to your app to take advantage of the 2.7 runtime's support for multithreading. Today, we'll start looking at the changes to the included libraries, and how we'll have to modify our app to use them.

webapp2

First up is the replacement of App Engine's lightweight webapp framework with webapp2. Webapp2 is an external open-source project, which says it "follows the simplicity of webapp, but improves it in some ways: it adds better URI routing and exception handling, a full featured response object and a more flexible dispatching mechanism". Webapp2 does an excellent job of maintaining compatibility with webapp, which is why it was chosen as a replacement, but it also adds a number of useful improvements on it.

To see what's changed in concrete terms, let's assume we have a very straightforward app that looks something like this:

import os

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

class BaseHandler(webapp.RequestHandler):
  def render_template(self, filename, **template_args):
	path = os.path.join(os.path.dirname(__file__), 'templates', filename)
	self.response.out.write(template.render(path, template_args))

class IndexHandler(BaseHandler):
  def get(self):
    self.render_template('index.html', name=self.request.get('name'))

application = webapp.WSGIApplication([
    ('/', IndexHandler),
], debug=True)

So what do we have to change in order to get things running on 2.7? In short, nothing! webapp2 does such a good job of maintaining backwards compatibility with webapp that you can use it in exactly the same way, and the Python 2.7 runtime has aliased the google.appengine.ext.webapp module to webapp2 in order to facilitate this. The template module likewise continues to work, though it now points to a private copy of Django templates to avoid conflicts with other libraries your app may use. We'll go into more details about templates later.

That said, relying on the aliased module is a bit icky, and if we want to take advantage of new features in webapp2, we really should update our app to use it explicitly. Here's the code after changing it to use webapp2 explicitly:

import os

import webapp2
from google.appengine.ext.webapp import template

class BaseHandler(webapp2.RequestHandler):
  def render_template(self, filename, **template_args):
	path = os.path.join(os.path.dirname(__file__), 'templates', filename)
	self.response.write(template.render(path, template_args))

class IndexHandler(BaseHandler):
  def get(self):
    self.render_template('index.html', name=self.request.get('name'))

application = webapp2.WSGIApplication([
    ('/', IndexHandler),
], debug=True)

As you might expect, there's very few changes - mostly relating to the module name for webapp2. Note we also replaced 'self.response.out.write' with 'self.response.write' - this is one of the convenience methods webapp2 provides to neaten our code up a little bit.

Since we're using webapp2 now, we can take advantage of webapp2's enhanced features, like more sophisticated routing, custom error handlers, URI building, and a wide variety of extras including session support. webapp2's manual is definitely worth browsing through to see what you might find useful in your app.

Templating with Jinja2

Next up is the templating engine. As our demo above demonstrated, our app ought to continue working fine if we continue to use the templates from 'google.appengine.ext.webapp.templates', but this is really only there for legacy support, and it'd be nice if we can avoid relying on deprecated features. Fortunately, App Engine provides jinja2 as an optional library, and webapp2 provides some glue to make using it easier. Jinja2's a templating engine that's heavily inspired by the Django templating engine that webapp provides, so we ought to be able to switch to it without making any significant changes to our templates.

First up, we need to tell App Engine we want to use the library. We do this by adding a clause to our app.yaml:

libraries:
- name: jinja2
  version: "2.6"

Note we specified a version explicitly here, 2.6. We could have specified "latest", which would cause App Engine automatically use the most recent version, but it's better to keep control of this ourselves, to ensure we don't get unexpected breakage if a new release contains a reversion. You can see the list of available optional libraries here.

Next, we need to modify our app to make use of jinja2. Here's the updated webapp:

import os
import webapp2
from webapp2_extras import jinja2

class BaseHandler(webapp2.RequestHandler):
  @webapp2.cached_property
  def jinja2(self):
	return jinja2.get_jinja2(app=self.app)

  def render_template(self, filename, **template_args):
	self.response.write(self.jinja2.render_template(filename, **template_args))

class IndexHandler(BaseHandler):
  def get(self):
    self.render_template('index.html', name=self.request.get('name'))

application = webapp2.WSGIApplication([
    ('/', IndexHandler)
], debug=True)

Following the instructions in the webapp2 docs, we've made a couple of changes. We've set up a cached property on our base handler called `jinja2` that will return a jinja2 environment. The jinja2 environment encapsulates all the settings and parameters needed for template rendering, and for our basic needs we'll be using it unmodified.

The other change we've made is to change the render_response method to use jinja2. This actually made our method even simpler, since jinja2's `render_template` method takes care of resolving paths for us (it assumes your templates are in a directory called 'templates' - this is just one of the settings you can configure on the environment).

With those two changes made, we've done everything necessary to port our (trivial) webapp to the App Engine Python 2.7 runtime. Tune back in next week for more about the new runtime and what you can do with it!

Is there something in the new runtime you're desperate to hear more about? Leave a note in the comments!

Comments

blog comments powered by Disqus