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.