Expression Templates¶
Expression templates let you define parameterized filter expressions that are fully serializable to JSON. Named placeholders ($start, $end) are computed from a runtime context via a transform pipeline DSL, making templates suitable for persistent storage in a database or config file.
Quick Start¶
import datetime
from therismos import F, ExprTemplate
from therismos.expr._expr import TemplateParam
from therismos.expr.template import RuleSerializer, TemplateParamSpec
field = F("created", datetime.date)
expr = (field >= TemplateParam("start", datetime.date)) & (field <= TemplateParam("end", datetime.date))
rule_ser = RuleSerializer()
tmpl = ExprTemplate(
expr=expr,
params={
"start": TemplateParamSpec(description="Range start (inclusive)"),
"end": TemplateParamSpec(description="Range end (inclusive)"),
},
rules={
"end": rule_ser.deserialize("$now | extract_date"),
"start": rule_ser.deserialize("$now | extract_date | sub_time(7d)"),
},
)
now = datetime.datetime(2026, 3, 18, 16, 30)
bound = tmpl.bind({"now": now})
# → created{date}>=2026-03-11; created{date}<=2026-03-18
Template Parameters¶
A TemplateParam is a named placeholder that can appear in any value position of an expression node:
from therismos import F
from therismos.expr._expr import TemplateParam
age = F("age", int)
threshold = TemplateParam("min_age", int) # optional type_ for casting
expr = age >= threshold
Use collect_params() to inspect placeholders and bind() to substitute them:
from therismos import bind, collect_params
params = collect_params(expr)
# {"min_age": TemplateParam(name="min_age", type_=<class 'int'>)}
bound = bind(expr, {"min_age": 18})
# age >= 18
Serialization Grammar¶
TemplateParam nodes serialize as $name or $name{type}:
from therismos import F, Serializer
from therismos.expr._expr import TemplateParam
import datetime
ser = Serializer(type_registry={datetime.date: "date"})
expr = F("created", datetime.date) >= TemplateParam("start", datetime.date)
print(ser.serialize(expr)) # created{date}>=$start{date}
Backend visitors and optimize() raise UnboundTemplateParamError if any TemplateParam remains — call bind() first.
Transform Pipeline DSL¶
Rules use a $source | step1 | step2(arg) pipeline syntax:
Duration arguments: 7d, 1h, 30m, 90s, 500ms.
Built-in transforms:
| Category | Transforms |
|---|---|
| Date/time extraction | extract_date, extract_time, extract_year, extract_month, extract_day |
| Date/time rounding | start_of_day, end_of_day, start_of_week, end_of_week, start_of_month, end_of_month |
| Arithmetic | add_time(duration), sub_time(duration) |
| Type casting | as_int, as_float, as_str, as_date, as_datetime |
| String | lower, upper, strip |
| Math | add(n), sub(n), mul(n), div(n), abs, round(n) |
Register custom transforms:
from therismos import DEFAULT_TRANSFORM_REGISTRY
@DEFAULT_TRANSFORM_REGISTRY.register_decorator("fiscal_year_start")
def fiscal_year_start(dt):
import datetime
return datetime.date(dt.year if dt.month >= 4 else dt.year - 1, 4, 1)
JSON Persistence¶
import json
d = tmpl.to_dict()
# {
# "version": "1",
# "expr": "created>=$start; created<=$end",
# "params": {"start": {"description": "Range start (inclusive)"}, ...},
# "rules": {"end": "$now | extract_date", "start": "$now | extract_date | sub_time(7d)"}
# }
json_str = tmpl.to_json()
restored = ExprTemplate.from_json(json_str)
bound = restored.bind({"now": datetime.datetime(2026, 3, 18, 16, 30)})