Skip to content

Pydantic integration

Use Pydantic models with DynamoDB. If you already have Pydantic models in your application, you can add DynamoDB persistence without rewriting them.

Key features

  • Use existing Pydantic models
  • Automatic validation
  • Type coercion
  • All pydynox methods available

Getting started

Installation

Install pydynox with Pydantic support:

pip install pydynox[pydantic]

Basic usage

Use the @dynamodb_model decorator on a Pydantic model:

"""Basic Pydantic integration example."""

from pydantic import BaseModel
from pydynox import DynamoDBClient, dynamodb_model

# Create a client
client = DynamoDBClient(region="us-east-1")


# Define your Pydantic model with the decorator
@dynamodb_model(table="users", hash_key="pk", range_key="sk", client=client)
class User(BaseModel):
    pk: str
    sk: str
    name: str
    age: int = 0


# Create and save
user = User(pk="USER#1", sk="PROFILE", name="John", age=30)
user.save()

# Get by key
user = User.get(pk="USER#1", sk="PROFILE")
print(user.name)  # John

# Update
user.update(name="Jane", age=31)

# Delete
user.delete()

The decorator adds these methods to your model:

  • save() - Save to DynamoDB
  • get() - Get by key
  • delete() - Delete from DynamoDB
  • update() - Update specific fields
  • _set_client() - Set client after creation

Your Pydantic model works exactly as before - validation, serialization, and all other Pydantic features still work.

With range key

Add a range key for composite keys:

@dynamodb_model(table="users", hash_key="pk", range_key="sk", client=client)
class User(BaseModel):
    pk: str
    sk: str
    name: str

Pydantic validation

All Pydantic validation works. Invalid data raises ValidationError before anything is saved:

from pydantic import BaseModel, EmailStr, Field

@dynamodb_model(table="users", hash_key="pk", client=client)
class User(BaseModel):
    pk: str
    name: str = Field(min_length=1, max_length=100)
    email: EmailStr
    age: int = Field(ge=0, le=150)

# This raises ValidationError - email is invalid
user = User(pk="USER#1", name="", email="not-an-email", age=-1)

Advanced

Configuration options

Option Type Description
table str DynamoDB table name (required)
hash_key str Field name for partition key (required)
range_key str Field name for sort key (optional)
client DynamoDBClient Client instance (optional, can set later)

Setting client later

You can set the client after defining the model:

@dynamodb_model(table="users", hash_key="pk")
class User(BaseModel):
    pk: str
    name: str

# Later, when you have the client
client = DynamoDBClient(region="us-east-1")
User._set_client(client)

# Now you can use it
user = User.get(pk="USER#1")

Alternative: from_pydantic function

If you prefer not to use decorators:

from pydynox.integrations.pydantic import from_pydantic

class User(BaseModel):
    pk: str
    sk: str
    name: str

UserDB = from_pydantic(User, table="users", hash_key="pk", range_key="sk", client=client)
user = UserDB(pk="USER#1", sk="PROFILE", name="John")
user.save()

Why use Pydantic integration?

Benefits of using Pydantic with pydynox:

  • Validation - Pydantic validates data before it reaches DynamoDB
  • Type coercion - Strings become ints, etc.
  • IDE support - Better autocomplete than raw dicts
  • Reuse models - Use the same models for API and database
  • JSON Schema - Auto-generated schemas for documentation

Using Pydantic validators

Use Pydantic's @field_validator and @model_validator for validation:

from pydantic import BaseModel, field_validator, model_validator

@dynamodb_model(table="users", hash_key="pk", client=client)
class User(BaseModel):
    pk: str
    email: str
    name: str

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email")
        return v.lower()  # Also normalize

    @model_validator(mode="before")
    @classmethod
    def set_defaults(cls, data: dict) -> dict:
        # Add computed fields, defaults, etc.
        return data

Pydantic vs dataclass

Choose Pydantic when:

  • You need validation
  • You want type coercion
  • You're already using Pydantic in your app

Choose dataclass when:

  • You want zero dependencies
  • You don't need validation
  • Simple data structures are enough

TTL with Pydantic

For TTL, use a datetime field:

from datetime import datetime, timedelta, timezone

@dynamodb_model(table="sessions", hash_key="pk", client=client)
class Session(BaseModel):
    pk: str
    user_id: str
    expires_at: datetime

    @classmethod
    def create(cls, user_id: str, hours: int = 24) -> "Session":
        return cls(
            pk=f"SESSION#{uuid4()}",
            user_id=user_id,
            expires_at=datetime.now(timezone.utc) + timedelta(hours=hours),
        )

    @property
    def is_expired(self) -> bool:
        return datetime.now(timezone.utc) > self.expires_at

Note

Remember to enable TTL on your DynamoDB table and set the attribute name to expires_at.