pydynox
An async-first DynamoDB ORM for Python with a Rust core.
📢 1.0 Release: March 2-6, 2026
The API is stable and fully async. We're collecting feedback before the official release. Try it out and let us know what you think!
The problem
DynamoDB's API is verbose. Every value needs a type annotation:
# boto3
response = table.put_item(Item={
'pk': {'S': 'USER#123'},
'name': {'S': 'John'},
'age': {'N': '30'},
'tags': {'L': [{'S': 'admin'}, {'S': 'active'}]}
})
This gets old fast. You write the same boilerplate for every operation, and typos in type annotations ('S' vs 'N') cause runtime errors.
The solution
pydynox gives you a Pythonic API:
# pydynox
user = User(pk="USER#123", name="John", age=30, tags=["admin", "active"])
await user.save()
You define models with typed attributes. pydynox handles serialization, validation, and all the DynamoDB quirks.
Why Rust?
The slow part of any ORM is serialization - converting Python objects to DynamoDB format and back. Pure Python ORMs do this with loops and dictionary operations, which is slow.
pydynox does serialization in Rust. Even for a single item, you get faster serialization and the GIL is released during network calls. For batch operations with thousands of items, the difference is 10-50x.
GIL-free async
Python has a Global Interpreter Lock (GIL). Only one thread runs Python code at a time. When you call await on a network operation, Python should be free to run other coroutines. But if the library holds the GIL while waiting, nothing else can run.
pydynox releases the GIL before making network calls. The Rust core uses tokio (an async runtime for Rust) to handle the actual HTTP requests. When you await a pydynox operation, PyO3's future_into_py bridges Python's asyncio with tokio, and the GIL is released during the entire network call.
Python Rust (tokio) DynamoDB
| | |
|-- await user.save() --->| |
| (GIL released) |-- HTTP request --------->|
| | |
| (other coroutines | (waiting) |
| can run here) | |
| |<-- HTTP response --------|
|<-- result --------------| |
| (GIL reacquired) | |
This means your FastAPI or aiohttp app can handle other requests while waiting for DynamoDB.
Python 3.13+ free-threaded mode
Python 3.13 introduced experimental free-threaded mode (no GIL), and 3.14 improves it. Even with free-threading, pydynox benefits from having the heavy work in Rust - serialization runs in parallel without competing for Python's interpreter. When free-threaded Python becomes mainstream, pydynox will work even better.
Built on AWS SDK for Rust
pydynox uses the official AWS SDK for Rust under the hood.
What this means for you:
- Official support - The SDK is maintained by AWS, not a third-party library
- Correct behavior - Retry logic, error handling, and edge cases follow AWS best practices
- Future-proof - As DynamoDB evolves, the SDK evolves with it
You get the ergonomics of a Python ORM with the reliability of an AWS-maintained SDK.
Key features
pydynox does what you'd expect from a DynamoDB ORM: models, CRUD, queries, batch operations, transactions, and indexes. The interesting parts are:
Async-first - Methods are async by default. Use sync_ prefix for sync code.
Rust core - Serialization happens in Rust. This matters for batch operations and queries with thousands of items.
Auto pagination - Query and scan iterate through all pages automatically. No more LastEvaluatedKey loops.
Rate limiting - Built-in RCU/WCU control. No more throttling surprises.
Pydantic integration - Use your existing Pydantic models with a decorator. Validation included.
Memory backend for tests - Test your code without DynamoDB. Just add a pytest fixture.
Lifecycle hooks - Run validation or logging before/after any operation.
Field encryption - KMS encryption for sensitive attributes. Transparent to your code.
S3 attribute - Store files larger than 400KB. Upload on save, download on demand.
Get started
Ready to try it? Head to Getting started for installation and your first model.
Guides
Core
| Guide | Description |
|---|---|
| Getting started | Installation and first model |
| Client | Configure the DynamoDB client |
| Models | Attributes, keys, defaults, and CRUD |
| Attributes | All attribute types |
| Query | Query items with conditions |
| Scan | Scan and count items |
| Indexes | GSI and LSI |
| Conditions | Filter and conditional writes |
Operations
| Guide | Description |
|---|---|
| Async | Async-first API |
| Batch | Batch get and write |
| Transactions | All-or-nothing writes |
| Tables | Create and manage tables |
| Atomic updates | Increment, append, remove |
Features
| Guide | Description |
|---|---|
| Hooks | Before/after operation hooks |
| Rate limiting | RCU/WCU control |
| TTL | Auto-expire items |
| Optimistic locking | Version-based concurrency |
| Encryption | KMS field encryption |
| S3 attribute | Large file storage |
| Projections | Fetch specific fields |
| Testing | Memory backend for tests |
Integrations
| Guide | Description |
|---|---|
| Pydantic | Use Pydantic models |
| Dataclass | Use dataclasses |
Reference
| Guide | Description |
|---|---|
| Exceptions | Error handling |
| IAM permissions | Required AWS permissions |
| Observability | Logging and metrics |