Cheatsheet for pydantic.dataclasses vs BaseModel.

Basic

from pydantic.dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str

User(id=1, name="Alice")              # validates

Looks like stdlib @dataclass + Pydantic validation.

Config

from pydantic.dataclasses import dataclass
from pydantic import ConfigDict

@dataclass(config=ConfigDict(strict=True, extra="forbid"))
class User:
    id: int
    name: str

Defaults

@dataclass
class User:
    id: int
    name: str = "anonymous"
    tags: list[str] = Field(default_factory=list)

Same defaults as BaseModel.

Validators

from pydantic import field_validator

@dataclass
class User:
    age: int
    
    @field_validator("age")
    @classmethod
    def positive(cls, v):
        if v < 0: raise ValueError("non-negative")
        return v

Works the same as BaseModel.

Validate at runtime

from pydantic import TypeAdapter

UserAdapter = TypeAdapter(User)
user = UserAdapter.validate_python({"id": 1, "name": "x"})

Or call constructor directly: User(id=1, name="x").

Convert from stdlib dataclass

from dataclasses import dataclass as std_dataclass
from pydantic import TypeAdapter

@std_dataclass
class StdUser:
    id: int
    name: str

# Validate stdlib dataclass via TypeAdapter
adapter = TypeAdapter(StdUser)
user = adapter.validate_python({"id": 1, "name": "x"})

Works for stdlib @dataclass (no Pydantic-specific behavior, just type checking).

attrs comparison

from attrs import define

@define
class User:
    id: int
    name: str

user = User(id=1, name="Alice")       # no validation by default

attrs: no validation; configurable via converters / validators. Pydantic dataclass: validation by default.

When to use pydantic.dataclass

  • You want stdlib @dataclass syntax + Pydantic validation.
  • Mixing with libraries that expect dataclasses.

When NOT:

  • New code: BaseModel is the canonical Pydantic style.
  • Need rich serialization features (model_dump, model_dump_json, computed_field) — BaseModel.

Serialization

from dataclasses import asdict

user = User(id=1, name="x")
asdict(user)                       # works (stdlib)
# But Pydantic features not directly:
TypeAdapter(User).dump_python(user)
TypeAdapter(User).dump_json(user)

Mixed with BaseModel

@dataclass
class Address:
    street: str
    city: str

class User(BaseModel):
    name: str
    address: Address

Pydantic validates the Address via its dataclass schema.

Limitations

  • model_config is config arg here.
  • model_validateTypeAdapter(...).validate_python(...) or direct constructor.
  • Some BaseModel methods unavailable.

Performance

Same Rust core. Slightly less ergonomic API.

Common mistakes

  • Expecting model_dump() etc. on dataclass — use TypeAdapter or stdlib asdict.
  • Mixing pydantic.dataclass with BaseModel and getting confused validation behavior.
  • Using pydantic.dataclass when BaseModel would do — adds friction.

Recommendation

Use BaseModel by default. Reach for pydantic.dataclass only when:

  • An external API expects a stdlib-compatible dataclass.
  • Migration path from stdlib @dataclass codebase.

Read this next

If you want my BaseModel-vs-dataclass comparison code, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .