Voando com o Flask no Google App Engine

ago 12 2010

FlaskDepois de uma pequena pausa, chegamos finalmente à terceira parte da série de posts sobre o uso de frameworks Python no Google App Engine. Após abordar o uso do Django e do web2py no App Engine, agora veremos como usar o Flask, um microframework para Python baseado no Werkzeug, no Jinja2 e em boas intenções. Diferente do Django e do web2py, o Flask não possui uma camada de abstração do banco de dados, não é um framework full stack. Trata-se de uma excelente decisão de design, uma vez que é possível trabalhar com o SQLAlchemy (ou qualquer outro ORM) em sistemas que usam bancos de dados relacionais, e a API nativa para os bancos de dados não relacionais, quando for o caso.

O fato de o Flask ser um microframework significa que há mais espaço para customização e menos acoplamento, porém é um pouco mais trabalhoso (mas não difícil) construir uma aplicação com Flask, pois há muitos blocos a serem montados, e o Flask não faz 10 bilhões de coisas pelo desenvolvedor. Apesar disso tudo, desenvolver com Flask é prazeroso e divertido! Como o Flask não conta com uma camada de abstração de dados nem um ORM, vamos utilizar a API nativa de armazenamento de dados do Google App Engine.

Como foi feito nas duas primeiras partes, vamos criar um blog versão mini onde teremos duas views: uma para listar os posts, de acesso público, e uma para escrever posts, com acesso restrito por login. O primeiro passo, como nos outros casos, é configurar o ambiente de desenvolvimento. É algo simples, mas da um pouco de trabalho :) Primeiro, é necessário criar o diretório da aplicação e colocar lá dentro o arquivo app.yaml do Google App Engine, com o seguinte conteúdo:

application: gaeseries
version
: 3
runtime
: python
api_version
: 1

handlers
:
- url
: .*
  script
: main.py

Apenas definimos o ID da aplicação, a versão e um handler para todas as requisições: um script chamado main.py, que definiremos mais adiante. Vamos primeiro criar a aplicação do Flask, e depois lidar com o Google App Engine.

Depois de configurar o app.yaml, vamos instalar o Flask e suas dependências (Werkzeug, Jinja2 e simplejson) dentro da nossa aplicação. Basta baixar as bibliotecas e adicionar na raiz do projeto. Eis abaixo os comandos necessários num Linux/Mac para instalar as ferramentas citadas acima:

$ 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/

Note que, no meu caso, o projeto está dentro de ~/Projetos/blog/gaeseries, por isso copiei tudo que era necessário para este diretório. Agora temos tudo que precisamos para começar a codificar nossa aplicação. Para isso, vamos criar um pacote chamado blog:

$ mkdir blog
$ touch blog/__init__.py

Adicionamos então ao __init__.py o código que define a nossa aplicação, nada muito complexo, digno de um microframework:

from flask import Flask
import settings

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

import views

Importamos dois módulos: settings e views. Estes módulos irão conter algumas configurações da aplicação e as views da aplicação, respectivamente. Vamos usar novamente o nosso amigo touch para criar os arquivos:

$ touch blog/settings.py
$ touch blog/views.py

Aqui está um exemplo de código para o arquivo settings.py:

DEBUG=True
SECRET_KEY='dev_key_h8hfne89vm'
CSRF_ENABLED=True
CSRF_SESSION_LKEY='dev_key_h8asSNJ9s9=+'

Bom, agora podemos finalmente criar nosso único modelo, o modelo Post. Dentro do diretório da aplicação, vamos criar um arquivo chamado models.py, onde ficarão armazenados nossos modelos. Eis abaixo o código do modelo:

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)

A última propriedade é uma instância UserProperty, uma “chave estrangeira” para um usuário, um objeto fornecido pela API de usuários do Google App Engine. Nosso modelo já está pronto, e podemos começar as criar as views da aplicação, onde vamos lidar com as requisições do usuário. Primeiro, vamos criar a view de listagem dos posts, que vai ser acessada na URL /posts, dentro do arquivo views.py:

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)

