Desenvolvimento Web com Python: Arquitetura e Práticas do Framework Flask

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 epoll para 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

Tags: Python Flask Werkzeug jinja2 Blueprint

Publicado em 7-1 17:05