Flask

O que é Flask?

  • Microframework web em Python.
  • Framework sobre o Werkzeug (outro framework).
  • Sem ORM, mas templates.

Aplicativo Flask Básico


from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello world'
                        


#!/usr/bin/env python
# -*- encoding: utf-8 -*-

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello world'
                        

... mais o header...


#!/usr/bin/env python
# -*- encoding: utf-8 -*-

"""Meu aplicativo web em Flask."""

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello world'
                        

... mais a documentação do módulo...


#!/usr/bin/env python
# -*- encoding: utf-8 -*-

"""Meu aplicativo web em Flask."""

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    """Apresentação do 'root' do aplicativo."""
    return 'Hello world'
                        

... mais a documentação das funções...


#!/usr/bin/env python
# -*- encoding: utf-8 -*-

"""Meu aplicativo web em Flask."""

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
def index():
    """Apresentação do 'root' do aplicativo."""
    return render_template('hello.html')
                        

... mais retornar templates ao invés de texto puro...


#!/usr/bin/env python
# -*- encoding: utf-8 -*-

"""Meu aplicativo web em Flask."""

class Settings:
    SECRET_KEY = 'Sup3rs3cr33t'

from flask import Flask
from flask import render_template

app = Flask(__name__)
app.config.from_object(Settings)
app.config.from_envvar('MEU_APLICATIVO_CONFIG')


@app.route('/')
def index():
    """Apresentação do 'root' do aplicativo."""
    return render_template('hello.html')
                        

... mais adicionar uma configuração...

... mais tratamento de erros...

... mais outras rotas...

... mais blueprints/applications...

... mais inicialização do ORM...

Criando uma app


app = Flask(__name__)
                    

__name__ é usado para que, internamente, os módulos/imports sejam encontrados.

Se o app estiver em "meuservico/app.py", as duas linhas abaixo funcionam de forma idêntica:

app = Flask(__name__)
app = Flask("meuservico")

Contextos

Essa é a parte chata do Flask.

Existem dois contextos: Contexto de aplicação e Contexto que requisição.

Contexto de aplicação só existe quando o app está rodando.

Acessado com current_app.


from flask import current_app
						

É a única forma de acessar dados da aplicação enquanto ela está rodando.

Contexto de requisição só existe quando o sistema está atendendo uma requisção (recebeu uma URL).

Acessado com request.


from flask import request
						

Mais sobre request mais adiante.

Para acessar as configurações, usa-se a propriedade config da aplicação quando esta está rodando.

(Contexto de aplicação, lembra?)


from flask import current_app
from flask import render_template

@app.route('/')
def index():
	return render_template('template.html',
	                       order=current_app.config.get('ORDER_FIELD'))
						

Rotas

Rotas são definidas com o decorator @[app].route([rota]). Por exemplo:


app = Flask(__name__)
@app.route('/')
def index():
    return 'Olá mundo'
                        

Rotas também podem definir quais métodos HTTP são aceitos:


@app.route('/', methods=['POST', 'GET'])
def index():
    return 'Olá mundo'
                        

"PUT /" irá retornar status 405: Method Not Allowed.

Rotas podem ser repetidas, desde que os métodos não colidam:


@app.route('/', methods=['GET'])
def list():
    return 'Olá mundo'


@app.route('/', methods=['POST'])
def update():
    return 'O seu mundo foi atualizado'
                        

Rotas podem ter parâmetros:


app = Flask(__name__)
@app.route('/<usuario>/data')
def index(usuario):
    return 'Olá {nome}'.format(nome=usuario)
                        

Parâmetros podem ter um tipo definido:


@app.route('/add/<int:var1>/<int:var2>')
def add(var1, var2):
    return 'Soma = {sum}'.format(sum=var1+var2)
                        

Problemas: Número de rotas tente a crescer. E como passar app para cima e para baixo?

Blueprints

Blueprints são funções relacionadas (por exemplo, pelo recurso base) que podem ser desenvolvidos separados do módulo principal e entre si.

(Conceito semelhante aos "apps" do Django ou "scaffolding" do Pyramid/Pylons.)

Esqueleto de um Blueprint


from flask import Blueprint

blueprint_exemplo = Blueprint('exemplo', __name__)

@blueprint_exemplo.route('/say/<usuario>')
def blueprint_index(usuario):
    return 'Olá {nome}'.format(nome=usuario)
                        

E para ativar o Blueprint...


app = Flask(__name__)

from exemplo import blueprint_exemplo
app.register(blueprint_exemplo, url_prefix='/exemplo')
                        

A combinação do blueprint anterior com esse carregamento, gera a URL /exemplo/say/<usuario>.


blueprint_exemplo = Blueprint('exemplo', __name__)
                        

  • exemplo é o nome do blueprint (para que isso serve, mais adiante)
  • __name__ tem a mesma finalidade do __name__ do app: encontrar módulos/recursos.

Tudo que foi visto sobre rotas continua valendo:


@blueprint_exemplo.route('/<int:var1>/<int:var2>', methods=['GET'])
def sum(var1, var2):
    return 'Soma = {sum}'.format(sum=var1+var2)
                        

Templates

Flask vem com Jinja2 como renderizado de templates.

A sintaxe é muito semelhante ao sistema de templates do Django:


