pydantic-python
Use for writing, reviewing, debugging, migrating, or testing Python code built on Pydantic v2. Trigger for BaseModel, RootModel, Field or Annotated constraints, validators, serializers, TypeAdapter, d
Files10
View on GitHubSKILL.mdskills/pydantic-python/SKILL.md
GitHubPydantic validation and serialization
Produce version-grounded boundaries whose input source, conversion policy, validated output type, invariants, and serialized shape are explicit and tested.
Boundary
Use this skill when the project uses Pydantic or the user
explicitly requests them. Do not introduce runtime validation for a trusted
internal record that only needs a dataclass or TypedDict. Route BaseSettings,
environment, dotenv, secrets-directory, or settings-source work to the
pydantic-settings-python skill. Preserve public validation errors and
serialized schemas unless the task explicitly changes them.
Know the two compiled paths
Pydantic builds a core schema from annotations, field metadata, configuration, and decorators. Validation and serialization use that schema but are different contracts:
Python / JSON / string input -> validator -> typed value or ValidationError
typed value -> serializer (python or json mode) -> dict / JSON-compatible data / JSON
| Object | Runtime meaning | Use it for |
|---|---|---|
BaseModel class |
A named object schema plus class validation/JSON Schema APIs. | Domain or boundary objects with named fields. |
| Model instance | Validated typed state; not the original input mapping. | Internal typed work and explicit serialization. |
Field / Annotated metadata |
Constraints, defaults, aliases, exclusion, and schema details attached to a type. | Rules expressible without custom code; reusable constrained types. |
| Field/model validator | User code inserted before, after, plain, or around core validation. | Only invariants or normalization the schema cannot express clearly. |
| Field/model serializer | User code inserted into output conversion. | Output rules that differ from validation; never use validators as serializers. |
TypeAdapter[T] |
A compiled validator/serializer for any supported type T without an artificial model. |
Collections, unions, TypedDict, dataclasses, and standalone types. Reuse it. |
RootModel[T] |
A named model whose payload is one root value. | A true root-value public type that needs model behavior. |
Read the core object model when choosing an abstraction or reasoning about validation versus serialization.
Ordered workflow
- Recover the boundary: input source and shape, trust, desired output type, accepted coercions, extra-data policy, aliases, and serialized contract.
- Confirm the installed Pydantic version from project locks and the active environment.
- Choose
BaseModel,TypeAdapter, orRootModelfrom the required runtime object—not from habit. - Express structure and constraints in types/fields. Add the narrowest deterministic validator only for remaining rules.
- Choose the entrypoint matching Python, JSON, or string-mapping input.
- Design serialization separately: mode, aliases, exclusions, subclass policy, and sensitive fields.
- Test valid, invalid, conversion, extra-key, alias, invariant, and serialized cases.
Choose by intent
| Intent | Use |
|---|---|
| Validate a Python mapping/object as a model | Model.model_validate(...) |
| Validate JSON text/bytes as a model | Model.model_validate_json(...) |
| Validate a nested string-key/string-value mapping in JSON mode | Model.model_validate_strings(...) only when that source contract fits |
Validate list[Item], a union, TypedDict, or another standalone type |
Create and reuse TypeAdapter(type) |
| Represent a named single root payload | RootModel[T] |
| Constrain one field | Annotated[T, Field(...)] or Field(...) |
| Reuse a custom constraint/normalizer | A named Annotated type with functional metadata |
| Select one tagged variant predictably | A discriminated union with Literal tags and Field(discriminator=...) |
| Normalize raw input before typing | a mode="before" validator that accepts arbitrary input safely |
| Enforce a typed field rule | an after field validator |
| Enforce a cross-field invariant | an after model validator |
| Emit Python-native objects | model_dump(mode="python") (the default) |
| Emit JSON-compatible Python values | model_dump(mode="json") |
| Emit JSON text | model_dump_json() |
| Use external names on input only | validation_alias |
| Use external names on output only | serialization_alias plus by_alias=True |
Read the intent-to-API map for aliases, unions, attribute loading, JSON Schema, dataclasses, call validation, and dynamic models.
Canonical strict boundary
from decimal import Decimal
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
PositiveMoney = Annotated[Decimal, Field(gt=0, strict=True)]
class CardPayment(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
kind: Literal["card"]
amount: PositiveMoney
currency: str = Field(pattern=r"^[A-Z]{3}$")
payments = TypeAdapter(list[CardPayment])
validated = payments.validate_python(payload)
wire_value = payments.dump_python(validated, mode="json")
Pydantic may coerce in lax mode, sometimes with information loss. Strictness can be chosen per call, field, or configuration, and JSON strict behavior can differ from Python strict behavior. Test the actual input modes; do not infer one from the other. Read validation boundaries.
Validator rules
- Prefer types,
Fieldconstraints, tagged unions, and configuration over validators. They compose and generate schemas more predictably. - A before validator receives arbitrary raw input; do not assume its type or mutate a value that may later flow to another union branch.
- An after validator receives the typed value. Return the value/model on every success path.
- Use a model validator only for genuinely cross-field invariants. Do not rely on field order for cross-field logic.
- Raise documented validation failures such as
ValueErrorfor bad user data. Keep I/O, database access, clocks, randomness, and remote lookups outside. - Catch
ValidationErrorat the boundary that can translate it. Assert stableerrors()fields needed by callers, not whole human-formatted messages.
Serialization is a separate contract
- Choose Python mode, JSON mode, or JSON text deliberately.
dict(model)leaves nested model objects intact; it is not a substitute formodel_dump(). - Name input and output aliases independently.
aliasaffects both directions;validation_aliasandserialization_aliasexpress asymmetric contracts. - Use
exclude_unset,exclude_defaults, orexclude_noneonly when that omission policy is part of the wire contract and is tested. - By default, a field annotated as a base model serializes fields declared on that annotation, limiting accidental subclass-secret exposure. Treat any serialize-as-runtime-type option as security-sensitive and version-ground it.
- Add serializers only when the output rule cannot be expressed by normal modes/configuration. Test their return shape; serialization does not revalidate arbitrary post-construction mutation.
Read serialization and aliases.
Version grounding and completion
If code uses v1-shaped parse_obj, .dict(), .json(), @validator,
@root_validator, class Config, or orm_mode, inspect the declared version
and read v1-to-v2 grounding. Do not mechanically rename:
optional-field requirements, aliases, equality, attribute loading, validators,
and serialization can change.
Run python scripts/inspect_pydantic.py from the installed skill directory for
installed versions, API availability, and signatures. Read API grounding.
Do not declare completion until input mode, output type, coercion, extras, aliases, invariants, and serialization are tested; sensitive fields cannot leak; validation bypasses are absent from untrusted paths; version-supported APIs are used; and project checks pass or skipped evidence and consequences are reported. Use the testing matrix.