Back to catalog
Season 9 20 Episodes 1h 11m 2026

Pydantic: Data Validation

v2.12 — 2026 Edition. A deep dive into Pydantic v2.12, the most widely used data validation library for Python, from basic usage to advanced features like custom core schemas and Logfire observability.

Data Validation Python Core
Pydantic: Data Validation
Now Playing
Click play to start
0:00
0:00
1
The Pydantic Philosophy: Type Hints as Validation
This episode introduces the core premise of Pydantic. You will learn how Python type hints can be used to enforce schemas and how the Rust core drives immense performance gains.
3m 16s
2
The Anatomy of a BaseModel
Dive into BaseModel, the foundational abstraction of Pydantic. You will learn how instantiation validates data, how fields are coerced, and how validation errors are surfaced.
3m 43s
3
Field Constraints and the Annotated Pattern
Learn how to enforce limits beyond basic types. You will discover how to use the Field function and the Annotated typing construct to add constraints like minimums and maximum lengths.
3m 59s
4
Field Aliases for Validation and Serialization
Solve the naming convention clash between external APIs and internal Python code. You will learn how to decouple your Python attribute names from JSON keys using validation and serialization aliases.
3m 42s
5
Data Coercion vs Strict Mode
Take control of Pydantic's eagerness to coerce data. You will learn how to enforce exact type matches by enabling Strict Mode at the field or model level.
3m 54s
6
Real-World Observability with Logfire
Bring transparency to your data pipelines. You will learn how to integrate Pydantic with Logfire to monitor successful and failed validations in real-time.
3m 27s
7
Validating Arbitrary Types with TypeAdapter
Learn how to validate standalone primitives and lists without creating a BaseModel. You will discover how TypeAdapter turns any Python type into a fully-fledged validation target.
3m 47s
8
Union Types and Smart Validation
Understand the complexities of Union type validation. You will learn how Pydantic's Smart Mode evaluates exactness and valid fields to choose the best match.
3m 24s
9
Power Tool: Discriminated Unions
Supercharge your validation performance. You will learn how to use Discriminated (Tagged) Unions to tell Pydantic exactly which schema to apply based on a specific field.
3m 03s
10
Pre-processing with Before and Wrap Validators
Handle messy incoming data before it hits your schema. You will learn how to use Before and Wrap field validators to clean raw input before Pydantic evaluates it.
3m 30s
11
Post-processing with After and Plain Validators
Enforce strict business logic rules. You will learn how to use After validators to verify already-parsed data, and Plain validators to short-circuit Pydantic entirely.
3m 30s
12
Model-Level Validation Hooks
Validate interactions across multiple fields. You will learn how to use the model_validator decorator to enforce rules that depend on the entire payload.
3m 35s
13
Serialization: Dumping Data Safely
Control how your data leaves the system. You will learn the differences between dumping to Python dicts versus JSON strings, and how to exclude unset or default fields.
3m 39s
14
Customizing Serialization Logic
Change how your types are represented on the way out. You will learn how to write custom Field and Model serializers to mutate data during the dumping phase.
3m 33s
15
Generating JSON Schema from Models
Turn your models into self-documenting API contracts. You will learn how to generate OpenAPI-compliant JSON Schemas and inject examples directly into the schema.
3m 44s
16
RootModel: When Your Payload Isn't a Dictionary
Handle non-standard JSON payloads gracefully. You will discover how RootModel allows you to parse root-level arrays and primitives while retaining BaseModel powers.
3m 14s
17
Standard Dataclasses vs Pydantic Dataclasses
Bring validation to your native Python classes. You will learn when to use the Pydantic dataclass decorator to retrofit legacy codebases without rewriting everything.
3m 37s
18
Fine-Tuning Model Configuration
Control the strictness of your entire model. You will learn how to use the ConfigDict to forbid extra attributes, freeze instances, and validate assignments.
3m 20s
19
Application Configuration with Pydantic Settings
Manage your environment variables like a pro. You will learn how the pydantic-settings package automates the parsing of secrets, dot-env files, and prefixes.
3m 36s
20
Under the Hood: Custom Core Schemas
This is the final episode of the series! Take ultimate control of the validation engine. You will learn how to write a __get_pydantic_core_schema__ method to teach the Rust core how to handle completely alien Python objects.
3m 40s

Episodes

1

The Pydantic Philosophy: Type Hints as Validation

3m 16s

This episode introduces the core premise of Pydantic. You will learn how Python type hints can be used to enforce schemas and how the Rust core drives immense performance gains.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 1 of 20. Most data validation libraries force you to learn a completely new domain-specific language just to define a schema. But what if Python's built-in syntax was already enough to enforce your rules? The Pydantic Philosophy: Type Hints as Validation resolves this exact tension. Before we look at how it works, we need to clear up a common confusion. Pydantic is not a static type checker like mypy. Mypy analyzes your source code before you run it to catch logic errors. Pydantic operates at runtime. It takes untrusted data from the outside world, like a JSON payload from an API request, and forces it to conform to the types you defined, right as your program is executing. The core philosophy here is simplicity. You define a data model using standard Python type annotations. You create a class representing a user, and you state that the user ID is an integer, and the signup date is a datetime object. That is your entire schema. You do not write custom validation functions or import proprietary field types. When a messy dictionary arrives from a web form, you simply pass it to your class. Pydantic intercepts it and guarantees that the resulting object strictly adheres to those types. Here is the key insight. Pydantic is fundamentally a parsing library, not just a strict validation gate. If a user submits a web form where the user ID is the string forty-two, Pydantic will recognize that your model expects an integer. It converts that string into a native Python integer automatically. It tries to make the data fit your schema before raising an error. This handles the tedious data coercion so you do not have to write manual parsing logic for every input field. Validating and converting every single piece of incoming data at runtime sounds inherently slow, especially in Python. To solve this, the execution logic does not actually run in Python. Pydantic delegates the heavy lifting to a dedicated core engine written entirely in Rust. This design gives you the fast developer experience of writing pure Python, combined with the raw execution speed of compiled system code. Consider a background task parsing a massive JSON file where every record contains a web address. If you write pure Python code to loop through thousands of dictionaries, extract every string, load a regular expression library, and verify that each string is a valid URL, your process will crawl. Pydantic handles this seamlessly. You just annotate the address field as a URL type and feed the raw JSON to your model. The Rust engine blazes through the text, parsing and validating the formats at speeds native Python cannot match. The real power of this approach is that your editor autocomplete, your static analysis tools, and your runtime validation all share the exact same source of truth, which is your standard type hints. If you find these episodes useful and want to help support the show, you can search for DevStoriesEU on Patreon. That is all for this one. Thanks for listening, and keep building!
2

The Anatomy of a BaseModel

3m 43s

