Pydantic Models
Overview
Use this skill to create or refactor Pydantic models for API integration work where request and response contracts must stay explicit, stable, and safe.
The upstream intent is preserved: use a multi-model pattern instead of one overloaded model for every purpose. The enhanced workflow modernizes that pattern for Pydantic v2 and focuses on operational API contract design:
- separate create, update, public response, and internal shapes when their contracts differ
- use aliases intentionally when wire format differs from Python naming
- treat PATCH semantics as distinct from create semantics
- adapt ORM or document objects with
from_attributes=True instead of legacy orm_mode
- prevent accidental field leakage by serializing public models, not internal ones
This skill is framework-agnostic Python guidance. FastAPI-style patterns are referenced because they are common and well documented, but the workflow applies to general API integration work.
Version guard: this workflow assumes Pydantic v2 semantics such as model_validate, model_dump, ConfigDict, field_validator, and from_attributes.
When to Use This Skill
Use this skill when:
- you need Pydantic models for an external or internal API contract
- one model is starting to mix input validation, storage fields, and response serialization concerns
- the task requires separate handling for create vs update vs response payloads
- the API uses a different field naming style on the wire, such as camelCase externally and snake_case in Python
- partial updates must preserve the difference between omitted fields and explicit
null
- ORM, document, or service-layer objects must be adapted into response models safely
Do not use this skill as the primary router when:
- the user only needs a single ad hoc data container with no contract boundary concerns
- the task is mainly about database schema design rather than API payload design
- the task depends on framework-specific response plumbing more than on Pydantic model design
Operating Table
| Situation |
Recommended model family |
Key choices |
Primary methods |
Main risk |
| Create or POST request |
Create / input model |
required writable fields, extra='forbid', clear constraints |
model_validate(...) |
accepting undeclared fields or weak validation |
| Partial update or PATCH request |
Update model |
all fields optional as transport inputs, use exclude_unset=True |
model_dump(exclude_unset=True) |
clearing stored values by mistake |
| Public API response |
Public / Read model |
expose only contract fields, serialize with aliases if wire format needs them |
model_validate(...), model_dump(by_alias=True) |
leaking internal fields or wrong field names |
| Internal service response |
Internal model |
include operational metadata only if not public |
model_validate(...), model_dump(...) |
reusing internal models as public DTOs |
| ORM or document adaptation |
Public or Internal model with attribute adaptation |
from_attributes=True when validating from objects |
model_validate(obj) |
legacy orm_mode assumptions or field shape mismatch |
| Persistence shape |
separate InDB / persistence model only if contract differs materially |
add store-only fields only when needed |
validate near persistence boundary |
unnecessary model sprawl |
For selection guidance and config tradeoffs, see references/integration-patterns.md.
Workflow
Identify contract boundaries before writing code.
Decide whether the task truly needs separate models for create, update, public response, internal response, or persistence. Do not create extra model families unless the payloads differ in a way that matters.
Start from the external contract, not the database shape.
For API work, define what clients may send and receive first. Persistence-only fields such as internal IDs, doc_type, audit flags, revision tokens, or secret material should not appear in public response models unless explicitly required.
Choose a model taxonomy.
A practical default is:
ThingBase: shared constraints only when reuse is genuinely helpful
ThingCreate: fields accepted on creation
ThingUpdate: partial-update transport model
ThingPublic: fields returned to API clients
ThingInternal or ThingInDB: only when internal or persistence needs differ materially
If Base inheritance makes public/private boundaries fuzzy, prefer separate models instead of inheritance.
Set explicit config for request safety.
For externally sourced request models, start conservative:
from pydantic import BaseModel, ConfigDict
class WidgetCreate(BaseModel):
model_config = ConfigDict(extra='forbid')
Consider stricter settings intentionally. Reject undeclared fields unless you have a compatibility reason not to. Use default_factory for dynamic defaults instead of mutable literals.
Define field naming policy and aliases deliberately.
If Python code uses snake_case but the API contract uses camelCase, encode that policy explicitly. Be consistent about whether validation accepts Python names, wire aliases, or both.
Preferred rule of thumb:
- keep Python attributes idiomatic in code
- expose the wire contract through aliases
- serialize public payloads with
by_alias=True when aliases define the contract
- avoid mixing ad hoc field-level aliasing and broad alias generators without a reason
Separate create and update semantics.
Create models usually express required writable fields. Update models usually make all updatable fields optional so the transport layer can represent omission.
Important distinction:
- omitted field: leave current stored value unchanged
- field present with
null / None: clear it, if the contract allows
- field present with value: replace or update
Use model_dump(exclude_unset=True) when applying PATCH-like updates.
Validate and adapt at the correct boundary.
Use model_validate(...) when building models from request payloads, trusted dictionaries, or class instances. When validating from ORM or document objects, configure the target model with from_attributes=True.
Serialize public responses from public models.
Prefer:
- validate an internal object into
ThingPublic
- serialize
ThingPublic with model_dump(by_alias=True) if aliases define the contract
Avoid dumping a richer internal model and trying to hide fields with ad hoc exclude lists.
Place business rules carefully.
Use field_validator or model_validator for transport and shape validation that belongs to the data contract. Keep workflow-specific side effects, repository checks, and cross-service orchestration in endpoint or service logic.
Check generated payloads explicitly.
Before finalizing, verify:
- required vs optional fields match the real contract
- public output excludes internal metadata
- aliases serialize exactly as clients expect
- PATCH behavior preserves omitted fields
Troubleshooting
Incoming camelCase fields do not populate snake_case model fields
Symptoms
- requests fail validation even though field names look correct to the client
- response output uses the wrong field style
Likely causes
- aliases were defined but serialization is not using
by_alias=True
- validation settings do not match the accepted request field style
- alias strategy is mixed inconsistently across fields
Corrective actions
- define one alias policy for the model family
- ensure the public serialization path uses
model_dump(by_alias=True) when aliases define the API contract
- avoid silently accepting multiple naming styles unless compatibility requires it
PATCH or update requests clear values unexpectedly
Symptoms
- omitted fields overwrite stored data with
None
- updates behave like full replacement instead of partial modification
Likely causes
- create and update models are being reused as if they were identical
- update application logic uses the full dumped model instead of
exclude_unset=True
- explicit
None is not being distinguished from field omission
Corrective actions
- create a dedicated
Update model
- apply updates using
model_dump(exclude_unset=True)
- decide explicitly whether
None means clear-the-value or invalid input for each field
ORM or document objects fail validation
Symptoms
- validation errors occur when passing model instances or row objects into response models
- nested attributes do not serialize as expected
Likely causes
- target model is missing
from_attributes=True
- attribute names on the object do not match the response model fields or aliases
- the object contains richer nested state than the response contract allows
Corrective actions
- enable attribute-based validation on the target response model
- validate into a public DTO rather than serializing the raw ORM object
- inspect field names and nested object shapes before assuming the failure is a Pydantic bug
Extra fields are accepted or rejected unexpectedly
Symptoms
- clients can send undeclared fields without error
- valid-looking requests fail because of unrecognized keys
Likely causes
extra handling was left implicit
- request compatibility expectations are unclear
Corrective actions
- set
extra='forbid' for external request models unless a looser contract is intentional
- document any compatibility exceptions clearly
- do not use permissive extra handling as a shortcut for poor contract definition
Public responses leak internal metadata
Symptoms
- internal IDs, persistence fields, or operational flags appear in API output
Likely causes
- internal and public models were merged for convenience
- responses are serialized from internal models with ad hoc exclusions
Corrective actions
- create a dedicated public response model
- validate internal data into the public model before serialization
- verify the exact wire payload with
model_dump(by_alias=True) when aliases are used
Examples
See examples/request-response-example.md for a complete worked example that includes:
- separate create, update, public, internal, and persistence-adaptation models
- a camelCase wire contract with snake_case Python attributes
extra='forbid' on request models
from_attributes=True for adapting an object into a public DTO
- a PATCH scenario showing omitted vs explicit
null
Additional Resources
references/integration-patterns.md — model-family selection matrix, alias policy options, config defaults, and contract safety guidance
- Pydantic v2 documentation for models, config, aliases, serialization, validators, and attribute-based validation
- FastAPI documentation on extra models and body updates for common API usage patterns
- JSON Schema and OpenAPI references for contract-oriented field semantics
Related Skills
Use related skills when the task drifts into:
- framework-specific endpoint wiring rather than model design
- database migrations or persistence schema design
- TypeScript client generation or OpenAPI publication workflow
When in doubt, keep this skill focused on Pydantic model boundaries and API contract correctness rather than general backend implementation.
1---2name: pydantic-models-py-23description: Pydantic Models workflow skill. Use this skill when the user needs Create Pydantic models following the multi-model pattern for clean API contracts and the operator should design explicit request, update, response, and internal model boundaries before merging or handing off.4---56# Pydantic Models78## Overview910Use this skill to create or refactor Pydantic models for API integration work where request and response contracts must stay explicit, stable, and safe.1112The upstream intent is preserved: use a multi-model pattern instead of one overloaded model for every purpose. The enhanced workflow modernizes that pattern for **Pydantic v2** and focuses on operational API contract design:1314- separate create, update, public response, and internal shapes when their contracts differ15- use aliases intentionally when wire format differs from Python naming16- treat PATCH semantics as distinct from create semantics17- adapt ORM or document objects with `from_attributes=True` instead of legacy `orm_mode`18- prevent accidental field leakage by serializing public models, not internal ones1920This skill is framework-agnostic Python guidance. FastAPI-style patterns are referenced because they are common and well documented, but the workflow applies to general API integration work.2122> Version guard: this workflow assumes **Pydantic v2** semantics such as `model_validate`, `model_dump`, `ConfigDict`, `field_validator`, and `from_attributes`.2324## When to Use This Skill2526Use this skill when:2728- you need Pydantic models for an external or internal API contract29- one model is starting to mix input validation, storage fields, and response serialization concerns30- the task requires separate handling for create vs update vs response payloads31- the API uses a different field naming style on the wire, such as camelCase externally and snake_case in Python32- partial updates must preserve the difference between omitted fields and explicit `null`33- ORM, document, or service-layer objects must be adapted into response models safely3435Do **not** use this skill as the primary router when:3637- the user only needs a single ad hoc data container with no contract boundary concerns38- the task is mainly about database schema design rather than API payload design39- the task depends on framework-specific response plumbing more than on Pydantic model design4041## Operating Table4243| Situation | Recommended model family | Key choices | Primary methods | Main risk |44| --- | --- | --- | --- | --- |45| Create or POST request | `Create` / input model | required writable fields, `extra='forbid'`, clear constraints | `model_validate(...)` | accepting undeclared fields or weak validation |46| Partial update or PATCH request | `Update` model | all fields optional as transport inputs, use `exclude_unset=True` | `model_dump(exclude_unset=True)` | clearing stored values by mistake |47| Public API response | `Public` / `Read` model | expose only contract fields, serialize with aliases if wire format needs them | `model_validate(...)`, `model_dump(by_alias=True)` | leaking internal fields or wrong field names |48| Internal service response | `Internal` model | include operational metadata only if not public | `model_validate(...)`, `model_dump(...)` | reusing internal models as public DTOs |49| ORM or document adaptation | `Public` or `Internal` model with attribute adaptation | `from_attributes=True` when validating from objects | `model_validate(obj)` | legacy `orm_mode` assumptions or field shape mismatch |50| Persistence shape | separate `InDB` / persistence model only if contract differs materially | add store-only fields only when needed | validate near persistence boundary | unnecessary model sprawl |5152For selection guidance and config tradeoffs, see `references/integration-patterns.md`.5354## Workflow55561. **Identify contract boundaries before writing code.**57 Decide whether the task truly needs separate models for create, update, public response, internal response, or persistence. Do not create extra model families unless the payloads differ in a way that matters.58592. **Start from the external contract, not the database shape.**60 For API work, define what clients may send and receive first. Persistence-only fields such as internal IDs, `doc_type`, audit flags, revision tokens, or secret material should not appear in public response models unless explicitly required.61623. **Choose a model taxonomy.**63 A practical default is:64 - `ThingBase`: shared constraints only when reuse is genuinely helpful65 - `ThingCreate`: fields accepted on creation66 - `ThingUpdate`: partial-update transport model67 - `ThingPublic`: fields returned to API clients68 - `ThingInternal` or `ThingInDB`: only when internal or persistence needs differ materially6970 If `Base` inheritance makes public/private boundaries fuzzy, prefer separate models instead of inheritance.71724. **Set explicit config for request safety.**73 For externally sourced request models, start conservative:7475 ```python76 from pydantic import BaseModel, ConfigDict7778 class WidgetCreate(BaseModel):79 model_config = ConfigDict(extra='forbid')80 ```8182 Consider stricter settings intentionally. Reject undeclared fields unless you have a compatibility reason not to. Use `default_factory` for dynamic defaults instead of mutable literals.83845. **Define field naming policy and aliases deliberately.**85 If Python code uses `snake_case` but the API contract uses `camelCase`, encode that policy explicitly. Be consistent about whether validation accepts Python names, wire aliases, or both.8687 Preferred rule of thumb:88 - keep Python attributes idiomatic in code89 - expose the wire contract through aliases90 - serialize public payloads with `by_alias=True` when aliases define the contract91 - avoid mixing ad hoc field-level aliasing and broad alias generators without a reason92936. **Separate create and update semantics.**94 `Create` models usually express required writable fields. `Update` models usually make all updatable fields optional so the transport layer can represent omission.9596 Important distinction:97 - **omitted field**: leave current stored value unchanged98 - **field present with `null` / `None`**: clear it, if the contract allows99 - **field present with value**: replace or update100101 Use `model_dump(exclude_unset=True)` when applying PATCH-like updates.1021037. **Validate and adapt at the correct boundary.**104 Use `model_validate(...)` when building models from request payloads, trusted dictionaries, or class instances. When validating from ORM or document objects, configure the target model with `from_attributes=True`.1051068. **Serialize public responses from public models.**107 Prefer:108 - validate an internal object into `ThingPublic`109 - serialize `ThingPublic` with `model_dump(by_alias=True)` if aliases define the contract110111 Avoid dumping a richer internal model and trying to hide fields with ad hoc exclude lists.1121139. **Place business rules carefully.**114 Use `field_validator` or `model_validator` for transport and shape validation that belongs to the data contract. Keep workflow-specific side effects, repository checks, and cross-service orchestration in endpoint or service logic.11511610. **Check generated payloads explicitly.**117 Before finalizing, verify:118 - required vs optional fields match the real contract119 - public output excludes internal metadata120 - aliases serialize exactly as clients expect121 - PATCH behavior preserves omitted fields122123## Troubleshooting124125### Incoming camelCase fields do not populate snake_case model fields126127**Symptoms**128- requests fail validation even though field names look correct to the client129- response output uses the wrong field style130131**Likely causes**132- aliases were defined but serialization is not using `by_alias=True`133- validation settings do not match the accepted request field style134- alias strategy is mixed inconsistently across fields135136**Corrective actions**137- define one alias policy for the model family138- ensure the public serialization path uses `model_dump(by_alias=True)` when aliases define the API contract139- avoid silently accepting multiple naming styles unless compatibility requires it140141### PATCH or update requests clear values unexpectedly142143**Symptoms**144- omitted fields overwrite stored data with `None`145- updates behave like full replacement instead of partial modification146147**Likely causes**148- create and update models are being reused as if they were identical149- update application logic uses the full dumped model instead of `exclude_unset=True`150- explicit `None` is not being distinguished from field omission151152**Corrective actions**153- create a dedicated `Update` model154- apply updates using `model_dump(exclude_unset=True)`155- decide explicitly whether `None` means clear-the-value or invalid input for each field156157### ORM or document objects fail validation158159**Symptoms**160- validation errors occur when passing model instances or row objects into response models161- nested attributes do not serialize as expected162163**Likely causes**164- target model is missing `from_attributes=True`165- attribute names on the object do not match the response model fields or aliases166- the object contains richer nested state than the response contract allows167168**Corrective actions**169- enable attribute-based validation on the target response model170- validate into a public DTO rather than serializing the raw ORM object171- inspect field names and nested object shapes before assuming the failure is a Pydantic bug172173### Extra fields are accepted or rejected unexpectedly174175**Symptoms**176- clients can send undeclared fields without error177- valid-looking requests fail because of unrecognized keys178179**Likely causes**180- `extra` handling was left implicit181- request compatibility expectations are unclear182183**Corrective actions**184- set `extra='forbid'` for external request models unless a looser contract is intentional185- document any compatibility exceptions clearly186- do not use permissive extra handling as a shortcut for poor contract definition187188### Public responses leak internal metadata189190**Symptoms**191- internal IDs, persistence fields, or operational flags appear in API output192193**Likely causes**194- internal and public models were merged for convenience195- responses are serialized from internal models with ad hoc exclusions196197**Corrective actions**198- create a dedicated public response model199- validate internal data into the public model before serialization200- verify the exact wire payload with `model_dump(by_alias=True)` when aliases are used201202## Examples203204See `examples/request-response-example.md` for a complete worked example that includes:205206- separate create, update, public, internal, and persistence-adaptation models207- a camelCase wire contract with snake_case Python attributes208- `extra='forbid'` on request models209- `from_attributes=True` for adapting an object into a public DTO210- a PATCH scenario showing omitted vs explicit `null`211212## Additional Resources213214- `references/integration-patterns.md` — model-family selection matrix, alias policy options, config defaults, and contract safety guidance215- Pydantic v2 documentation for models, config, aliases, serialization, validators, and attribute-based validation216- FastAPI documentation on extra models and body updates for common API usage patterns217- JSON Schema and OpenAPI references for contract-oriented field semantics218219## Related Skills220221Use related skills when the task drifts into:222223- framework-specific endpoint wiring rather than model design224- database migrations or persistence schema design225- TypeScript client generation or OpenAPI publication workflow226227When in doubt, keep this skill focused on **Pydantic model boundaries and API contract correctness** rather than general backend implementation.