Collection Operations
Cassandra and ScyllaDB support four collection types: set, list, map, and tuple. coodie maps them directly to their Python counterparts and lets you update them in place with special double-underscore prefixes.
Defining Collections
Declare collection columns using standard Python type hints:
from coodie.fields import PrimaryKey
from typing import Annotated
from uuid import UUID, uuid4
from pydantic import Field
class Developer(Document):
id: Annotated[UUID, PrimaryKey()] = Field(default_factory=uuid4)
name: str
skills: set[str] = set()
todo: list[str] = []
config: dict[str, str] = {}
coordinates: tuple[float, float] = (0.0, 0.0)
class Settings:
name = "developers"
Python Type |
CQL Type |
Mutable In-Place? |
|---|---|---|
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
|
No (replace only) |
Creating Documents with Collections
Pass collection values normally when creating a document:
dev = Developer(
name="Alice",
skills={"Python", "Rust"},
todo=["Write docs", "Fix bug"],
config={"theme": "dark", "editor": "vim"},
)
dev.save() # sync
# await dev.save() # async
Set Operations
Add Elements
Use the add__ prefix to add elements to a set:
dev.update(add__skills={"Go"}) # sync
# await dev.update(add__skills={"Go"}) # async
Generated CQL:
UPDATE developers SET skills = skills + {'Go'} WHERE id = ?;
Remove Elements
Use the remove__ prefix to remove elements from a set:
dev.update(remove__skills={"jQuery"})
Generated CQL:
UPDATE developers SET skills = skills - {'jQuery'} WHERE id = ?;
Replace Entirely
A plain assignment replaces the whole set:
dev.update(skills={"Python", "Rust", "Go"})
List Operations
Append
Use the append__ prefix to add an element to the end of a list:
dev.update(append__todo="Deploy v2")
Generated CQL:
UPDATE developers SET todo = todo + ['Deploy v2'] WHERE id = ?;
Prepend
Use the prepend__ prefix to add an element to the beginning:
dev.update(prepend__todo="Fix prod")
Generated CQL:
UPDATE developers SET todo = ['Fix prod'] + todo WHERE id = ?;
Remove
Use the remove__ prefix to remove all occurrences of an element:
dev.update(remove__todo=["Write docs"])
Generated CQL:
UPDATE developers SET todo = todo - ['Write docs'] WHERE id = ?;
Replace Entirely
dev.update(todo=["New task 1", "New task 2"])
Map Operations
Update / Add Keys
A plain dict assignment merges keys into the existing map:
dev.update(config={"theme": "light", "font_size": "14"})
Remove Keys
Use the remove__ prefix with a set of keys to remove:
dev.update(remove__config={"font_size"})
Replace Entirely
Replacing the full map is the same as setting it:
dev.update(config={"theme": "dark"})
Tuple Columns
Tuples are immutable in CQL — you can only replace the entire value:
dev.update(coordinates=(37.7749, -122.4194))
Frozen Collections
Wrap a collection with Frozen() to store it as a frozen CQL type.
Frozen collections are serialised as a single value and can be used in
primary keys:
from coodie.fields import PrimaryKey, Frozen
class Route(Document):
path: Annotated[list[str], PrimaryKey(), Frozen()]
class Settings:
name = "routes"
Warning
Frozen collections cannot be partially updated. You must replace the entire value on every write.
Combining Collection Updates
You can mix collection operations with regular field updates in a
single update() call:
dev.update(
name="Alice Smith",
add__skills={"TypeScript"},
append__todo="Review PR",
config={"editor": "neovim"},
)
Collection Update Reference
Prefix |
Operation |
Works On |
|---|---|---|
|
Add elements |
|
|
Remove elements |
|
|
Append element |
|
|
Prepend element |
|
(none) |
Replace entirely |
|
What’s Next?
QuerySet & Chaining — QuerySet chaining and terminal methods
Filtering (Django-Style Lookups) — Django-style lookup operators
TTL (Time-To-Live) — time-to-live support
Counter Tables — counter tables and increment/decrement