Comparativo de Frameworks Web em Python
O ecossistema Python oferece diversas soluções para desenvolvimento web, cada uma com focos distintos:
- Django: Conhecido pela filosofia "batteries-included", é ideal para desenvolvimento rápido e robusto. No entanto, sua natureza monolítica pode exigir refatorações profundas (como substituir o ORM ou implementar sockets customizados em C/C++) para cenários de altíssima concorrência.
- Flask: Um microframework extremamente flexível e leve. Seu núcleo é mínimo, delegando funcionalidades como ORM, validação de formulários e autenticação para extensões de terceiros. Baseia-se no WSGI Werkzeug para roteamento e no Jinja2 para renderização de templates.
- Tornado: Destaca-se por ser um servidor web assíncrono e não-bloqueante. Utiliza
epollpara gerenciar milhares de conexões simultâneas, sendo a escolha preferencial para aplicações que exigem comunicação em tempo real (WebSockets, long-polling).
Estrutura e Instalação
Diferente do padrão MVC (Model-View-Controller), o Flask adota a arquitetura MTV (Model-Template-View), onde a "View" atua como a lógica de controle (controller) e o "Template" lida com a apresentação.
Para iniciar um projeto, instale o núcleo e as dependências auxiliares comuns:
pip install flask flask-sqlalchemy flask-migrate pymysql redis
Aplicação Mínima
from flask import Flask
web_app = Flask(__name__)
@web_app.route('/')
def pagina_inicial():
return 'Serviço Flask Operacional!'
if __name__ == '__main__':
# host='0.0.0.0' expõe o servidor para a rede local
web_app.run(host='0.0.0.0', port=8080, debug=True)
Gerenciamento de Configurações e Segurança
A propriedade SECRET_KEY é vital para operações criptográficas, como a assinatura de cookies de sessão e proteção CSRF.
A configuração pode ser gerecniada via classes orientadas a objetos, facilitando a separação por ambientes:
class ConfigBase:
DEBUG = False
TESTING = False
SECRET_KEY = 'chave_criptografica_super_segura'
SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db'
class ConfigDesenvolvimento(ConfigBase):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:senha@localhost/dev_db'
class ConfigProducao(ConfigBase):
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://usuario:senha@servidor/prod_db'
# Carregando a configuração
web_app.config.from_object(ConfigDesenvolvimento)
Sistema de Rotas Avançado
O roteamento no Flask é flexível, suportando tipagem dinâmica, métodos HTTP e subdomínios.
Conversores Personalizados
É possível estender o werkzeug.routing.BaseConverter para validar formatos específicos na URL, como um documento CPF brasileiro:
from werkzeug.routing import BaseConverter
class CPFConverter(BaseConverter):
regex = r'\d{3}\.\d{3}\.\d{3}-\d{2}'
web_app.url_map.converters['cpf'] = CPFConverter
@web_app.route('/cidadao/<cpf:documento>')
def buscar_cidadao(documento):
return f'Processando dados para o CPF: {documento}'
Redirecionamentos e Subdomínios
# Redirecionamento automático de rotas antigas
@web_app.route('/antigo-perfil/<int:uid>', redirect_to='/novo-perfil/<uid>')
def perfil_antigo(uid):
pass
# Roteamento baseado em subdomínio (requer SERVER_NAME configurado)
@web_app.route('/painel', subdomain='admin')
def painel_administrativo():
return 'Área restrita administrativa'
Templates com Jinja2
Para prevenir ataques de Cross-Site Scripting (XSS), o Jinja2 escapa automaticamente o conteúdo. Caso seja necessário renderizar HTML puro, utiliza-se a classe Markup no backend ou o filtro |safe no frontend.
from flask import render_template
from markupsafe import Markup
@web_app.route('/artigo')
def exibir_artigo():
conteudo_html = Markup('<strong>Texto em Negrito</strong>')
return render_template('artigo.html', conteudo=conteudo_html)
No template HTML, macros podem ser definidas para reutilização de componentes:
<!-- macro_form.html -->
{% macro campo_input(nome, tipo='text', valor='') %}
<input type="{{ tipo }}" name="{{ nome }}" value="{{ valor }}" class="form-control">
{% endmacro %}
Requisições, Respostas e Cookies
O objeto request centraliza dados da requisição HTTP, enquanto make_response e jsonify constroem a resposta.
from flask import request, jsonify, make_response
@web_app.route('/api/dados', methods=['POST'])
def processar_dados():
# Acessando JSON e Cabeçalhos
payload = request.get_json()
token = request.headers.get('Authorization')
# Manipulação de Cookies
resposta = make_response(jsonify({"status": "sucesso", "dados": payload}))
resposta.set_cookie('ultimo_acesso', '2023-10-25', max_age=3600)
return resposta, 201
Gerenciamento de Sessão
Por padrão, as sessões do Flask são armazenadas em cookies assinados criptograficamente. Para aplicações distribuídas, recomenda-se o uso da extensão Flask-Session com Redis:
from flask_session import Session
import redis
web_app.config['SESSION_TYPE'] = 'redis'
web_app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379/0')
Session(web_app)
@web_app.route('/login', methods=['POST'])
def autenticar():
from flask import session
session['usuario_id'] = 99
return 'Login efetuado'
Mensagens Flash
O recurso de flash utiliza a sessão para exibir mensagens temporárias ao usuário, ideais para feedbacks de formulários.
from flask import flash, get_flashed_messages, redirect
@web_app.route('/salvar', methods=['POST'])
def salvar_registro():
flash('Registro atualizado com êxito!', category='info')
return redirect('/dashboard')
@web_app.route('/dashboard')
def dashboard():
mensagens = get_flashed_messages(category_filter=['info'])
return f"Notificações: {mensagens}"
Modularização com Blueprints
Blueprints permitem dividir aplicações monolíticas em módulos coesos, cada um com suas próprias rotas, templates e arquivos estáticos.
from flask import Blueprint
# Definindo o Blueprint
bp_pagamentos = Blueprint('pagamentos', __name__, url_prefix='/api/v1/pagamentos')
@bp_pagamentos.route('/processar', methods=['POST'])
def processar_pagamento():
return 'Pagamento processado'
# Registrando na aplicação principal
web_app.register_blueprint(bp_pagamentos)
Ciclo de Vida da Requisição (Hooks)
Os ganchos de requisição interceptam o fluxo de processamento, permitindo lógica transversal como autenticação, logging e formatação de resposta.
@web_app.before_request
def verificar_autenticacao():
if request.path.startswith('/api/') and 'usuario_id' not in session:
return jsonify({"erro": "Não autorizado"}), 401
@web_app.after_request
def adicionar_cabecalhos_cors(response):
response.headers['Access-Control-Allow-Origin'] = '*'
return response
@web_app.errorhandler(404)
def pagina_nao_encontrada(erro):
return jsonify({"mensagem": "Recurso inexistente"}), 404
Contextos: Aplicação e Requisição
O Flask utiliza proxies locais (Thread-Local) para garantir que variáveis globais como request, session, current_app e g sejam isoladas por requisição.
- Contexto de Requisição: Mantém dados específicos da chamada HTTP atual (
request,session). - Contexto de Aplicação: Mantém configurações globais e conexões de banco de dados (
current_app,g).
O Objeto g
Utilizado para armazenar dados temporários durante o ciclo de vida de uma única requisição, evitando a passagem excessiva de parâmetros entre funções.
from flask import g
@web_app.before_request
def carregar_usuario():
g.cliente_atual = {"id": 101, "nivel_acesso": "admin"}
@web_app.route('/perfil')
def exibir_perfil():
return f"Bem-vindo, cliente {g.cliente_atual['id']}"
Implementação Subjacente (ThreadLocal)
Para suportar concorrência sem vazamento de dados entre requisições simultâneas, o framework implementa uma estrutura de pilha baseada no identificador da thread (ou greenlet/corrotina). Abaixo, uma representação simplificada de como o isolamento é alcançado:
import threading
class ArmazenamentoLocal:
def __init__(self):
object.__setattr__(self, '_banco_dados', {})
object.__setattr__(self, '_identificador', threading.get_ident)
def __setattr__(self, chave, valor):
ident = self._identificador()
if ident not in self._banco_dados:
self._banco_dados[ident] = {}
self._banco_dados[ident][chave] = valor
def __getattr__(self, chave):
ident = self._identificador()
try:
return self._banco_dados[ident][chave]
except KeyError:
raise AttributeError(f"Atributo '{chave}' não encontrado no contexto atual")
def limpar_contexto(self):
ident = self._identificador()
self._banco_dados.pop(ident, None)
class PilhaLocal:
def __init__(self):
self._local = ArmazenamentoLocal()
def empilhar(self, item):
pilha = getattr(self._local, 'pilha', [])
pilha.append(item)
self._local.pilha = pilha
def desempilhar(self):
pilha = getattr(self._local, 'pilha', None)
if not pilha:
return None
return pilha.pop()
@property
def topo(self):
pilha = getattr(self._local, 'pilha', None)
return pilha[-1] if pilha else None