Filtering (Django-Style Lookups)
coodie uses a Django-inspired double-underscore syntax to express
comparison operators inside find() and filter() calls. Plain
keyword arguments produce equality checks; append a suffix like
__gte to get a different operator.
Setup
from coodie.fields import PrimaryKey, ClusteringKey, Indexed
from typing import Annotated, Optional
from uuid import UUID, uuid4
from datetime import datetime
class Metric(Document):
sensor_id: Annotated[str, PrimaryKey()]
recorded_at: Annotated[datetime, ClusteringKey(order="DESC")]
temperature: float
tags: set[str] = set()
metadata: dict[str, str] = {}
class Settings:
name = "metrics"
Equality
The default — no suffix needed:
Metric.find(sensor_id="sensor-1").all()
Generated CQL:
SELECT * FROM metrics WHERE sensor_id = 'sensor-1';
Comparison Operators
Suffix |
CQL Operator |
Example |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
# Range query — temperature between 20 and 30
results = (
Metric.find(sensor_id="sensor-1")
.filter(temperature__gte=20.0, temperature__lte=30.0)
.all()
)
Generated CQL:
SELECT * FROM metrics WHERE sensor_id = 'sensor-1'
AND temperature >= 20.0 AND temperature <= 30.0;
Warning
Range filters on non-clustering columns require allow_filtering().
IN Queries
__in matches any value in a list:
results = Metric.find(
sensor_id__in=["sensor-1", "sensor-2", "sensor-3"]
).all()
Generated CQL:
SELECT * FROM metrics WHERE sensor_id IN ('sensor-1', 'sensor-2', 'sensor-3');
CONTAINS (Collections)
__contains checks whether a collection column contains an element:
results = (
Metric.find(tags__contains="outdoor")
.allow_filtering()
.all()
)
Generated CQL:
SELECT * FROM metrics WHERE tags CONTAINS 'outdoor' ALLOW FILTERING;
CONTAINS KEY (Maps)
__contains_key checks whether a map column has a specific key:
results = (
Metric.find(metadata__contains_key="unit")
.allow_filtering()
.all()
)
Generated CQL:
SELECT * FROM metrics WHERE metadata CONTAINS KEY 'unit' ALLOW FILTERING;
LIKE (Text Pattern)
__like performs a CQL LIKE match. Requires a SASI or SAI index on
the column:
results = Metric.find(sensor_id__like="sensor-%").all()
Generated CQL:
SELECT * FROM metrics WHERE sensor_id LIKE 'sensor-%';
TOKEN Queries
Token-based range scans use a double-underscore prefix
__token__<op>:
Suffix |
CQL Equivalent |
|---|---|
|
|
|
|
|
|
|
|
results = Metric.find(sensor_id__token__gt="sensor-5").all()
Generated CQL:
SELECT * FROM metrics WHERE TOKEN("sensor_id") > ?;
Combining Filters
Multiple keyword arguments in the same call are ANDed together.
You can also chain .filter() calls — each one adds more conditions:
results = (
Metric.find(sensor_id="sensor-1")
.filter(temperature__gte=20.0)
.filter(temperature__lte=30.0)
.all()
)
This is equivalent to:
results = (
Metric.find(sensor_id="sensor-1")
.filter(temperature__gte=20.0, temperature__lte=30.0)
.all()
)
Both produce:
SELECT * FROM metrics WHERE sensor_id = 'sensor-1'
AND temperature >= 20.0 AND temperature <= 30.0;
Reference Table
Suffix |
Operator |
Works On |
|---|---|---|
(none) |
|
Any column |
|
|
Clustering / numeric columns |
|
|
Clustering / numeric columns |
|
|
Clustering / numeric columns |
|
|
Clustering / numeric columns |
|
|
Partition key / clustering columns |
|
|
|
|
|
|
|
|
|
|
|
Partition key |
|
|
Partition key |
|
|
Partition key |
|
|
Partition key |
Debugging Generated CQL
coodie uses prepared statements under the hood, so the actual CQL sent
to the cluster uses ? placeholders. To see the generated queries,
enable DEBUG-level logging on the cassandra-driver:
import logging
logging.getLogger("cassandra").setLevel(logging.DEBUG)
This will print every CQL statement the driver prepares and executes.
What’s Next?
QuerySet & Chaining — QuerySet chaining and terminal methods
Collection Operations — set, list, and map operations
TTL (Time-To-Live) — time-to-live support
Counter Tables — counter tables and increment/decrement