Skip to content

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:

$end   = $now | extract_date
$start = $now | extract_date | sub_time(7d)

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)})