Browse Source

the whole code

master
Julio Biason 7 years ago
parent
commit
fb17fbe2b1
  1. 698
      flask-40mins.html

698
flask-40mins.html

@ -64,7 +64,7 @@
<div class="reveal"> <div class="reveal">
<div class="slides"> <div class="slides">
<section> <section>
<section data-background="_images/flask.png"> <!-- data-header --> <section data-background="_images/flask.png"> <!-- data-header -->
<h1 class="semi-opaque">Flask em 40 Minutos ou Menos</h1> <h1 class="semi-opaque">Flask em 40 Minutos ou Menos</h1>
</section> </section>
</section> </section>
@ -103,53 +103,53 @@
</section> </section>
</section> </section>
<section> <section>
<section> <section>
<h2>Motivação</h2> <h2>Motivação</h2>
<ul> <ul>
<li class="fragment">Flask é super simples, mas poderoso.</li> <li class="fragment">Flask é super simples, mas poderoso.</li>
<li class="fragment">Python é super simples, mas poderoso.</li> <li class="fragment">Python é super simples, mas poderoso.</li>
<li class="fragment">Ninguém fala como é colocar isso em produção.</li> <li class="fragment">Ninguém fala como é colocar isso em produção.</li>
</ul> </ul>
<aside class="notes"> <aside class="notes">
Uma das coisas que o pessoal sempre comenta é Uma das coisas que o pessoal sempre comenta é
como é fácil programar em Python, como Flask é como é fácil programar em Python, como Flask é
simples, como Django consegue fazer um monte de simples, como Django consegue fazer um monte de
coisa (e é fácil), mas sempre falta aquela parte coisa (e é fácil), mas sempre falta aquela parte
de "e como eu coloco isso em um servidor de de "e como eu coloco isso em um servidor de
verdade ao invés de ficar executando no development verdade ao invés de ficar executando no development
server?" server?"
É isso que eu vou tentar responder nessa É isso que eu vou tentar responder nessa
apresentação. apresentação.
</aside> </aside>
</section> </section>
</section> </section>
<section> <section>
<section> <section>
<h2>A aplicação</h2> <h2>A aplicação</h2>
<p> <p>
Um sistema de atas de reunião onde as atas Um sistema de atas de reunião onde as atas
são arquivos MarkDown, com a data no nome. são arquivos MarkDown, com a data no nome.
</p> </p>
<aside class="notes"> <aside class="notes">
Não que seja um sistema super complexo (nem Não que seja um sistema super complexo (nem
banco de dados tem!) mas serve pra mostrar banco de dados tem!) mas serve pra mostrar
Flask o suficiente e ser complicado o Flask o suficiente e ser complicado o
suficiente para ter as "pegadinhas" de se suficiente para ter as "pegadinhas" de se
colocar algo desse tipo em produção. colocar algo desse tipo em produção.
</aside> </aside>
</section> </section>
<section> <section>
<h2>Estrutura de diretórios</h2> <h2>Estrutura de diretórios</h2>
<pre>├── ata <pre>├── ata
   ├── defaults.py    ├── defaults.py
   ├── __init__.py    ├── __init__.py
   ├── main.py    ├── main.py
@ -164,116 +164,498 @@
├── contents ├── contents
   └── 2017-10-31.md    └── 2017-10-31.md
