name: "pydantic-models-py" description: "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." version: "0.0.1" category: "machine-learning" tags:
- "pydantic-models-py"
- "pydantic"
- "api-contracts"
- "request-models"
- "response-models"
- "patch-updates"
- "omni-enhanced" complexity: "advanced" risk: "safe" tools:
- "codex-cli"
- "claude-code"
- "cursor"
- "gemini-cli"
- "opencode" source: "omni-team" author: "Omni Skills Team" date_added: "2026-03-27" date_updated: "2026-04-19" source_type: "omni-curated" maintainer: "Omni Skills Team" family_id: "pydantic-models-py" family_name: "Pydantic Models" variant_id: "omni" variant_label: "Omni Curated" is_default_variant: true derived_from: "skills/pydantic-models-py" upstream_skill: "skills/pydantic-models-py" upstream_author: "sickn33" upstream_source: "community" upstream_pr: "79" upstream_head_repo: "diegosouzapw/awesome-omni-skills" upstream_head_sha: "6bf093920a93e68fa8263cf6ee767d7407989d56" curation_surface: "skills_omni" enhanced_origin: "omni-skills-private" source_repo: "diegosouzapw/awesome-omni-skills" replaces:
- "pydantic-models-py"
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=Trueinstead of legacyorm_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, andfrom_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 helpfulThingCreate: fields accepted on creationThingUpdate: partial-update transport modelThingPublic: fields returned to API clientsThingInternalorThingInDB: only when internal or persistence needs differ materially
If
Baseinheritance 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_factoryfor dynamic defaults instead of mutable literals. -
Define field naming policy and aliases deliberately. If Python code uses
snake_casebut the API contract usescamelCase, 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=Truewhen aliases define the contract - avoid mixing ad hoc field-level aliasing and broad alias generators without a reason
-
Separate create and update semantics.
Createmodels usually express required writable fields.Updatemodels 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 withfrom_attributes=True. -
Serialize public responses from public models. Prefer:
- validate an internal object into
ThingPublic - serialize
ThingPublicwithmodel_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.
- validate an internal object into
-
Place business rules carefully. Use
field_validatorormodel_validatorfor 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
Noneis not being distinguished from field omission
Corrective actions
- create a dedicated
Updatemodel - apply updates using
model_dump(exclude_unset=True) - decide explicitly whether
Nonemeans 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
extrahandling 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 modelsfrom_attributes=Truefor 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.