From One Shape to Many — Patterns Review
Remember the if/elif chain for different webhook types? You replaced it with one line.
That's the transformation this week delivered. You went from thinking about data as fixed shapes — one model, one structure, one validation path — to thinking about data as patterns that compose, adapt, and generate themselves.
Discriminated unions were the first revelation. Instead of a giant model with optional fields for every possible event type, you defined separate models for Payment, Refund, and Dispute, tagged them with a Literal type field, and let Pydantic route incoming data to the right model automatically. No if/elif. No isinstance. Just a union that knows its own shape.
Then generics showed you reusability at a level you didn't know Pydantic supported. Envelope[T] — one model template that wraps any inner type while preserving full validation. You wrote it once and used it for Envelope[Customer], Envelope[Payment], Envelope[list[Order]]. Same wrapper, different content, type-safe all the way down.
Root models broke you out of the dict assumption. Not everything is a key-value mapping. Sometimes your API returns a list. Sometimes it returns a single string. RootModel lets you validate these directly without wrapping them in an artificial object.
Dynamic schemas felt like magic. Building models at runtime from configuration — create_model() generating validation logic from a database schema, a YAML file, or an admin dashboard. The shape of your data isn't hardcoded anymore. It's data itself.
And custom types tied it all together. Annotated[str, AfterValidator(normalize)] gave you reusable validation contracts that work across any model.
These patterns are elegant. But elegance alone doesn't survive production. What happens when 10,000 records arrive and 47 of them are broken? Next week, you'll build the armor.
Practice your skills
Sign up to write and run code in this lesson.
From One Shape to Many — Patterns Review
Remember the if/elif chain for different webhook types? You replaced it with one line.
That's the transformation this week delivered. You went from thinking about data as fixed shapes — one model, one structure, one validation path — to thinking about data as patterns that compose, adapt, and generate themselves.
Discriminated unions were the first revelation. Instead of a giant model with optional fields for every possible event type, you defined separate models for Payment, Refund, and Dispute, tagged them with a Literal type field, and let Pydantic route incoming data to the right model automatically. No if/elif. No isinstance. Just a union that knows its own shape.
Then generics showed you reusability at a level you didn't know Pydantic supported. Envelope[T] — one model template that wraps any inner type while preserving full validation. You wrote it once and used it for Envelope[Customer], Envelope[Payment], Envelope[list[Order]]. Same wrapper, different content, type-safe all the way down.
Root models broke you out of the dict assumption. Not everything is a key-value mapping. Sometimes your API returns a list. Sometimes it returns a single string. RootModel lets you validate these directly without wrapping them in an artificial object.
Dynamic schemas felt like magic. Building models at runtime from configuration — create_model() generating validation logic from a database schema, a YAML file, or an admin dashboard. The shape of your data isn't hardcoded anymore. It's data itself.
And custom types tied it all together. Annotated[str, AfterValidator(normalize)] gave you reusable validation contracts that work across any model.
These patterns are elegant. But elegance alone doesn't survive production. What happens when 10,000 records arrive and 47 of them are broken? Next week, you'll build the armor.
Practice your skills
Sign up to write and run code in this lesson.