coodie
The modern Pydantic-based ODM for Cassandra & ScyllaDB
Cassandra + Beanie (hoodie) = coodie 🧥
📖 Documentation • 🚀 Quick Start • 🤝 Contributing • 📋 Changelog
Define your data models as Python classes, and coodie handles schema synchronization, serialization, and query building — with both sync and async APIs.
✨ Feature Highlights
|
🧬 Pydantic v2 Models — full type-checking & validation |
🔄 Automatic Schema Sync — |
🔍 How Does coodie Compare?
| Feature | coodie | beanie | cqlengine |
|---|---|---|---|
| Database | Cassandra / ScyllaDB | MongoDB | Cassandra |
| Schema Definition | Pydantic v2 BaseModel |
Pydantic v2 BaseModel |
Custom columns.* classes |
| Type Hints | ✅ Native Annotated[] |
✅ Native Pydantic | ❌ No type hints |
| Async Support | ✅ First-class | ✅ First-class | ❌ Sync only |
| Sync Support | ✅ coodie.sync |
❌ Async only | ✅ Sync only |
| Query API | Chainable QuerySet |
Chainable FindMany |
Chainable QuerySet |
| Schema Migration | ✅ sync_table() |
❌ Manual | ✅ sync_table() |
| LWT (Compare-and-Set) | ✅ if_not_exists() |
N/A | ✅ iff() |
| Batch Operations | ✅ BatchQuery |
❌ | ✅ BatchQuery |
| Counter Columns | ✅ Counter() |
❌ | ✅ columns.Counter |
| User-Defined Types | ✅ UserType |
❌ | ✅ UserType |
| TTL Support | ✅ Per-save TTL | ❌ | ✅ Per-save TTL |
| Pagination | ✅ Token-based PagedResult |
✅ Cursor-based | ❌ Manual |
| Multiple Drivers | ✅ 3 drivers | motor only | cassandra-driver only |
| Polymorphic Models | ✅ Discriminator |
❌ | ❌ |
| Vector Search (ANN) | ✅ Vector() + order_by_ann() |
❌ | ❌ |
| Python Version | 3.10+ | 3.8+ | 3.6+ |
📦 Installation
pip install coodie
Choose a driver extra for your cluster:
pip install "coodie[scylla]" # ScyllaDB / Cassandra (recommended)
pip install "coodie[cassandra]" # Cassandra via cassandra-driver
pip install "coodie[acsylla]" # Async-native via acsylla
🚀 Quick Start
1. Start a local ScyllaDB (or use an existing cluster):
docker run --name scylla -d -p 9042:9042 scylladb/scylla --smp 1
# Wait for it to be ready (~30s), then create a keyspace
docker exec -it scylla cqlsh -e \
"CREATE KEYSPACE IF NOT EXISTS my_ks
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"
2. Install coodie:
pip install "coodie[scylla]"
3. Write your first script:
from coodie.sync import Document, init_coodie
from coodie.fields import PrimaryKey
from pydantic import Field
from typing import Annotated
from uuid import UUID, uuid4
# Connect
init_coodie(hosts=["127.0.0.1"], keyspace="my_ks")
# Define a model
class User(Document):
id: Annotated[UUID, PrimaryKey()] = Field(default_factory=uuid4)
name: str
email: str
class Settings:
name = "users"
# Sync schema & insert
User.sync_table()
user = User(name="Alice", email="alice@example.com")
user.save()
# Query
print(User.find(name="Alice").allow_filtering().all())
💡 Async? Just swap
coodie.syncforcoodie.aioand addawait— that's it!
📖 Usage
Define a Document
from typing import Annotated
from uuid import UUID, uuid4
from pydantic import Field
from coodie import Document, PrimaryKey, ClusteringKey, Indexed
class Product(Document):
id: Annotated[UUID, PrimaryKey()] = Field(default_factory=uuid4)
category: Annotated[str, ClusteringKey()] = "general"
name: str
brand: Annotated[str, Indexed()] = "Unknown"
price: float = 0.0
class Settings:
name = "products" # table name (defaults to snake_case class name)
keyspace = "my_ks"
Async API — coodie / coodie.aio
import asyncio
from coodie import init_coodie
# Product defined above — same field definitions, using coodie.aio.Document
async def main():
await init_coodie(hosts=["127.0.0.1"], keyspace="my_ks")
await Product.sync_table()
p = Product(name="Gadget", brand="Acme", price=9.99)
await p.save()
results = await Product.find(brand="Acme").limit(10).all()
for product in results:
print(product.name, product.price)
gadget = await Product.get(id=p.id)
await gadget.delete()
asyncio.run(main())
Sync API — coodie.sync
from coodie.sync import Document, init_coodie
class Product(Document):
... # same field definitions
init_coodie(hosts=["127.0.0.1"], keyspace="my_ks")
Product.sync_table()
p = Product(name="Widget", price=4.99)
p.save()
results = Product.find(brand="Acme").allow_filtering().all()
one = Product.find_one(name="Widget")
QuerySet Chaining
# Filter, sort, and limit
products = (
await Product.find()
.filter(brand="Acme")
.order_by("price")
.limit(20)
.all()
)
# Count
n = await Product.find(brand="Acme").allow_filtering().count()
# Async iteration
async for p in Product.find(brand="Acme"):
print(p)
# Delete matching rows
await Product.find(brand="Discontinued").allow_filtering().delete()
Field Annotations Reference
| Annotation | Purpose |
|---|---|
PrimaryKey(partition_key_index=0) |
Partition key column (composite keys via index) |
ClusteringKey(order="ASC", clustering_key_index=0) |
Clustering column |
Indexed(index_name=None) |
Secondary index |
Counter() |
Counter column |
Discriminator() |
Polymorphic model discriminator |
Vector(dimensions=N) |
Vector column — maps list[float] to vector<float, N> |
VectorIndex(similarity_function="COSINE") |
SAI vector index for ANN queries |
Vector Search (ANN)
coodie supports ScyllaDB's vector<float, N> column type and ANN similarity search via VectorIndex.
class ProductEmbedding(Document):
product_id: Annotated[UUID, PrimaryKey()] = Field(default_factory=uuid4)
embedding: Annotated[
list[float],
Vector(dimensions=384),
VectorIndex(similarity_function="COSINE"),
]
...
results = await ProductEmbedding.find().order_by_ann("embedding", query_vector).limit(10).all()
📖 Full guide: Vector Search (ANN)
📚 Learn More
| Resource | Link |
|---|---|
| 📖 Full Documentation | scylladb.github.io/coodie |
| 🚀 Quick Start Guide | Installation & Quickstart |
| 📊 Benchmark History | Performance Trends |
| 🎮 Demo Apps | demos/ — FastAPI, Flask, Django & CLI examples |
| 🔄 Migrating from cqlengine | Migration Guide |
| 🤝 Contributing | CONTRIBUTING.md |
| 📋 Changelog | CHANGELOG.md |
| 🐛 Bug Reports | GitHub Issues |
Contributors ✨
Thanks goes to these wonderful people (emoji key):
Israel Fruchter 💻 🤔 📖 |
This project follows the all-contributors specification. Contributions of any kind welcome!
Credits
This package was created with Cookiecutter and the browniebroke/cookiecutter-pypackage project template.
Metadata
Release files for coodie 1.7.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| coodie-1.7.5.tar.gz | 4.0 MB | With provenance | Details for coodie-1.7.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| coodie-1.7.5-py3-none-any.whl | Python 3 | none | any | Details for coodie-1.7.5-py3-none-any.whl |
Total release size: 4.1 MB
Release files / coodie-1.7.5.tar.gz
| Download URL | coodie-1.7.5.tar.gz |
|---|---|
| Size | 4.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e24fde363d0246cb813c648c97b78a85ce8841459931a0a796fa503242190714
|
|
BLAKE2b-256 checksum How to use checksums |
d2128284156ea451ff8b638e80d73474c8b73c5b26a9970f4b23629a8381f665
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / coodie-1.7.5-py3-none-any.whl
| Download URL | coodie-1.7.5-py3-none-any.whl |
|---|---|
| Size | 82.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12c39fb26e2fac68431d3fa62aad55bc4c8e66376226535acd515de9efea06df
|
|
BLAKE2b-256 checksum How to use checksums |
e1b71ba83ecefe8cafbe6461e9381d872da60b5ab93685e363868bf3c5512045
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logSecurity for coodie 1.7.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for coodie-1.7.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.