Dive into BaseModel, the foundational abstraction of Pydantic. You will learn how instantiation validates data, how fields are coerced, and how validation errors are surfaced.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 2 of 20. We often talk about data validation, but if you look closely at Pydantic, its primary goal is actually coercion. It guarantees the structure of your output, not your input. The Anatomy of a BaseModel is what makes this distinction possible. The BaseModel class is the foundation of Pydantic. To define a schema, you create a standard Python class and inherit from BaseModel. Once you do that, Pydantic transforms your class into a strict data container. You define the shape of your data using standard Python type hints. You do not need proprietary functions to declare a string or a number. You simply write the attribute name, a colon, and the expected Python type. Consider a User model. Inside the class, you define a single field named id, and you type it as an integer. You might also add a name field typed as a string. That is the entire definition. Here is the key insight. The actual work happens the moment you instantiate the model. You create an instance by passing your data as keyword arguments, matching the field names you defined. When you do this, Pydantic intercepts the data before the object is fully initialized. It compares the incoming values against your type hints. This is where the coercion happens. If you pass the integer one-two-three into the id field, Pydantic accepts it immediately. But suppose you are receiving data from a web request or a text file, and you pass the string "123" into that exact same field. Pydantic does not instantly throw an error. It recognizes that the target type is an integer and attempts to convert the string. Because the characters can be safely cast to a number, Pydantic performs the conversion silently. The object is created, and the id field stores a true Python integer, not a string. This behavior proves that Pydantic is essentially a parsing library. It transforms incoming data to fit the strict schema you defined. Of course, this conversion has logical limits. If you instantiate the User model and pass the string "not an int" to the id field, Pydantic attempts the conversion and fails. When coercion is impossible, Pydantic raises a ValidationError. This error halts execution immediately. One crucial detail about the ValidationError is that Pydantic evaluates all fields before raising it. If your model has multiple fields with invalid data, the exception will contain the details for all the failures, rather than stopping at the very first mistake. The error points out the exact fields that caused the problem, the specific values provided, and the reasons they failed to parse. Once your data successfully passes through instantiation, the resulting object is guaranteed to match your type hints. You access the data exactly like any standard Python object, using dot notation. If you assigned the instance to a variable called user, you simply type user dot id to retrieve the integer. There are no getter or setter methods required. You are interacting with a clean, strongly typed object. The true value of inheriting from BaseModel is not just that it rejects bad inputs, but that it safely normalizes unpredictable external data into a highly predictable internal state. Thanks for listening. Take care, everyone.
3

Field Constraints and the Annotated Pattern

3m 59s

Learn how to enforce limits beyond basic types. You will discover how to use the Field function and the Annotated typing construct to add constraints like minimums and maximum lengths.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 3 of 20. Standard type hints tell you a field is an integer, but they cannot tell you it has to be greater than zero. When a negative age or a ten-thousand character username slips past your application boundaries, basic type checking is no longer enough to protect your backend. To enforce strict boundaries on your data, you need Field Constraints and the Annotated Pattern. In Pydantic, constraints allow you to restrict the allowed values for a specific type. Instead of writing custom validator functions for every minor rule, you can define mathematical or structural boundaries directly on the field. For numeric types, you can enforce greater than or less than bounds using parameters like g-t and l-t. For strings, you can restrict the size of the input using max length and min length. To apply these rules, Pydantic provides a utility function called Field. The traditional way developers applied these constraints was by assigning a Field function call as the default value of a model attribute. For example, you would declare an age attribute, type hint it as an integer, and set it equal to Field, passing in g-t equals zero. This structure works, but it introduces friction. If you need a positive integer across fifteen different models, you are writing that exact same Field assignment fifteen times. Worse, because the Field function occupies the default value assignment slot on the model, it complicates things when you actually want to assign a real default integer to that attribute. Here is the key insight. You do not have to tie your validation rules to the attribute assignment at all. You can bake them directly into the type definition itself using Python's typing dot Annotated. Annotated is a standard library feature that lets you attach arbitrary metadata to a base type hint. Pydantic is specifically designed to look inside an Annotated type, find any Field functions you provided, and extract their validation rules automatically. When you use Annotated, you pass it two distinct pieces of information. First, you provide the base Python type, like an integer or a string. Second, you provide the metadata, which in our case is the Pydantic Field function containing your constraints. Let us build a reusable age type to see how this flows. You define a new variable in your code called PositiveInt. You assign it to Annotated. Inside Annotated, you pass integer as the base type, followed by a comma, and then the Field function with g-t equals zero. You have just created a custom, reusable type constraint. Now, whenever you define a user model or an employee model, you simply type hint your age field with PositiveInt. You do not need to call the Field function on the model attribute. This approach separates your type definitions from your model structures. Your models stay incredibly clean, reading just like standard Python classes without clutter. If your business logic changes later and you suddenly need an age field to be greater than eighteen instead of zero, you update the PositiveInt definition in exactly one place. That updated constraint cascades instantly to every model using it. Furthermore, because Annotated is a native Python feature, static type checkers understand perfectly that your custom PositiveInt is ultimately evaluated as a standard integer. By decoupling validation metadata from the default value slot, the Annotated pattern transforms field constraints from repetitive boilerplate into a shared library of strict, reusable domain types. Thanks for spending a few minutes with me. Until next time, take it easy.
4

Field Aliases for Validation and Serialization

3m 42s

Solve the naming convention clash between external APIs and internal Python code. You will learn how to decouple your Python attribute names from JSON keys using validation and serialization aliases.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 4 of 20. Have you ever been forced to name a Python variable with camel case just to parse an external API payload? You know it breaks standard Python naming conventions, but the incoming JSON dictates the key. The solution to this is Field Aliases for Validation and Serialization. When you ingest data, the keys in that payload often do not match how you want to structure your Python code. If a third party service sends a user profile with the key user capital N name, you want to map that to a standard snake case variable called user underscore name. Pydantic handles this translation layer using the Field function, specifically through its alias arguments. The simplest approach is the base alias argument. When you declare your model attribute, you assign it a Field and set the alias parameter to the string expected from the outside world. If you set the alias to the camel case version, Pydantic uses that exact string for both reading and writing. When validating incoming data, it looks for the camel case key. When you later serialize the model to dump it back out to JSON, it writes the camel case key. Your internal Python code operates entirely on the snake case attribute, completely isolated from the external naming convention. That handles symmetric data, where the input format and output format are identical. Now, the second piece of this is asymmetric data. What happens when you consume data from a legacy system using one naming convention, but you need to serve it to a new client using another? This is where you split the logic using validation aliases and serialization aliases. These are separate arguments you pass to the Field function. A validation alias strictly dictates what Pydantic looks for when creating the model. If you provide a validation alias, Pydantic will use it to pull the value from the incoming payload, overriding any base alias you might have set. Conversely, a serialization alias controls only the output phase. When you call a method to dump the model into a dictionary or JSON string, Pydantic uses the serialization alias as the output key. Here is the key insight. You can define a single field with three distinct identities. The validation alias catches the messy input string from the legacy API. The Python attribute holds the clean, snake-case variable you use in your business logic. Finally, the serialization alias defines the polished, standardized key that gets sent to your frontend. Sometimes the problem is not a rigid naming convention, but an inconsistent one. You might receive payloads where the user identifier is sometimes camel case and sometimes a single word with no spaces. To handle this, Pydantic provides a utility called alias choices. Instead of passing a single string to the validation alias, you pass this utility containing a list of strings. During validation, Pydantic scans the incoming payload for each string in the order you provided. The moment it finds a matching key, it extracts the value, assigns it to your Python attribute, and ignores the rest. By separating how data is parsed from how it is exported, aliases decouple your internal Python object design from the arbitrary naming constraints of the outside world. That is all for this one. Thanks for listening, and keep building!
5

Data Coercion vs Strict Mode

3m 54s

Take control of Pydantic's eagerness to coerce data. You will learn how to enforce exact type matches by enabling Strict Mode at the field or model level.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 5 of 20. Pydantic's eagerness to turn the string "123" into an integer is a killer feature, right until it silently hides a data-type bug in your JSON payload. You expect numbers, you receive strings, and your application happily proceeds until a strict downstream system crashes. The mechanism controlling this behavior is Data Coercion, and taming it requires understanding Strict Mode. By default, Pydantic operates in what the documentation calls lax mode. In this mode, Pydantic acts as a data parser rather than just a type checker. It actively tries to coerce, or convert, incoming data into the type you declared. If you define a field as an integer, and the input payload provides the string "123", Pydantic evaluates the string, extracts the valid number, and transforms it into an actual integer. This behavior is incredibly useful when processing user input from web forms or query parameters, where every incoming value is fundamentally a string. However, when you are building machine-to-machine APIs, silent conversion is often dangerous. If a client promises to send an integer but sends a string instead, they are breaking the API contract. Lax mode covers up this violation. This is where strict mode comes in. Strict mode disables automatic type coercion. When you turn it on, Pydantic demands that the incoming data type exactly matches your type annotation. Pass the string "123" to a strict integer field, and Pydantic immediately rejects it with a validation error. It forces the data source to respect the schema. You have two ways to apply strict mode: globally across a model, or locally on specific fields. To enforce it globally, you modify the model configuration. By setting the strict configuration flag to true, every single field within that model stops coercing types. A boolean field will only accept a true or false boolean value, not the string "true" or the integer one. An integer field will only accept integers. Here is the key insight. Global strictness is often too rigid for real-world applications where data arrives from mixed sources. You usually want to lock down a few critical identifiers while leaving the rest of the model flexible. To achieve this, Pydantic allows local strictness. You can enforce strict mode on an individual field by using specialized types provided by the library, such as StrictInt, StrictStr, or StrictBool. If you define a user ID field using StrictInt, that specific field will reject string representations of numbers, while the rest of your model continues operating in lax mode. You can also achieve this by passing a strict flag directly into the field definition function for any standard type. Consider a service processing a JSON payload for a financial transaction. The payload contains an account ID. If defined as a normal integer, a payload delivering the account ID as the string "123" passes right through. Pydantic fixes the data type in memory. If you update that field to use StrictInt, the exact same JSON payload fails validation. The client receives an explicit error stating that the input must be a valid integer, catching the contract violation at the system boundary before it pollutes your database. Strict mode shifts Pydantic from being a helpful parser that cleans up messy inputs into a rigid enforcer that guarantees data contracts. Thanks for listening. Take care, everyone.
6

Real-World Observability with Logfire

3m 27s

Bring transparency to your data pipelines. You will learn how to integrate Pydantic with Logfire to monitor successful and failed validations in real-time.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 6 of 20. When a complex payload fails validation in production, the standard stack trace alone rarely tells you exactly why the data was rejected. You know a request failed, but you are completely blind to the actual incoming values causing the crash. That is the exact problem Real-World Observability with Logfire solves. Logfire is an observability platform built by the Pydantic team, and it features direct integration with Pydantic itself. The goal is to give you insight into what your data validation layer is actually doing in production. Instead of treating validation as a black box that occasionally throws exceptions, this integration turns every validation check into tracked telemetry. Consider a background worker processing user signups from a message queue. Hundreds of events arrive per second. Suddenly, one signup fails with a validation error. Without proper instrumentation, your logs show a generic crash. You have to hunt down the raw queue payload to figure out that the user provided an age of negative five. To fix this, you import the Logfire package and call a single function named instrument underscore pydantic. You place this right after initializing your Logfire client. From that moment on, your Pydantic models are fully observable. You do not need to change how you define your models or how you instantiate them. Here is the key insight. Once instrumented, every time Pydantic validates data, Logfire automatically creates a span. A span is simply a timed record of an operation. If the signup payload is perfectly valid, Logfire records a successful span showing exactly how long the validation took. This is highly useful if you have complex custom validators and need to monitor performance bottlenecks. If the payload is invalid, Pydantic raises a validation error. Logfire catches this event and attaches the details to the trace. It captures the specific model involved and the exact fields that triggered the failure. When you look at your observability dashboard, you do not just see a generic error message. You see the exact rejected values, like that negative age or a malformed email string. The setup is entirely hands-off. First, configure the client. Second, call the instrument function. Third, let your worker process data. When the worker attempts to parse a bad JSON string into your signup model, the telemetry captures the failure context automatically. You write absolutely zero custom exception blocks to log the bad input. Because Logfire understands Pydantic natively, it respects your data structures. It knows the difference between a missing field and a type mismatch, and it formats that telemetry so you can query it later. You can filter your metrics to find out exactly how many times the email field failed validation across your entire worker cluster today. The true value of this integration is that it elevates data validation from a simple code check to a first-class observability event, transforming silent data rejections into structured, actionable telemetry. If you want to support the show, you can find us by searching for DevStoriesEU on Patreon. That is all for this one. Thanks for listening, and keep building!
7

Validating Arbitrary Types with TypeAdapter

3m 47s

