Theme
Version
GitHub PyPI Discord
On this page

Constant values

Some inputs have the same value in every render, such as a column label in every table row or a layout setting that is fixed for the whole site. Citry still evaluates and escapes the template parts that use them on every render.

Wrap such a value in Const. Citry then prepares the template parts that depend only on constant values once, and reuses them in later renders. Measure a real page first, and mark only values that repeat.

Mark a fixed input

Wrap the value where you pass it to the component:

from citry import Component, Const

class Metric(Component):
    class Kwargs:
        label: str
        value: str

    template = """
      <p>
        <strong>{{ label }}</strong>
        <span>{{ value }}</span>
      </p>
    """

rows = [
    Metric(label=Const("Status"), value="Ready"),
    Metric(label=Const("Status"), value="Waiting"),
]

Both rows reuse the prepared <strong> content. value is an ordinary input and renders as usual.

Inside the component, label is a plain string. Citry removes the Const wrapper before your code and templates see the value.

Const is a promise. Citry does not check the value again, so do not change it afterwards. If you change a marked list or object in place, the page can show the old output.

Mark repeated values

Each Citry instance keeps the prepared templates for the 512 most recently used combinations of component and constant values. Citry.clear empties this store.

Mark values that recur across many renders: fixed labels, small layout choices, and site-wide settings. Do not mark a value that differs on almost every render, such as Const(user.id). Each new value adds an entry that is never reused and pushes out useful ones.

See what runs once

Citry prepares a template part in advance when every value it uses is constant:

  • {{ expression }} becomes ready-made escaped text.
  • A <c-if> chain keeps only the branch that applies.
  • A <c-for> loop that produces only text is expanded once, up to 1,000 iterations.
  • An attribute expression becomes ready-made attribute text, unless an installed extension implements the on_attrs_resolved hook to inspect or change the final attributes on every render.

Child components and slot content always render again, because they can create new components or depend on the template that supplied the content. Constant expressions inside them can still be prepared in advance.

Keep template expressions free of side effects. Citry may evaluate a constant expression in a branch that the current render does not show.

Values written in tags

A value written directly on a component tag is the same in every render, so Citry treats it as constant without Const:

<c-Grid columns="3" compact="" />
<c-Grid c-columns="1 + 2" c-breakpoints="[480, 900]" />

The same applies to an expression attribute that uses no variables. An expression attribute whose variables are all constant also passes a constant result to the child.

Citry marks the result as a whole. In c-total="add(1, 2)", the child's total input is constant when add is a constant value too; the arguments 1 and 2 stay ordinary integers inside the call.

Mark a default

Wrap a default in Const so calls that omit the input get the same benefit:

from citry import Component, Const

class Grid(Component):
    class Kwargs:
        columns: int = Const(3)

    template = """
      <div c-style="{'--columns': columns}">
        {{ columns }} columns
      </div>
    """

Grid() uses the constant default. Grid(columns=4) passes an ordinary value, unless the caller writes Const(4).

Keep values constant

With the default template_data(), every constant input stays constant in the template, unless the Kwargs class converts or copies it. The Metric and Grid examples need nothing more.

A custom template_data() can return anything, so Citry does not guess. An output stays constant only when it has the same name as a constant input and is the exact same object:

def template_data(self, kwargs, slots):
    return {
        "label": kwargs.label,
    }

return kwargs works the same way. Citry compares the objects with Python's is test, after the Kwargs class, the output data class, and any extension data hooks have run. If one of them converts or replaces a value, that value loses its mark.

Mark a renamed or computed output yourself, but only when it really is stable:

def template_data(self, kwargs, slots):
    return {
        "heading": Const(kwargs.label),
        "value": Const(kwargs.value.strip()),
    }

Edge cases

True is not 1

Values of different types never share a stored entry, even when Python considers them equal, as it does True and 1.

Recomputed values

Because Citry uses the is test, a recomputed value that Python happens to reuse, such as a small integer or a short string, counts as the same object and keeps its mark.

Skip generators

Preparing the template can use it up and leave later work with an empty iterator. Pass a list or tuple.

Custom schemas

Citry keeps Const defaults and default factories constant on the Kwargs dataclasses it generates. It does not assume anything about default factories in schema classes you write yourself, so mark their results with Const(...) in template_data() when needed.

Using marked values

Before Citry receives it, Const(value) is a wrapper object that stands in for the value. An API that needs the real built-in object, or checks its identity, can reject the wrapper. Use is_const and const_value to unwrap it first:

from citry import Const, const_value, is_const

marked = Const("status")
if is_const(marked):
    plain = const_value(marked)

Nested markers

Citry removes nested markers only when the outer value is marked: Const([Const(1)]) reaches the component as [1]. It looks inside built-in lists, tuples, sets, frozensets, and dictionaries, but not inside your own classes.

An unmarked list keeps its markers, so [Const(1)] still contains a wrapper, and so does a list that component or extension code puts a marker into. Unwrap it where you use it:

from operator import add

from citry import Const, const_value

items = [Const(1)]
total = add(const_value(items[0]), 2)

Inputs that share data

Citry unwraps markers passed in the same call together, so two inputs that refer to the same object still do afterwards.

When a marked and an unmarked input share data that Citry must rebuild, the marked input can get its own cleaned copy. The original stays unchanged.

Cyclic data

A marker that contains itself raises ValueError. A marked tuple or frozenset that contains itself can raise it too.

Unhashable values

A marked value that cannot be hashed, such as an unhashable custom object, renders normally without the speed-up.