Skip to content

Sorting

Therismos provides a sorting system for modeling sort criteria as object structures, similar to how expressions model filters.

Quick Start

from therismos.sorting import SortSpec, SortCriterion, SortOrder

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

from therismos.sorting.visitors import StringVisitor
print(spec.accept(StringVisitor()))
# "age DESC, name ASC"

Sort Orders

Value Meaning
SortOrder.ASCENDING (1) Sort in ascending order
SortOrder.DESCENDING (-1) Sort in descending order
SortOrder.NONE (0) No sorting — filtered out during optimization

Creating Sort Specifications

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

spec.append(SortCriterion("id", SortOrder.ASCENDING))
print(len(spec))  # 4

Optimization

Removes NONE criteria and keeps only the last occurrence of each field:

from therismos.sorting.optimizer import optimize

spec = SortSpec([
    SortCriterion("age", SortOrder.ASCENDING),
    SortCriterion("name", SortOrder.NONE),      # removed
    SortCriterion("age", SortOrder.DESCENDING), # overrides first "age"
])

optimized, records = optimize(spec)
# SortSpec([SortCriterion("age", SortOrder.DESCENDING)])

for record in records:
    print(record.reason)

Optimization rules:

  1. Remove NONE orders: Criteria with SortOrder.NONE are removed
  2. Remove redundant criteria: When a field appears multiple times, only the last occurrence is kept

Built-in Visitors

from therismos.sorting.visitors import StringVisitor, DictVisitor, FieldGathererVisitor

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

print(spec.accept(StringVisitor()))
# "age DESC, name ASC"

result = spec.accept(DictVisitor())
# [{"field": "age", "order": "DESC"}, {"field": "name", "order": "ASC"}]

field_visitor = FieldGathererVisitor()
spec.accept(field_visitor)
print(field_visitor.field_names)
# {"age", "name"}

Custom Visitors

from therismos.sorting import SortCriterion, SortSpec

class SQLVisitor:
    def visit_sort_criterion(self, criterion: SortCriterion) -> str:
        order_str = "ASC" if criterion.order == SortOrder.ASCENDING else "DESC"
        return f"{criterion.field} {order_str}"

    def visit_sort_spec(self, spec: SortSpec) -> str:
        if not spec:
            return ""
        parts = [criterion.accept(self) for criterion in spec]
        return "ORDER BY " + ", ".join(parts)

result = spec.accept(SQLVisitor())
# "ORDER BY created_at DESC, name ASC"