[Francisco Souza] Flying with tipfy on Google App Engine

Hooray, there is a bonus part in the series (after a looooooooooooong wait)! In the first blog post, about Django, I received a comment about the use of tipfy, a small Python web framework made specifically for Google App Engine. Like Flask, tipfy is not a full stack framework and we will not use a database abstraction layer, we will use just the Google App Engine Datastore API, but tipfy was designed for Google App Engine, so it is less laborious to work with tipfy on App Engine.

First, we have to download tipfy. There are two options on official tipfy page: an all-in-one package and a do-it-yourself packaged. I am lazy, so I downloaded and used the all-in-one package. That is so easy:

% wget http://www.tipfy.org/tipfy.build.tar.gz% tar -xvzf tipfy.0.6.2.build.tar.gz% mv project gaeseries

After it, we go to the project folder and see the project structure provided by tipfy. There is a directory called “app”, where the App Engine app is located. The app.yaml file is in the app directory, so we open that file and change the application id and the application version. Here is the app.yaml file:

application: gaeseriesversion: 4runtime: pythonapi_version: 1

derived_file_type:- python_precompiled

handlers:- url: /(robots\.txt|favicon\.ico)  static_files: static/\1  upload: static/(.*)

- url: /remote_api  script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py  login: admin

- url: /_ah/queue/deferred  script: main.py  login: admin

- url: /.*  script: main.py

After this, we can start to code our application. tipfy deals with requests using handlers. A handler is a class that has methods to deal with different kinds of requests. That remember me a little the Strut Actions (blergh), but tipfy is a Python framework, what means that it is easier to build web application using it!

Understanding tipfy: a URL is mapped to a handler that do something with the request and returns a response. So, we have to create two handlers: one to the list of posts and other to create a post, but let’s create first an application called blog, and a model called Post. Like Django, Flask and web2py, tipfy also works with applications inside a project.

To create an application, we just need to create a new Python package with the application name:

% mkdir blog% touch blog/__init__.py

After create the application structure, we install it by putting the application inside the “apps_installed” list on config.py file:

# -*- coding: utf-8 -*-"""    config    ~~~~~~

    Configuration settings.

    :copyright: 2009 by tipfy.org.    :license: BSD, see LICENSE for more details."""config = {}

# Configurations for the 'tipfy' module.config['tipfy'] = {    # Enable debugger. It will be loaded only in development.    'middleware': [        'tipfy.ext.debugger.DebuggerMiddleware',    ],    # Enable the Hello, World! app example.    'apps_installed': [        'apps.hello_world',        'apps.blog',    ],}

See the line 22. Inside the application folder, let’s create a Python module called models.py. This module is exactly the same of Flask post:

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)

After create the model, let’s start building the project by creating the post listing handler. The handlers will be in a module called handlers.py, inside the application folder. Here is the handlers.py code:

# -*- coding: utf-8 -*-from tipfy import RequestHandlerfrom tipfy.ext.jinja2 import render_responsefrom models import Post

class PostListingHandler(RequestHandler):    def get(self):        posts = Post.all()        return render_response('list_posts.html', posts=posts)

See that we get a list containing all posts from the database and send it to the list_posts.html template. Like Flask, tipfy uses Jinja2 as template engine by default. Following the same way, let’s create a base.html file who represents the layout of the project. This file should be inside the templates folder and contains the following code:

<html>    <head>      <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>      <title>{% block title %}{% endblock %}</title>    </head>    <body id="">        {% block content %}{% endblock %}    </body></html>

And now we can create the list_posts.html template extending the base.html template:

{% extends "base.html" %}

{% block title %}    Posts list{% endblock %}

{% block content %}    Listing all posts:

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

Can we access the list of posts now by the URL? No, we can’t yet. Now we have to map the handler to a URL, and we will be able to access the list of posts through the browser. On tipfy, all URL mappings of an application are located in a Python module called urls.py. Create it with the following code:

from tipfy import Rule

def get_rules(app):    rules = [        Rule('/posts', endpoint='post-listing', handler='apps.blog.handlers.PostListingHandler'),    ]

    return rules

It is very simple: a Python module containing a function called get_rules, that receives the app object as parameter and return a list containing the rules of the application (each rule is an instance of tipfy.Rule class). Now we can finally see the empty post list on the browser, by running the App Engine development server and touching the http://localhost:8080/posts URL on the browser. Run the following command on the project root:

% /usr/local/google_appengine/dev_appserver.py app

And check the browser at http://localhost:8080/posts. And we see the empty list. Now, let’s create the protected handler which will create a new post. tipfy has an auth extension, who makes very easy to deal with authentication using the native Google App Engine users API. To use that, we need to configure the session extension, changing the conf.py module, by adding the following code lines:

config['tipfy.ext.session'] = {    'secret_key' : 'just_dev_testH978DAGV9B9sha_W92S',}

Now we are ready to create the NewPostHandler. We will need to deal with forms, and tipfy has an extension for integration with WTForms, so we have to download and install WTForms and that extension in the project:

% wget http://bitbucket.org/simplecodes/wtforms/get/tip.tar.bz2% tar -xvf tip.tar.bz2% cp -r wtforms/wtforms/ ~/Projetos/gaeseries/app/lib/% wget http://pypi.python.org/packages/source/t/tipfy.ext.wtforms/tipfy.ext.wtforms-0.6.tar.gz% tar -xvzf tipfy.ext.wtforms-0.6.tar.gz% cp -r tipfy.ext.wtforms-0.6/tipfy ~/Projetos/gaeseries/app/distlib

Now we have WTForms extension installed and ready to be used. Let’s create the PostForm class, and then create the handler. I put both classes in the handlers.py file (yeah, including the form). Here is the PostForm class code:

class PostForm(Form):    csrf_protection = True    title = fields.TextField('Title', validators=[validators.Required()])    content = fields.TextAreaField('Content', validators=[validators.Required()])

Add this class to the handlers.py module:

class NewPostHandler(RequestHandler, AppEngineAuthMixin, AllSessionMixins):    middleware = [SessionMiddleware]

    @login_required    def get(self, **kwargs):        return render_response('new_post.html', form=self.form)

    @login_required    def post(self, **kwargs):        if self.form.validate():            post = Post(                title = self.form.title.data,                content = self.form.content.data,                author = self.auth_session            )            post.put()            return redirect('/posts')        return self.get(**kwargs)

    @cached_property    def form(self):        return PostForm(self.request)

A lot of news here: first, tipfy explores the multi-inheritance Python feature and if you will use the auth extension by the native App Engine users API, you have to create you handler class extending AppEngineAuthMixin and AllSessionMixins classes, and add to the middleware list the SessionMiddleware class. See more at the tipfy docs.

The last step is create the new_post.html template and deploy the application. Here is the new_post.html template code:

{% extends "base.html" %}

{% block title %}    New post{% endblock %}

{% block content %}    <form action="" method="post" accept-charset="utf-8">        <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, we can deploy the application on Google App Engine by simply running this command:

% /usr/local/google_appengine/appcfg.py update app

And you can check the deployed application live here: http://4.latest.gaeseries.appspot.com.

The code is available at Github: https://github.com/fsouza/gaeseries/tree/tipfy.