404 Not Found

404 Not Found


nginx

Modelo de Resposta — Controle Preciso da Saída da API

Um modelo de resposta é como a iluminação de palco—ilumina apenas as áreas que o público deve ver, enquanto mantém os equipamentos dos bastidores (campos internos) no escuro, garantindo tanto estética quanto segurança.

1. O Que Você Vai Aprender


2. A História Real da Alice

(1) Problema: Preços de custo vazados para concorrentes

O PriceTracker da Alice armazena os preços de varejo e atacado dos produtos. Um dia, quando Bob solicitou detalhes do produto pelo front end, a API retornou os preços de atacado também. Um concorrente fez scraping da API e obteve todas as informações de preço de atacado, causando grande insatisfação dos clientes da Alice. A causa raiz do problema foi que o Flask não possui um mecanismo de filtragem de resposta, e todos os campos do objeto ORM foram serializados e retornados diretamente.

(2) Solução com response_model

Filtragem declarativa do response_model do FastAPI—retorna apenas os campos declarados no modelo, exclui automaticamente campos não declarados, e previne vazamentos de dados em nível de schema.

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

class ProductPublic(BaseModel):
    id: int
    name: str
    retail_price: float  # Apenas preço de varejo

class ProductAdmin(BaseModel):
    id: int
    name: str
    retail_price: float
    wholesale_price: float  # Inclui atacado

app = FastAPI()

@app.get("/products/{id}", response_model=ProductPublic)
async def get_product_public(id: int):
    # Mesmo que o BD tenha wholesale_price, response_model filtra
    return {"id": id, "name": "Widget", "retail_price": 29.99, "wholesale_price": 15.0}

(3) Resultado

A filtragem de resposta mudou de "Lembre-se de deletar campos manualmente" para "Filtragem automática baseada em modelos declarados," eliminando completamente o problema de vazamento de preços de atacado. A API pública agora retorna apenas os campos declarados em ProductPublic, enquanto a API de gerenciamento retorna os dados completos usando ProductAdmin.


3. Noções Básicas do response_model

(1) Princípios da Filtragem Automática

100%
flowchart LR
    A[ORM Object] --> B{response_model}
    B -->|Campos declarados| C[JSON Response]
    B -->|Campos não declarados| D[Filtrados]
    
    subgraph ProductPublic
        E[id]
        F[name]
        G[retail_price]
    end
    
    subgraph Hidden
        H[wholesale_price]
        I[cost_price]
        J[supplier_id]
    end

(1) ▶ Exemplo: Filtragem automática com response_model

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

class ProductResponse(BaseModel):
    id: int
    name: str
    category: str

# Registro de banco de dados simulado com campos extras
DB_PRODUCT = {
    "id": 1,
    "name": "Widget",
    "category": "electronics",
    "cost_price": 8.50,  # NÃO deve estar na resposta
    "supplier_id": 42,   # NÃO deve estar na resposta
}

app = FastAPI()

@app.get("/products/{product_id}", response_model=ProductResponse)
async def get_product(product_id: int):
    # cost_price e supplier_id são auto-filtrados
    return DB_PRODUCT

Saída:

TEXT
{"id": 1, "name": "Widget", "category": "electronics"}

(2) Comparação entre response_model e Tipos de Retorno

Método Comportamento de Filtragem Geração de Documentação Cenários Recomendados
response_model=X Auto-filtragem Sim Sempre recomendado
Tipo de Retorno -> X Sem Filtragem Sim Apenas Anotação de Tipo
Sem declaração Sem filtragem Nenhum Não recomendado

(2) ▶ Exemplo: response_model vs. tipo de retorno

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

class ProductBrief(BaseModel):
    id: int
    name: str

app = FastAPI()

@app.get("/demo/model", response_model=ProductBrief)
async def with_response_model():
    # response_model FILTRA: apenas id e name na resposta
    return {"id": 1, "name": "Widget", "secret": "hidden"}