Learn how to validate standalone primitives and lists without creating a BaseModel. You will discover how TypeAdapter turns any Python type into a fully-fledged validation target.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 7 of 20. Sometimes you just need to validate a simple list of strings coming from an API request, but creating an entire model class just to hold that single list feels like massive overkill. You do not want a wrapper object, you just want the validated list. The solution to this is Validating Arbitrary Types with TypeAdapter. In Pydantic, the standard workflow revolves around defining a class that inherits from the base model. That model gives you access to powerful methods for parsing and serializing data. But Pydantic is perfectly capable of validating standard Python types, like a plain dictionary, a standalone integer, a standard dataclass, or a typed dict. The catch is that these standard types do not inherently possess Pydantic validation methods. You cannot call validate on a standard Python list. This is where the type adapter comes in. It acts as a bridge, wrapping any arbitrary Python type and exposing all the familiar model methods for it. Consider a web endpoint that accepts a raw JSON array of strings. In the past, you might have created a dummy model with a single field called items, just so you could pass the JSON payload to it. That forces the client to send a JSON object with an items key, or forces you to unpack the validated model later. With a type adapter, you skip the dummy model entirely. First, you instantiate the adapter and pass it the exact type definition you expect. In this case, you pass the Python type hint for a list of strings. This creates an adapter object specifically configured for that exact structure. Now, you have access to the standard validation methods. You take the raw JSON payload from your endpoint and pass it to the validate json method on your adapter instance. Pydantic parses the raw byte string, checks that it is a JSON array, verifies that every element inside is a string, and returns a standard Python list. If the payload contains an integer or a boolean, it raises a validation error exactly like a regular model would. Here is the key insight. The adapter is not limited to simple validation. It completely mirrors the core API of a standard model. This means you can also use it to serialize data. If you have a complex dictionary or a standard Python dataclass and you need to convert it back to a JSON string, you pass that data to the dump json method on your adapter. It applies all the same serialization rules, custom encoders, and formatting that Pydantic applies to regular models. This feature is particularly useful when working with typed dicts. A typed dict provides structural typing for standard Python dictionaries, but it performs zero runtime validation. By passing a typed dict to an adapter, you get full runtime enforcement of the dictionary structure. It ensures all required keys are present and the values match the expected types, without converting the dictionary into an object instance. The output remains a simple dictionary. Instantiating an adapter requires Pydantic to build internal validation schemas. Because this setup process takes a small amount of compute time, you should create your adapter instances at the module level rather than rebuilding them inside a function every time an endpoint is called. Define the adapter once at the top of your file, and reuse it across multiple requests. The type adapter gives you the full power of the core validation engine for any standard Python type, keeping your data structures clean and free of unnecessary wrapper classes. That is it for today. Thanks for listening — go build something cool.
8

Union Types and Smart Validation

3m 24s

Understand the complexities of Union type validation. You will learn how Pydantic's Smart Mode evaluates exactness and valid fields to choose the best match.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 8 of 20. When a field is annotated as a Union of a string or an integer, and you pass the integer 123, which validation does Pydantic actually attempt first? If you assume it just reads your code from left to right, you might be surprised when your data behaves differently than expected. This brings us to Union Types and Smart Validation. A union type allows a single field to accept multiple distinct data types. Validating unions is inherently complex because Pydantic actively tries to coerce data into the requested type. An integer can easily become a string, and a string containing digits can be parsed into an integer. If the validation engine simply took the first match it found, your output would change based entirely on the arbitrary order you listed the types in your code. This rigid behavior does exist, and it is called Left to Right mode. In this mode, the system evaluates the input against the first type defined in the union. If validation succeeds, it stops immediately. If it fails, it moves to the second type. If your field is typed as string or integer, in that order, and you pass the integer 123, Left to Right mode evaluates the string condition first. Because the integer 123 can be seamlessly coerced into the string "123", validation passes. Your integer is silently converted into a string solely because string was written first. Here is the key insight. By default, Pydantic avoids this trap by using Smart Mode. Instead of stopping at the first passable match, Smart Mode evaluates the input against all the possible types in the union. It then compares the successful validations and picks the best match based on specific scoring criteria. The primary criterion for simple types is exactness. Smart mode heavily penalizes data coercion. Going back to our previous scenario with a field typed as string or integer. When you pass the integer 123, Smart mode tests both options. It acknowledges that the input can be coerced into a valid string, but it also sees that the input is already a perfect, exact match for an integer. Because an exact match always outscores a coerced match, Smart mode correctly returns the integer 123, regardless of which type was written first in the union. When your union contains complex data models rather than basic types, Smart mode relies on a different metric. It counts the number of valid fields set. The engine evaluates the input dictionary against every model in the union. It calculates how many fields in the input exactly map to the defined fields of each model. The model that successfully absorbs the highest number of input fields without throwing validation errors is declared the winner. This prevents a smaller, less specific model from swallowing data that was clearly intended for a larger, more detailed model in the same union. Smart validation ensures your data retains its precise original shape whenever possible, protecting you from silent coercion errors that are notoriously difficult to track down in production. Thanks for listening, happy coding everyone!
9

Power Tool: Discriminated Unions

3m 03s

Supercharge your validation performance. You will learn how to use Discriminated (Tagged) Unions to tell Pydantic exactly which schema to apply based on a specific field.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 9 of 20. Validating an incoming payload against twenty different data models by trial and error is a performance nightmare. When your system receives a generic event, guessing which schema to apply wastes CPU cycles and generates deeply confusing error messages when validation fails. You can bypass this guessing game entirely using a Power Tool: Discriminated Unions. A standard union tells Pydantic that a field could be one of several different models. By default, Pydantic takes the incoming data and tries it against the first model. If that fails, it tries the second, and so on. This sequential parsing is inefficient. Discriminated unions solve this by using an explicit tag. Suppose you are building an analytics pipeline that receives different event types, like a click event, a scroll event, and a purchase event. Each event requires different data fields. The click model might need an element ID, while the purchase model needs a transaction amount. To set up a discriminated union, you add a shared field to every single model in that group. You might name this field event type. You then assign a literal string type to this field. The click model enforces that the event type is exactly the string click. The purchase model strictly enforces the string purchase. Next, you create your main payload model. This model has a single event field, defined as a union of your specific event models. This is where you configure the discriminator. You wrap the union definition in a Pydantic field configuration, and you assign the string event type to the discriminator argument. Here is the key insight. When a payload arrives, Pydantic no longer tests the models one by one. It looks directly at the event type key in the incoming data. If the value is purchase, Pydantic immediately routes the entire payload to the purchase model. It is a direct lookup. If the transaction amount is missing, the error message clearly states that a required field for a purchase event is missing, rather than generating a massive wall of text explaining how the data failed to match all possible event types. That covers a clean structure where every payload shares a top-level key. Sometimes you are dealing with unstructured third-party data where the identifying tag is nested inside another object, or the correct model depends on the presence of specific keys rather than a single dedicated value. For these situations, Pydantic provides callable discriminators. Instead of passing a string field name to the discriminator argument, you pass a custom function. This function receives the raw, unvalidated input data. You write the logic inside this function to inspect the raw dictionary, find whatever clue determines the data type, and return a simple string tag representing the correct model. Pydantic executes your function first, gets the string tag back, and uses it to route the data to the precise model in the union. Using a discriminated union transforms validation from a sequential guessing game into a precise, constant-time lookup. Thanks for listening, have a great day everyone!
10

Pre-processing with Before and Wrap Validators

3m 30s

Handle messy incoming data before it hits your schema. You will learn how to use Before and Wrap field validators to clean raw input before Pydantic evaluates it.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 10 of 20. Sometimes your incoming data is so messy that standard parsing fails the moment it touches your model. You have to intercept and sanitize the raw input before the framework even tries to read it. This is exactly what Pre-processing with Before and Wrap Validators allows you to do. Pydantic normally does an excellent job of coercing data into the right type automatically. But if the fundamental shape of the data is completely wrong, coercion fails immediately. A Before validator is designed for this problem. It is a function attached to a field that executes before Pydantic does any of its own type checking or conversion. It receives the raw, untouched input exactly as it was passed to the model. Consider a scenario where your model defines a field that strictly requires a list of integers. However, you are consuming an external API that incorrectly sends this data as a single continuous string of numbers separated by commas, such as the string one comma two comma three. Pydantic expects an array, sees a single string, and rejects it with a validation error. You fix this by writing a Before validator. Inside your validator function, you look at the raw input. You check if the value is a string. If it is, you split that string at every comma, which creates a list of individual string characters. You do not have to convert those characters into integers yourself. You simply return the newly created list. Pydantic takes over from there. It sees the list it was expecting, runs its standard internal coercion, and turns those string values into integers for you. You only needed to fix the structural shape of the data. That covers inputs running before the main logic. The next tier of control is the Wrap validator. Wrap validators are the most flexible validation tool Pydantic offers. Instead of just running before the validation step, a Wrap validator literally surrounds Pydantic's internal validation engine for that field. When you write a Wrap validator, your function receives two arguments. The first is the raw input data, just like the Before validator. The second argument is a handler function. This handler represents the core Pydantic validation and coercion logic. This is where it gets interesting. You decide exactly when, or even if, that handler function runs. The logic flow is entirely in your hands. You can inspect the raw input and modify it. Then, you explicitly call the handler, passing it your cleaned data. Pydantic runs its internal type checking on what you provided and returns the fully parsed value back to your validator. You can then modify that parsed value again before passing it to the final model. Because you control when the handler executes, you can place it inside a standard try except block. If Pydantic's internal validation throws an error, you catch it right there in the Wrap validator. You can log the error, alter the data and try again, or just return a safe default value. You can even write logic that skips the handler completely for certain specific inputs, bypassing standard validation altogether. Before validators sanitize bad input shapes so Pydantic can read them, while Wrap validators give you total authority over the entire validation lifecycle around a single field. That is all for this one. Thanks for listening, and keep building!
11

Post-processing with After and Plain Validators

3m 30s

Enforce strict business logic rules. You will learn how to use After validators to verify already-parsed data, and Plain validators to short-circuit Pydantic entirely.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 11 of 20. Checking if a string looks like an email format is simple. Verifying that the data meets strict business logic, like cross-referencing an external system or enforcing specific mathematical rules, requires a safe place to run custom code. That is exactly what Post-processing with After and Plain Validators provides. When you define a field in a Pydantic model, the library automatically checks the input against the type hint. Types only take you so far. An integer is an integer, whether it is one or one million. To enforce rules beyond basic types, you add custom validation functions. Pydantic allows you to inject these functions into the validation lifecycle. The most common injection point is the After validator. As the name suggests, this runs after Pydantic finishes its internal parsing and type coercion. Here is the key insight. When your custom function receives the data in an After validator, Pydantic guarantees that the data already matches the field type. You do not have to write boilerplate code to handle type conversion or catch unexpected data types. Consider a scenario where you need to ensure an integer field is an even number. You define a model with a field typed as an integer. Then, you write a custom function that takes a value as an argument. Because you configure this function as an After validator, you know the value is absolutely an integer. You simply use the modulo operator to check if dividing the value by two leaves a remainder. If the remainder is not zero, you raise a standard Python value error with a custom message. If the remainder is zero, you return the value. Pydantic takes that returned value and assigns it to the model. The division of labor is clean. Pydantic enforces the type, and you enforce the business rule. Sometimes Pydantic default parsing gets in your way. That is where the Plain validator comes in. A Plain validator completely replaces Pydantic internal validation for a specific field. Pydantic steps aside and hands the raw, unvalidated input directly to your custom function. You use a Plain validator when the standard coercion rules do not fit your use case, or when you are dealing with a highly custom data structure that Pydantic does not natively understand. In this scenario, your function is responsible for everything. It receives the raw input, performs any necessary type checking, converts the data, and applies business logic. If anything fails, your function must raise a value error or an assertion error. If it succeeds, it returns the final, clean value for the model. You attach both of these validators to fields using Pydantic decorators on class methods, or by adding them as metadata directly inside the type hint using annotations. The choice between them comes down to how much of the work you want to do yourself. Use an After validator when you want Pydantic to do the heavy lifting of type conversion, and reach for a Plain validator only when you need absolute control over parsing the raw input from scratch. Appreciate you listening — catch you next time.
12

Model-Level Validation Hooks

3m 35s

Validate interactions across multiple fields. You will learn how to use the model_validator decorator to enforce rules that depend on the entire payload.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 12 of 20. Validating fields in isolation works perfectly, right up until the moment your business logic dictates that field B is only allowed to be set if field A has a specific value. When data integrity relies on multiple fields interacting, you need Model-Level Validation Hooks. In Pydantic, you handle interdependent fields using the model validator decorator. Unlike field validators that focus on a single piece of incoming data, a model validator looks at the entire data structure at once. Pydantic offers three modes for this decorator: before, after, and wrap. They determine exactly when your custom logic executes during the parsing lifecycle. Let us apply this to a standard user registration model. You have two fields: password and password repeat. A field-level check can verify length or complexity, but it cannot compare the two. For that, you use a model validator in after mode. The after mode executes once Pydantic has successfully parsed and validated all individual fields. At this stage, your validation function receives the instantiated model itself. You simply write logic that checks if the model's password attribute equals its password repeat attribute. If they differ, you raise a value error. Here is the key insight. When using the after mode, your function must explicitly return the model instance, usually referred to as self, at the end of the method. If you forget to return self, Pydantic will drop your validated data and return nothing. Sometimes you need to intercept data earlier. That is where before mode comes in. A model validator in before mode runs before Pydantic attempts any parsing or type coercion. Instead of a typed model instance, your function receives the raw input, which is typically a dictionary. You use this mode when the raw data structure is messy and needs adjustment before standard validation can even begin. For example, if legacy API requests send password configurations in a nested dictionary, a before validator can extract those strings and flatten them into the top-level keys your Pydantic model expects. The function then returns the modified dictionary, passing it down the chain. The third mode is wrap. This is for total control over the validation lifecycle. A wrap validator takes the raw input data and a handler function. You run your pre-processing logic, explicitly call the handler to trigger Pydantic's internal validation, and then run post-processing on the result. You use this when you need to catch internal Pydantic validation errors, log them to an external system, and perhaps return a modified error message or a default fallback object. Choosing the right mode is just a matter of timing. Use before for shaping raw input, after for comparing strongly typed fields, and wrap for controlling the validation execution itself. If you find these episodes helpful and want to support the show, you can search for DevStoriesEU on Patreon. That is your lot for this one. Catch you next time!
13

