Skip to content

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