Getting started
This guide walks you through installing pydynox and creating your first model. By the end, you'll have a working DynamoDB model with CRUD operations.
Key features
- Install with pip or uv
- Define models with typed attributes
- CRUD operations with simple methods
- Local development with DynamoDB Local
Installation
To verify the installation:
import pydynox
print(pydynox.__version__)
# 0.1.0
# For detailed version info
print(pydynox.version_info())
# pydynox version: 0.1.0
# python version: 3.11.0
# platform: macOS-14.0-arm64
# related packages: boto3-1.34.0 pydantic-2.5.0
Your first model
Let's create a simple User model. A model is a Python class that represents items in a DynamoDB table.
from pydynox import Model, ModelConfig
from pydynox.attributes import BooleanAttribute, NumberAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(partition_key=True)
sk = StringAttribute(sort_key=True)
name = StringAttribute()
age = NumberAttribute(default=0)
active = BooleanAttribute(default=True)
Here's what each part does:
model_config = ModelConfig(table="users")- Configuration for the model.tableis the DynamoDB table name.pk = StringAttribute(partition_key=True)- The partition key. Every item needs one.sk = StringAttribute(sort_key=True)- The sort key. Optional, but useful for complex access patterns.- Other attributes - Regular fields with their types and optional defaults.
Async-first
Python async is growing. FastAPI, aiohttp, Starlette - modern web frameworks are async. pydynox follows this trend.
Every async method has a sync version with sync_ prefix:
| Async (default) | Sync |
|---|---|
await user.save() |
user.sync_save() |
await User.get() |
User.sync_get() |
await user.delete() |
user.sync_delete() |
async for x in User.query() |
for x in User.sync_query() |
Both versions release the GIL during network calls. The difference: async lets you run multiple operations at the same time with asyncio.gather(). Sync runs one after another.
For the full story on why async matters and how pydynox handles it, see Async-first.
Basic operations
Now let's use the model to work with DynamoDB. pydynox uses an async-first API - methods without prefix are async (default), methods with sync_ prefix are sync.
"""Example: CRUD operations (async - default)."""
import asyncio
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(partition_key=True)
sk = StringAttribute(sort_key=True)
name = StringAttribute()
age = NumberAttribute(default=0)
async def main():
# Create
user = User(pk="USER#123", sk="PROFILE", name="John", age=30)
await user.save()
# Read
user = await User.get(pk="USER#123", sk="PROFILE")
if user:
print(user.name) # John
# Update - full
user.name = "Jane"
await user.save()
# Update - partial
await user.update(name="Jane", age=31)
# Delete
await user.delete()
if __name__ == "__main__":
asyncio.run(main())
"""Example: CRUD operations (sync - use sync_ prefix)."""
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(partition_key=True)
sk = StringAttribute(sort_key=True)
name = StringAttribute()
age = NumberAttribute(default=0)
# Create
user = User(pk="USER#123", sk="PROFILE", name="John", age=30)
user.sync_save()
# Read
user = User.sync_get(pk="USER#123", sk="PROFILE")
if user:
print(user.name) # John
# Update - full
user.name = "Jane"
user.sync_save()
# Update - partial
user.sync_update(name="Jane", age=31)
# Delete
user.sync_delete()
Create
Instantiate your model and call save():
user = User(pk="USER#123", sk="PROFILE", name="John", age=30)
await user.save() # async
user.sync_save() # sync
Read
Use get() with the key attributes:
# Async
user = await User.get(pk="USER#123", sk="PROFILE")
if user:
print(user.name)
# Sync
user = User.sync_get(pk="USER#123", sk="PROFILE")
Update
Change attributes and save, or use update() for partial updates:
# Full update (async)
user.name = "Jane"
await user.save()
# Partial update (async)
await user.update(name="Jane", age=31)
# Sync versions
user.sync_save()
user.sync_update(name="Jane", age=31)
Delete
Call delete() on an instance:
Configuration
ModelConfig configures how your model connects to DynamoDB:
from pydynox import Model, ModelConfig
class User(Model):
model_config = ModelConfig(
table="users", # Required - table name
region="us-east-1", # Optional - AWS region
endpoint_url=None, # Optional - for local testing
)
Local development
Memory backend (recommended for tests)
pydynox has a built-in memory backend. No Docker, no setup - just a pytest fixture:
import pytest
from pydynox.testing import memory_backend
@pytest.fixture
def dynamo():
with memory_backend():
yield
def test_create_user(dynamo):
user = User(pk="USER#123", name="John")
user.sync_save()
found = User.sync_get(pk="USER#123")
assert found.name == "John"
The memory backend is fast and isolated. Each test starts with a clean slate.
For more details, see Testing.
DynamoDB Local
For integration tests that need real DynamoDB behavior, use DynamoDB Local:
Then point your model to it:
from pydynox import Model, ModelConfig
class User(Model):
model_config = ModelConfig(
table="users",
endpoint_url="http://localhost:8000",
)
Next steps
Now that you have the basics working:
- Models - Learn about all attribute types and options
- Batch operations - Work with multiple items efficiently
- Rate limiting - Control throughput to avoid throttling
- Lifecycle hooks - Add validation and logging