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?

set[X]

set<X>

Yes

list[X]

list<X>

Yes

dict[K, V]

map<K, V>

Yes

tuple[X, ...]

tuple<X, ...>

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__

Add elements

set

remove__

Remove elements

set, list, map (keys)

append__

Append element

list

prepend__

Prepend element

list

(none)

Replace entirely

set, list, map, tuple

What’s Next?