Na última linha da view, foi feita uma chamada à função render_template, fornecida pelo Flask. O primeiro parâmetro da função é uma string contendo o nome do template a ser renderizado. Vamos então criar este template para que a listagem funcione. Para isto, é necessário criar um diretório chamado templates dentro do diretório da aplicação, dentro deste diretório, foi criado um arquivo base.html, que define o layout dos templates. O código do arquivo base.html pode ser visto aqui: http://gist.github.com/519845.

Podemos então criar o template list_posts.html, que herda de base.html. O código deste template está aqui: http://gist.github.com/519846.

Para testar a view que acabamos de criar, precisamos executar o servidor de desenvolvimento do Google App Engine localmente, que lê o arquivo app.yaml. Assim, precisamos agora criar o script main.py para executar nossa aplicação do Flask. Toda aplicação Flask é também uma aplicação WSGI. Podemos utilizar então a biblioteca padrão do Google App Engine para executar aplicações WSGI:

from google.appengine.ext.webapp.util import run_wsgi_app
from blog import app

run_wsgi_app(app)

Agora nossa aplicação está pronta para ser executada, e podemos fazer isso da mesma forma que fizemos no post sobre web2py, executando o script dev_appserver.py provido no SDK do Google App Engine:

$ /usr/local/google_appengine/dev_appserver.py .

Agora é possível acessar a listagem de posts na URL http://localhost:8080/posts, o único problema é que não há nenhum post ainda para ser listado :) Então vamos criar a view onde será possível escrever posts, esta view será protegida por login. Logo, para restringir o acesso a esta view, podemos usar um decorator. O único problema aqui é que nem o Flask nem o App Engine fornecem um decorator bacanudo para restringir o acesso à view new_post. O que fazer? Vamos criar um decorator, e colocá-lo dentro de um módulo decorators.py e importar este decorator no módulo views.py. Aqui está o código do módulo decorators.py:

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

Na view new_post vamos trabalhar com formulários, e para isto também precisamos de outra biblioteca. Uma biblioteca muito boa é o WTForms (que eu apresentei aqui certa vez, no post de integração entre WTForms e Pylons). É possível integrar o WTForms com o Flask utilizando a extensão Flask-WTF. Novamente, precisamos instalar na raiz do nosso projeto as duas ferramentas, da seguinte forma:

$ 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/

Agora sim, com o WTForms e o Flask-WTF instalados, podemos construir a classe de formulário e depois, finalmente, a view new_post e o template que exibirá o formulário. Eu coloquei a definição do formulário dentro do módulo views.py, mas em caso de aplicações maiores, o ideal é por os formulários dentro de um ou mais módulos separados. Aqui está a definição do formulário:

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()])

E, finalmente (antes tarde do que nunca =P), podemos criar a nossa view new_post:

@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)

E aqui está o template new_post.html: http://gist.github.com/520110. Agora nossa aplicação está pronta: é necessário efetuar login para escrever posts, e a listagem de posts está disponível publicamente.

Você pode acessar a aplicação online neste endereço: http://3.latest.gaeseries.appspot.com (utilize sua conta do Google para efetuar login e escrever posts).

O código completo da aplicação está disponível no Github: http://github.com/fsouza/gaeseries/tree/flask.

2 responses so far

  1. Apesar de você não ter usado RESTful nesse blog, como você resolveu o problema RESTful no Flask?

    Vi um snippets, mais não funcionou.

    http://flask.pocoo.org/snippets/1/

  2. Olá Henrique,
    eu não tenho utilizado RESTful com Flask :)

    É possível fazer isso criando um middleware: http://flask.pocoo.org/snippets/38/

    Para saber mais sobre os middlewares: http://flask.pocoo.org/docs/quickstart/#hooking-in-wsgi-middlewares

    Abraços,
    Francisco

Leave a Reply

Spam protection by WP Captcha-Free