Dataclasses vs Pydantic

RoadmapsPython

Overview

Standard library `@dataclass` generates boilerplate methods (like `__init__`, `__repr__`) for storing structured data, but it does not strictly enforce types at runtime. Pydantic's `BaseModel` does data parsing, type coercion, and strict validation. Mental model: Dataclasses are lightweight 'storage boxes' trusting the developer to provide correct types. Pydantic models are 'bouncers' that actively inspect, cast, and reject invalid data coming from the outside world. A common edge-case trap is that Pydantic only validates data during initialization. Mutating fields later bypasses checks: class User(BaseModel): age: int u = User(age=20) u.age = 'twenty' # Silently breaks the type contract! To catch this, you must explicitly enable `validate_assignment` in the model configuration.

Choosing the right tool prevents performance overhead when validation isn't needed, and prevents bugs when dealing with untrusted I/O data (like JSON from a web request).

Where used: FastAPI request bodies (Pydantic), Internal application state (`@dataclass`), Environment variable parsing (Pydantic)

Why learn this

Code walkthrough

from pydantic import BaseModel

class Settings(BaseModel):
    port: int

s = Settings(port='8080')
print(repr(s.port))

Focus: `s = Settings(port='8080')` causes Pydantic to parse the string `'8080'` and coerce it into the integer `8080`.

Aha moment

from pydantic import BaseModel, ValidationError

class Config(BaseModel):
    timeout: int

try:
    c = Config(timeout='five')
    print(c.timeout)
except ValidationError:
    print('Validation Failed!')

Prediction: Will this code crash with a standard Python TypeError or print a message?

Common guess: It will crash with a `TypeError`.

It prints `'Validation Failed!'`. Unlike standard Python types or dataclasses, Pydantic actively intercepts the instantiation, catches the type mismatch, and raises its own rich `ValidationError`.

Common mistakes

Glossary

type coercion
Automatically converting a value from one data type to another, like turning the string `'5'` into the integer `5`, e.g. `int('5')`.
overhead
The extra computing power or time required to run a specific feature, like data validation, e.g. Pydantic checking types on `User()`.

Recall questions

Understanding checks

What is printed when this code is executed?

'123'

Standard dataclasses do not perform type validation or coercion at runtime. The string `'123'` is stored exactly as passed, even though the type hint is `int`.

If you receive a JSON payload from an external API, which library should you use to load it into a Python object, and why?

Pydantic.

Because external APIs are untrusted I/O. Pydantic will validate the types and coerce data (e.g., parsing ISO datetime strings into `datetime` objects) or raise a `ValidationError`, whereas a dataclass will blindly accept bad data.

Practice tasks

Upgrade to Validated Models

Given this standard dataclass that blindly accepts bad data, modify it to be a Pydantic `BaseModel` so that it coerces types and validates inputs.

Challenge

Choosing the Right Tool

Create two classes: an internal, unvalidated `InternalState` using `@dataclass` (with field `status: str`), and an external, validated `Payload` using Pydantic's `BaseModel` (with field `amount: float`).

Return to Python Roadmap