Wednesday, August 11, 2010

Flying with Flask on Google App Engine

A little late, finally I introduce the third part of using Python frameworks in Google App Engine. I wrote before about web2py and Django, and now is the time of Flask, a Python microframework based on Werkzeug, Jinja2 and good intentions. Unlike Django and web2py, Flask is not a full stack framework, it has not a database abstraction layer or an object relational mapper, Flask is totally decoupled from model layer. It is really good, because we can use the power of SQLAlchemy when we are working with relational databases, and when work with non-relational databases, we can use the native API.

Flask is a microframework, what means that we have more power on customizing the applications, but it is also a little more painful to build an application, because the framework is not a father that does about 10 billion of things for us: it is simple, but still fun! As Flask has no data abstraction layer, we will use the BigTable API directly.

So, as we have done in other parts of the series, the sample application will be a very simple blog, with a public view listing all posts and other login protected view used for writing posts. The first step is to setup the environment. It is very simple, but I little laborious: first we create an empty directory and put the app.yaml file inside it (yes, we will build everything from scratch). Here is the app.yaml code:
application: gaeseries
version: 3
runtime: python
api_version: 1

handlers:
- url: .*
  script: main.py
We just set the application ID, the version and the URL handlers. We will handle all request in main.py file. Late on this post, I will show the main.py module, the script that handles Flask with Google App Engine. Now, let’s create the Flask application, and deal with App Engine later :)

Now we need to install Flask inside the application, so we get Flask from Github (I used 0.6 version), extract it and inside the flask directory get the flask subdirectory. Because Flask depends on Werkzeug and Jinja2, and Jinja2 depends on simplejson, you need to get these libraries and install in your application too. Here is how you can get everything:
% wget http://github.com/mitsuhiko/flask/zipball/0.6
% unzip mitsuhiko-flask-0.6-0-g5cadd9d.zip
% cp -r mitsuhiko-flask-5cadd9d/flask ~/Projetos/blog/gaeseries
% wget http://pypi.python.org/packages/source/W/Werkzeug/Werkzeug-0.6.2.tar.gz
% tar -xvzf Werkzeug-0.6.2.tar.gz
% cp -r Werkzeug-0.6.2/werkzeug ~/Projetos/blog/gaeseries/
% wget http://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.5.tar.gz
% tar -xvzf Jinja2-2.5.tar.gz
% cp -r Jinja2-2.5/jinja2 ~/Projetos/blog/gaeseries/
% wget http://pypi.python.org/packages/source/s/simplejson/simplejson-2.1.1.tar.gz
% tar -xvzf simplejson-2.1.1.tar.gz
% cp -r simplejson-2.1.1/simplejson ~/Projetos/blog/gaeseries/
On my computer, the project is under ~/Projetos/blog/gaeseries, put all downloaded tools on the root of your application. Now we have everything that we need to start to create our Flask application, so let’s create a Python package called blog, it will be the application directory:
% mkdir blog
% touch blog/__init__.py
Inside the __init__.py module, we will create our Flask application and start to code. Here is the __init__.py code:
from flask import Flask
import settings

app = Flask('blog')
app.config.from_object('blog.settings')

