Render custom values
Sometimes one of your own Python objects already knows how it should look on the page. A payment status, for example, always shows as a green or red badge. Instead of choosing the badge component in every template, you can teach the object to turn itself into that component. Then a template only writes {{ status }}.
When the page already knows which component it wants, call that component directly.
Use this sparingly
A template that writes {{ amount }} shows whatever the value's class turns itself into. If code passes in an object whose class defines __citry_element__, the same template renders something very different, and nothing in the template says so. Check input types where it matters.
__citry_element__ hook
Add a __citry_element__(citry) method to the class. Citry calls it with the Citry instance that is rendering the page. Use that instance to look up the component, and return the component called with its inputs:
from dataclasses import dataclass
from citry import Citry, CitryElement
@dataclass(frozen=True)
class PaymentStatus:
label: str
successful: bool
def __citry_element__(
self,
citry: Citry,
/,
) -> CitryElement:
badge = citry.get("acme-badge")
tone = "success" if self.successful else "danger"
return badge(label=self.label, tone=tone)
The class does not need to inherit from anything. Having this method is enough for Citry to treat it as a ComponentLike value.
Look the component up on the citry argument, as above. Do not use a component class bound to the default instance or to another application's Citry; Citry raises ValueError.
Use it in a template
Pass the object to the template like any other value:
from citry import Component
class Receipt(Component):
citry = app
class Kwargs:
status: PaymentStatus
template = """
<p>Payment: {{ status }}</p>
"""
Each time the template inserts status, Citry calls __citry_element__() once and renders the returned component in that place. The same works when you pass the object as slot content.
Render it elsewhere
The object knows which component to build only while a page is rendering, because that is when Citry supplies the citry argument. It has no .render() method of its own. To render it on its own, choose the Citry instance yourself and build the component directly.
Library components are the exception: calling one, such as AcmeBadge(label="Ready"), returns a value with a render(citry=app) method. See Component libraries.
Return value errors
Citry checks what the method returns:
- a value that is not a
CitryElementraisesTypeError; - a component from a different
Citryinstance than the one rendering the page raisesValueError.
Rendering the object while no component is rendering, for example by calling a Slot that holds it from plain Python, raises RuntimeError.