pydantic-v2
Pydantic v2
Pydantic v2 (released 2023-06, current stable 2.13 as of 2026-04-19, paired with Python 3.10-3.14 including 3.14 free-threaded builds) is a near-complete rewrite on pydantic-core (Rust). API is similar but not identical to v1 -- several v1 patterns silently break or behave differently. This skill documents the v2 idioms, the v1 migration gotchas, and the FastAPI integration surface.
Notable recent releases:
- 2.11 (2025): 2x schema build-time improvements, 2-5x memory reduction for nested models, PEP 695/696 generic syntax, experimental free-threaded Python 3.13,
validate_by_alias/validate_by_name/serialize_by_aliasconfig (populate_by_namepending v3 deprecation),Pathanddequeno longer accept constraints (2.11 release). - 2.12: Python 3.14 support (PEP 649/749 annotations), experimental
MISSINGsentinel,exclude_ifon fields,ensure_asciion JSON output,serialize_as_anyunified behavior,@model_validator(mode="after")classmethod deprecated -- write as instance method (2.12 release). - 2.13 (April 2026): Polymorphic serialization (
model_dump(polymorphic_serialization=True)),exclude_ifextended to computed fields,ascii_onlyinStringConstraints,model_fields_settracks post-instantiation extras (2.13 release).
When to load which section
- Writing a new model from scratch -> "Core model patterns" + "Validators"
- Migrating from v1 -> "v1 -> v2 migration checklist"
- Working with money / decimals -> "Monetary precision (CWE-681 defense)"
- FastAPI request/response models -> "FastAPI integration"
- Performance-sensitive hot path -> "Performance notes"
- Secret handling / redaction -> "Security and secrets"
- Validation observability or LLM agents -> "PydanticAI and Logfire"
Core model patterns
from datetime import datetime
from decimal import Decimal
from typing import Annotated, Literal
from pydantic import (
BaseModel,
ConfigDict,
Field,
StringConstraints,
computed_field,
field_validator,
model_validator,
)
# Type aliases with constraints -- preferred in v2 over `constr()` / `conint()`
NonEmptyStr = Annotated[str, StringConstraints(min_length=1, strip_whitespace=True)]
USDAmount = Annotated[Decimal, Field(max_digits=14, decimal_places=2, ge=0)]
class LineItem(BaseModel):
# model_config replaces inner `Config` class
model_config = ConfigDict(
strict=True, # refuse string "1" for int field
frozen=True, # hashable + immutable
populate_by_name=True, # accept alias AND field name
str_strip_whitespace=True, # strip on all str fields
extra="forbid", # reject unknown keys
)
sku: NonEmptyStr
quantity: int = Field(ge=1)
unit_price: USDAmount
currency: Literal["USD", "EUR", "GBP"] = "USD"
@computed_field
@property
def total(self) -> USDAmount:
return self.unit_price * self.quantity
@field_validator("sku")
@classmethod
def sku_format(cls, v: str) -> str:
if not v.isalnum():
raise ValueError("sku must be alphanumeric")
return v.upper()
@model_validator(mode="after")
def cross_field_check(self) -> "LineItem":
if self.currency == "USD" and self.unit_price > Decimal("10000"):
raise ValueError("USD line item exceeds single-item limit")
return self
Key points
model_config = ConfigDict(...)replaces the v1 innerclass Config:pattern. ImportingConfigDictgives you autocomplete.Annotated[T, Field(...)]orAnnotated[T, StringConstraints(...)]is the idiomatic v2 way to attach constraints -- prefer overconstr(),conint(),condecimal()wrappers (still supported, but Annotated composes better with Python's type system).@computed_fieldreplaces the v1@property+validator(always=True)dance; shows up inmodel_dump()automatically.@field_validator("field_name")replaces v1@validator("field_name"). Decorator is classmethod; must have@classmethoddecorator after@field_validator.@model_validator(mode="before" | "after")replaces v1@root_validator(pre=True | False). Inmode="after"the method returnsself, not a dict. Note (2.12+):@classmethodonmode="after"is deprecated -- write as a plain instance method.- Prefer
validate_by_alias/validate_by_name/serialize_by_alias(2.11+) over the legacypopulate_by_name=True. The latter is pending deprecation in v3. Pathanddequeno longer accept Field constraints (2.11+) -- use a custom validator for length / pattern checks.MISSINGsentinel (2.12, experimental) lets you distinguish "not provided" from "explicit default" at validator time.
Validators
Mode decision rules
| Mode | When to use | Performance | Returns |
|---|---|---|---|
after (default) |
90% of cases -- logic on already-coerced value | Fast | Same type as field |
before |
Reshape raw input (string split, legacy key mapping) | Python callback overhead | Any (will be type-coerced afterward) |
wrap |
Catch ValidationError, add logging, force PydanticUseDefault |
Slowest -- materializes data in Python | Same type as field |
plain |
Fully custom, no coercion, no downstream validators | N/A (terminates validation) | Trusted as-is -- dangerous for type safety |
Performance rule: express constraints in Field(...) or Annotated metadata whenever possible -- the Rust engine handles them. Python-level validators (@field_validator, mode="before", mode="wrap") incur per-value Python-call overhead (Performance guide).
Field validators
from pydantic import BaseModel, ValidationInfo, field_validator
class User(BaseModel):
email: str
password: str
@field_validator("email")
@classmethod
def email_lowercase(cls, v: str) -> str:
return v.lower()
@field_validator("password")
@classmethod
def password_strength(cls, v: str, info: ValidationInfo) -> str:
# info.data: already-validated fields (in declaration order -- cannot access later fields)
# info.context: user-supplied dict via Model.model_validate(data, context={...})
# info.mode: "python" | "json" | "strings"
# info.field_name: the field being validated
if len(v) < 12:
raise ValueError("password must be at least 12 chars")
return v
Annotated validators (reusable pattern)
Prefer Annotated metadata over decorator validators when the same logic applies across multiple models:
from typing import Annotated
from pydantic import AfterValidator, BeforeValidator, WrapValidator, BaseModel
def lower(v: str) -> str:
return v.lower()
def ensure_https(v: str) -> str:
if not v.startswith("https://"):
raise ValueError("URL must be https")
return v
LowerStr = Annotated[str, AfterValidator(lower)]
HttpsUrl = Annotated[str, AfterValidator(ensure_https)]
# Reuse across any model
class User(BaseModel):
email: LowerStr
avatar_url: HttpsUrl
Ordering rule: Before and Wrap validators run right-to-left; After validators run left-to-right; decorator-style @field_validators are appended last.
ValidationInfo and info.context
Stable API across 2.x. Context passes through to custom serializers (2.7+):
user = User.model_validate(payload, context={"tenant_id": request.tenant_id})
# Inside a field_validator / model_validator / field_serializer:
# info.context["tenant_id"] is available
Use context for tenant-aware validation, feature flags, or bypass switches -- NOT for data that should be on the model.
Model validators
@model_validator(mode="before")
@classmethod
def normalize_input(cls, data: dict) -> dict:
# mode="before": classmethod, first arg cls, receives raw input (often dict)
# Do NOT mutate then raise -- mutations leak to downstream validators
if isinstance(data, dict) and "userEmail" in data and "user_email" not in data:
data["user_email"] = data.pop("userEmail")
return data
@model_validator(mode="after")
def check_date_order(self) -> "Event":
# mode="after": INSTANCE method (not classmethod; @classmethod deprecated in 2.12)
# returns self, not a dict
if self.start_date > self.end_date:
raise ValueError("start_date must be <= end_date")
return self
Error customization
from pydantic_core import PydanticCustomError
@field_validator("sku")
@classmethod
def sku_format(cls, v: str) -> str:
if not v.isalnum():
raise PydanticCustomError(
"invalid_sku", # machine-readable error type
"{field} must be alphanumeric", # template (formatted by Pydantic)
{"field": "sku"}, # context dict
)
return v
- Raising
ValueErrorauto-wraps into a Pydantic error -- simplest path. - Avoid
assert-- skipped underpython -O. loc(error location) is determined by validator position; not user-editable at raise site.
Inheritance
Base-class model validators run on subclass instances. Overriding in a subclass replaces (does not extend) the parent. Prefer composition via Annotated validators over deep inheritance.
Serialization: model_dump, model_dump_json, model_validate
v2 renamed the v1 methods:
| v1 | v2 |
|---|---|
model.dict() |
model.model_dump() |
model.json() |
model.model_dump_json() |
Model.parse_obj(...) |
Model.model_validate(...) |
Model.parse_raw(...) |
Model.model_validate_json(...) |
Model.construct(...) |
Model.model_construct(...) |
Model.schema() |
Model.model_json_schema() |
Model.__fields__ |
Model.model_fields |
All deprecated v1 names still work with deprecation warnings. Production: use the v2 names.
Serialization modes
model.model_dump(mode="python") # native Python types (Decimal stays Decimal)
model.model_dump(mode="json") # JSON-compatible types (Decimal -> str, UUID -> str, datetime -> ISO)
model.model_dump(mode="strings") # everything coerced to str (CSV / form encodings)
model.model_dump(exclude={"password_hash"})
model.model_dump(include={"id", "name"})
model.model_dump(exclude_none=True)
model.model_dump(by_alias=True) # use field aliases as keys
model.model_dump(round_trip=True) # output can be fed back into model_validate
# 2.13+ polymorphic: subclass instances serialize by their actual type, exposing subclass-only fields
model.model_dump(polymorphic_serialization=True)
Custom serializers
Three flavors, use the one that matches the shape:
from typing import Annotated
from pydantic import (
BaseModel, field_serializer, model_serializer,
PlainSerializer, WrapSerializer,
SerializerFunctionWrapHandler,
)
class Order(BaseModel):
total: Decimal
created_at: datetime
# Per-field decorator
@field_serializer("total", when_used="json")
def serialize_total(self, v: Decimal) -> str:
return f"${v:.2f}"
# Whole-model wrap: call handler() for default output, then post-process
@model_serializer(mode="wrap")
def add_envelope(self, handler: SerializerFunctionWrapHandler) -> dict:
return {"version": 1, "data": handler(self)}
# Reusable Annotated serializers
UsdAmount = Annotated[
Decimal,
PlainSerializer(lambda v: f"${v:.2f}", return_type=str, when_used="json"),
]
when_used values: "always" (default), "json", "json-unless-none", "unless-none".
JSON Schema modes
model_json_schema(mode="validation" | "serialization") returns different shapes:
Model.model_json_schema(mode="validation") # request shape -- computed_fields absent, input-only aliases
Model.model_json_schema(mode="serialization") # response shape -- computed_fields required, output aliases
FastAPI uses both: validation mode for request schemas, serialization mode for response schemas. Pydantic v2 targets OpenAPI 3.1 natively.
Aliases: three flavors
| Field arg | Affects |
|---|---|
alias="x" |
Both validation input AND serialization output |
validation_alias="x" |
Input only |
serialization_alias="x" |
Output only |
Pydantic 2.11 added per-config control:
class ApiResponse(BaseModel):
model_config = ConfigDict(
validate_by_alias=True, # accept aliases in input
validate_by_name=True, # also accept field names in input
serialize_by_alias=True, # emit aliases in output
)
user_id: str = Field(alias="userId")
populate_by_name still works but is pending v3 deprecation -- migrate now.
Fast JSON emission
For hot paths, skip the Python dict materialization:
# Standard
raw = model.model_dump_json() # already faster than json.dumps(model.model_dump())
# Fast-path: bytes directly from Rust
raw_bytes = MyModel.__pydantic_serializer__.to_json(model)
Cache __pydantic_serializer__ at module scope for ultra-hot paths (SSE, WebSocket, bulk export).
Round-tripping
model.model_dump(round_trip=True) guarantees Model.model_validate(dumped) yields an equivalent instance for RootModels, discriminated unions, and aliased fields. Mutable cached state (computed_fields with side-effects) is not guaranteed to survive round-trip.
Discriminated unions
The pattern for "this field determines which model to parse":
from typing import Annotated, Literal, Union
from pydantic import BaseModel, Field
class EmailEvent(BaseModel):
type: Literal["email"]
to: str
subject: str
class SmsEvent(BaseModel):
type: Literal["sms"]
to: str
body: str
Event = Annotated[Union[EmailEvent, SmsEvent], Field(discriminator="type")]
class Envelope(BaseModel):
id: str
event: Event
Envelope.model_validate({"id": "1", "event": {"type": "email", "to": "x@y", "subject": "hi"}})
Faster than v1 unions (pydantic-core picks the variant without trying each) and produces cleaner error messages.
Monetary precision (CWE-681 defense)
Never use float for money. Two idiomatic v2 options:
from decimal import Decimal
from typing import Annotated
from pydantic import BaseModel, Field, condecimal
# Option A (preferred): Annotated with Field constraints
USDAmount = Annotated[Decimal, Field(max_digits=14, decimal_places=2, ge=0)]
# Option B: condecimal() wrapper (still supported)
USDAmountV1Style = condecimal(max_digits=14, decimal_places=2, ge=0)
class Invoice(BaseModel):
subtotal: USDAmount
tax: USDAmount
total: USDAmount
For currency arithmetic, pair with decimal.getcontext() for rounding mode:
from decimal import ROUND_HALF_EVEN, getcontext
getcontext().rounding = ROUND_HALF_EVEN # banker's rounding for financial totals
See senior-review:defect-taxonomy CWE-681 / CWE-682 for the full monetary-precision rules.
Settings management (pydantic-settings)
v2 extracted settings into a separate package pydantic-settings:
# pip install pydantic-settings
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_prefix="APP_",
case_sensitive=False,
)
database_url: str
stripe_secret_key: SecretStr # redacted in repr / model_dump_json
debug: bool = False
port: int = Field(default=8000, ge=1, le=65535)
settings = AppSettings()
SecretStr/SecretByteshide the value in logs / tracebacks -- use for every API key, password, or token- Reading the value:
settings.stripe_secret_key.get_secret_value() - Source priority (high -> low): init args > env vars > .env file > defaults
FastAPI integration
Response models
from fastapi import FastAPI
from pydantic import BaseModel
class UserOut(BaseModel):
id: int
email: str
class UserDB(UserOut):
password_hash: str # server-side only, never exposed
app = FastAPI()
@app.get("/users/{user_id}", response_model=UserOut)
async def get_user(user_id: int) -> UserDB:
# Returning the broader UserDB is fine; FastAPI projects through UserOut.
return await fetch_user(user_id)
FastAPI uses the response_model for serialization, which strips password_hash. Prefer this to manual dict construction.
Request validation and custom error envelopes
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": "validation_failed",
"details": exc.errors(), # Pydantic v2 error format
"request_id": request.headers.get("x-request-id"),
},
)
v2 error shape differs from v1: ctx, input, and url fields changed. If you serialize errors to clients, pin the shape in an integration test.
Streaming / long-lived response models
model_dump_json() is faster than json.dumps(model.model_dump()) for streaming. For SSE / WebSocket, cache the serializer:
from typing import Any
serializer = MyModel.__pydantic_serializer__
raw = serializer.to_json(instance) # bytes
Performance notes
Ordered guide from the official Performance concept:
model_validate_json(raw_bytes)overmodel_validate(json.loads(raw_bytes))-- skips Python dict materialization; pydantic-core streams directly from bytes.- Instantiate
TypeAdapterat module scope, never inside request handlers. Each instantiation rebuilds validators. - Concrete types beat abstract ones --
list[int]beatsSequence[int];dictbeatsMapping. - Discriminated unions >> untagged unions -- direct lookup via the tag field, no scoring of all variants.
TypedDictviaTypeAdapteris ~2.5x faster than an equivalent nestedBaseModelfor pure-data shapes.- Avoid wrap validators in hot paths -- they materialize data in Python.
- Core-side constraints (
Field(ge=...)) >> Python@field_validator-- express ranges, lengths, regex patterns as metadata. FailFastannotation on sequences (2.8+) stops validation on first failure instead of collecting all errors:from typing import Annotated from pydantic import FailFast ids: Annotated[list[int], FailFast()]cache_strings="all"(default) -- Pydantic caches repeated strings across validation calls. Memory-safe for typical API payloads; set to"none"only for adversarial input with high string entropy.defer_build=TrueinConfigDict-- delay schema build until first validation. Useful when you define many models at import but use only a subset per request. 2.11 extended to@validate_call; 2.8 added experimentalTypeAdaptersupport. Harmful if you want startup-time errors on malformed schemas.MyModel.__pydantic_serializer__.to_json(instance)-- bytes fast-path for SSE / WebSocket / bulk export. Cache the serializer at module scope.model_construct(**data)-- skips all validation and coercion. Safe for already-validated data (DB rows matching schema, internal cloning, test fixtures). Unsafe for any input that has crossed a trust boundary.
Free-threaded Python (3.13+)
Pydantic 2.11 added experimental free-threaded (nogil) build support; 2.12 ships official builds for Python 3.14 free-threaded. Treat as preview, not production, until a stability signal from the Pydantic team.
model_rebuild()
Needed when a forward-referenced model is defined out of order or when forward refs haven't resolved. Harmful if called in a hot path -- rebuilds the Rust-side schema from scratch.
Security and secrets
SecretStr / SecretBytes
from pydantic import BaseModel, SecretStr
class Credentials(BaseModel):
api_key: SecretStr
c = Credentials(api_key="sk-real-secret-123")
print(c) # api_key=SecretStr('**********')
print(c.model_dump_json()) # {"api_key":"**********"}
c.api_key.get_secret_value() # the real value, use only at the trusted sink
Redacted in repr() and JSON output by default -- stops accidental logging and traceback leaks. To expose to a trusted sink (secret manager persistence), use a custom @field_serializer("api_key", when_used="always") that calls .get_secret_value().
Untrusted input safety
TypeAdapter.validate_python(untrusted)has the same safety model asBaseModel.model_validate-- no more, no less permissive.model_validate_json(raw_bytes)streams through pydantic-core with built-in depth / size bounds; prefer overjson.loads+model_validate.- Deep recursive models: pydantic-core has Rust-side stack safety for JSON traversal, but Python-level validators at depth can still overflow. Cap recursion via a
mode="before"validator that counts. - Mutable nested objects in
model_dump()output alias back to the model (shallow copy) -- see gotcha above.
PydanticAI and Logfire
Two first-party tools worth knowing when building LLM-driven features:
- Logfire -- Pydantic's observability tool.
logfire.instrument_pydantic(record="failure")traces every failed validation in production with span metadata;record="all"traces successful validations too (use sparingly).logfire.instrument_pydantic_ai()instruments PydanticAI agents end-to-end (tool calls, LLM requests, token usage). - PydanticAI -- type-safe LLM agent framework built on Pydantic models. Supports OpenAI, Anthropic, Gemini, DeepSeek, Grok, Cohere, Mistral, Perplexity, Azure, Bedrock, Vertex, Ollama, Groq, OpenRouter, Together, Fireworks. Features: durable-agent execution (resume after failure), built-in evals, MCP support, Logfire tracing. Use when your domain models are already Pydantic and you want agent outputs validated against those same models.
v1 -> v2 migration checklist
Run bump-pydantic for the mechanical transforms:
uvx bump-pydantic src/
Then handle the cases the bot can't:
@validator->@field_validator+@classmethoddecorator above the validator@root_validator(pre=True)->@model_validator(mode="before"), method is classmethod, first arg iscls, takes raw dict@root_validator(no pre) ->@model_validator(mode="after"), method returnsselfand is a plain instance method (2.12+ deprecated the classmethod form)Config.allow_population_by_field_name->ConfigDict(validate_by_name=True)(andvalidate_by_alias=True,serialize_by_alias=Trueas needed). The olderpopulate_by_name=Truestill works but is pending v3 deprecation.Config.orm_mode->model_config = ConfigDict(from_attributes=True)Config.schema_extra->model_config = ConfigDict(json_schema_extra={...})Config.extra = "allow"|"forbid"|"ignore"->ConfigDict(extra=...)parse_obj->model_validate;parse_raw->model_validate_json;dict()->model_dump();json()->model_dump_json()- Generic
BaseModel[T]-- v2 uses native Python generics, notGenericModel; PEP 695 syntax supported since 2.11 Field(..., env="FOO")in settings -> moved topydantic-settingspackage (BaseSettingsis no longer inpydantic)__fields__->model_fields(the shape changed:FieldInfoobjects, notModelField)__root__model -> useRootModel[T](from pydantic import RootModel)Optional[X] = Nonewithout default -> still optional but now must have default; v1 allowed no default on Optional, v2 requires itsmart_union-> default behavior in v2; oldsmart_union=Trueis a no-opValidationError.errors()format changed:msg,type,loc,input,ctx,url-- regenerate any error-envelope testsparse_obj_as(list[Foo], data)->TypeAdapter(list[Foo]).validate_python(data)(instantiate adapter at module scope)
What bump-pydantic misses
Requires manual conversion:
- Custom
__get_validators__/__modify_schema__->__get_pydantic_core_schema__+__get_pydantic_json_schema__ each_item=Truevalidators on sequence fields- Custom
json_encodersinConfig->@field_serializeror AnnotatedPlainSerializer - Complex wrap validators with branching logic
@validator(always=True)on computed fields ->@computed_field
Always diff the changes manually after running bump-pydantic.
Common gotchas
strict=Truebreaks JSON API boundaries: by default, FastAPI coerces"1"->1for path/query params. If you setstrict=Trueglobally, int query params must be sent as JSON numbers. Apply strict selectively (per-field or per-call).extra="forbid"+ evolving clients: rejects unknown fields. Fine for internal APIs, risky for public APIs where clients may send forward-compatible fields. Use"ignore"(default) for public ingress.extra="allow"+model_dump(): v2 excludes extras from dumps by default (v1 included them). 2.13 tracks post-instantiation extra assignments inmodel_fields_set; setConfigDict(extra="allow")and explicitly include extras when needed (issue #5683).@computed_fieldwith expensive work: runs on everymodel_dump(). Cache if expensive, or use a regular property (which won't be serialized).- Frozen models are hashable: useful for use as dict keys / in sets, but
model_copy(update={...})is now required to "change" one -- can't just assign. model_validate_jsonraw bytes vs str: accepts both; bytes path avoids decode cost on hot paths.- Discriminator must be a
Literal, notstr:Field(discriminator="type")requires each union member to declaretype: Literal["..."], nottype: str. - v2 does NOT validate on
__setattr__by default: assigning to a mutable model attribute after construction skips validation. SetConfigDict(validate_assignment=True)if you need it. @model_validator(mode="after")classmethod is deprecated (2.12+): write as a plain instance method returningself.populate_by_namepending v3 deprecation: usevalidate_by_name=True(and optionallyvalidate_by_alias=True,serialize_by_alias=True) instead.- UTC / timezone handling:
AwareDatetimecoerces naive input by treating it as local time then converting to UTC;NaiveDatetimerejects tz-aware input. Date-only strings (YYYY-MM-DD) can produce surprising aware datetimes -- pin test cases (issue #8859). 2.12 added explicit timestamp-unit control (seconds vs ms) -- no more ambiguous inference. model_dump()is shallow: mutating a nested custom-class object in the dumped dict mutates the original model instance. Usemodel.model_copy(deep=True).model_dump()if downstream code mutates (issue #10735).Pathanddequeno longer accept constraints (2.11+): use a custom validator for length / pattern checks.arbitrary_types_allowed=Trueis a pass-through: the attached value receives no validation. Necessary for stdlib types Pydantic doesn't know and third-party ORM rows; dangerous because it bypasses the safety model.from __future__ import annotations(PEP 563): may requiremodel_rebuild()or module-scope model definitions for forward refs to resolve. Pyright handles it cleanly; mypy may need the plugin.- Mutable defaults: v2 deep-copies unhashable defaults per instance -- safer than stdlib dataclasses. Still prefer
Field(default_factory=list)for readability.
Integration
- Python architecture / API design that uses Pydantic v2 ->
python-development:python-engineeragent - Testing Pydantic models ->
python-development:python-tddskill - FastAPI project scaffolding with v2 defaults ->
python-development:python-scaffoldcommand (--type fastapi) - Monetary precision patterns ->
senior-review:defect-taxonomy(CWE-681 / CWE-682) - TypeScript equivalent (Zod / valibot) ->
typescript-development:mastering-typescriptskill
References
Concept docs (live)
- Pydantic v2 Migration Guide
- Pydantic v2 Core Concepts -- Models
- Validators concept
- Serialization concept
- Strict mode
- Performance guide
- JSON Schema modes
- Unions and discriminators
- Configuration API (defer_build, cache_strings, etc.)
- Functional Validators API
- Pydantic Settings
- Secret Types
Release notes
- Pydantic 2.13 release (2026-04)
- Pydantic 2.12 release
- Pydantic 2.11 release (2025)
- Pydantic 2.8 release
- Pydantic 2.7 release
- Pydantic changelog
- Pydantic releases on GitHub
Tooling and ecosystem
FastAPI
- FastAPI + Pydantic v2 response models
- FastAPI migrate from Pydantic v1 to v2
- FastAPI SSE tutorial
- FastAPI settings
Known issues referenced above
More from acaprino/alfio-claude-plugins
python-refactor
>
159file-organizer
>
60legal-advisor
Use PROACTIVELY for any legal question -- contracts, compliance, privacy, IP, employment law, terms of service, NDAs, corporate governance. Expert legal advisor specializing in technology law, compliance, and risk mitigation.
39deep-dive-analysis
>
34python-comments
>
34python-performance-optimization
>
30