@app.get("/demo/type") -> ProductBrief
async def with_return_type():
    # Tipo de retorno NÃO filtra: campo secret vaza!
    return {"id": 1, "name": "Widget", "secret": "leaked"}

Saída:

TEXT
# Função definida com sucesso

4. Filtragem Refinada em Nível de Campo

(1) exclude e include

Quando você não quer criar um novo modelo para cada cenário, pode usar response_model_exclude e response_model_include para filtrar campos dinamicamente.

(1) ▶ Exemplo: exclude para excluir campos sensíveis

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel, Field

class ProductFull(BaseModel):
    id: int
    name: str
    retail_price: float
    wholesale_price: float = Field(exclude=True)  # Excluir por padrão
    cost_price: float = Field(exclude=True)
    supplier: str = Field(exclude=True)

app = FastAPI()

@app.get("/products/{id}", response_model=ProductFull)
async def get_product(id: int):
    return {
        "id": id,
        "name": "Widget",
        "retail_price": 29.99,
        "wholesale_price": 15.0,
        "cost_price": 8.50,
        "supplier": "Acme Corp",
    }

Saída:

TEXT
INFO:     127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK

Saída (wholesale_price, cost_price, e supplier foram filtrados):

TEXT
{"id": 1, "name": "Widget", "retail_price": 29.99}

(2) ▶ Exemplo: response_model_exclude Exclusão Dinâmica

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

class ProductFull(BaseModel):
    id: int
    name: str
    retail_price: float
    wholesale_price: float
    cost_price: float

app = FastAPI()

@app.get(
    "/products/{id}",
    response_model=ProductFull,
    response_model_exclude={"wholesale_price", "cost_price"},
)
async def get_product_public(id: int):
    return {
        "id": id, "name": "Widget",
        "retail_price": 29.99, "wholesale_price": 15.0, "cost_price": 8.50,
    }

@app.get(
    "/admin/products/{id}",
    response_model=ProductFull,
)
async def get_product_admin(id: int):
    return {
        "id": id, "name": "Widget",
        "retail_price": 29.99, "wholesale_price": 15.0, "cost_price": 8.50,
    }

Saída:

TEXT
# Função definida com sucesso
Método de Filtragem Cenários Aplicáveis Granularidade
response_model=ModelA Modelos Diferentes de Perspectivas Diferentes Nível de Modelo
response_model_exclude={fields} Excluir poucos campos Nível de campo
response_model_include={fields} Contém apenas poucos campos Nível de campo
Field(exclude=True) Excluir um campo específico Nível de definição de campo

5. Modelos de Múltipla Resposta e Técnicas Avançadas

(1) Respostas Diferentes para o Mesmo Endpoint

(1) ▶ Exemplo: Modelo de Resposta Union

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Union

class ProductFound(BaseModel):
    id: int
    name: str
    price: float

class ProductNotFound(BaseModel):
    error: str
    product_id: int

app = FastAPI()

@app.get("/products/{id}", response_model=Union[ProductFound, ProductNotFound])
async def search_product(id: int):
    if id == 1:
        return ProductFound(id=1, name="Widget", price=9.99)
    return ProductNotFound(error="Not found", product_id=id)

Saída:

TEXT
# Função definida com sucesso

(2) ▶ Exemplo: Modelo de Resposta de Lista

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

class PriceEntry(BaseModel):
    product_id: int
    price: float
    recorded_at: str

app = FastAPI()

@app.get("/products/{id}/prices", response_model=list[PriceEntry])
async def get_price_history(id: int):
    return [
        {"product_id": id, "price": 29.99, "recorded_at": "2026-01-01"},
        {"product_id": id, "price": 24.99, "recorded_at": "2026-02-01"},
        {"product_id": id, "price": 34.99, "recorded_at": "2026-03-01"},
    ]

Saída:

TEXT
# Função definida com sucesso

(2) exclude_unset e exclude_none

(3) ▶ Exemplo: exclude_unset retorna apenas campos com valores

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

