Flask, parte 2


Templates do Jinja2


Um aplicativo web (ou outro qualquer) deve ser escrito de forma clara, para facilitar sua expansão e manutenção. Uma das formas usadas pelo Flask para implementar esse estratégia é a de colocar código python e html separados. Os templates, como vimos, são modelos ou estruturas básicas que podem ser preenchidas dinamicamente, de acordo com as requisições. Esse é o chamado de modelo de separação entre lógica de negócio e lógica de exibição (business and presentation logic). Templates são tratados por um dos módulos que compõem o Flask: o módulo Jinja2.

Um exemplo básico de template para a exibição de um artigo poderia ser o seguinte:

# template.html
<h1>{{ titulo }} </h1>
<p>{{ autor }}, {{ data }} </p>
<p>{{ texto_do_artigo }} </p>
<p>{{ pe_de_pagina }} </p>

Os campos {{ variavel }} são chamados de localizadores (placeholders) para os valores que serão passados pelas funções view. Em muitos casos as informações usadas para popular essas variáveis são lidas em um banco de dados.

Já vimos o exemplo:

@app.route(“/frutas/<nome_da_fruta>”)
def frutas(nome_da_fruta):
return render_template(“frutas.html”, nome_da_fruta = nome_da_fruta)

onde /frutas/<nome_da_fruta>, fornece o valor da varíavel passada para o parâmetro da função (em vermelho). Dentro do corpo da função a variável de mesmo nome (em verde) recebe esse valor. Esses nomes não precisam ser os mesmo, embora esse seja uma prática comum entre programadores do python.

O método render_template() é parte do Jinja2 para integrar templates e lógica do aplicativo.

Filtros


Variáveis e objetos do python podem ser integrados nos templates de algumas formas. Por meio do módulo Jinja temos diversos filtros para manipular campos em templates. Já vimos como inserir uma variável em um template. Um exemplo de filtro é title(), que torna a string no formado de título, com a primeira letra de cada palavra em maísculo.

# suponha que temos a variável titulo = "a casa da mãe joana"
# essa string pode ser exibida dentro de uma tag <h1>
<h1> {{ titulo }} </h1>
↳ a casa da mãe joana

# para maísculas na primeira letra de cada palavra
<h1> {{ titulo | title() }} </h1>
↳ A Casa Da Mãe Joana

Uma descrição dos filtros para texto está na tabela abaixo.

Filtro Descrição
capitalize Converte 1º caracter em maiúsculo, os demais em minúsculo
lower Converte todos os caracteres minúsculo
upper Converte todos os caracteres maiúsculo
title Converte 1º caracter de cada palavra em maiúsculo
trim Remove espaços em branco no início e no fim
safe Renderiza o valor sem aplicar escape (inclui tags)
striptags Remove todas as tags HTML do valor

safe: O filtro safe informa ao Flask que a tag html pode ser renderizada com segurança. Mais exemplos abaixo.
striptags: remove as tags <tag> e </tag> e retorna o texto puro.

Exemplos de Filtros em Strings

Suponha que temos uma variável de nome titulo. Nos templates ela pode ser exibida diretamente, como uma string, ou passando por algum dos vários filtros. Nos quadros seguintes os comentários são iniciados por # enquanto outputs são identificados pelo sinal .

# suponha que a variável titulo2 não está definida
# default fornece um valor default (se titulo2 não está definido).
<h1> {{titulo2 | default ("Título Não Encontrado")}} </h1>
↳ Título Não Encontrado

# torna maiúscula a primeira letra
<h1> {{"mercado" | capitalize()}} </h1>
↳ Mercado

# em linha anterior ao uso podemos definir um valor
# capitalize torna maiúscula a 1ª letra de cada palavra
{% set titulo2 = "um título para a página" %}
<h1> {{ titulo2 | capitalize()}} </h1>
↳ Um título para a página

# title() torna maiúscula a 1ª letra de cada palavra
<h1> {{titulo2 | title()}} </h1>
↳ Um Título Para A Página

# substituir um trecho em uma string
{{ "Bom dia galera!" | replace("Bom dia", "Boa noite") }}
↳ Boa noite galera!

# inverter a ordem dos elementos
{{ "Olá galera!" | reverse() }}
↳ !arelag álO

Conversores

Por default os valores passados em uma url e capturados como valores do python são strings. Alguns conversores podem transformar essas strings em caminhos (que usam barras / ), inteiros ou decimais.

@app.route("/usuario/<int:id>")
def exibir_id(id):
    # esta função recebe id como um inteiro e o exibe
    return f"O id digitado é {id}"

@app.route("/path/")
def exibir_caminho(caminho):
    # recebe e retorna o caminho passado
    return f"Caminho {caminho}"

Os seguintes conversores estão disponívies:

string (default) qualquer string sem barras / ou \
int converte em inteiros positivos
float converte em números decimais
path strings contendo barras de caminho
uuid strings UUID†

† Uma string UUID (Universally Unique IDentifier), também chamada de GUID (Globally Unique IDentifier) é um número de 128-bits usado na troca de informações em computação.

Valores numéricos podem ser convertidos entre inteiros e decimais, e um valor default ser fornecido.

# números inteiros podem ser convertidos em decimais, ou decimais em inteiros
{{ 10 | float() }}
↳ 10.0 ou 0.0      # 0.0 se a conversão não for possível
{{ 10.0 | int() }}
↳ 10