Serialization: Dumping Data Safely

3m 39s

Control how your data leaves the system. You will learn the differences between dumping to Python dicts versus JSON strings, and how to exclude unset or default fields.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 13 of 20. Getting valid data into your system is only half the battle. When it is time to return that data to a frontend, you often find your payloads bloated with empty fields and default values that the user never actually provided. Serialization, or dumping data safely, is how you clean up that outward flow. Once your data sits safely inside a Pydantic model, you eventually need to extract it to send it somewhere else. You have two primary ways to do this. The first is calling model dump. This method reads your model and returns a plain Python dictionary. The second is calling model dump json. This method skips the dictionary step and returns a fully formatted JSON string, ready to be sent over the wire. The difference between these two goes slightly deeper than just a dictionary versus a string. It comes down to serialization modes. By default, model dump operates in Python mode. If your model contains a complex type, like a datetime object, the resulting dictionary will still contain a Python datetime object. On the other hand, model dump json operates in JSON mode. JSON does not know what a Python datetime object is, so Pydantic automatically converts it into a standard string representation. Here is the key insight. You can actually force the dictionary output to use JSON mode. If you call model dump and pass the mode argument set to json, Pydantic returns a dictionary where all the complex types have already been translated into basic, JSON-safe formats like strings and integers. That handles the types, but you still have to manage the shape of the payload. Take a user profile model returning data to a frontend. The model might define twenty possible fields. A new user signs up and only provides their name and email. The other eighteen fields fall back to empty strings, nulls, or default avatar URLs. If you dump that model normally, you send all twenty fields to the frontend. To fix this, Pydantic provides three specific keyword arguments you can pass to either dump method. The most precise one is exclude unset. When you set exclude unset to true, Pydantic tracks exactly which fields were populated when the model was created. It will only dump the name and email. The eighteen default fields are completely left out of the dictionary or JSON string. This ensures you never leak fallback data that the user did not actually submit. If you want a slightly different behavior, you can use exclude defaults. This flag tells Pydantic to omit any field that currently matches its default value. It does not care if the user explicitly submitted that default value or if the system filled it in automatically. If the value matches the default, it gets removed from the output. Finally, there is exclude none. Set this to true, and Pydantic will strip out any field where the value is currently none. This is purely a value check and ignores defaults or unset tracking entirely. These exclusion flags give you tight control over your network traffic and API responses. Keep your internal models completely comprehensive, but use your dump methods to ensure your external payloads remain exactly as lean as they need to be. Thanks for tuning in. Until next time!
14

Customizing Serialization Logic

3m 33s

Change how your types are represented on the way out. You will learn how to write custom Field and Model serializers to mutate data during the dumping phase.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 14 of 20. What if you store a date internally as a Python datetime object, but your frontend API contract demands a raw integer Unix timestamp? You could loop over your data right before sending it, but that is messy and prone to error. The cleaner way is Customizing Serialization Logic right inside your Pydantic models. Pydantic gives you decorators to intercept the exact moment a model turns back into a dictionary or JSON. We will start with field serializers. A field serializer targets a specific attribute. To make one, you write a regular method inside your model class and put the field serializer decorator directly above it. You pass the decorator the name of the field you want to modify. Let us use that datetime scenario. You have a model with an attribute called created at, typed as a standard Python datetime. Inside the model, you create a method called serialize created at. The name of the method does not matter. Above it, you place the field serializer decorator, pointing at the created at field. The method takes the datetime value as its input. Inside the method, you call the standard timestamp function on that datetime and return the resulting integer. Now, whenever the model dumps its data, Pydantic intercepts the created at field, runs your custom method, and outputs a clean integer instead of an ISO formatted string. Here is the key insight. Serializers operate in two distinct modes: plain and wrap. The plain mode is the default. When a serializer is in plain mode, Pydantic completely discards its own internal logic for that field and only runs your custom code. It is a full override. But sometimes you need to run the default logic first, and then modify the result. Or maybe you want to catch errors around the default behavior. That is when you use wrap mode. To enable it, you pass mode equals wrap into the decorator. In wrap mode, your method receives a second argument called a handler. This handler is a reference to Pydantic internal serialization logic. You can call the handler to get the default output, inspect it, modify it, or fall back to it if your custom logic fails. That covers single fields. If you need to change the structure of the whole output, you use a model serializer. The setup is almost identical, but the decorator goes above a method that operates on the entire model instance. Instead of receiving a single field value, the method accesses the attributes of the model itself. If you put a model serializer in plain mode, you return a completely new dictionary structure from scratch. If you put it in wrap mode, your method takes a handler argument, just like the field serializer. You call the handler to get the standard model dictionary first. Then you can add new top-level keys, remove private data, or flatten nested structures before returning the final dictionary. The most powerful aspect of these decorators is that they decouple your internal Python types from your external API contracts, keeping validation strict on the way in and formatting precise on the way out. That is all for this one. Thanks for listening, and keep building!
15

Generating JSON Schema from Models

3m 44s

Turn your models into self-documenting API contracts. You will learn how to generate OpenAPI-compliant JSON Schemas and inject examples directly into the schema.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 15 of 20. If your API schemas are already strictly typed in Python, having to maintain a separate YAML file for your OpenAPI documentation is a massive waste of time. You end up maintaining two sources of truth that inevitably drift apart. Generating JSON Schema directly from your models solves this synchronization problem. Every Pydantic model comes with a method called model json schema. When you call this method, Pydantic inspects your model. It reads the fields, the types, the default values, and the validation constraints. It then translates all of that internal Python logic into a standard JSON Schema dictionary. Specifically, Pydantic emits Draft 2020-12 of the JSON Schema specification. This is the part that matters. Because the output adheres to this widely accepted standard, it is natively compatible with OpenAPI. You define your strict validation rules once in Python, and the framework automatically provides the exact schema definition that your external consumers need. There is no manual translation required. The standard schema generation handles types and basic constraints perfectly. But sometimes you need to communicate business logic or specific formats that cannot be inferred from a Python type hint alone. Consider a scenario where you are building a Configuration model for a new service. The model contains a field that accepts a dictionary of custom settings. The frontend team needs to know exactly what a valid payload looks like, rather than just knowing it is a generic object. To solve this, you use a feature called json schema extra. This parameter allows you to inject arbitrary custom metadata directly into the generated JSON Schema. You can use it to add mock examples, custom descriptions, or specific keyword markers that your API gateway might require. You can apply json schema extra at two distinct levels. You can attach it to an individual field, or you can apply it to the entire model. To provide a mock example for that specific settings field, you define your Configuration model. For the complex settings attribute, you assign it a Field function. Inside that Field function, you provide the json schema extra argument. You pass it a dictionary containing the standard JSON Schema key named examples. The value mapped to that key is a list holding your exact mock configuration payload. Alternatively, if you want to document the entire model rather than a single field, you define a model config dictionary on the class. Inside that configuration dictionary, you provide json schema extra with a schema level example. This approach is highly useful when you want to show how multiple fields interact together in a complete request body. When you execute model json schema on your Configuration model, Pydantic builds the standard schema tree. When it reaches your fields or model configuration, it merges your custom examples dictionary directly into the standard output. The frontend tooling reads this schema, parses the examples property, and immediately displays the mock payload to the developers. They know exactly what to send, and your Python code remains the single source of truth. The real power of Pydantic schema generation is that any external tool built for the JSON Schema ecosystem can immediately consume your Python validation rules without custom integration. Thanks for spending a few minutes with me. Until next time, take it easy.
16

