A fast DynamoDB ORM for Python with a Rust core
Project description
pydynox 🐍⚙️
A fast DynamoDB ORM for Python with a Rust core.
⚠️ Alpha Software: This project is in early development. The API may change before v1.0. Use in production at your own risk. Contributions are welcome!
Why "pydynox"?
Py(thon) + Dyn(amoDB) + Ox(ide/Rust)
Features
- Simple class-based API like PynamoDB
- Fast serialization with Rust
- Batch operations with auto-splitting
- Transactions
- Global Secondary Indexes
- Async support
- Pydantic integration
Performance
pydynox is faster than PynamoDB and boto3 in all operations. Benchmarks run against moto (local DynamoDB mock):
| Operation | pydynox | PynamoDB | boto3 | vs PynamoDB | vs boto3 |
|---|---|---|---|---|---|
| batch_get (100 items) | 2.55ms | 4.24ms | 3.92ms | 1.66x faster | 1.54x faster |
| batch_write (100 items) | 7.74ms | 9.53ms | 9.68ms | 1.23x faster | 1.25x faster |
| get_item (10x) | 13.83ms | 16.57ms | 16.86ms | 1.20x faster | 1.22x faster |
| put_item (10x) | 14.13ms | 16.78ms | 16.80ms | 1.19x faster | 1.19x faster |
| query (100 items) | 12.92ms | 14.88ms | 14.79ms | 1.15x faster | 1.15x faster |
| update_item (10x) | 19.04ms | 38.15ms | 21.81ms | 2.00x faster | 1.15x faster |
| delete_item (10x) | 26.96ms | 33.07ms | 32.93ms | 1.23x faster | 1.22x faster |
Highlights:
- batch_get: 66% faster than PynamoDB
- update_item: 2x faster than PynamoDB
Run benchmarks yourself:
uv run maturin develop --release
uv run pytest benchmark/benchmark.py -v --benchmark-only
Installation
pip install pydynox
For Pydantic support:
pip install pydynox[pydantic]
Quick Start
Define a Model
from pydynox import Model, String, Number, Boolean, List
class User(Model):
class Meta:
table = "users"
pk = String(hash_key=True)
sk = String(range_key=True)
name = String()
email = String()
age = Number(default=0)
active = Boolean(default=True)
tags = List(String)
CRUD Operations
# Create
user = User(pk="USER#123", sk="PROFILE", name="John", email="john@test.com")
user.save()
# Read
user = User.get(pk="USER#123", sk="PROFILE")
# Update
user.name = "John Doe"
user.save()
# Delete
user.delete()
Query
from pydynox import Condition
# Simple query
users = User.query(pk="USER#123")
# With filters
users = User.query(pk="USER#123") \
.where(Condition.begins_with("sk", "ORDER#")) \
.where(Condition.gt("age", 18)) \
.exec()
# Iterate (auto pagination)
for user in users:
print(user.name)
Conditions
from pydynox import Condition
# Save with condition
user.save(condition=Condition.not_exists("pk"))
# Delete with condition
user.delete(condition=Condition.eq("version", 5))
# Combine conditions
user.save(
condition=Condition.not_exists("pk") | Condition.eq("version", 1)
)
Available conditions:
Condition.eq(field, value)- equalsCondition.ne(field, value)- not equalsCondition.gt(field, value)- greater thanCondition.gte(field, value)- greater than or equalCondition.lt(field, value)- less thanCondition.lte(field, value)- less than or equalCondition.exists(field)- attribute existsCondition.not_exists(field)- attribute does not existCondition.begins_with(field, prefix)- string starts withCondition.contains(field, value)- string or list containsCondition.between(field, low, high)- value in range
Atomic Updates
from pydynox import Action
# Simple set
user.update(name="New Name", email="new@test.com")
# Increment a number
user.update(Action.increment("age", 1))
# Append to list
user.update(Action.append("tags", ["verified"]))
# Remove field
user.update(Action.remove("temp_field"))
# Combine with condition
user.update(
Action.increment("age", 1),
condition=Condition.eq("status", "active")
)
Batch Operations
# Batch write
with User.batch_write() as batch:
batch.save(user1)
batch.save(user2)
batch.delete(user3)
# Batch get
users = User.batch_get([
("USER#1", "PROFILE"),
("USER#2", "PROFILE"),
])
Global Secondary Index
from pydynox import GlobalIndex
class User(Model):
class Meta:
table = "users"
pk = String(hash_key=True)
sk = String(range_key=True)
email = String()
email_index = GlobalIndex(hash_key="email")
# Query on index
users = User.email_index.query(email="john@test.com")
Transactions
with User.transaction() as tx:
tx.save(user1)
tx.delete(user2)
tx.update(user3, Action.increment("age", 1))
Async Support
# All methods work with await
user = await User.get(pk="USER#123", sk="PROFILE")
await user.save()
async for user in User.query(pk="USER#123"):
print(user.name)
Pydantic Integration
from pydynox import dynamodb_model
from pydantic import BaseModel, EmailStr
@dynamodb_model(table="users", hash_key="pk", range_key="sk")
class User(BaseModel):
pk: str
sk: str
name: str
email: EmailStr
age: int = 0
# All pydynox methods available
user = User(pk="USER#123", sk="PROFILE", name="John", email="john@test.com")
user.save()
Table Management
# Create table
User.create_table()
# Create with custom capacity
User.create_table(read_capacity=10, write_capacity=5)
# Create with on-demand billing
User.create_table(billing_mode="PAY_PER_REQUEST")
# Check if table exists
if not User.table_exists():
User.create_table()
# Delete table
User.delete_table()
Documentation
Full documentation: https://pydynox.readthedocs.io
License
Apache 2.0 License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file pydynox-0.1.0.tar.gz.
File metadata
- Download URL: pydynox-0.1.0.tar.gz
- Upload date:
- Size: 141.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0e995dee4b4d23feef2bf99a054e12d12279b8bc868b82b06bb513061c54358
|
|
| MD5 |
020522301d52808b8b3a5e6ad6d8f9db
|
|
| BLAKE2b-256 |
7dd4554b59dc49953ba8ebcb4290bf5331a32b674919d36f8640022c4edc85cb
|
Provenance
The following attestation bundles were made for pydynox-0.1.0.tar.gz:
Publisher:
release.yml on leandrodamascena/pydynox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydynox-0.1.0.tar.gz -
Subject digest:
d0e995dee4b4d23feef2bf99a054e12d12279b8bc868b82b06bb513061c54358 - Sigstore transparency entry: 780752263
- Sigstore integration time:
-
Permalink:
leandrodamascena/pydynox@88bfe344c8a6b08bd9fc675be35ef30af131d997 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/leandrodamascena
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@88bfe344c8a6b08bd9fc675be35ef30af131d997 -
Trigger Event:
workflow_dispatch
-
Statement type: