# Pydantic Models Py

> 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.

- Skill: `diegosouzapw/pydantic-models-py-2` (Agent Skill, multi-file: 17 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/pydantic-models-py-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/pydantic-models-py-2/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/diegosouzapw/pydantic-models-py-2

---


# 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

1. **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.

2. **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.

3. **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.

4. **Set explicit config for request safety.**
   For externally sourced request models, start conservative:

   ```python
   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.

5. **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

6. **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.

7. **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`.

8. **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.

9. **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.

10. **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.

