FastAPI: From Basics to Production · Pydantic v2 in Depth
Annotated Types and Discriminated Unions
Reusable constrained types and polymorphic payloads.
Define reusable constrained types with Annotated (e.g., PositiveCents = Annotated[int, Field(ge=0)]). For payloads that can take several shapes (payment methods, events), use a discriminated union with a literal type field: Pydantic picks the right model quickly and produces clear errors, and OpenAPI documents the variants.
from typing import Annotated, Literal
from pydantic import BaseModel, Field
PositiveCents = Annotated[int, Field(ge=0)]
Slug = Annotated[str, Field(pattern=r"^[a-z0-9-]+$", max_length=80)]
class CardPayment(BaseModel):
type: Literal["card"]
token: str
class BankTransfer(BaseModel):
type: Literal["bank_transfer"]
iban: str
Payment = Annotated[CardPayment | BankTransfer, Field(discriminator="type")]
class CheckoutIn(BaseModel):
amount_cents: PositiveCents
payment: Payment
Common pitfalls
- Plain unions without a discriminator — confusing errors and slower validation.
- Duplicating constraints instead of reusable Annotated types.
- Using floats for money.
Key points
- Annotated for reusable constraints
- Discriminated unions for polymorphic data
- Clear OpenAPI variants
Further reading: Pydantic: Discriminated unions
Practice questions
Why add a discriminator to a union of models?
Answer
Pydantic reads the discriminator field to choose the exact model, giving faster validation and precise errors instead of trying every variant.
How do you reuse 'non-negative integer' validation across models?
Answer
Define NonNegative = Annotated[int, Field(ge=0)] once and use it as a type in any model or parameter.
Open in the app to mark lessons complete, take notes and track your progress.