Curso de Python / Criar uma API JSON com Flask e validar entradas
Uma pequena API Flask conecta requisições HTTP a funções Python e devolve dados JSON. Confira o objeto recebido, campos obrigatórios, tipos e limites, usando códigos de status para distinguir uma criação válida de uma entrada rejeitada.
Definir o escopo local e preparar o ambiente
Esta API é uma prática acessível pelo próprio computador. As despesas ficam em uma lista na memória do processo: desaparecem quando ele termina e não são compartilhadas automaticamente com outros processos. Persistência, autenticação, autorização, implantação e concorrência exigem projeto adicional para um serviço público.
Selecione o ambiente virtual da prática e instale Flask, que é um pacote externo:
python -m pip install flask
Salve os arquivos seguintes em uma pasta própria, em UTF-8. Evite chamar o programa de flask.py, pois esse nome conflita com o pacote. A introdução oficial ao Flask apresenta a aplicação e seu servidor de desenvolvimento.
JSON é um formato de representação, não um mecanismo de armazenamento durável. As aulas de dados JSON e armazenamento com SQLite mostram a diferença entre representar um objeto e manter seus dados depois da execução.
Salvar e executar a aplicação completa
Salve o programa como app.py:
from flask import Flask, jsonify, request
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 4096
expenses = []
@app.errorhandler(413)
def too_large(error):
return jsonify(error="request body too large"), 413
@app.get("/api/expenses")
def list_expenses():
return jsonify(expenses)
@app.post("/api/expenses")
def add_expense():
data = request.get_json(silent=True)
if not isinstance(data, dict):
return jsonify(error="expected JSON object"), 400
name, amount = data.get("name"), data.get("amount")
if not isinstance(name, str) or not name.strip() or type(amount) is not int or amount < 0:
return jsonify(error="invalid name or amount"), 400
row = {"id": len(expenses) + 1, "name": name.strip(), "amount": amount}
expenses.append(row)
return jsonify(row), 201
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=False)
Para experimentar o servidor, execute python app.py no terminal do ambiente selecionado. No macOS ou Ubuntu, fora de um ambiente virtual, use python3 se esse for o comando do interpretador. Abra http://127.0.0.1:5000/api/expenses no navegador para fazer um GET: inicialmente deve aparecer uma lista vazia. A raiz / não tem rota definida neste exemplo.
O endereço 127.0.0.1 limita a escuta à interface de loopback. Se a porta 5000 já estiver ocupada, altere o número no código e no endereço usado para acessar a API. O servidor de desenvolvimento não deve ser exposto como um serviço de produção; o exemplo mantém debug=False. Ao terminar a experiência com o servidor, pressione Ctrl+C no terminal.
@app.get associa a leitura da lista a uma função; @app.post associa a criação de uma despesa a outra. Uma criação válida devolve o novo objeto com status 201; uma leitura válida devolve a lista com status 200. Entradas rejeitadas pelas condições explícitas devolvem um objeto de erro e status 400.
Verificar as rotas com o cliente de testes
Salve o segundo arquivo completo como test_client.py, ao lado de app.py. O cliente de testes chama o fluxo de requisições da aplicação diretamente; você não precisa manter um servidor separado aberto para executar esse arquivo.
from app import app, expenses
expenses.clear()
with app.test_client() as client:
created = client.post("/api/expenses", json={"name":"lunch", "amount":8000})
assert created.status_code == 201
for payload in [{"name":"x","amount":True}, {"name":"x","amount":-1}, {"name":" ","amount":1}, [1,2]]:
response = client.post("/api/expenses", json=payload)
assert response.status_code == 400 and response.is_json
assert client.post("/api/expenses", data="{", content_type="application/json").status_code == 400
too_large = client.post("/api/expenses", data="x"*5000, content_type="application/json")
assert too_large.status_code == 413 and too_large.is_json
rows = client.get("/api/expenses")
assert rows.status_code == 200 and len(rows.get_json()) == 1
print(created.status_code, rows.get_json()[0]["amount"])
print("validation and 413 checks passed")
Execute sem a opção de otimização -O, que pode remover as instruções assert:
python test_client.py
A saída esperada tem duas linhas: 201 8000 e validation and 413 checks passed. A primeira confirma a criação e a quantia consultada; a segunda indica que o script passou por suas verificações sem interromper por uma asserção.
O teste limpa somente a lista importada no próprio processo, cria um registro válido e verifica rejeição de booleano, negativo, nome em branco, array e JSON quebrado. Depois verifica uma requisição maior que o limite e confirma que somente o registro válido permaneceu. A lista de um servidor iniciado em outro processo é independente dessa lista do teste.
Esse procedimento não verifica a rede real, um proxy reverso ou uma implantação pública. Uma resposta correta no cliente de testes e uma resposta acessível pelo servidor local são evidências de camadas diferentes. Mantenha clara essa distinção ao ampliar a suíte.
Interpretar a validação e a duração dos dados
request.get_json(silent=True) devolve um resultado ausente quando não consegue obter o JSON esperado; a aplicação o rejeita porque não é um dicionário. Mesmo um JSON válido pode ter o formato errado, como uma lista em vez de um objeto.
O nome precisa ser uma string não vazia após strip(); o valor salvo é aparado. A quantia precisa ter exatamente o tipo int e ser não negativa. JSON true vira um booleano Python; como isinstance(True, int) é verdadeiro, o teste de tipo exato impede que esse booleano seja aceito como dinheiro.
MAX_CONTENT_LENGTH=4096 limita o corpo da requisição a 4096 bytes. O exemplo registra um tratador de 413 para devolver um erro JSON com o status correto. Isso não limita sozinho a quantidade de registros já acumulados, nem estabelece todas as restrições de recursos ou de domínio. Consulte a configuração de MAX_CONTENT_LENGTH e o tratamento de erros do Flask.
O contrato JSON cobre estas rotas e os erros tratados. Outros erros padrão, como uma rota inexistente, podem devolver HTML; uma requisição recusada antes pelo servidor ou proxy também não está coberta por esse tratador. Não prometa que todo erro de qualquer camada terá o mesmo corpo JSON.
- Execute o teste original e confirme as duas linhas de sucesso. Confira que os casos inválidos não aumentaram a lista.
- Experimente um nome com espaços nas extremidades e uma quantia zero: os valores válidos devem ser aceitos, com nome aparado.
- Inicie o servidor local, consulte a lista, encerre-o e reinicie-o. As despesas anteriores daquele processo não devem persistir. Não use essa lista como armazenamento definitivo.
Os IDs são calculados a partir do tamanho de uma lista em um processo. Essa estratégia é suficiente para a demonstração controlada, mas não substitui uma política de identificadores e sincronização para um serviço concorrente. Ao adicionar armazenamento durável, defina as transações e a política de repetição de requisições antes de considerar a criação concluída.




