Models

A collection is plain-dict by default. Bind it to a type and it converts on the way down and rebuilds instances on the way up.

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

users = db.collection("users", User)
users["alice"] = User(name="alice", age=30)
users["alice"]                  # User(name='alice', age=30)

pydantic works identically:

import pydantic

class User(pydantic.BaseModel):
    name: str
    age: int

users = db.collection("users", User)
users["alice"] = User(name="alice", age=30)
users["alice"].age              # 30

Binding lives on the handle

Nothing about the type is written to storage. The binding is a property of the handle you are holding, so the same data is readable as dicts at any time:

db["users"]["alice"]            # {'name': 'alice', 'age': 30}

That has a practical consequence worth planning around: renaming or moving a class cannot break stored data, because the class name was never stored. It also means nothing validates documents that were written through an unbound handle.

Models are accepted anywhere

You do not need a bound handle to write a model. Any collection accepts one and flattens it:

db["users"]["bob"] = User(name="bob", age=41)
db["users"]["bob"]              # {'name': 'bob', 'age': 41} — dict, unbound

Binding is what you need to get instances back.

Inside transactions

collection() exists on transactions too, so model writes can be atomic:

with db.transaction() as tx:
    tx.collection("users", User)["carol"] = User(name="carol", age=22)

Iteration

Bound handles rebuild instances everywhere a document is returned — values(), items(), first(), last(), range(), and prefix():

[u.name for _, u in users.items()]
users.first()                   # ('alice', User(name='alice', age=30))

How detection works

MonaDB never imports pydantic. It recognizes both shapes by duck-typing:

ShapeWritten withRead with
has model_dump / model_validatemodel_dump()Model.model_validate(doc)
dataclasses.is_dataclassdataclasses.asdict()Model(**doc)

Anything else raises TypeError when you try to bind it. Since pydantic is matched structurally, an attrs class or any other type exposing the same two methods works too.