RootModel: When Your Payload Isn't a Dictionary

3m 14s

Handle non-standard JSON payloads gracefully. You will discover how RootModel allows you to parse root-level arrays and primitives while retaining BaseModel powers.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 16 of 20. Standard models assume your incoming JSON payload is an object mapped with keys and values. But what happens when an endpoint needs to accept a top-level array directly, or just a standalone string, without wrapping it in a dictionary? That is exactly what RootModel: When Your Payload Isn't a Dictionary is built to handle. When you use a standard base model, Pydantic maps your class attributes to JSON keys. If you send a standard model a raw list, like a JSON array of numbers, the validation fails. It expects a dictionary structure. To get around this, developers often force the client to change the payload. They create a dummy key, perhaps called items, and wrap the array in an object. You end up bending your API design just to make your validation library happy. RootModel eliminates that friction. It is a special Pydantic model designed specifically for payloads where the outermost structure is a list, a tuple, or a primitive type like an integer or a string. Consider a bulk-delete endpoint. You want the client to send a raw JSON array containing integer user IDs, nothing else. To handle this, you import RootModel from Pydantic. You define a new class, perhaps called UserIdList, and have it inherit from RootModel. You parameterize that inheritance with a list of integers. When the raw JSON array arrives at the server, you pass it directly to the model validate method on your UserIdList class. Pydantic accepts the top-level array natively. It iterates through the payload, ensures every single item is a valid integer, and returns a fully validated model instance. Here is the key insight. Even though it validates a flat list, a RootModel provides the exact same interface as a regular model. You still have access to the standard methods. You can call model dump to convert the data back to Python objects, or model dump json to generate a raw string. Because there are no named fields in your payload, you need a way to access the data once it is validated. Pydantic stores the parsed data in a single attribute named exactly root. If you want to loop over those validated user IDs, you simply iterate over the root attribute on your model instance. This mechanism is not restricted to lists. You can define a RootModel for a single string or an integer. If you receive a standalone string payload but need to run it through strict length checks or pattern matching, defining it as a RootModel of type string allows you to attach those validation rules natively. The data remains a simple string in the payload, but gains the full protection of the validation engine. Whenever you find yourself inventing a dictionary key just so Pydantic has something to parse, you are using the wrong tool. Use RootModel to adapt your validation layer to your API contract, rather than altering your API contract to satisfy your validation layer. Thanks for listening. Take care, everyone.
17

Standard Dataclasses vs Pydantic Dataclasses

3m 37s

Bring validation to your native Python classes. You will learn when to use the Pydantic dataclass decorator to retrofit legacy codebases without rewriting everything.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 17 of 20. You have a massive codebase built entirely on standard Python dataclasses, and you realize you desperately need runtime validation. You might think you have to rewrite everything to inherit from a base model. You do not. Today, we are looking at Standard Dataclasses versus Pydantic Dataclasses. Python's standard library provides a dataclass decorator that is excellent for reducing boilerplate. It writes the initialization, representation, and equality methods for you. However, it trusts your inputs blindly. If you declare an age attribute as an integer and pass it a string, the standard dataclass simply accepts the string. It provides type hints for static analysis, but offers zero runtime safety. Pydantic solves this with its own dataclass decorator. It is designed specifically as a drop-in replacement for the standard library version. You keep your exact same class structure. You do not add any base classes. You merely change your import statement to pull the decorator from the pydantic dot dataclasses module instead of the standard library. Consider that legacy codebase built on standard dataclasses. You find a dataclass handling a configuration object. You swap the standard decorator for the Pydantic decorator. Instantly, whenever your application creates that configuration object, Pydantic intercepts the initialization. It reads your existing type hints and enforces them. If an invalid type is passed, Pydantic attempts to coerce it. If coercion fails, it immediately throws a validation error. You gain strict, type-checked assignments with zero structural changes to your inheritance tree. Here is the key insight. If both options give you validation, you must understand the difference between a Pydantic dataclass and a standard BaseModel. They handle the underlying data differently. A BaseModel is fundamentally built around dictionary parsing and provides a wide range of built-in methods for exporting data. A Pydantic dataclass remains a standard Python class at its core, keeping its traditional memory footprint and behavior. Because it remains a standard class, the way Pydantic applies validation changes. When you instantiate a Pydantic dataclass, the arguments are copied. The data passes through Pydantic's core validation engine, which parses and constructs new objects to match your type hints. It does not just reference the original mutable inputs you provided. If you pass a list into a Pydantic dataclass, the validator processes it, validates the internal elements, and assigns a completely new list to the instance. The original input list and the attribute on your dataclass are no longer the same object in memory. This copying behavior ensures your data strictly adheres to the schema without mutating the original input variables. The core utility of the Pydantic dataclass is bridging the gap between standard Python paradigms and rigorous validation. If you need a clean migration path for legacy code that already uses standard dataclasses, simply swap the decorator to gain instant type safety while preserving all your existing class semantics. If you enjoy the podcast and want to help support the show, you can search for DevStoriesEU on Patreon. That is all for this one. Thanks for listening, and keep building!
18

Fine-Tuning Model Configuration

3m 20s

Control the strictness of your entire model. You will learn how to use the ConfigDict to forbid extra attributes, freeze instances, and validate assignments.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 18 of 20. By default, Pydantic silently ignores any extra JSON keys you send it. In a strict API, swallowing unknown data without warning is a massive security risk. To fix this, you need Fine-Tuning Model Configuration. To change how a model behaves globally, you define a class attribute named model config. You assign it a ConfigDict, which is a typed dictionary imported directly from Pydantic. You place this assignment right inside your model alongside your field definitions. Because ConfigDict is typed, your code editor will catch typos if you try to pass an invalid configuration option. Whatever rules you put in this dictionary will dictate how the entire model parses, validates, and stores data. Let us build a strict security settings model for an application. A malicious client might send a JSON payload with unexpected fields, perhaps trying to inject an admin flag or overwrite a hidden parameter. By default, Pydantic sets the extra fields behavior to ignore. It reads the fields it expects, silently drops the unknown data entirely, and creates the model. To close this loophole, you add the extra parameter to your ConfigDict and set its value to the string forbid. Now, if a client sends an unexpected key, Pydantic immediately throws a validation error. The request fails outright, explicitly rejecting the bad payload before your application logic even processes it. That handles the boundary between the outside world and your system. But what happens inside your system after the model is created? Pydantic normally only validates data during initialization. If another developer writes code that changes an attribute on an existing model instance later on, Pydantic stays out of the way. You could accidentally assign a raw string to an integer field, and the model would accept it. To prevent this, you can set validate assignment to true inside your ConfigDict. With this enabled, any time an attribute is modified in memory, Pydantic intercepts the change and runs the exact same validation logic it uses during creation. Sometimes, even validating mutations is too permissive for security configurations. You might want an absolute guarantee that the settings cannot be modified in memory by any part of your code once they pass the initial check. This is where it gets interesting. You can set the frozen parameter to true in your model configuration. This makes the entire model instance immutable. If any downstream function attempts to update a field, Pydantic raises an error. A frozen model acts exactly like a Python tuple. Because it cannot change, a frozen model is completely safe to share across different parts of your application, and it can even be safely used as a key in a Python dictionary or an in-memory cache. By stacking these configuration options inside your ConfigDict, you shift a model from a flexible data parser into an absolute, unyielding boundary. That is all for this one. Thanks for listening, and keep building!
19

Application Configuration with Pydantic Settings

3m 36s

Manage your environment variables like a pro. You will learn how the pydantic-settings package automates the parsing of secrets, dot-env files, and prefixes.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 19 of 20. Stop pulling strings out of the operating system and crossing your fingers that they cast cleanly to an integer. You have probably written dozens of utility files wrapping standard environment variable calls just to prevent a missing configuration value from crashing your application at runtime. Application Configuration with Pydantic Settings replaces all of that boilerplate. While standard Pydantic handles general data validation, managing environment variables requires installing the separate pydantic-settings package. This package provides a specific class called BaseSettings. Instead of manually fetching variables and writing custom parsing logic, you declare a class that inherits from BaseSettings. You define your configuration fields exactly like a standard Pydantic model, complete with Python type hints and default values. When you instantiate this class, Pydantic automatically reads your system environment variables. It maps the variable names to your class attributes, case-insensitively by default. If you defined a database timeout field as an integer, and the environment variable provides a string, Pydantic coerces that string into an actual integer object. If the environment variable contains arbitrary text instead of a number, Pydantic raises a clear validation error immediately upon application startup. Your program fails fast, rather than crashing unpredictably later during an active database call. In larger environments, global variable names frequently collide. You might have multiple services running on the same host, all looking for a variable simply named database URL. BaseSettings solves this by allowing you to define an environment prefix in the model configuration. If you set the prefix to app underscore, Pydantic stops looking for exact field name matches. Instead, to populate a field named database URL, it specifically targets the environment variable app underscore database URL. Your Python application code still accesses the property as just database URL, entirely hiding the operating system prefix from your business logic. Here is the key insight. You do not have to flatten your Python objects just to read environment variables. Configuration often grows complex, requiring nested data structures. You might have a main settings class that includes a dedicated sub-model for database settings, and another for logging. Pydantic supports resolving these nested structures using a double underscore convention. If your main settings class has a field called database, which itself points to a model containing a timeout field, Pydantic will search for an environment variable named database double underscore timeout. If you are using a prefix, it naturally prepends that as well, resulting in something like app underscore database double underscore timeout. This mechanism allows you to maintain strictly typed, hierarchical configuration objects in your source code while still mapping cleanly to standard, flat environment variables. The true value of BaseSettings is not just eliminating boilerplate code, it is guaranteeing that if your application successfully starts, its entire configuration state is fully present, explicitly typed, and entirely safe to consume. Thanks for listening, happy coding everyone!
20

Under the Hood: Custom Core Schemas

3m 40s

This is the final episode of the series! Take ultimate control of the validation engine. You will learn how to write a __get_pydantic_core_schema__ method to teach the Rust core how to handle completely alien Python objects.

Download
Hi, this is Alex from DEV STORIES DOT EU. Pydantic: Data Validation, episode 20 of 20. You pull in a rigid, undocumented library from another team, and you need to pass its objects through your strict validation pipeline. The object has absolutely no concept of Pydantic, and you cannot touch its source code. When annotations and simple validators are not enough, you have to talk directly to the engine — and that means opening up the hood to write custom core schemas. Normally, Pydantic figures out how to validate your data by reading type hints. When you hand it an alien object, it hits a wall. To fix this, you define a method called dunder get pydantic core schema. This method acts as a direct translation layer. It bypasses the high-level Python wrappers and feeds instructions straight into Pydantic Core, the underlying Rust engine that handles the actual validation logic. When you implement this method, it receives the source type and a handler. The handler functions just like middleware in a web framework. You do not have to write the entire validation schema from scratch. You can ask the handler to generate the default schema for a specific type, and then wrap your own custom logic around that output. Take the legacy database connection wrapper as an example. Suppose the only way to initialize this object is by passing it a specific integer, like a connection ID. You want Pydantic to accept an integer from an API request, validate it, and hand you back the fully instantiated connection object. Because you cannot modify the legacy class directly, you define a custom type using Python annotations. Inside that annotation, you provide your custom core schema method. Here is the key insight. Instead of writing raw dictionaries, you use Pydantic's core schema module, which provides helper functions to build these structures safely. First, you ask the handler to build a standard integer schema. Next, you build a chain schema. You instruct the engine to run the standard integer validation first. If the input is genuinely an integer, the engine passes it into a custom Python function you provide. This function takes the connection ID, initializes the legacy database wrapper, and returns the object. Finally, you append an instance check schema to the chain, ensuring the final output is exactly the legacy class you expect. This is where it gets interesting. You return this nested structure to Pydantic. Under the hood, Pydantic takes these nested dictionary definitions and compiles them into a native Rust execution graph. You have programmed the Rust engine from Python, telling it step-by-step how to ingest a raw integer, validate it, and safely construct an alien object at maximum speed. This represents the absolute lowest level of integration Pydantic offers, granting total control over the validation tree without sacrificing performance. Since this is our final episode, I strongly encourage you to dive into the official Pydantic documentation and try building a custom core schema hands-on. It is the best way to solidify how the engine actually thinks. You can also visit dev stories dot eu to suggest topics you want covered in future series. That is it for today. Thanks for listening — go build something cool.