{% for nome in usuarios %}
    <div class='usuario'>Olá {{ nome }}<div>
{% endfor %}
                        

Para usar um template, basta usar a função render_template.

Parâmetros para o template devem ser passados como parâmetros adicionais na função:


from flask import render_template

@app.index('/')
def index():
    return render_template('hello-world.html', 
                           usuarios=['Julio', 'Leandro', 'Bruna', 'Cláudio'])
                        

Responses

render_template() é simplesmente um parser de templates com um gerador de Responses.

(Ou seja, o resultado esperado das funções -- qualquer função -- é um Response.)

(Criar um response especializado é algo que somente e feito em 2% dos casos.)

Por que isso é importante?

Responses tem algumas propriedades a mais, como o tipo do retorno (text/html, por exemplo) e o status.

Para retornar um status diferente de 200:


def index():
    resp = render_template('404-not-found.html')
    resp.status_code = 404
    return resp
                        

Para retornar um tipo diferente, é preciso criar o Response do zero:


def index():
    resp = make_response('Olá mundo')
    resp.status_code = 200
    resp.mimetype = 'text/plain'
                        

Para JSON, já existe uma função pronta:


from flask import jsonify

@app.route('/')
def index():
    usuarios=['Julio', 'Leandro', 'Bruna', 'Cláudio']
    return jsonify(status='OK',
                   usuarios=usuarios)
                        

Isso gera o JSON

{status: "OK", usuarios=["Julio", "Leandro", "Bruna", "Claudio"]}.

Como jsonify() gera um Response, ele ainda pode ser mexido:


def index():
    resp = jsonify(status='ERROR', code='404')
    resp.status = 404
    return resp
                        

Flask também tem funções para auxiliar na geração de respostas que não são "páginas":


from flask import abort

def index():
    abort(404)
                        

Gera um response com status 404 padrão do sistema.


from flask import redirect

def index():
    redirect('/correct-path')
                        

Gera um redirectionamento para /correct-path.

Requests

Request contém as informações que estão vindo na requisição:

  • request.method: Método HTTP utilizado.
  • request.form: Dados do formulário com POST/PUT.
  • request.args: Dados do querystring (GET).
  • request.values: form + args.
  • request.cookies: Cookies da página.
  • request.headers: Headers recebidos.
  • request.files: Arquivos enviados.
  • request.get_json(): Parseia a resposta se ela for JSON.


from flask import request     # acesso ao objeto de request atual
from flask import abort
from flask import render_template

def index():
    nome = request.values.get('usuario')
    if not nome:
        abort(400)     # Bad Request

    return render_template('hello-world.html',
                           usuarios=[nome])
                        

Tratamento de erros

Para mostrar páginas diferentes da default, basta adicionar um errorhandler.


app = Flask(__name__)

@app.errorhandler(404)
def not_found():
    return jsonify(status='ERROR',
                   message='Not found')
                        

errorhandler também pode capturar excessões:


from flask import Flask
from mongoengine import NotFoundError

app = Flask(__name__)

@app.errorhandler(NotFoundError)
def not_found():
    return jsonify(status='ERROR',
                   message='Mongo object not found')
                        

Isso captura qualquer ocorrência de NotFoundError, inclusive dentro dos blueprints.

Configurações

Configurações podem vir de 3 lugares diferentes:

  • De uma classe.
  • De um arquivo Python.
  • De um arquivo apontando por uma variável de ambiente.

Todos os três podem ser executados em sequência, o último valor encontrado é o que vale.


class Settings(objects):
	FILE_PATH = './here'
	ORDER_FIELD = 'name'
						


app = Flask(__name__)

app.config.from_object(Settings)
app.config.from_pyfile('/etc/meuaplicativo.cfg')
app.config.from.envvar('MEUAPLICATIVO_CFG')
						

URLs reversas/Endpoints

Se eu posso registrar um Blueprint com qualquer prefixo, como eu descubro depois qual a URL de um recurso (se eu precisar fazer um redirect)?

url_for()

url_for() recebe um endpoint e retorna a URL para aquele endpoint.

O que diabos é um endpoint?


@app.route('/')
def index():
    return 'Olá mundo'
                        

Endpoint = index


exemplo = Blueprint('meuexemplo', __name__)

@exemplo.route('/')
def index():
    return 'Olá mundo'
                        

Endpoint = meuexemplo.index


exemplo = Blueprint('meuexemplo', __name__)

@exemplo.route('/')
def index():
    return redirect(url_for('meuexemplo.list'))

@exemplo.route('/list')
def list():
    return 'Olá todos vocês.'
                        

Se registrar o Blueprint com prefix = /exemplo, o index() irá fazer um redirect para /exemplo/list.

Se registrar o Blueprint com prefix = /, o index() irá fazer um redirect para /list.

url_for() também funciona dentro de templates.


<button onClick='redirect("{{ url_for('meuexemplo.list') }}")'>
                        

Resumo

  • Sintaxe simples (a sintaxe foi uma brincadeira de 1o. de abril)
  • Controle centralizado de erros.
  • Total controle sobre as respostas.
  • Acesso total ao request.
  • Módulos completamente isolados mas ainda permite que esses sejam conectados por endpoints.
  • Não comentado, mas o Werkzeug também expõe todo o controle da aplicação.
  • Ainda não tem um ORM integrado, mas é fácil de plugar qualquer um.
  • Pythônico.