O Modelo de Limite Pydantic
Todo serviço Python eventualmente tem que responder à mesma pergunta: o que você faz com dados que chegam de fora do controle do seu programa - um corpo de requisição HTTP, um arquivo de configuração, uma mensagem de uma fila, uma linha de um CSV que alguém carregou?
A resposta do Pydantic é transformar esses dados que chegam em um objeto Python fortemente tipado exatamente uma vez, no ponto em que ele cruza para o seu código, e rejeitar ou converter qualquer coisa que não se encaixe na forma que você declarou.
Pydantic Basics mostra a sintaxe para definir essa forma com BaseModel; esta página é sobre o modelo subjacente - por que a validação acontece onde acontece, como o pydantic-core a torna rápida o suficiente para ser usada em todos os lugares, e por que despejar dados de volta é uma operação genuinamente diferente de descrever sua forma.
Resumo
- Pydantic analisa entrada não confiável e fracamente tipada em um objeto Python fortemente tipado em um único cruzamento de limite, e então trata esse objeto como confiável durante o resto de sua vida útil.
- Por que Importa: Sem uma camada de limite, as verificações de correção de tipo ficam espalhadas por todas as funções que tocam dados externos, e "isso é um
Userválido?" se torna uma pergunta que cada chamador tem que responder novamente em vez de uma garantia que o sistema de tipos já lhe dá. - Conceitos Chave: validação de limite, pydantic-core, esquema principal (core schema), conversão (coercion), modo estrito (strict mode), serialização.
- Quando Usar: Análise de corpos de requisição, arquivos de configuração e variáveis de ambiente; validação de linhas vindas de um CSV ou mensagem de fila; construção de objetos de domínio tipados nos quais o código downstream pode confiar sem revalidar.
- Limitações / Compromissos: Pydantic valida forma e tipo, não regras de negócios que dependem de outras linhas ou estado externo; a validação ocorre na construção, portanto, mutar um modelo após a criação contorna essas verificações, a menos que você opte explicitamente por participar novamente.
- Tópicos Relacionados: dataclasses e attrs, geração de JSON Schema, validadores em nível de campo, gerenciamento de configurações.
Fundamentos
A frase "analisar, não validar" descreve a mudança mental que o Pydantic pede que você faça.
Uma função de validação que retorna True ou False informa que os dados eram aceitáveis em um momento, mas devolve o mesmo dict ou str não tipado com o qual você começou, então cada função posterior ainda tem que confiar que a verificação ocorreu corretamente a montante.
Um analisador, em vez disso, converte esses dados em um novo tipo mais específico - um objeto User em vez de um dict - e uma vez que esse objeto existe, sua existência é a prova de que a validação já foi bem-sucedida.
BaseModel é o analisador do Pydantic: você declara a forma de destino com dicas de tipo Python comuns, e instanciar a classe com dados brutos produz uma instância totalmente tipada ou levanta um ValidationError que informa exatamente o que não se encaixou.
Uma analogia útil é um posto de controle de fronteira que também troca moeda.
Os dados não são apenas inspecionados e liberados em sua forma original; eles são convertidos na moeda local (seu modelo tipado) no ponto de cruzamento, para que nada a jusante tenha que continuar perguntando qual moeda está segurando.
O Pydantic 2 reconstruiu este posto de controle com pydantic-core, um motor de validação escrito em Rust, que é por que o mesmo estilo declarativo que já era conveniente no Pydantic 1 se tornou rápido o suficiente para ser usado em todas as requisições em uma API de alto tráfego, em vez de reservá-lo para envios de formulários e carregamento de configurações.
Mecânicas e Interações
A história de desempenho começa na definição da classe, não na instanciação.
Quando uma subclasse de BaseModel é definida, o Pydantic percorre suas dicas de tipo e configuração de campo e as compila uma vez em um esquema principal (core schema) - uma estrutura de dados que o pydantic-core entende - em vez de reinterpretar suas dicas de tipo do zero toda vez que uma instância é criada.
Cada chamada subsequente de User(**data) reutiliza esse esquema já compilado e faz a verificação real campo a campo dentro da extensão Rust, que é por que validar mil registros com Pydantic 2 custa aproximadamente uma compilação de esquema mais mil validações nativas baratas, não mil inspeções de tipo dinâmicas caras.
class User(BaseModel):
id: int
email: str
# O esquema para User é compilado uma vez, aqui, na definição da classe:
User.__pydantic_core_schema__ # a forma compilada contra a qual o pydantic-core valida
# Cada instanciação abaixo reutiliza esse esquema - ele não é recompilado por chamada:
User(id="1", email="a@example.com") # "1" é convertido para int no modo lax (padrão)Esse trecho também mostra conversão (coercion), que é o comportamento padrão, "lax": uma string "1" para um campo int é aceita e convertida, porque na prática muitos dados legítimos (campos de formulário, strings de consulta, números JSON que chegaram como texto) são tecnicamente do tipo errado, mas inequivocamente conversíveis.
O modo estrito (strict mode) desliga isso, recusando qualquer coisa que já não seja o tipo declarado, o que importa para limites onde a conversão silenciosa ocultaria um erro real - uma chamada interna de serviço para serviço onde um id str em vez de um id int geralmente sinaliza um erro do chamador, não uma peculiaridade de formatação.
Validadores (field_validator, model_validator) adicionam lógica personalizada sobre o esquema compilado em vez de substituí-lo: um field_validator é executado após o pydantic-core já ter convertido e verificado o tipo desse campo, então ele opera em dados que ele já pode confiar que são do tipo Python correto, não na entrada bruta.
Considerações Avançadas e Aplicações
model_dump(), model_dump_json() e model_json_schema() parecem três sabores da mesma funcionalidade, mas respondem a três perguntas não relacionadas, e confundi-las é um dos erros arquitetônicos mais comuns em bases de código com uso intensivo de Pydantic.
model_dump() e model_dump_json() são serialização: eles pegam os dados de uma instância real e produzem um dict Python ou uma string JSON a partir dela, e o Pydantic executa isso através de seu próprio esquema de serialização compilado, que é por que a lógica personalizada de field_serializer e opções como exclude_none ou by_alias afetam apenas essas chamadas.
model_json_schema() nunca olha para uma instância; ele inspeciona a definição da classe e produz um documento JSON Schema descrevendo como seria uma instância válida, que é a forma que o FastAPI publica como OpenAPI e a forma que uma ferramenta de geração de código cliente consome - é uma descrição estática, não uma transformação de dados.
A consequência prática é que alterar um field_serializer muda o que model_dump_json() produz, mas nunca muda model_json_schema(), e vice-versa - uma Field(description=...) em nível de esquema aparece no JSON Schema, mas não tem efeito em um dump.
Onde a validação deve residir é uma decisão arquitetônica relacionada e mais consequente: empurrar todas as verificações para o modelo no limite HTTP não é o mesmo que empurrar todas as verificações apenas para lá.
| Localização da validação | Força | Fraqueza | Melhor Encaixe |
|---|---|---|---|
| Limite do framework (modelos de requisição FastAPI) | Rejeita entrada malformada antes que qualquer código de handler seja executado; documentação OpenAPI gratuita | Vê apenas uma requisição isoladamente; não pode verificar regras que abrangem múltiplos recursos | Forma, tipo e regras de negócios de campo único em cada requisição de entrada |
Validadores em nível de modelo (model_validator) | Regras entre campos permanecem próximas aos campos que elas restringem | Ainda limitado ao que está dentro dessa única instância do modelo | Regras como "data de término deve ser após a data de início" |
| Verificações na camada de serviço | Pode consultar outros dados (unicidade, propriedade, cotas) | Fácil de pular se um método de serviço for chamado de múltiplos pontos de entrada | Regras que precisam de consulta ao banco de dados ou estado de outro serviço |
| Restrições de banco de dados | Impostas mesmo que o código da aplicação tenha um bug | Erros surgem como exceções genéricas de DB, não como ValidationErrors amigáveis | Último recurso para garantias de unicidade, chaves estrangeiras e não nulas |
Nenhuma linha substitui as outras - uma restrição de e-mail único no banco de dados é o último recurso para uma verificação que já deveria ter ocorrido, mais rápido e com uma mensagem de erro melhor, na camada do modelo ou de serviço.
O papel do Pydantic se estreita à medida que você desce nessa tabela: ele é responsável pela forma e tipo na borda, e não é a camada responsável por "este e-mail já existe" ou "este usuário tem permissão para atualizar este registro".
Concepções Errôneas Comuns
- "A validação Pydantic significa que meus dados estão corretos de acordo com as regras de negócios." Pydantic confirma que um campo é um
intdentro de quaisquer restrições declaradas; ele não tem ideia se esse inteiro é um saldo de conta válido para este usuário específico, o que é uma preocupação da camada de serviço ou de banco de dados. - "
model_dump()e o antigo.dict()fazem a mesma coisa.".dict()é um shim de compatibilidade do Pydantic 1;model_dump()é o caminho nativo v2 que respeita serializadores personalizados e a distinçãomode="json", e misturar os dois em uma base de código produz resultados sutilmente inconsistentes. - "
model_json_schema()reflete o comportamento de serialização em tempo de execução." Ele reflete apenas a forma declarada da classe; umfield_serializerque altera como um dump se parece não tem efeito no JSON Schema gerado, porque a geração de esquema nunca executa uma instância através da serialização. - "Uma vez que um modelo é construído, seus campos permanecem válidos." A validação ocorre no momento do
__init__; atribuir um novo valor a um atributo de uma instância existente contorna completamente essa verificação, a menos quemodel_config = ConfigDict(validate_assignment=True)seja definido. - "O modo estrito é o que você deveria usar em todos os lugares para segurança." O modo estrito rejeita dados legítimos e comumente recebidos, como strings numéricas de parâmetros de consulta; a conversão lax (o padrão) é o que torna o Pydantic utilizável nas bordas HTTP em primeiro lugar, e o modo estrito é para limites internos mais estreitos e já tipados.
FAQs
O que "analisar, não validar" realmente significa no contexto do Pydantic?
Significa que o Pydantic não apenas retorna True/False sobre se os dados são aceitáveis - ele converte esses dados em um novo objeto tipado.
Uma vez que uma instância User existe, sua existência é a prova de que a validação já passou, então os chamadores não precisam revalidá-la.
Por que o Pydantic 2 é tão mais rápido que o Pydantic 1?
O Pydantic 2 moveu o trabalho real de validação para o pydantic-core, uma extensão Rust, em vez de fazê-lo em Python puro. A API voltada para Python (BaseModel, dicas de tipo) permaneceu familiar, mas o esquema é compilado uma vez e executado nativamente em cada instanciação.
O que é um "esquema principal (core schema)" e quando ele é construído?
É a representação interna compilada da forma de um modelo contra a qual o pydantic-core realmente valida. Ele é construído uma vez, quando a subclasse BaseModel é definida - não é rederivado de suas dicas de tipo a cada chamada.
Qual é a diferença entre modo lax e modo estrito?
- Modo lax (o padrão) converte tipos compatíveis, como uma string numérica em um
int. - Modo estrito rejeita qualquer coisa que já não seja o tipo exato declarado.
- O modo lax se encaixa em limites fracamente tipados como strings de consulta HTTP; o modo estrito se encaixa em limites internos já tipados.
Como `model_dump()` é diferente de `model_json_schema()`?
model_dump() pega os dados de uma instância real e os serializa para um dict ou JSON. model_json_schema() nunca toca em uma instância - ele inspeciona a definição da classe e produz um JSON Schema descrevendo a forma válida. Alterar um serializador afeta o primeiro, nunca o segundo.
Os validadores de campo são executados antes ou depois da conversão de tipo?
Depois. Um field_validator recebe dados que o pydantic-core já converteu e confirmou que correspondem ao tipo declarado, então o código do validador personalizado pode assumir que está trabalhando com o tipo Python correto.
O Pydantic continua validando um modelo após sua criação?
Não, por padrão. A validação ocorre no momento da construção; mutar um atributo posteriormente pula essa verificação completamente, a menos que o modelo defina model_config = ConfigDict(validate_assignment=True).
Onde devem viver as regras de negócios que precisam de consulta ao banco de dados?
Não dentro do modelo Pydantic - ele não tem acesso a uma sessão de banco de dados. Essas pertencem a uma camada de serviço que pode consultar coisas como unicidade ou propriedade, com o próprio banco de dados como um último recurso de restrição.
O Pydantic é útil apenas para corpos de requisição FastAPI?
Não - é usado com a mesma frequência para carregamento de configurações/ajustes, análise de mensagens de fila, validação de linhas de um arquivo e definição de objetos de domínio tipados passados entre serviços internos, independentemente de qualquer framework web.
Por que o Pydantic rejeita campos desconhecidos por padrão em algumas configurações?
Rejeitar chaves inesperadas (extra="forbid") pega erros de digitação e formas de payload inesperadas precocemente, na borda, em vez de ignorar silenciosamente dados que o chamador pensava que estavam sendo usados.
Dois pedaços de código diferentes podem obter resultados inconsistentes do mesmo modelo?
Sim, se um usar o método de compatibilidade legado .dict() e outro usar model_dump() - eles podem divergir em como serializadores personalizados e conversões específicas de JSON são aplicados. Padronizar em model_dump()/model_dump_json() evita essa deriva.
Qual é a limitação honesta do modelo de validação de limite?
Ele só sabe o que está dentro do payload que está sendo validado - ele não pode verificar coisas como unicidade contra outras linhas ou o estado de um segundo recurso, então ele tem que passar para uma camada de serviço ou restrição de banco de dados para qualquer coisa que não seja autocontida.
Relacionados
- Pydantic Basics - a sintaxe prática para definir modelos que esta página explica conceitualmente
- Performance & pydantic-core - como o esquema compilado e o núcleo Rust se comportam sob carga
- Serialization -
model_dump, serializadores personalizados e a distinção do modo JSON em detalhes - Validators - mecânicas de
field_validatoremodel_validator - Dataclasses vs Pydantic vs attrs - quando a validação de limite vale a pena o overhead em comparação com um dataclass simples
Versões da Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13) e Pydantic 2.