class ProductUpdate(BaseModel):
    name: Optional[str] = None
    category: Optional[str] = None
    price: Optional[float] = None

app = FastAPI()

@app.get(
    "/products/{id}",
    response_model=ProductUpdate,
    response_model_exclude_unset=True,
)
async def get_product_partial(id: int):
    # Apenas name foi definido, category e price permanecem None
    return ProductUpdate(name="Updated Widget")

Saída:

TEXT
INFO:     127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK

Saída (inclui apenas campos com valores):

TEXT
{"name": "Updated Widget"}
Opção Efeito Casos de Uso
response_model_exclude_unset=True Excluir campos que não foram definidos Resposta PATCH: retorna apenas os campos atualizados
response_model_exclude_none=True Excluir campos com valor None Simplifica a resposta removendo valores nulos
response_model_exclude_defaults=True Excluir campos que usam valores padrão Remover valores padrão redundantes
response_model_by_alias=True Exibir campos usando aliases O front end requer nomenclatura camelCase

(4) ▶ Exemplo: response_model_by_alias

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel, Field, ConfigDict

class ProductResponse(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    id: int
    product_name: str = Field(alias="productName")
    unit_price: float = Field(alias="unitPrice", description="Price in USD")

app = FastAPI()

@app.get("/products/{id}", response_model=ProductResponse, response_model_by_alias=True)
async def get_product(id: int):
    return {"id": id, "productName": "Widget", "unitPrice": 9.99}

Saída:

TEXT
INFO:     127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK

Saída (usando aliases para nomes de campos):

TEXT
{"id": 1, "productName": "Widget", "unitPrice": 9.99}

❓ Perguntas Frequentes

P Qual é a diferença entre response_model e a anotação de tipo de retorno?
R response_model filtra campos não declarados e valida a saída, enquanto a anotação de tipo de retorno afeta apenas a geração de documentação OpenAPI e não filtra campos. Sempre use response_model.
P "exclude" e "include" podem ser usados ao mesmo tempo?
R Não, não podem ser usados ao mesmo tempo. Escolha um ou outro: "exclude" exclui os campos especificados, enquanto "include" inclui apenas os campos especificados.
P Em quais cenários exclude_unset é mais útil?
R Em respostas a requisições PATCH—quando o cliente atualiza apenas alguns campos, a resposta retorna apenas os campos atualizados para evitar confusão.
P Campos em um modelo aninhado podem ser excluídos?
R Sim, você pode excluir campos aninhados usando um caminho separado por pontos {"nested_model": {"secret_field"}}.
P Como declarar uma resposta de lista?
R Use response_model=list[ItemModel], e o FastAPI filtrará automaticamente cada elemento na lista de acordo com o modelo.
P response_model afeta o desempenho?
R Há uma leve sobrecarga (serialização + filtragem), mas isso garante segurança de dados e consistência da documentação. Em cenários com milhões de QPS, você pode usar orjson para otimizar a serialização.

📖 Resumo


📝 Exercícios

  1. Problema Básico (Dificuldade ⭐): Crie um modelo ProductPublic (id, name, price), e use response_model para garantir que o endpoint retorne apenas esses três campos—mesmo que retorne um dict contendo cost_price, nenhuma informação é vazada. Dica: @app.get(..., response_model=ProductPublic)
  2. Exercício Avançado (Dificuldade ⭐⭐): Crie dois endpoints para o PriceTracker—um endpoint público que oculta wholesale_price, e um endpoint admin que exibe todos os campos. Implemente isso usando response_model_exclude. Dica: response_model_exclude={"wholesale_price"}
  3. Desafio (Dificuldade ⭐⭐⭐): Crie um modelo ProductDetail com campos opcionais (como description, image_url, etc.), e use response_model_exclude_unset=True para garantir que o endpoint retorne apenas os campos fornecidos pelo cliente, com valores vazos omitidos do JSON. Dica: Optional[str] = None + response_model_exclude_unset=True

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%