# um valor default, em caso de erro
{{ "qualquer" | float (default = "Erro: texto não pode ser convertido em decimal") }}
↳ Erro: texto não pode ser convertido em decimal

Manipulação de Listas

Diversas operações são disponíveis em listas.

# join: junta elementos de uma lista
{{ [1, 2, 3] | join() }}
↳ 123
{{ ["Um", "Dois", "Tres"] | join() }}
↳ UmDoisTres

# inserindo um separador
{{ [1, 2, 3] | join ("|") }}
↳ 1|2|3
{{ ["Um", "Dois", "Tres"] | join("-") }}
↳ Um-Dois-Tres

# o filtro list() retorna uma lista
{{ "Guilherme" | list()}}
↳ ["G","u","i","l","h","e","r","m","e"]

# random() seleciona um item aleatorio da lista
{{ ["Mercúrio", "Venus", "Terra"] | random() }}
↳ Venus

{% set pe_pagina = ["citacao 1", "citacao 2", "citacao 3", "citacao 4", "citacao 5"] %}
{{ pe_pagina | random() }}
↳ citacao 4

# replace (visto acima para strings) também pode ser usado em listas
{% set lista = ["Nada", "a", "dizer"] %}
{{ lista | replace ("Nada", "Tudo") }}
↳ ["Tudo", "a", "dizer"]

# o filtro reverse() também pode inverter uma lista
# mas seu resultado é um objeto iterador
{% set lista = ["unidade", "dezena", "centena"] %}
{{ list | reverse() }}
↳ <list_reverseiterator object at 0x7fc0b6262518>

# para usar o objeto lista sem usar iterações temos que usar o método list()
{{ list | reverse() | list() }}
↳ ["centena", "dezena", "unidade"]

O filtro random() pode ser útil para exibir um artigo aleatório do site na homepage, para escolher uma imagem ou um pé de página, etc.

Outros exemplos de manipulação de listas incluem o uso de first(), last(), uso de índices e de laços para percorrer toda a lista.

# first() é 1º elemento da lista, last() é o último elemento
{% set nomes = ["João", "Pedro", "da", "Silva"]  %}
<p> Nome: {{ nomes | first() }} </p>
<p> Segundo Nome: {{ nomes [1] }} </p>
<p> Sobrenome: {{ nomes | last() }} </p>
↳ Nome: João
↳ Segundo Nome: Pedro
↳ Sobrenome: Silva

# o tamanho de uma lista é retornado com {{ lista | length }}
# laços for são usados para percorrer os elementos
{% set comentarios = ["Comenta 1", "Comenta 2", "Comenta 3", "Comenta 4"]%}
<p>Temos ({{comentarios | length}}): comentários</p>
{% for comentario in comentarios %}
    <p> {{ comentario }} </p>
{% endfor%}
# resulta em
↳ Temos 4: comentários
↳ Comenta 1
↳ Comenta 2
↳ Comenta 3
↳ Comenta 4

O filtro safe

O filtro safe serve para passar para o interpretador do Flask a informação de que as tags html devem ser renderizadas. Sem ele a string "<texto>" é exibida literalmente, inclusive com os delimitadores "<>".

Os códigos &lt; (<) e &gt; (>) são entidades html, descritas nesse site.

Por motivo de segurança o Jinja2 remove as tags html. Por exemplo: uma variável com valor "<li> TEXTO </li>" será renderizada como "&lt;li&gt; TEXTO &lt;/li&gt;" por extenso e sem provocar a renderização do navegador. Com o filtro safe o TEXTO é exibido como um ítem de lista.

# exibição literal de uma string
{{ "<b>Texto a exibir!</b>" }}
↳ <b>Texto a exibir!</b>

# para forçar a renderização da tag <b> (negrito)
{{ "<b>Texto a exibir!</b>" | safe }}
↳ Texto a exibir!

# define uma lista
{% set lista = ["<li>Um elefante</li>", "<li>Dois elefantes</li>", "<li>Três elefantes</li>"] %}
<ul>
{% for item in list %}
    {{ item | safe }}
{% endfor %}
</ul>
# será renderizado como
↳
⏺ Um elefante
⏺ Dois elefantes
⏺ Três elefantes

# alternativamente
{% set lista = ["Um elefante", "Dois elefantes", "Três elefantes"] %}
<ul>
{% for item in list %}
    <li> {{ item }} </li>
{% endfor %}
</ul>
# será renderizado da mesma forma.
# Nesse caso não existem tags na lista e safe é desnecessário.

Observação importante: Qualquer input digitado por usuários deve passar pelo filtro safe para evitar que alguma instrução danosa seja processada pelo navegador.

Laços e bifurcações


Vimos que um template recebe variáveis do python e pode processá-las com código. Por exemplo, modificamos o template frutas.html da seguinte forma:

# frutas.html
<body>
    {% if nome_da_fruta == None %}
        <p>Você não escolheu uma fruta!</p>
    {% elif nome_da_fruta == "laranja" %}
        <p>Você escolheu laranja, a melhor fruta!</p>
    {% else %}
        <p>Você escolheu a fruta: {{ nome_da_fruta }}</p>
    {% endif %}
</body>