└── requirements.txt</pre> └── requirements.txt</pre>
</section> </section>
<section> <section>
<h3><code>├── ata</code></h3> <h3><code>├── ata</code></h3>
<p>Módulo/fontes.</p> <p>Módulo/fontes.</p>
<aside class="notes"> <aside class="notes">
Normalmente se recomenda que os Normalmente se recomenda que os
fontes fiquem num diretório com o nome fontes fiquem num diretório com o nome
do projeto. Qualquer coisa fora desse do projeto. Qualquer coisa fora desse
diretório não é código. diretório não é código.
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>   ├── defaults.py</code></h3> <h3><code>   ├── defaults.py</code></h3>
<p>Valores default da aplicação.</p> <p>Valores default da aplicação.</p>
<aside class="notes"> <aside class="notes">
O Flask trabalha com arquivos de configuração O Flask trabalha com arquivos de configuração
em Python mesmo (carregando objetos Python). em Python mesmo (carregando objetos Python).
Vamos ver esse arquivo mais pra frente. Vamos ver esse arquivo mais pra frente.
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>   ├── __init__.py</code></h3> <h3><code>   ├── __init__.py</code></h3>
<p> <p>
<code>__init__.py</code> é necessário <code>__init__.py</code> é necessário
para indicar ao Python que o diretório para indicar ao Python que o diretório
pode se lido. pode se lido.
</p> </p>
</section> </section>
<section> <section>
<h3><code>   ├── main.py</code></h3> <h3><code>   ├── main.py</code></h3>
<p>O arquivo inicial da aplicação.</p> <p>O arquivo inicial da aplicação.</p>
<aside class="notes"> <aside class="notes">
Qualquer nome (desde que seja importável Qualquer nome (desde que seja importável
pelo interpretador Python) é valido. Outros pelo interpretador Python) é valido. Outros
nomes podem ser "server.py" ou "ata.py". nomes podem ser "server.py" ou "ata.py".
A comunidade Node tem o costume de marcar A comunidade Node tem o costume de marcar
o ponto inicial de uma aplicação com "index.js". o ponto inicial de uma aplicação com "index.js".
Embora "index.py" funcione, eu não sugiro porque Embora "index.py" funcione, eu não sugiro porque
o arquivo pode conter mais do que simplesmente o arquivo pode conter mais do que simplesmente
o o index da aplicação (e é o que vamos fazer o o index da aplicação (e é o que vamos fazer
nesse aplicação). nesse aplicação).
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>   ├── static</code></h3> <h3><code>   ├── static</code></h3>
<p> <p>
Arquivos estáticos. Arquivos estáticos.
</p> </p>
<aside class="notes"> <aside class="notes">
Qualquer coisa que não precise ser processada Qualquer coisa que não precise ser processada
pelo Flask. Por exemplo, arquivos CSS, JS e pelo Flask. Por exemplo, arquivos CSS, JS e
imagens. imagens.
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>   └── templates</code></h3> <h3><code>   └── templates</code></h3>
<p> <p>
Arquivos de template. Arquivos de template.
</p> </p>
<aside class="notes"> <aside class="notes">
O formato utilizado é o Jinja, que é O formato utilizado é o Jinja, que é
praticamente a mesma coisa que os templates praticamente a mesma coisa que os templates
do Django (com algumas coisas a mais, que do Django (com algumas coisas a mais, que
infelizmente não iremos ver). infelizmente não iremos ver).
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>├── contents</code></h3> <h3><code>├── contents</code></h3>
<p> <p>
Onde o conteúdo a ser apresentado está. Onde o conteúdo a ser apresentado está.
</p> </p>
<aside class="notes"> <aside class="notes">
Isso é usado apenas para testes em desenvolvimento. Isso é usado apenas para testes em desenvolvimento.
</aside> </aside>
</section> </section>
<section> <section>
<h3><code>└── requirements.txt</code></h3> <h3><code>└── requirements.txt</code></h3>
<p> <p>
Dependências do projeto. Dependências do projeto.
</p> </p>
<aside class="notes"> <aside class="notes">
... assim como todo bom e qualquer ... assim como todo bom e qualquer
projeto Python. projeto Python.
</aside> </aside>
</section> </section>
</section> </section>
<section>
<section>
<h2><code>requirements.txt</code></h2>
<pre><code>flask~=0.12.2
Markdown~=2.6.9</code></pre>
<aside class="notes">
Nossas duas dependências: Flask e
Markdown.
"~=" é um validador de versões com
a definição de "compatível". Ou seja
estamos buscando o Flask compatível com
a versão 0.12.2 e o Markdown compatível
com a versão 2.6.9.
Como o PIP sabe disso? Por vesionamento
semantico.
</aside>
</section>
</section>
<section>
<section>
<h2><code>main.py</code></h2>
<h3>Imports</h3>
<pre><code>import os
import os.path
import markdown
from flask import Flask
from flask import render_template</code></pre>
<aside class="notes">
Dos imports, a única coisa "diferente"
aqui é o import do markdown -- uma biblioteca
externa que vai estar no nosso requirements.txt
-- e os imports do Flask. Aqui só vamos usar dois
elementos: o Flask, que é o objeto principal
e o render_template, para renderizar templates
Jinja.
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>A App</h3>
<pre><code>app = Flask(__name__)</code></pre>
<aside class="notes">
Flask é o objeto principal da aplicação.
Ele precisa de um namespace e o nome
da própria classe serve.
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>Configuração</h3>
<pre><code>app.config.from_object('ata.defaults')
app.config.from_envvar('ATA_SETTINGS', silent=True)</code></pre>
<aside class="notes">
O objeto da aplicação (ou simplesmente app)
tem um objeto "config" com as configurações.
As configurações podem ser carregadas de vários
lugares e aqui temos duas:
from_object vai tentar importar o objeto e usar
as variáveis definidas no mesmo.
from_envvar vai procurar o arquivo apontando
pela variável de ambiente indicada (por exemplo,
se alguém tivesse feito `export ATA_SETTINGS=/etc/ata.cfg`
esse arquivo seria carregado). Normalmente, quando
uma das configs indicadas não for encontrada (por
exemplo, o arquivo não existe ou a variável de
ambiente não foi definida) o Flask vai abortar
a execução. Para não abortar, precisamos do
`silent=True` -- fail silently.
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>A primeira rota.</h3>
<pre><code>@app.route('/')
def index():</code></pre>
<aside class="notes">
Nossa primeira iteração com o sistema de
rotas do Flask. Vale notar que, diferente do
Django, que tem um arquivo com todas as rotas,
Flask utiliza decorators para indicar qual
função será chamada. É possível também indicar
quais métodos serão aceitos (e assim é possível
ter uma função para GET e outra para POST, na
mesma rota).
Assim como o Django, Flask trabalha com endpoints.
O nome do endpoint, nesse caso, é o nome da
própria função (então, neste caso, o endpoint
é "index"). Mais pra frente veremos porque isso
é importante.
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>O índice</h3>
<pre><code> content = os.listdir(app.config['STORAGE'])
data = []
for entry in sorted(content, reverse=True):
app.logger.debug('Found %s', entry)
if not entry.endswith('.md'):
continue
with open(os.path.join(app.config['STORAGE'], entry)) as origin:
content = origin.read()
output = markdown.markdown(content)
try:
first_paragraph_end = output.index('</p>')
paragraph = output[:first_paragraph_end]
except ValuError:
paragraph = output
entry_name = entry[:entry.find('.md')]
data.append((entry_name, output))
return render_template('index.html', data=data)</code></pre>
<aside class="notes">
Infelizmente, não tem como diminuir a quantidade
de código a ser apresentado aqui. Mas basicamente
temos o seguinte:
Estamos usando o app.config para buscar uma
configuração, "STORAGE". Nesse exemplo, é o
diretório com o conteúdo e ele é configurado
em "defaults.py" (que vamos ver na sequencia).
Segundo, estamos usando a biblioteca Markdown para
converter o arquivo MarkDown para HTML. De forma
"boba", estamos pegando o primeiro parágrafo.
Terceiro, estamos usando a função "render_template"
para renderizar o arquivo "index.html", com uma
variável chamada "data". Essa variável vai aparecer
nos templates -- e a única forma de passar variáveis
é através da passagem pelos parametros da função
render_template.
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>Entradas específicas</h3>
<pre><code>@app.route('/&lt;entry_name&gt;')
def show_entry(entry_name):
filename = os.path.join(app.config['STORAGE'], entry_name + '.md')
with open(filename) as origin:
content = origin.read()
output = markdown.markdown(content)
return render_template('entry.html', output=output,
entry=entry_name)</code></pre>
<aside class="notes">
Mais uma rota, com uma diferença: ela aceita
um parametro a mais, "entry_name". Esse mesmo
nome deve aparecer na função.
Assim como visto anteriormente, render_template
recebe um arquivo template a ser carregado e
duas variáveis: "output" com o conteúdo renderizado
do Markdown e "entry" com o nome da entrada.
Ponto para quem achar o erro aqui (não é erro: é
pra mostrar umas das facilidades do Flask).
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>Tratamento de erros.</h3>
<pre><code>@app.errorhandler(404)
def page_not_found():
return render_template('page_not_found.html')</code></pre>
<aside class="notes">
Com "errorhandler" é possível indicar o que
deve ser apresentado quando ocorre um erro.
No caso, estamos definindo que quando ocorrer
um 404, deve ser feito o render_template do
arquivo "page_not_found.html"
</aside>
</section>
<section>
<h2><code>main.py</code></h2>
<h3>Tratamento de erros</h3>
<pre><code>@app.errorhandler(FileNotFoundError)
def entry_not_found(_):
return render_template('entry_not_found.html')</code></pre>
<aside class="notes">
Além de tratar com códigos de erro,
errorhandler também sabe lidar com
exceções. No caso, se em qualquer
lugar da aplicação ocorrer um
"FileNotFoundError", esse será tratado
aqui.
</aside>
</section>
</section>
<section>
<section>
<h2><code>defaults.py</code></h2>
<pre><code>DEBUG=True
STORAGE='/home/jbiason/src/ata/contents'</code></pre>
<aside class="notes">
Contém as configurações do sistema.
DEBUG é uma variável controlada pelo
próprio Flask (assim como o DEBUG do Django).
Existem várias variáveis que podem ser
configuradas e todas estão listadas
na documentação do Flask.
Entre essas variáveis está o DEBUG, o diretório
onde se encontram os templates, o diretório
dos arquivos estáticos e assim por diante.
No nosso caso, como não estamos substiuindo
essas variáveis, elas estão com o valor padrão
de estarem junto com os fontes (assim como
criamos nossa estrutura de diretórios).
</aside>
</section>
</section>
<section>
<section>
<h3><code>style.css</code></h3>
<pre><code>h1#header {
background-color: black;
color: white;
padding: 10px;
}
.entry {
margin-left: 30px;
margin-right: 30px;
margin-bottom: 15px;
}</code></pre>
<aside class="notes">
O arquivo de estilos é bem bobo, mas
serve para vermos onde ele deve ficar
quando colocarmos o Flask em produção.
</aside>
</section>
</section>
<section>
<section>
<h2><code>templates</code></h2>
<h3><code>layout.html</code></h3>
<pre><code>&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Atas de Reunião&lt;/title&gt;
&lt;link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1 id='header'&gt;
Atas de reuniao
&lt;/h1&gt;
{% block maincontent %}{% endblock %}
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<aside class="notes">
Para templates Jinja, assim como Django, é
possível ter um arquivo base do qual todos
os demais decendem.
Importante notar aqui o url_for(), que faz
a conversão do endpoint para uma URL (no nosso
caso, do static) e quando endpoint requer um
parametro (como o nosso "entry_name") ele *tem*
que estar no comando. static requer filename
para saber qual arquivo estático deve ser
carregado.
</aside>
</section>
<section>
<h2><code>templates</code></h2>
<h3><code>index.html</code></h3>
<pre><code>{% extends "layout.html" %}
{% block maincontent %}
&lt;div class='entry'&gt;
{% for entry, text in data %}
&lt;a href="{{ url_for('show_entry', entry_name=entry) }}"&gt;{{ entry }}&lt;/a&gt;
{{ text|safe }}
{% endfor %}
&lt;/div&gt;
{% endblock %}</code></pre>
<aside class="notes">
O arquivo apresentado na raíz da aplicação.
Variáveis com {{ }}, url_for() usando a função
"show_entry" com um parametro
</aside>
</section>
<section>
<h2><code>templates</code></h2>
<h3><code>entry.html</code></h3>
<pre><code>{% extends "layout.html" %}
{% block maincontent %}
&lt;div class="entry"&gt;
&lt;div class="title"&gt;{{ entry }}&lt;/div&gt;
{{ output|safe }}
&lt;/div&gt;
{% endblock %}</code></pre>
<aside class="notes">
Como apresentar somente uma entrada.
</aside>
</section>
<section>
<h2><code>templates</code></h2>
<h3><code>page_not_found.html</code></h3>
<pre><code>{% extends "layout.html" %}
{% block maincontent %}
&lt;h2&gt;Page not found&lt;/h2&gt;
{% endblock %}</code></pre>
<aside class="notes">
A página quando ocorrer um 404...
</aside>
</section>
<section>
<h2><code>templates</code></h2>
<h3><code>entry_not_found.html</code></h3>
<pre><code>{% extends "layout.html" %}
{% block maincontent %}
&lt;h2&gt;Entry not found&lt;/h2&gt;
{% endblock %}</code></pre>
<aside class="notes">
... e quando a entrada não existir
(que é a captura do nosso FileNotFoundError)
</aside>
</section>
<section data-background='_images/thats-all-folks.jpg'> <section data-background='_images/thats-all-folks.jpg'>
<section> <section>

Loading…
Cancel
Save