My presentations, using Reveal.js (mostly in Portuguese).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

782 lines
25 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Flask</title>
<meta name="author" content="Julio Biason">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="_external/reveal.min.css">
<link rel="stylesheet" href="_external/default.css" id="theme">
<!-- For syntax highlighting -->
<link rel="stylesheet" href="_external/zenburn.css">
<!-- If the query includes 'print-pdf', include the PDF print sheet -->
<script>
if( window.location.search.match( /print-pdf/gi ) ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '_external/pdf.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
</script>
<!--[if lt IE 9]>
<script src="reveal.js/lib/js/html5shiv.js"></script>
<![endif]-->
<style>
.semi-opaque {
background-color: rgba(0, 0, 0, 0.7);
}
* {
hyphens: none !important;
-moz-hyphens: none !important;
}
</style>
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section data-background='_images/flask.png'>
<h1>Flask</h1>
</section>
<section>
<h2>O que é Flask?</h2>
<ul>
<li>Microframework web em Python.</li>
<li>Framework sobre o Werkzeug (outro framework).</li>
<li>Sem ORM, mas templates.</li>
</ul>
</section>
<section>
<section>
<h2>Aplicativo Flask Básico</h2>
<p><pre><code data-trim>
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world'
</code></pre></p>
</section>
<section>
<p><pre><code data-trim>
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world'
</code></pre></p>
<p><small>... mais o header...</small></p>
</section>
<section>
<p><pre><code data-trim>
#!/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'
</code></pre></p>
<p><small>... mais a documentação do módulo...</small></p>
</section>
<section>
<p><pre><code data-trim>
#!/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'
</code></pre></p>
<p><small>... mais a documentação das funções...</small></p>
</section>
<section>
<p><pre><code data-trim>
#!/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')
</code></pre></p>
<p><small>... mais retornar templates ao invés de texto puro...</small></p>
</section>
<section>
<p><pre><code data-trim>
#!/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')
</code></pre></p>
<p><small>... mais adicionar uma configuração...</small></p>
</section>
<section>
<p><small>... mais tratamento de erros...</small></p>
<p class='fragment'><small>... mais outras rotas...</small></p>
<p class='fragment'><small>... mais blueprints/applications...</small></p>
<p class='fragment'><small>... mais inicialização do ORM...</small></p>
</section>
</section>
<section>
<img src='_images/baby-steps.jpg'>
</section>
<section>
<h2>Criando uma app</h2>
<p><pre><code data-trim>
app = Flask(__name__)
</code></pre></p>
<p><code>__name__</code> é usado para que, internamente, os módulos/imports sejam encontrados.</p>
<p>Se o app estiver em "meuservico/app.py", as duas linhas abaixo funcionam de forma idêntica:<br/>
<pre><code>app = Flask(__name__)</code></pre>
<pre><code>app = Flask("meuservico")</code></pre>
</p>
</section>
10 years ago
<section>
<section>
<h2>Contextos</h2>
10 years ago
</section>
<section>
<p>Essa é a parte chata do Flask.</p>
10 years ago
<p>Existem dois contextos: Contexto de aplicação e Contexto que requisição.</p>
</section>
10 years ago
<section>
<p>Contexto de aplicação só existe quando o app está rodando.</p>
<p>Acessado com <code>current_app</code>.</p>
<p><pre><code data-trim>
from flask import current_app
</code></pre></p>
<p>É a única forma de acessar dados da aplicação enquanto ela
está rodando.</p>
10 years ago
</section>
<section>
<p>Contexto de requisição só existe quando o sistema está
atendendo uma requisção (recebeu uma URL).</p>
<p>Acessado com <code>request</code>.</p>
10 years ago
<p><pre><code data-trim>
from flask import request
10 years ago
</code></pre></p>
<p>Mais sobre <code>request</code> mais adiante.</p>
</section>
</section>
10 years ago
<section>
<section>
<h2>Rotas</h2>
</section>
<section>
<p>Rotas são definidas com o decorator <code>@[app].route([rota])</code>. Por exemplo:</p>
<p><pre><code data-trim>
app = Flask(__name__)
@app.route('/')
def index():
return 'Olá mundo'
</code></pre></p>
</section>
<section>
<p>Rotas também podem definir quais métodos HTTP são aceitos:</p>
<p><pre><code data-trim>
@app.route('/', methods=['POST', 'GET'])
def index():
return 'Olá mundo'
</code></pre></p>
<p>"<code>PUT /</code>" irá retornar status 405: Method Not Allowed.</p>
</section>
<section>
<p>Rotas podem ser repetidas, desde que os métodos não colidam:</p>
<p><pre><code data-trim>
@app.route('/', methods=['GET'])
def list():
return 'Olá mundo'
@app.route('/', methods=['POST'])
def update():
return 'O seu mundo foi atualizado'
</code></pre></p>
</section>
<section>
<p>Rotas podem ter parâmetros:</p>
<p><pre><code data-trim>
app = Flask(__name__)
@app.route('/&lt;usuario&gt;/data')
def index(usuario):
return 'Olá {nome}'.format(nome=usuario)
</code></pre></p>
</section>
<section>
<p>Parâmetros podem ter um tipo definido:</p>
<p><pre><code data-trim>
@app.route('/add/&lt;int:var1&gt;/&lt;int:var2&gt;')
def add(var1, var2):
return 'Soma = {sum}'.format(sum=var1+var2)
</code></pre></p>
</section>
<section>
<p>Problemas: Número de rotas tente a crescer. E como passar <code>app</code> para cima e para baixo?</p>
</section>
</section>
<section>
<section>
<h2>Blueprints</h2>
</section>
<section>
<p>Blueprints são funções relacionadas (por exemplo, pelo recurso base)
que podem ser desenvolvidos separados do módulo principal e entre si.</p>
<p>(Conceito semelhante aos "apps" do Django ou "scaffolding" do Pyramid/Pylons.)</p>
</section>
<section>
<h3>Esqueleto de um Blueprint</h3>
<p><pre><code>
from flask import Blueprint
blueprint_exemplo = Blueprint('exemplo', __name__)
@blueprint_exemplo.route('/say/&lt;usuario&gt;')
def blueprint_index(usuario):
return 'Olá {nome}'.format(nome=usuario)
</code></pre></p>
</section>
<section>
<h3>E para ativar o Blueprint...</h3>
<p><pre><code>
app = Flask(__name__)
from exemplo import blueprint_exemplo
app.register(blueprint_exemplo, url_prefix='/exemplo')
</code></pre></p>
<p>A combinação do blueprint anterior com esse carregamento, gera a URL
<code>/exemplo/say/&lt;usuario&gt;</code>.</p>
</section>
<section>
<p><pre><code>
blueprint_exemplo = Blueprint('exemplo', __name__)
</code></pre></p>
<ul>
<li><code>exemplo</code> é o nome do blueprint (para que isso serve, mais adiante)</li>
<li><code>__name__</code> tem a mesma finalidade do <code>__name__</code> do app: encontrar módulos/recursos.</li>
</ul>
</section>
<section>
<p>Tudo que foi visto sobre rotas continua valendo:</p>
<p><pre><code data-trim>
@blueprint_exemplo.route('/&lt;int:var1&gt;/&lt;int:var2&gt;', methods=['GET'])
def sum(var1, var2):
return 'Soma = {sum}'.format(sum=var1+var2)
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>Templates</h2>
</section>
<section>
<p>Flask vem com Jinja2 como renderizado de templates.</p>
<p>A sintaxe é muito semelhante ao sistema de templates do Django:</p>
<p><pre><code data-trim>
{% for nome in usuarios %}
&lt;div class='usuario'&gt;Olá {{ nome }}&lt;div&gt;
{% endfor %}
</code></pre></p>
</section>
<section>
<p>Para usar um template, basta usar a função <code>render_template</code>.</p>
<p>Parâmetros para o template devem ser passados como parâmetros adicionais na função:</p>
<p><pre><code data-trim>
from flask import render_template
@app.index('/')
def index():
return render_template('hello-world.html',
usuarios=['Julio', 'Leandro', 'Bruna', 'Cláudio'])
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>Responses</h2>
</section>
<section>
<p><code>render_template()</code> é simplesmente um parser de templates com um gerador
de Responses.</p>
<p class='fragment'>(Ou seja, o resultado esperado das funções -- qualquer função -- é um Response.)</p>
<p class='fragment'>(Criar um response especializado é algo que somente e feito em 2% dos casos.)</p>
</section>
<section>
<p>Por que isso é importante?</p>
<p class='fragment'>Responses tem algumas propriedades a mais, como o tipo do retorno (text/html, por exemplo)
e o status.</p>
</section>
<section>
<p>Para retornar um status diferente de 200:</p>
<p><pre><code data-trim>
def index():
resp = render_template('404-not-found.html')
resp.status_code = 404
return resp
</code></pre></p>
</section>
<section>
<p>Para retornar um tipo diferente, é preciso criar o Response do zero:</p>
<p><pre><code data-trim>
def index():
resp = make_response('Olá mundo')
resp.status_code = 200
resp.mimetype = 'text/plain'
</code></pre></p>
</section>
<section>
<p>Para JSON, já existe uma função pronta:</p>
<p><pre><code data-trim>
from flask import jsonify
@app.route('/')
def index():
usuarios=['Julio', 'Leandro', 'Bruna', 'Cláudio']
return jsonify(status='OK',
usuarios=usuarios)
</code></pre></p>
<p>Isso gera o JSON</p>
<p><code>{status: "OK", usuarios=["Julio", "Leandro", "Bruna", "Claudio"]}</code>.</p>
</section>
<section>
<p>Como <code>jsonify()</code> gera um Response, ele ainda pode ser mexido:</p>
<p><pre><code data-trim>
def index():
resp = jsonify(status='ERROR', code='404')
resp.status = 404
return resp
</code></pre></p>
</section>
<section>
<p>Flask também tem funções para auxiliar na geração de respostas que não são "páginas":</p>
<p><pre><code data-trim>
from flask import abort
def index():
abort(404)
</code></pre></p>
<p>Gera um response com status 404 padrão do sistema.</p>
<p><pre><code data-trim>
from flask import redirect
def index():
redirect('/correct-path')
</code></pre></p>
<p>Gera um redirectionamento para <code>/correct-path</code>.</p>
</section>
</section>
<section>
<section>
<h2>Requests</h2>
</section>
<section>
<p>Request contém as informações que estão vindo na requisição:</p>
<ul>
<li><code>request.method</code>: Método HTTP utilizado.</li>
<li><code>request.form</code>: Dados do formulário com POST/PUT.</li>
<li><code>request.args</code>: Dados do querystring (GET).</li>
<li><code>request.values</code>: form + args.</li>
<li><code>request.cookies</code>: Cookies da página.</li>
<li><code>request.headers</code>: Headers recebidos.</li>
<li><code>request.files</code>: Arquivos enviados.</li>
<li><code>request.get_json()</code>: Parseia a resposta se ela for JSON.</li>
</ul>
</section>
<section>
<p><pre><code data-trim>
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])
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>Tratamento de erros</h2>
</section>
<section>
<p>Para mostrar páginas diferentes da default, basta adicionar um <code>errorhandler</code>.</p>
<p><pre><code data-trim>
app = Flask(__name__)
@app.errorhandler(404)
def not_found():
return jsonify(status='ERROR',
message='Not found')
</code></pre></p>
</section>
<section>
<p><code>errorhandler</code> também pode capturar excessões:</p>
<p><pre><code data-trim>
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')
</code></pre></p>
<p>Isso captura qualquer ocorrência de <code>NotFoundError</code>, inclusive
dentro dos blueprints.</p>
</section>
</section>
<section>
<section>
<h2>Configurações</h2>
</section>
<section>
<p>Configurações podem vir de 3 lugares diferentes:</p>
<ul>
<li>De uma classe.</li>
<li>De um arquivo Python.</li>
<li>De um arquivo apontando por uma variável de ambiente.</li>
</ul>
<p>Todos os três podem ser executados em sequência, o último valor
encontrado é o que vale.</p>
</p>
</section>
<section>
<p><pre><code data-trim>
class Settings(objects):
FILE_PATH = './here'
ORDER_FIELD = 'name'
</code></pre></p>
<p><pre><code data-trim>
app = Flask(__name__)
app.config.from_object(Settings)
app.config.from_pyfile('/etc/meuaplicativo.cfg')
app.config.from.envvar('MEUAPLICATIVO_CFG')
</code></pre></p>
</section>
<section>
<p>Para acessar as configurações, usa-se a propriedade <code>config</code>
da aplicação quando esta está rodando.</p>
<p>(Contexto de aplicação, lembra?)</p>
<p><pre><code data-trim>
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'))
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>URLs reversas/Endpoints</h2>
</section>
<section>
<p>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)?</p>
<p class='fragment'><code>url_for()</code></p>
</section>
<section>
<p><code>url_for()</code> recebe um endpoint e retorna a URL para aquele endpoint.</p>
<p class='fragment'>O que diabos é um endpoint?</p>
</section>
<section>
<p><pre><code data-trim>
@app.route('/')
def index():
return 'Olá mundo'
</code></pre></p>
<p>Endpoint = <code>index</code></p>
</section>
<section>
<p><pre><code data-trim>
exemplo = Blueprint('meuexemplo', __name__)
@exemplo.route('/')
def index():
return 'Olá mundo'
</code></pre></p>
<p>Endpoint = <code class='fragment'>meuexemplo.index</code></p>
</section>
<section>
<p><pre><code data-trim>
exemplo = Blueprint('meuexemplo', __name__)
@exemplo.route('/')
def index():
return redirect(url_for('meuexemplo.list'))
@exemplo.route('/list')
def list():
return 'Olá todos vocês.'
</code></pre></p>
<p>Se registrar o Blueprint com <code>prefix = /exemplo</code>, o <code>index()</code> irá
fazer um redirect para <code>/exemplo/list</code>.</p>
<p>Se registrar o Blueprint com <code>prefix = /</code>, o <code>index()</code> irá
fazer um redirect para <code>/list</code>.</p>
</section>
<section>
<p><code>url_for()</code> também funciona dentro de templates.</p>
<p><pre><code data-trim>
&lt;button onClick='redirect("{{ url_for('meuexemplo.list') }}")'&gt;
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>Executando</h2>
</section>
<section>
<p>Flask vem com um servidor de desenvolvimento integrado.
Para utilizar:</p>
<p><pre><code data-trim>
app.run()
</code></pre></p>
</section>
<section>
<p>Ou ainda:</p>
<p><pre><code data-trim>
if __name__ == '__main__':
app.run()
</code></pre></p>
</section>
</section>
<section>
<section>
<h2>Resumo</h2>
</section>
<section>
<ul>
<li>Sintaxe simples <span class='fragment'>(a sintaxe foi uma brincadeira de 1o. de abril)</span></li>
<li class='fragment'>Controle centralizado de erros.</li>
<li class='fragment'>Total controle sobre as respostas. <span class='fragment'>"Explícito é melhor que implícito."</span></li>
<li class='fragment'>Acesso total ao request.</li>
<li class='fragment'>Módulos completamente isolados<span class='fragment'> mas ainda permite que esses sejam
conectados por endpoints.</span></li>
<li class='fragment'>Não comentado, mas o Werkzeug também expõe todo o controle da aplicação.</li>
<li class='fragment'>Ainda não tem um ORM integrado, mas é fácil de plugar qualquer um.</li>
<li class='fragment'>Pythônico.</li>
</ul>
</section>
</section>
<section data-background='_images/thats-all-folks.jpg'>
<section></section>
</section>
</div>
</div>
<script src="_external/head.min.js"></script>
<script src="_external/reveal.min.js"></script>
<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
theme: 'night',
transition: 'linear',
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: '_external/classList.js', condition: function() { return !document.body.classList; } },
{ src: '_external/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '_external/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '_external/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: '_external/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: '_external/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
</script>
</body>
</html>