Custom component values
Sometimes a value from your domain already knows how it should appear. A payment status might choose a status badge, for example. The ComponentLike protocol lets that object become a Citry component when a template inserts it.
This is an advanced composition tool. A direct component call is clearer when the page already knows which component it wants.
Define the conversion
Add __citry_element__(citry) to the value. Use the supplied engine to find or create the component element:
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)
ComponentLike is a structural protocol. You do not need to inherit from it. Implementing this method with the right shape is enough.
The method must return a CitryElement that belongs to the engine Citry supplied. Do not use the module-level default engine or keep a component class from another application.
Insert the value in a template
Return the object as ordinary template data:
from citry import Component
class Receipt(Component):
class Kwargs:
status: PaymentStatus
citry = app
template = """
<p>Payment: {{ status }}</p>
"""
When the expression is inserted, Citry calls __citry_element__() once for that occurrence and renders the returned element in the current component tree. The same conversion works for a value passed into a slot.
There is no ambient engine outside a component render. A general custom value does not gain a .render() method, so application code should choose a Citry instance and build the concrete component directly when rendering on its own.
LibraryComponentInvocation implements the same protocol and adds render(citry=app) for that library-specific use case. See Component libraries.