Skip to content

MongoDB Backend

Therismos provides MongoDB visitors for expressions, sorting, and grouping. Compatible with PyMongo and Motor.

Installation

pip install therismos[mongodb]        # synchronous PyMongo
pip install therismos[mongodb-async]  # asynchronous Motor

Expression Filtering

from therismos import F, optimize
from therismos.expr.visitors.mongo import MongoVisitor

age = F("age")
status = F("status")
country = F("country")

expr = (age >= 21) & (status == "active") & (country.is_in("US", "UK", "CA"))
optimized, _ = optimize(expr)

visitor = MongoVisitor()
mongo_filter = optimized.accept(visitor)
# {"age": {"$gte": 21}, "status": "active", "country": {"$in": ["US", "UK", "CA"]}}

Expression Mapping

import re
from therismos import F, TRUE, FALSE
from therismos.expr.visitors.mongo import MongoVisitor

visitor = MongoVisitor()
email = F("email")
age = F("age")
status = F("status")
name = F("name")

# Regex (case-insensitive)
email.matches(r".*@example\.com$", re.IGNORECASE).accept(visitor)
# {"email": {"$regex": ".*@example\\.com$", "$options": "i"}}

# Range
((age >= 18) & (age <= 65)).accept(visitor)
# {"age": {"$gte": 18, "$lte": 65}}

# NOT
(~(age < 18)).accept(visitor)
# {"$nor": [{"age": {"$lt": 18}}]}

# Null check
name.is_not_null().accept(visitor)
# {"name": {"$ne": null}}

# Constants
TRUE.accept(visitor)   # {}
FALSE.accept(visitor)  # {"$expr": false}

AND Optimization

# Default: merge simple AND fields
visitor = MongoVisitor(optimize_simple_and=True)
# {"age": {"$gt": 18}, "name": "Alice"}

# Disable: always use $and
visitor = MongoVisitor(optimize_simple_and=False)
# {"$and": [{"age": {"$gt": 18}}, {"name": "Alice"}]}

Using with PyMongo

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
results = client["mydb"]["users"].find(mongo_filter)

Using with Motor (async)

from motor.motor_asyncio import AsyncIOMotorClient

async def find_users():
    client = AsyncIOMotorClient("mongodb://localhost:27017/")
    cursor = client["mydb"]["users"].find(mongo_filter)
    return await cursor.to_list(length=100)

Sorting

from therismos.sorting import SortSpec, SortCriterion, SortOrder
from therismos.sorting.visitors.mongo import MongoVisitor

spec = SortSpec([
    SortCriterion("created_at", SortOrder.DESCENDING),
    SortCriterion("name", SortOrder.ASCENDING),
])

mongo_sort = spec.accept(MongoVisitor())
# {"created_at": -1, "name": 1}

# Use with PyMongo
# cursor = collection.find().sort(list(mongo_sort.items()))

Grouping

from therismos.grouping import GroupSpec, Aggregation, AggregationFunction
from therismos.grouping.visitors.mongo import MongoVisitor

spec = GroupSpec(
    group_by=["category", "region"],
    aggregations=[
        Aggregation("total", AggregationFunction.COUNT),
        Aggregation("avg_price", AggregationFunction.AVERAGE, "price"),
        Aggregation("p95_latency", AggregationFunction.P95, "latency"),
    ],
)

group_stage = spec.accept(MongoVisitor())
# {
#     "$group": {
#         "_id": {"category": "$category", "region": "$region"},
#         "total": {"$sum": 1},
#         "avg_price": {"$avg": "$price"},
#         "p95_latency": {"$percentile": {"input": "$latency", "p": [0.95], "method": "approximate"}}
#     }
# }

# Pipeline usage
pipeline = [{"$match": filter_stage}, group_stage]
results = collection.aggregate(pipeline)

Single Field Simplification

spec = GroupSpec(group_by=["status"], aggregations=[Aggregation("count", AggregationFunction.COUNT)])

MongoVisitor().accept(spec)
# {"$group": {"_id": "$status", "count": {"$sum": 1}}}

MongoVisitor(simplify_single_group=False).accept(spec)
# {"$group": {"_id": {"status": "$status"}, "count": {"$sum": 1}}}

Note: Percentile functions (MEDIAN, Q1, Q3, P01P99) require MongoDB 7.0+.