No código de meu_site.py modificamos a função frutas para que por default ela receba None (caso nada seja escrito após o nome do diretório /frutas/:

# meu_site.py (apenas trecho)
@app.route("/frutas/")
@app.route("/frutas/<nome_da_fruta>")
def frutas(nome_da_fruta=None):
    return render_template("frutas.html", nome_da_fruta=nome_da_fruta)

Agora temos as respostas:

url resposta no navegador
http://127.0.0.1:5000/frutas/ Você não escolheu uma fruta!
http://127.0.0.1:5000/frutas/laranja Você escolheu laranja, a melhor fruta!
http://127.0.0.1:5000/frutas/goiaba Você escolheu a fruta: goiaba

No template, os trechos entre chaves não são parte do html e sim do Python, gerenciado pelo Flask. Dessa forma podemos integrar as páginas da web com as inúmeras bibliotecas do Python.

Quatro tipos de marcações estão disponíveis para para inserção do código nos templates.

sintaxe usadas para
{% ... %} linhas de instruções
{{ ... }} expressões
{# ... #} comentários
# ... ## instruções inline

Variáveis

Variáveis a serem usadas nos templates podem ser de qualquer tipo. Por exemplo:

{% set nomes = ["João", "Pedro", "da", "Silva"]  %}
<p>O segundo nome da lista: {{ nomes[1] }}.</p>
↳ O segundo nome da lista: Pedro.

{% set id = 3 %}
<p>Quem? {{ nomes[id] }}!</p>
↳  Quem: Silva!

# uso de um dicionário
{% set dicionario = {"Nome":"Paul"; "Sobrenome":"Dirac"; "Profissão":"Físico"}  %}
<p>Estes são os dados do: {{ dicionario["Nome"] }}.</p>
{% for chave, valor in dicionario.items() %}
    <p>{{ chave }} : {{ valor }}</p>
{% end for %}
↳ Estes são os dados do: Paul.
↳ Nome: Paul
↳ Sobrenome: Dirac
↳ Profissão :Físico

# no caso geral, se um objeto é passado para o template e tem um método, podemos usar:
<p>Obtendo um valor com um método de objeto disponível: {{ objeto.metodo() }}.</p>

Incluindo trechos com include

Se uma parte do template é repetida várias vezes ela pode ser colocada à parte, em arquivo separado, e incluída na template principal. Por ex., se temos um pé de página que aparece em diversas de nossas páginas ele pode ser gravado à parte.

# arquivo pe_de_pagina.html
<div class="pe_pagina">
<p>Esté é o meu pé de página</p>
</div>

Esse código assume que existe a definição de uma classe css chamada pe_pagina.
Em todos os arquivos que devem exibir o pé de página inserimos:

# todo o texto da página
# pé de página
{% include 'pe_de_pagina.html' %}

Macros

Macros são formas de implementar o princípio DRY (Don’t Repeat Yourself ), uma prática de desenvolvimento de software que visa reduzir a repetição do linhas semelhantes ou com mesma função no código. Ele torna o código mais legível e de fácil manutenção, uma vez que alterações devem ser feitas em um único bloco. Templates, macros, inclusão de conteúdo externo (como em include()) e heranças de modelos são todos instrumentos utilizados para isso.

Outra forma de gerar código reutilizável é através da criação de macros, um recurso similar às funções usuais do Python. Macros podem ser gravadas em arquivos separados e importadas dentro de todos os templates que fazem uso delas, facilitando a modularização do código.

Uma macro pode executar tarefas simples como simplesmente montar as linhas da lista. Suponha que temos uma lista de linhas e queremos montar uma lista não ordenada em html:

 {% macro montar_lista(linha) %}
     <li>{{ linha }}</li>
 {% endmacro %}
 <ul>
 {% for linha in linhas %}
     {{ montar_lista(linha) }}
 {% endfor %}
 </ul>

Outra possibilidade importante para a modularização consiste em gravar um arquivo macros.html que contém a macro vermelho(texto, marcas). Ele retorna linhas de uma lista coloridas de vermelho se texto == marcas, de azul caso contrário.

# arquivo macros.html
{% macro vermelho(texto, marcar) %}
{% if texto == marcar %}
    <li style="color:red;">{{ texto }}</li>
{% else %}
    <li style="color:blue;">{{ texto }}</li>
{% endif %}
{% endmacro %}

O arquivo mostrar_frutas.html importa a arquivo anterior, com a sua macro, e faz uso dela para exibir a lista ordenada.

# arquivo mostrar_frutas.html
{% from "macros.html" import vermelho %}
{% set frutas = ["Abacate", "Abacaxi", "Laranja", "Uva"]  %}
{% set selecionado = "Abacaxi"  %}
<ol>
 {% for fruta in frutas %}
     {{ vermelho(fruta, selecionado) }}
 {% endfor %}
</ol>

O resultado no navegador é o seguinte:

Assim como é válido no Python, podemos fazer a importação de forma alternativa (mostrando só linhas diferentes):

# arquivo mostrar_frutas.html
{% import "macros.html" as macros %}
{% for fruta in frutas %}
    {{ macros.vermelho(fruta, selecionado) }}
{% endfor %}

Herança de Templates

Similar à herança de classes no modelo POO do python, podemos criar um template base e derivar dele outros templates que herdam a sua estrutura. Os templates base definem blocos que podem ser sobrescritos nos templates filhos. Um template base pode ter a seguinte estrutura:

# arquivo base.html    
<html>
<head>
 {% block head %}
 <title> Artigo {% block title %}{% endblock %} </title>
 {% endblock %}
</head>
<body>
 {% block body %}
 {% endblock %}
 {% block final %}
 <p>Site construído com Python-Flask!</p>
 {% endblock %} 
</body>
</html>

A instrução {% block nome_do_bloco %}{% endblock %} pode ser substituída por conteúdo nos templates filhos (ou derivados). Para herdar desse template usamos extends e redefinimos os blocos da base:

# arquivo derivado.html    
{% extends "base.html" %}
{% block title %}Nome do Artigo{% endblock %}
{% block head %}
<style>
# estilos css ficam aqui
</style>
{% endblock %}
{% block body %}
<h1>Nome do Artigo</h1>
<p>Texto do artigo</p>
{% endblock %}
{% block final %}
 {{ super() }}
{% endblock %}

O bloco final, {% block final %}{{ super() }}{% endblock %}, usa super() para simplesmente importar o conteúdo do arquivo base. Se a base e o derivado contém texto o conteúdo da base é sobreposto.

Solicitações e Respostas do Servidor (Request, Response)

† O que é uma thread?

Ao receber uma solicitação de um cliente o Flask responde passando para as funções de visualização (view functions) os objetos que serão usados para a construção da página web. Um exemplo é o objeto request, que contém a solicitação HTTP enviada pelo cliente. Temos que nos lembrar que o aplicativo pode receber um grande volume de solicitações em múltiplas threads. Para evitar que todas as funções de visualização recebam essas informações como parâmetros, o que pode tornar o código complexo e gerar conflitos o Flask usa contextos para que esses objetos fiquem temporariamente acessíveis dentro desses contextos.

Para os exemplos que se seguem vamos trabalhar usando o python no terminal. Para isso ativamos o ambiente virtual e o próprio python:

$ cd ~/caminho_para_o_projeto
$ source ./bin/activate
# o prompt muda para indicar ativação de venv
$ (venv) $ python
Python 3.9.10 (main, Jan 17 2022, 00:00:00)

Graças aos contextos, funções de visualização como a seguinte podem ser escritas:

from flask import request
@app.route('/')
def index():
    navegador = request.headers.get('User-Agent')
    return f'<p>Verificamos que seu navegador é o {navegador} </p>'

Que retorna no navegador (no meu caso):

Contextos: Note que request não foi passado explicitamente para index(), agindo como se fosse uma variável global. Isso é obtido pelo Flask com os contextos ou ambientes reservados. Dois ambientes são usados: o contexto de aplicativo e o contexto de requisição.

As seguintes variáveis existem nesses contextos:

nome da variável Contexto Descrição
current_app Aplicativo A instância do aplicativo ativo.
g Aplicativo Objeto usado pelo aplicativo para armazenamento de dados durante uma requisição. Resetada a cada requisição.
request Requisição Objeto que encapsula o conteúdo da requisição HTTP enviada pelo cliente.
session Requisição Representa a sessão do usuário, um dicionário que o aplicativo usa para armazenar valores entre requisições.

Esses contextos só estão disponíveis quando o aplicativo recebe uma requisição (por meio de uma url digitada no navegador). O Flask ativa o contexto de aplicativo disponibilizando current_app e g, e o contexto de requisição disponibilizando request e session, para a thread, e em seguida os remove. Um exemplo desse comportamento pode ser visto no código, executado dentro do python:

from meu_site import app
from flask import current_app
current_app.name                     # um mensagem de erro é exibida
                                     # pois o contexto não está ativo
RuntimeError: working outside of application context

app_contexto = app.app_context()
app_contexto.push()                 # o flask ativa o contexto
current_app.name                    # o nome do app é impresso
'meu_site'
app_contexto.pop()

Flask usa os métodos objeto.push() e objeto.pop() para iniciar e terminar um contexto. A variável current_app.name só existe enquanto o contexto está ativado.

Preparando uma resposta

Para responder a uma solicitação o Flask armazena um mapa que associa URLs (e suas partes) à função resposta que deve ser executada. Esse mapa está armazendo em app.url_map.

from meu_site import app
app.url_map
Map([<Rule '/contatos/' (HEAD, OPTIONS, GET) -> contatos>,
 <Rule '/frutas/' (HEAD, OPTIONS, GET) -> frutas>,
 <Rule '/' (HEAD, OPTIONS, GET) -> index>,
 <Rule '/frutas/<nome_da_fruta>' (HEAD, OPTIONS, GET) -> frutas>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])

A lista mostra o mapeamento das funções view que criamos e mais um, denominado /static, acrescentado automaticamente para acessar arquivos estáticos como arquivos de estilo (cascading style sheets, *.css) e imagens. Os elementos (HEAD, OPTIONS, GET) são passados dentro da URL. Os dois primeiros são gerenciados internamento pelo Flask.

Objeto request

O objeto request, armazenado na variável request, contém toda a informação passada na requisição pela URL. Os atributos e métodos mais comuns desse objeto são listados a seguir.

Atributo/Método Descrição
form Dicionário com todos os campos de form submetidos na requisição.
args Dicionário com todos os argumentos passados na string de pesquisa da URL.
values Dicionário com valores combinados de form e args.
cookies Dicionário com todos os cookies incluídos na requisição.
headers Dicionário com todos os cabeçalhos HTTP incluídos na requisição.
files Dicionário com todos os arquivos de upload incluídos na requisição.
get_data() Retorna dados em buffer na requisição.
get_json() Retorna dicionário com dados JSON incluído na requisição.
blueprint Nome do blueprint que está processando a requisição.
endpoint Nome do endpoint processando a requisição. Flask usa a função view como nome do endpoint para um caminho.
method Método da requisição HTTP, (GET ou POST).
scheme Esquema da URL (http or https).
is_secure() Retorna True se a requisição veio de conexão segura (HTTPS).
host O host definido na requisição, incluindo o número da porta se fornecido pelo cliente.
path Parte da URL que define o caminho.
query_string Parte da URL que define a string de pesquisa (query), como um valor binary (raw).
full_path Parte da URL que define caminho e pesquisa (query).
url Requisição completa da URL fornecida pelo cliente.
base_url O mesmo que url, sem a parte de pesquisa (query).
remote_addr Endereço de IP do cliente.
environ Dicionário com o ambiente de WSGI da requisição.
(†) Blueprints serão vistos mais tarde.

Hooks de Solicitação (request hooks)

Com o Flask podemos registrar funções que devem ser chamadas antes ou depois de uma solicitação. Essas funções podem ser usadas para executar tarefas úteis, tais como autenticar um usuário, abrir e fechar a conexão com um banco de dados, etc. Quatro ganchos (hooks) são disponibilizados:

before_first_request Registra função para execução antes da primeira requisição. Útil para tarefas de inicialização do servidor.
before_request Registra função a ser executada antes de cada requisição.
after_request Registra função a ser executada após cada requisição, caso não ocorram exceções não tratadas.
teardown_request Registra função a ser executada após cada requisição, mesmo que ocorram exceções não tratadas.

Um exemplo de uso desses hooks seria o de usar before_request para coletar dados que serão usados ao longo do ciclo de vida do aplicativo para um usuário e os armazenar na variável g para uso posterior.

Como vimos, uma requisição resulta em uma resposta por meio de uma das funções view enviada ao cliente. Ela pode ser uma página simples de html construída com auxílio dos templates ou algo mais complexo. Junto com a resposta, de acordo com o protocolo HTTP, é enviado um código de status (status code) indicando o sucesso da solicitação. Por default o Flask retorna o status_code = 200 para solicitação bem sucedida. Podemos usar uma função view para retornar outro código.

@app.route('/')
def index():
    return 'Ocorreu um erro!', 400
42 é a resposta do Guia do Mochileiro das Galaxias!

A função acima retorna uma tupla com uma string e um inteiro. É possível e útil fazer com que essas funções retornem um objeto response no lugar da tupla.

from flask import make_response
@app.route('/')
def index():
   response = make_response('Resposta Final sobre o Universo!')
   response.set_cookie('resposta', '42') return response

Dessa forma response passa a conter um cookie (que é gerenciado pelo navegador que o recebe). A tabela seguinte mostra métodos e atributos mais usados no objeto response.

Métodos e atributos do objeto response

Atributo/Método Descrição
status_code Código numérico de status do HTTP
headers Objeto tipo dicionário com todos os cabeçalhos a serem eviados em response
set_cookie() Acrescenta um cookie no objeto response
delete_cookie() Remove um cookie
content_length Comprimento do corpo da response
content_type Tipo de midia do corpo da response
set_data() Define o corpo da response como string ou bytes
get_data() Retorna o corpo da response


Dois tipos de resposta especiais para casos que ocorrem com frequência são fornecidos como funções auxiliares. Uma delas é uma forma de lidar com erros, eviando um código por meio do método abort(). No exemplo abaixo usamos essa função para enviar uma mensagem 404 (página não encontrada) caso a id de um usuário não seja encontrada com load_user(id).

from flask import abort
@app.route('/usuario/<id>')
def usuario(id):
    usuario = load_user(id)
    if not usuario:
        abort(404)
    return f'<div class="usuario">Bem vindo {usuario.name}</div>'

Esse exemplo supõe que exista um template para usuario e que ele carrega instruções css para a classe usuario.

Se load_user(id) retornar None é executada abort(404) que retorna a mensagem de erro. Uma exceção é levantada e a função usuario() é abandonada antes de atingir a instrução return.

O outro tipo de resposta é o redirect que não retorna uma página mas sim uma nova URL redirecionando o navegador. redirect retorna status_code = 302 e a nova URL (dada no código).

from flask import redirect
@app.route('/')
def index():
    return redirect('https://phylos.com/programacao')

Bibliografia

Veja a bibliografia na Parte 1.


Flask, Parte 3 está em preparação!

Flask, parte 1


Web Frameworks

Para a construção de páginas e aplicativos web é essencial algum conhecimento de html e css, não cobertos nesse artigo.

Um aplicativo web web application ou simplesmente web app) é um aplicativo executado através de um servidor web, diferente de um aplicativo executado na máquina local, e geralmente rodados e visualizados por meio de um browser ou navegador. Eles podem ser um conjunto de páginas de texto dinâmicas contendo, por exemplo, pesquisas em uma biblioteca, um gerenciador de arquivos na nuvem, um gerenciador de contas bancárias ou emails, um servidor de músicas ou filmes, etc. Com frequência esses aplicativos estão conectados a um banco de dados, podendo fazer neles consultas e modificações.

Um framework para a web é um conjunto de softwares destinados a oferecer suporte ao desenvolvimento de aplicativos na Web. Eles buscam automatizar a construção dos web apps e seu gerenciamento durante o funcionamento, após sua publicação na web. Alguns frameworks web incluem bibliotecas para acesso a banco de dados, templates para a construção dinâmica das páginas e auxílio à reutilização de código. Eles podem facilitar o desenvolvimento de sites dinâmicos ou, em alguns casos, de sites estáticos.

Framework Flask

O Flask é um micro-framework em Python usado para desenvolvimento e gerenciamento de web apps. Ele é considerado micro porque possui poucas dependências para seu funcionamento e pode ser usado com uma estrutura inicial bem básica, voltada para aplicações simples. Apenas duas bibliotecas são instaladas junto com o Flask. Ele não contém, por ex., um gerenciador de banco de dados ou um servidor de email. No entanto esses serviços podem ser acrescentados para ampliar as funcionalidades do aplicativo.

Em particular, o Flask não inclui uma camada de abstração com banco de dados. Em vez disso é possível instalar extensões, escolhendo o banco de dados específico que se quer usar. Essas extensões também podem auxiliar a validação de formulário, manipulação de upload, tecnologias de autenticação aberta, entre outras.

Flask foi desenvolvido por Armin Ronacher e lançado em 2010. Algumas vantagens citadas em seu uso são: (a) projetos escrito com a Flask são simples (comparados àqueles gerados por frameworks maiores, como o Django) e tendem a ser mais rápidos. (b) Ele é ágil e modular: o desenvolvedor se concentra apenas nos aspectos utilizados de seu aplicativo, podendo ampliar sob demanda. (c) Os projetos são pequenos mas robustos. (d) Existe uma vasta comunidade de desenvolvedores contribuindo com seu desenvolvimento, apresentando bibliotecas que ampliam sua funcionalidade.

Criando um aplicativo básico

Nessa primeira parte vamos criar um aplicativo bem básico mas funcional. Depois entraremos em outros detalhes do Flask.

Para criar um projeto usando o Flask (ou, na verdade, outro projeto qualquer) é aconselhável criar antes um ambiente virtual para que os pacotes instalados não conflituem com outros em projetos diferentes. A criação e manutenção de ambientes virtuais está descrita na página Ambientes Virtuais, PIP e Conda. Alguns IDEs, como o Pycharm realizam automaticamente esse processo. No meu caso ele ficará abrigado em ~/Projetos/flask/venv. Para simplificar denotarei esse ambiente simplesmente pelo nome flask, que é também o nome da pasta que abriga esse projeto.

Nesse ambiente instalamos o Flask com pip install flask. Uma estrutura básica de ambiente já estará montada após esse passo. Em seguida criamos um arquivo do python, de nome meu_site.py.

# meu_site.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def homepage():
    return "Esta é a minha homepage"

if __name__ == "__main__":
    app.run()

# é exibido no console do python
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Esse arquivo não deve se chamar flask.py para evitar conflito de nomes.

A variável __name__, passada para o construtor do Flask contém o nome do módulo principal e é usada para determinar a localização do aplicativo no app. Outros diretórios e arquivos, como pastas de templates, arquivo de estilo e imagens, serão localizadas à partir dessa raiz.

Aplicativos do Flask incluem um servidor de desenvolvimento que pode ser iniciado com o comando run. Esse comando busca pelo nome do aplicativo na variável de ambiente FLASK_APP. Se queremos rodar o aplicativo meu_site.py executamos na linha de comando:

# Linux ou macOS
(venv) $ export FLASK_APP=meu_site.py
(venv) $ flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

# Microsoft Windows
(venv) $ set FLASK_APP=meu_site.py
(venv) $ flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 
 # alternativamente se pode iniciar o servidor com
 app.run()

No Pycharm, ou outros IDES, você pode executar diretamente esse código.

Da biblioteca flask importamos apenas (por enquanto) a classe Flask. Uma instância da classe é criada com app = Flask(__name__) onde a variável __name__ contém o nome do projeto. A linha @app.route("/") é um decorador que informa que a função seguinte será rodada na raiz / do site. Quando esse arquivo .py é executado dentro de uma IDE ou usando python meu_site.py, na linha de comando, é exibido no console várias mensagens, entre elas a url http://127.0.0.1:5000/, que pode ser clicada ou copiada para a linha de endereço do navegador. Isso resulta na exibição, dentro do navegador, da página:


Clientes e Servidores: O navegador age como o cliente que envia ao servidor uma solicitação, através de uma URL digitada na barra de endereços. O servidor da web transforma essa solicitação em ações a serem realizadas do lado do servidor e retorna uma página com conteúdo de texto e multimídia, renderizados pelo navegador. O Flask fica do lado do servidor, construindo a resposta. Entre outras coisas ele possui um mapeamento entre as URLs e as funções route() que serão executadas no código *.py.

O endereço e a porta 127.0.0.1:5000 são padrões para o Flask. app.run() cria um servidor que atende à requisição HTTP do navegador, exibindo a página html. Qualquer texto retornado pela função homepage() é renderizado no formato html e exibido no navegador. Por exemplo, se fizermos as alterações, colocando o texto entre tags h1:

@app.route("/")
def homepage():
    return "<h1>Esta é a minha homepage</h1>"

if __name__ == "__main__":
    app.run(debug=True)

o texto agora é renderizado como um título de nível 1:

o mesmo texto será exibido mas agora com formatação de título, a tag h1. Todas as demais tags podem ser utilizadas. O parâmetro debug=True faz com que alterações no código sejam imediatamente repassadas para as requisições ao servidor, sem a necessidade de rodar todo o projeto novamente. Com isso basta recarregar a página do navegador para que alterações sejam exibidas, clicando no ícone de atualização ou pressionando F5. No mode debug os módulos dois módulos chamados reloader e debugger estão ativados por default. Com o debugger ativado as mensagens de erro são direcionadas para a página exibida. O mode debug nunca deve ser ativado em um servidor em produção pois isso fragiliza a segurança do site.

Também podemos ativar o módulo no código que executa o aplicativo:

(venv) $ export FLASK_APP=meu_site.py
(venv) $ export FLASK_DEBUG=1
(venv) $ flask run

O decorador @app.route("/") registra a função homepage() junto com a página raiz do site. Outras páginas vão executar outras funções. Por exemplo, uma página de contatos pode ser inserida por meio da inserção de nova função no código. Nesse caso criaremos a função contatos().

# meu_site.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def homepage():
    return "<h1>Esta é a minha homepage</h1>"

@app.route("/contatos")
def contatos():
    txt = (
    "<h1>Página de Contatos</h1>"
    "<ul>"
    "<li>Contato 1</li>"
    "<li>Contato 2</li>"
    "</ul>"
    )
    return txt

if __name__ == "__main__":
    app.run(debug=True)

Usamos acima a concatenação de string com parênteses: (str1 str2 ... strn).
Agora, além da homepage temos a página de contatos em 127.0.0.1:5000/contatos, com a seguinte aparência.

A funções contatos() e homegage() são chamadas funções de visualização (view functions).

Html e templates: Notamos agora que o código em meu_site.py contém sintaxe misturada de Python e html e pode ficar bem complexo em uma página real exibida na web. Para evitar isso o Flask permite a criação de templates. Fazemos isso da seguinte forma: no diretório raiz onde está o projeto (o mesmo onde foi gravado meu_site.py) criamos o diretório template (o nome default do Flask). Dentro dele colocamos nossos templates. Por exemplo, criamos os arquivos homepage.html,

# homepage.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Homepage: teste Flask</title>
</head>
<body>
    <h1>Este é o título da Homepage</h1>
    <p>Com os devidos parágrafos...</p>
</body>
</html>

e contatos.html:

# contatos.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contatos</title>
</head>
<body>
    <h1>Página de Contatos</h1>
    <ul>
    <li>Contato 1</li>
    <li>Contato 2</li>
    </ul>
    </body>
</html>

Vários IDEs podem auxiliar na criação desses arquivos html, fornecendo um esqueleto básico a ser preenchido pelo programador.

Além disso modificamos nosso código python para usar a renderização dos templates, importando render_template.

# meu_site.py
from flask import Flask, render_template
app = Flask(__name__)

@app.route("/")
def homepage():
    return render_template("homepage.html")

@app.route("/contatos")
def contatos():
    return render_template("contatos.html")

if __name__ == "__main__":
    app.run(debug=True)

Quando esse código é executado temos a referência ao link que, se aberto, mostra as páginas criadas: digitando 127.0.0.1:5000 abrimos nossa homepage:

Mas, se digitarmos 127.0.0.1:5000/contatos a outra página é exibida:

Uma página pode receber parâmetros do código em python. Por exemplo, digamos que queremos exibir uma página para cada produto existente em uma loja virtual que vende frutas. Nesse caso acrescentamos no código de meu_site.py:

@app.route("/frutas/<nome_da_fruta>")
def frutas(nome_da_fruta):
    return render_template("frutas.html")

Para receber esse parâmetro temos que gravar a página frutas.html na pasta templates, com um conteúdo que receba essa variável.

# frutas.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Frutas disponíveis</title>
</head>
<body>
    <h1>Frutas</h1>
    <p>Você escolheu a fruta: {{nome_da_fruta}}</p>
</body>
</html>

Se for digitado no campo de endereços do navegador, ou passado por meio de um link na tag <a href="http://127.0.0.1:5000/frutas/laranja">Laranja</a> a parte do endereço <nome_da_fruta> = laranja é passado como valor de parâmetro na função frutas("laranja") que é disponibilizado dentro do código html como {{nome_da_fruta}}.

Resumindo: @app.route("/frutas/<nome_da_fruta>") envia uma string na variável nome_da_fruta para a função frutas que, por sua vez repassa ao código html. Dentro do html a variável fica disponível como {{nome_da_fruta}} (dentro de uma dupla chave).

Por exemplo, se digitamos na barra de endereços do navegador http://127.0.0.1:5000/frutas/laranja teremos a exibição de

Essa técnica pode ser usada, por ex., para criar páginas para diversos usuários usando um único template usuario.html.

@app.route('/usuario/<nome>')
def usuario(nome):
    return render_template("usuario.html")
# ou
@app.route('/usuario/<int:id>')
    return render_template("usuario.html")

A parte do código <int:id> é um filtro que transforma a entrada digitada em inteiro, quando possível e será melhor explicada adiante.

Formatando com CSS

O texto dentro de uma página html (HyperText Markup Language) pode ser formatado de algumas formas diferentes, usando css (Cascading Style Sheets). Quando se trata do uso de um framework a forma preferida consiste em apontar no cabeçalho para um arquivo externo css. No Flask isso é feito da seguinte forma: um arquivo css é gravado na pasta static da pasta do projeto. Digamos que gravamos o arquivo /static/styles.css com um conteúdo mínimo, apenas para demonstração, tornando vermelhas as letras do título e azuis as letras dos parágrafos:

# arquivo /static/styles.css
h1 { color:red; }
p { color:blue; }

No cabeçalho das páginas html, dentro da tag <head> colocamos um link para o arquivo de estilos:

# homepage.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="static/styles.css">
    <title>Homepage: teste Flask</title>
</head>

Agora, ao acessar a homepage veremos:


Com todas essas alterações o projeto tem agora a estrutura de pastas mostrada. Na figura à esquerda todas as pastas, inclusive aquelas criadas pelo gerenciador de ambientes virtuais são mostradas. No meu caso elas foram criadas automaticamente pelo IDE Pycharm, mas podem ser criadas pelo programador sem dificuldade. Na figura à direita são mostradas apenas as pastas criadas pela programador diretamente. Um projeto com esse formato roda perfeitamente, apesar de não contar com as vantagens do ambiente virtual (veja artigo).

Outras estruturas de código podem ser inseridas nos templates, como veremos.

Opções de comando de linha

Quando se roda o flask diretamente no terminal podemos ver uma mensagem de ajuda com (venv) $ flask --help, verificar os caminhos definidos no app ou entrar em uma shell interativa.

# Exibir ajuda do flask    
(venv) $ flask --help
  Usage: flask [OPTIONS] COMMAND [ARGS]...
  
    A general utility script for Flask applications.
  
    Provides commands from Flask, extensions, and the application. Loads the
    application defined in the FLASK_APP environment variable, or from a wsgi.py
    file. Setting the FLASK_ENV environment variable to 'development' will
    enable debug mode.
  
      $ export FLASK_APP=hello.py
      $ export FLASK_ENV=development
      $ flask run
  
  Options:
    --version  Show the flask version
    --help     Show this message and exit.
  
  Commands:
    routes  Show the routes for the app.
    run     Run a development server.
    shell   Run a shell in the app context.
  
# exibe os caminhos ativos no aplicativo
  (venv) $ flask routes
  Endpoint  Methods  Rule
  --------  -------  -----------------------
  contatos  GET      /contatos/
  frutas    GET      /frutas/
  frutas    GET      /frutas/
  homepage  GET      /
  static    GET      /static/
  
# entra em uma shell interativa    
  (venv) $ flask shell

A shell do flask inicia uma sessão python no contexto do atual aplicativo onde podemos executar testes ou tarefas de manutenção. O comando flask run admite vários parâmetros:

(venv) $ flask run --help
  Usage: flask run [OPTIONS]
  
    Run a local development server.
  
    This server is for development purposes only. It does not provide the
    stability, security, or performance of production WSGI servers.
  
    The reloader and debugger are enabled by default if FLASK_ENV=development or
    FLASK_DEBUG=1.
  
  Options:
    -h, --host TEXT                 The interface to bind to.
    -p, --port INTEGER              The port to bind to.
    --cert PATH                     Specify a certificate file to use HTTPS.
    --key FILE                      The key file to use when specifying a
                                    certificate.
    --reload / --no-reload          Enable or disable the reloader. By default
                                    the reloader is active if debug is enabled.
    --debugger / --no-debugger      Enable or disable the debugger. By default
                                    the debugger is active if debug is enabled.
    --eager-loading / --lazy-loading
                                    Enable or disable eager loading. By default
                                    eager loading is enabled if the reloader is
                                    disabled.
    --with-threads / --without-threads
                                    Enable or disable multithreading.
    --extra-files PATH              Extra files that trigger a reload on change.
                                    Multiple paths are separated by ':'.
    --help                          Show this message and exit.  

O argumento --host informa ao servido qual é o ambiente web que pode acessar nosso servidor de desenvolvimento. Por default o servidor de desenvovimento do Flask só aceita chamadas do computador local, em localhost. Mas é possível configurá-lo para receber chamadas da rede local ou de ambientes mais amplos. Por exemplo, como o código

(venv) $ flask run --host 0.0.0.0
 * Serving Flask app "hello"
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

todos os computadores conectados pelo mesmo ip terão acesso ao aplicativo.

Implantação de um aplicativo Flask

O processo de implantação (ou deploy) de um aplicativo consiste nas etapas necessários para colocá-lo acessível para seus usuários. No caso de um aplicativo web a implantação significa estabelecer um servidor ou usar servidores já disponíveis, que os usuários possam acessar, e colocar seu aplicativo como um de seus serviços.

O desenvolvimento do aplicativo se dá em um ambiente de desenvolvimento onde podem existir condições próprias para o debug e nem todas as medidas de segurança estão implementadas. Depois ele passa para a etapa de uso, no ambiente de produção. Uma conta no Heroku pode ser criada, e um site com poucos acessos pode ser mantido sem custos. Se o site for escalonado e crescer a conta deve ser atualizada e paga. A lista abaixo contém links para o Heroku e outros provedores.

Bibliografia

Livros sobre Flask

  • Aggarwal, Shalabh: Flask Framework Cookbook, 2.Ed., Packt, Birmingham-Mumbai, 2019.
  • Ashley, David: Foundation Dynamic Web Pages with Python Create Dynamic Web Pages with Django and Flask, Apress, 2020.
  • Gaspar, D.;StoufferHaider, J.: Mastering Flask Web Development, 2.Ed., Packt, Birmingham-Mumbai, 2018.
  • Grinberg, Miguel: The Flask Mega-Tutorial, Edição do autor, 2020.
  • Grinberg, Miguel: Flask Web Development, Developing Web Applications with Python, O’Reilly, Sebastopol, 2018.
  • Haider, Rehan: Web API Development With Python, CloudBytes, 2020.
  • Maia, Italo: Building Web Applications with Flask, 2.Ed., Packt, Birmingham-Mumbai, 2015.
  • Relan, Kunal: Building REST APIs with Flask, 2.Ed., Apress, 2019.

Referências na Web

Sobre HTML e CSS

todos acessados em fevereiro de 2022.