import views
We imported two modules: settings and views. So we should create the two modules, where we will put the application settings and the views of applications (look that Flask deals in the same way that Django, calling “views” functions that receives a request and returns a response, instead of call it “actions” (like web2py). Just create the files:
% touch blog/views.py
% touch blog/settings.py
Here is the settings.py sample code:
DEBUG=True
SECRET_KEY='dev_key_h8hfne89vm'
CSRF_ENABLED=True
CSRF_SESSION_LKEY='dev_key_h8asSNJ9s9=+'
Now is the time to define the model Post. We will define our models inside the application directory, in a module called models.py:
from google.appengine.ext import db

class Post(db.Model):
    title = db.StringProperty(required = True)
    content = db.TextProperty(required = True)
    when = db.DateTimeProperty(auto_now_add = True)
    author = db.UserProperty(required = True)
The last property is a UserProperty, a “foreign key” to a user. We will use the Google App Engine users API, so the datastore API provides this property to establish a relationship between custom models and the Google account model.

We have defined the model, and we can finally start to create the application’s views. Inside the views module, let’s create the public view with all posts, that will be accessed by the URL /posts:
from blog import app
from models import Post
from flask import render_template

@app.route('/posts')
def list_posts():
    posts = Post.all()
    return render_template('list_posts.html', posts=posts)
On the last line of the view, we called the function render_template, which renders a template. The first parameter of this function is the template to be rendered, we passed the list_posts.html, so let’s create it using the Jinja2 syntax, inspired by Django templates. Inside the application directory, create a subdirectory called templates and put inside it a HTML file called base.html. That file will be the application layout and here is its code:
<html>
    <head>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
        <title>{% block title %}Blog{% endblock %}</title>
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>
And now create the list_posts.html template, with the following code:
{% extends "base.html" %}

{% block content %}
<ul>
    {% for post in posts %}
    <li>
        {{ post.title }} (written by {{ post.author.nickname() }})

        {{ post.content }}
    </li>
    {% endfor %}
</ul>
{% endblock %}
Now, to test it, we need to run Google App Engine development server on localhost. The app.yaml file defined a main.py script as handler for all requests, so to use Google App Engine local development server, we need to create the main.py file that run our application. Every Flask application is a WSGI application, so we can use an App Engine tool for running WSGI application. In that way, the main.py script is really simple:
from google.appengine.ext.webapp.util import run_wsgi_app
from blog import app

run_wsgi_app(app)
The script uses the run_wsgi_app function provided by webapp, the built-in Google Python web framework for App Engine. Now, we can run the application in the same way that we ran in the web2py post:
% /usr/local/google_appengine/dev_appserver.py .
And if you access the URL http://localhost:8080/posts in your browser, you will see a blank page, just because there is no posts on the database. Now we will create a login protected view to write and save a post on the database. Google App Engine does not provide a decorator for validate when a user is logged, and Flask doesn’t provide it too. So, let’s create a function decorator called login_required and decorate the view new_post with that decorator. I created the decorator inside a decorators.py module and import it inside the views.py module. Here is the decorators.py code:
from functools import wraps
from google.appengine.api import users
from flask import redirect, request

def login_required(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if not users.get_current_user():
            return redirect(users.create_login_url(request.url))
        return func(*args, **kwargs)
    return decorated_view
In the new_post view we will deal with forms. IMO, WTForms is the best way to deal with forms in Flask. There is a Flask extension called Flask-WTF, and we can install it in our application for easy dealing with forms. Here is how can we install WTForms and Flask-WTF:
% wget http://pypi.python.org/packages/source/W/WTForms/WTForms-0.6.zip
% unzip WTForms-0.6.zip
% cp -r WTForms-0.6/wtforms ~/Projetos/blog/gaeseries/
% wget http://pypi.python.org/packages/source/F/Flask-WTF/Flask-WTF-0.2.3.tar.gz
% tar -xvzf Flask-WTF-0.2.3.tar.gz
% cp -r Flask-WTF-0.2.3/flaskext ~/Projetos/blog/gaeseries/
Now we have installed WTForms and Flask-WTF, and we can create a new WTForm with two fields: title and content. Remember that the date and author will be filled automatically with the current datetime and current user. Here is the PostForm code (I put it inside the views.py file, but it is possible to put it in a separated forms.py file):
from flaskext import wtf
from flaskext.wtf import validators

class PostForm(wtf.Form):
    title = wtf.TextField('Title', validators=[validators.Required()])
    content = wtf.TextAreaField('Content', validators=[validators.Required()])
Now we can create the new_post view:
@app.route('/posts/new', methods = ['GET', 'POST'])
@login_required
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title = form.title.data,
                    content = form.content.data,
                    author = users.get_current_user())
        post.put()
        flash('Post saved on database.')
        return redirect(url_for('list_posts'))
    return render_template('new_post.html', form=form)
Now, everything we need is to build the new_post.html template, here is the code for this template:
{% extends "base.html" %}

{% block content %}
    <h1 id="">Write a post</h1>
    <form action="{{ url_for('new_post') }}" method="post" accept-charset="utf-8">
        {{ form.csrf_token }}
        <p>
            <label for="title">{{ form.title.label }}</label>

            {{ form.title|safe }}

            {% if form.title.errors %}
            <ul class="errors">
                {% for error in form.title.errors %}
                <li>{{ error }}</li>
                {% endfor %}
            </ul>
            {% endif %}
        </p>
        <p>
            <label for="content">{{ form.content.label }}</label>

            {{ form.content|safe }}

            {% if form.content.errors %}
            <ul class="errors">
                {% for error in form.content.errors %}
                <li>{{ error }}</li>
                {% endfor %}
            </ul>
            {% endif %}
        </p>
        <p><input type="submit" value="Save post"/></p>
    </form>
{% endblock %}
Now everything is working. We can run Google App Engine local development server and access the URL http://localhost:8080/posts/new on the browser, then write a post and save it! Everything is ready to deploy, and the deploy process is the same of web2py, just run on terminal:
% /usr/local/google_appengine/appcfg.py update .
And now the application is online :) Check this out: http://3.latest.gaeseries.appspot.com (use your Google Account to write posts).

You can also check the code out in Github: https://github.com/fsouza/gaeseries/tree/flask.