Theme
Version
GitHub PyPI Discord
On this page

Component

The base class every component subclasses, and the warning Citry emits when a subclass's nested data class drops its parent's fields.

View source

Component class

Base class for all Citry components.

A component is a reusable unit of UI defined by:

  • A template (Citry template syntax)
  • Optional typed inputs (via inner Kwargs, Slots classes)
  • A data method that maps inputs to template variables

Subclass this to define your own components. At minimum, set template (inline string) or template_file (path to file).

View source

class_id attribute

class_id: str

Stable import-derived identity shared by reloads of this component path.

The read-only value is suitable for routes and cross-process logical identity. Combine it with Citry.engine_id and definition_id when retained metadata must match one exact live class generation in the current process.

View source

definition_id attribute

definition_id: str

Opaque process-lifetime identity of this exact component class object.

The read-only value exists before class-created extension hooks run. An alias or re-registration preserves it, while defining a replacement class creates a different value even when class_id remains the same.

View source

citry attribute

citry: Citry

The Citry instance that owns this component class.

Defaults to the module-level default instance. Set this inside the class body to assign a component to a specific instance. The binding cannot be changed or deleted after the class is defined. A subclass of a concrete component uses the same owner; define a fresh component tree when another engine needs its own copy.

View source

transparent attribute

transparent: bool

Whether this component's output joins the surrounding component's serialization frame.

A transparent component is structural rather than visual: its rendered output gets no data-cid-<id> marker and is not framed as a child component at serialize time. Used by built-ins like <c-provide> that only wrap content. Hooks, the render id, and dependency merging behave the same as for any component.

View source

simple attribute

simple: bool | Literal['vue']

Render a presentation template inside its caller's render frame.

simple=True renders in its caller's HTML frame. simple="vue" opts into an instance-free Vue occurrence with its own browser identity. Unsupported declarations and invocations raise errors. The setting inherits to subclasses and is fixed after class definition.

For simple="vue", use the default data method or a synchronous static template_data(kwargs, slots) method. Authored component JS/CSS and static js_data or css_data callbacks are supported; slots, child calls that pass content, provide/inject, instance hooks and instance configuration are not. The data callback runs for every invocation; neither mode makes the component pure. A simple="vue" component may also set pure = True, which reuses HTML only while an app-wide feature (configured translations, or an extension with component hooks) makes Citry render the component the ordinary way.

See Simple components for the full contract and the Performance overview to compare the available options.

View source

pure attribute

pure: bool

Whether repeated equal template data may reuse settled body strings.

Set pure = True only when rendering the template is a deterministic, side-effect-free function of its template variables. The memo lives for one root render. It can reuse safe strings around a child or Slot, but the child, Slot, ordinary component instances, IDs, and i18n work still run for every occurrence. Setting pure does not lift any simple restriction. A subclass must declare purity again rather than inheriting the promise. See Pure components for the full contract.

View source

name attribute

name: str | None

Override the name under which this component is registered.

By default, a component registers under its class name, so MyWidget is used as <c-MyWidget>. Letter case does not matter, and <c-my-widget> works too. Set name to register under a specific name instead::

class MyWidget(Component):
    name = "fancy-widget"
    # registered as "fancy-widget", not "mywidget" / "my-widget"
View source

template attribute

template: str | None

Inline template string (Citry template syntax).

Mutually exclusive with template_file. Read the loaded template with get_template(). Only the component's author writes the template; never build it from request data. Pass that data in as values instead.

View source

template_file attribute

template_file: str | None

Path to a template file. Mutually exclusive with template.

Resolved relative to the directory of the class that declares the value first, then relative to the owning component's Citry(dirs=...) entries; absolute paths are used as-is. A subclass that inherits this declaration therefore keeps the declaring class's file location. A plain mixin can declare the path while the component still supplies the owning engine.

View source

messages attribute

messages: str | None

Inline source-locale Fluent messages for this component.

Mutually exclusive with messages_file. Declare the source language with I18n.messages_locale. A registered message asset activates server source-mode translation for the complete engine catalog, even without engine i18n settings. Read the loaded source with get_messages(). Only the component's author writes the messages; never build them from request data. Pass that data as message variables instead.

View source

messages_file attribute

messages_file: str | None

Path to source-locale Fluent messages, resolved like template_file.

This has the same source-mode and I18n.messages_locale contract as messages. Like messages, the file is written by the component's author, never generated from request data.

View source

js attribute

js: str | None

Inline primary JS for this component. Mutually exclusive with js_file. Read the loaded content with get_js().

View source

js_file attribute

js_file: str | None

Path to the component's primary JS file. Mutually exclusive with js. Resolved like template_file.

View source

css attribute

css: str | None

Inline primary CSS for this component.

Mutually exclusive with css_file. Citry adds these selectors to the page exactly as written, so they can style any matching element. Use class names specific to the component to avoid styling something else by accident. Values returned by css_data() become custom properties for one rendered use of the component. Read the loaded content with get_css().

View source

css_file attribute

css_file: str | None

Path to the component's primary CSS file. Mutually exclusive with css. Resolved like template_file.

View source

Cache attribute

Cache: type | None

Optional output-cache settings owned by the Cache extension.

Define a nested Cache class to enable caching, set its TTL and version, or return additional variation values. Citry rebuilds the declaration on CacheConfig when it creates the component class.

View source

Dependencies attribute

Dependencies: type | None

Optional secondary JavaScript and CSS assets.

Define a nested Dependencies class with js, css, extend, or local_files. Read the normalized merged result with get_dependencies(). Citry rebuilds the declaration on DependenciesConfig.

View source

I18n attribute

I18n: type | None

Optional per-component settings for the built-in i18n extension.

Define client_messages here when browser code uses a finite dynamic message name that static analysis cannot discover. The instance-level i18n value provides translation, formatting, parsing, and the explicit locale context during a render.

View source

Kwargs attribute

Kwargs: type | None

Optional typed keyword arguments.

Define as a plain class with type annotations. Citry converts it to a dataclass (with slots) automatically::

class Card(Component):
    class Kwargs:
        title: str
        body: str = ""

When the component renders, Citry rejects a missing or unknown name, and checks each value's outer type against its annotation, so Card(title=123) raises TypeError. list[str] accepts any list without looking at its items. See Lint to turn the type check off.

A subclass inherits its parent's Kwargs unless it declares its own. Its own plain class Kwargs: replaces the parent's class, like any nested Python class, so the parent's fields are dropped and Citry emits NestedSchemaReplacedWarning. To keep them and add more, name the parent's class as the base::

class ImageCard(Card):
    class Kwargs(Card.Kwargs):
        image_url: str = ""

Kwargs = None declares no schema, so the component accepts any keyword argument. Any other value that is not a class raises ValueError when the class is defined. Slots, State, TemplateData, JsData, and CssData follow the same rule.

View source

Slots attribute

Slots: type | None

Optional typed slot definitions.

They are inherited, replaced, or extended like Kwargs.

Use SlotInput for places where people can add content. A field without a default must be filled whenever the component is used. The required attribute on <c-slot> checks something different: it raises an error only if Citry renders that tag without content.

View source

State attribute

State: type | None

Optional typed values that survive between server event calls.

Define State as a plain nested class with type annotations. The Events extension turns it into a mutable dataclass, or into a Pydantic model when model_base names one::

class Search(Component):
    class State:
        query: str = ""
        page: int = 1

State can also be a Pydantic v2 or v1 model written by hand, which Pydantic then checks; see "Pydantic State" in the Events State guide. State must contain only JSON-serializable values, or, for a model State, values Pydantic can write as JSON. By default, every field is readable and writable in the browser. Use _public to choose which fields the browser may read and _model to choose which public fields it may change. _storage is "signed" by default and may be set to "server". _max_bytes defaults to 8192 bytes, or 65536 with server storage, where it caps how large browser writes may make the stored State and None turns that check off. _max_age accepts a datetime.timedelta or None; with None, signed State never expires and server-stored State expires after 24 hours.

Citry starts State from same-named keyword arguments and field defaults. Define state_data(self, kwargs, slots) when the values need to be derived instead.

A subclass inherits its parent's State the same way as Kwargs. class State(Parent.State): keeps the parent's fields and settings, while a plain class State: starts from the default settings. Replacing a parent State that sets _storage = "server" without setting _storage raises ValueError, because the values would move into the page. Assign State = None on a subclass to declare no State.

View source

Events attribute

Events: type | None

Optional server event handlers for this component.

Define Events as a nested class. Every public method is an event handler; underscore-prefixed methods and attributes are private helpers or configuration::

class Counter(Component):
    class State:
        count: int = 0

    class Events:
        def increment(self, state):
            state.count += 1

Citry combines inherited Events declarations in component C3 order. A child method overrides a same-named parent method, while Events = None stops inherited declarations. The built-in Events extension rebuilds the effective nested class on its runtime config base.

A plain nested class works without imports. To type handler attributes such as self.state and self.request, subclass the generic Events base and parameterize it with the component's State class. See event for per-handler options.

View source

Lint attribute

Lint: type | None

Optional per-component template-lint and input type check settings.

Define a nested Lint class with any of the rule severities (rule_*), variable mappings, and check_input_types that LintSettings accepts, plus template_data_keys, which only a component's Lint class takes. Nested declarations compose in the same order Python uses to resolve methods across base classes. Assign None to return to the Citry instance's application lint settings, including its check_input_types. When the class is defined, an unknown setting or an invalid severity raises ValueError, and a check_input_types that is not a bool raises TypeError.

check_input_types = False skips the render-time check of each input's type for this component and its subclasses, for example in a component library that checks its own inputs with more specific errors, or that accepts values its annotations do not name. check_input_types = True checks this component even when the app turns the check off. To accept more values for one field, widen its annotation instead, for example title: str | int. Citry reads check_input_types once, when the class is defined (for a library component, when its library is registered), so changing the Lint class's attribute afterwards has no effect.

template_data_keys lists every key that template_data() returns. Use it when the method builds its result in a way Citry cannot read, such as by calling a helper function. citry check and the editor then accept the listed names and report any other name. Rendering ignores the list.

citry check warns about a key the method writes as a literal but the list leaves out.

The value must be a tuple, list, or frozenset of strings, or TypeError is raised. A name that cannot be a Python variable name, such as "class", or a name listed twice, raises ValueError. A component that declares both the list and a TemplateData schema raises TypeError, because the schema already names every key. A schema on a subclass replaces an inherited list, and template_data_keys = None clears one.

Example

class LegacyCard(Component):
    class Lint:
        check_input_types = False
class Avatar(Component):
    class Lint:
        template_data_keys = ("src", "alt", "img_attrs")

    def template_data(self, kwargs, slots):
        # Returns {"src": ..., "alt": ..., "img_attrs": ...}.
        return build_avatar_data(kwargs)
View source

TemplateData attribute

TemplateData: type | None

Optional typed template data output.

It is inherited, replaced, or extended like Kwargs.

View source

JsData attribute

JsData: type | None

Optional typed schema for the js_data() output.

A plain annotated class converts to a dataclass. It is inherited, replaced, or extended like Kwargs.

View source

CssData attribute

CssData: type | None

Optional typed schema for the css_data() output.

A plain annotated class converts to a dataclass. It is inherited, replaced, or extended like Kwargs.

View source

id attribute

id: str

Unique render ID for this component instance.

A fresh ID is minted every time a CitryElement is rendered, so the same CitryElement rendered twice produces two distinct IDs.

View source

kwargs attribute

kwargs: Any

The resolved keyword arguments.

If the component defines a Kwargs dataclass, this is an instance of that class. Otherwise, a plain dict.

View source

raw_kwargs attribute

raw_kwargs: dict[str, Any]

The keyword arguments as a plain dict, even if a Kwargs dataclass is defined. Useful when you need dict access regardless of typing.

View source

slots attribute

slots: Any

The resolved slot fills, with every value normalized to a Slot.

If the component defines a Slots dataclass, this is an instance of that class. Otherwise, a plain dict.

View source

raw_slots attribute

raw_slots: dict[str, Slot]

The slot fills as a plain dict of Slot values, even if a Slots dataclass is defined. Useful when you need dict access regardless of typing.

View source

cache attribute

The Cache extension settings bound to this rendered component.

View source

dependencies attribute

dependencies: DependenciesConfig

The Dependencies extension settings bound to this rendered component.

View source

events attribute

events: EventsConfig[Any]

The Events extension settings and event URL helper for this component.

View source

i18n attribute

i18n: I18nConfig

Translation, formatting, parsing, and locale access for this component.

View source

parent attribute

parent: Component | None

The component that wrote this one into its template. None for a root component, and for one rendered standalone (e.g. an element handed into an expression as {{ element }}).

The link follows authorship, not slot placement: a component written inside a <c-fill> keeps the fill's author as its parent, no matter whose slot the content lands in. (This differs from Vue, whose $parent points at the slot host.) To ask "what am I rendered inside, slots included", use provide/inject, which travels the render path and crosses slot boundaries.

View source

root attribute

root: Component

Return the component at the top of the authorship parent chain.

For root components, self.root is self. The root case is computed instead of stored, so preserving that public identity does not create a root-to-itself reference cycle.

View source

template_data function

template_data(kwargs: Any, slots: Any) -> dict[str, Any] | None

Return the template variables.

By default this returns kwargs, so a component's inputs are usable in its template without an override: a Kwargs field named title is available to the template as {{ title }}. Override this to map the inputs to a different set of variables. The returned value may be a dict, a NamedTuple, or the typed TemplateData instance, and a declared TemplateData validates and normalizes it either way. Schema defaults and coercions are materialized in the mapping that the template's expressions see.

An input whose outer value was marked with Const reaches kwargs as an ordinary Python value; exact builtin containers under that marker are cleaned recursively. Ordinary containers are not searched for manually nested markers. The base method keeps known const inputs in renderer metadata; validating or transforming schemas may discard that metadata. An override retains an input promise when its final same-name output is the recorded input object; renamed or replaced stable outputs need an explicit Const marker.

A returned variable wins over a template_globals entry of the same name, so an input shadows a same-named global (globals act as defaults). Unlike js_data and css_data, which stay opt-in and return None by default, template variables never cross into the browser: they only make names resolvable to the template's own expressions.

Parameters

  • kwargs Any - The keyword arguments passed to the component.
  • slots Any - The slot fills passed to the component.

Returns

dict[str, Any] | None: A mapping of template variables. Defaults to the component's

View source

js_data function

js_data(kwargs: Any, slots: Any) -> dict[str, Any] | None

Return the JS variables for this render.

Override this to expose per-render data on the component's Vue instance. The mapping is serialized to strict JSON, and its keys become reactive fields on that rendered component's public instance. Component JavaScript can read them from component in onServerRender({component}), and Vue template expressions can read them as instance values. Each rendered occurrence receives its own mutable value graph.

A key cannot start with $ or _, and cannot be citryId, because Vue and Citry already use those names on the instance.

Parameters

  • kwargs Any - The keyword arguments passed to the component.
  • slots Any - The slot fills passed to the component.

Returns

dict[str, Any] | None: A dict of JS variables, or None for no variables.

Raises

  • ValueError - When rendering, if a returned key starts with $ or _ or is citryId. The message names the component and the key.
View source

css_data function

css_data(kwargs: Any, slots: Any) -> dict[str, Any] | None

Return the CSS variables for this render.

Override this to expose per-render values to the component's CSS (Component.css) as CSS custom properties: a returned {"row-color": "red"} is usable in the CSS as var(--row-color), scoped to this component's elements. Identical data across renders shares one generated stylesheet. Consumed by the built-in dependencies extension.

Keys are custom-property name suffixes, without the leading --. Values must be strings, finite numbers, or None. Citry escapes quoted strings and rejects names or raw values that could escape the generated declaration. It checks structural containment, while the browser remains responsible for full CSS value grammar and whether a value is valid for the property that consumes it.

Parameters

  • kwargs Any - The keyword arguments passed to the component.
  • slots Any - The slot fills passed to the component.

Returns

dict[str, Any] | None: A dict of CSS variables, or None for no variables.

View source

on_dependencies function

on_dependencies(scripts: list[Dependency], styles: list[Dependency]) -> tuple[list[Dependency], list[Dependency]] | None

Hook to adjust this component's JS/CSS tags before they enter the page.

Called at serialize time, once per rendered instance of this component, with the Script/Style entries this component contributes (its Dependencies entries and its own Component.js/css). Return a (scripts, styles) pair to replace the lists, mutate them in place, or return None (the default) to keep them. Removing the component's own script entries can break the component's behavior in the browser; this hook is for adding attributes, reordering, or dropping entries you know are provided elsewhere.

To adjust the page-wide lists instead (every component's tags, after de-duplication), implement an extension with an on_dependencies method (see citry.ext.dependencies.OnDependenciesContext).

View source

on_render function

on_render() -> RenderReplacement | OnRenderGenerator | None

Hook to replace or post-process this component's rendered output.

Called when this component is rendered without a successful component cache hit, after template_data and just before the template renders. A cache hit reuses the completed output and skips data methods, slots, the template, and this hook. Return None (the default) to render the template as usual. Return content to use it as the component's whole output instead; the template is then not rendered at all. Accepted content:

  • a plain str, shown as text: Citry escapes it, as it escapes a {{ ... }} value, so "<b>hi</b>" shows the tags as characters
  • Markup, inserted as HTML without escaping (never build it from user input)
  • a composed element (OtherComponent(title="hi")), rendered in this component's place
  • an already-rendered CitryRender, inlined
  • a Slot, invoked with no data
  • a ComponentLike, resolved against this component's Citry instance

Because None means "no replacement", return "" to output literally nothing.

HTML you turn into a string inside the hook, such as str(result) after a yield, is a plain str, so wrap it in Markup before you return it. For a component that runs in the browser that string holds the whole browser app, so add fixed content in the template instead. Citry adds this component's marker to the HTML you return.

Everything the hook needs is on self: kwargs, slots, parent, inject(). To pass data to the template, use template_data; this hook is for replacing output. If the hook depends on ambient data while component caching is enabled, include that data in the cache variation inputs.

For example, render a placeholder instead of the template when there is no data::

class MyTable(Component):
    template = "<table>...</table>"

    def on_render(self):
        if not self.raw_kwargs.get("rows"):
            return Markup("<p>No data</p>")
        return None

Generator form. Include a yield to also see the component's finished output, children included, and react to it - for example to catch a failing child (this is how error boundaries work)::

class Guarded(Component):
    template = "..."

    def on_render(self):
        # BEFORE: runs just before the template renders.
        result, error = yield

        # AFTER: result is the completed CitryRender, or None
        # if rendering failed (then error is the exception).
        if error is not None:
            return Markup("<p>Something went wrong</p>")
        return None

The protocol:

  • A bare yield (or yield None) on the first yield means "render my template as usual"; yielding content means "use this as my output instead" (same accepted values as above).
  • The yield receives (result, error) once that output has fully settled: result is the live CitryRender (not a string; do not serialize it here unless you are replacing the output with the serialized form), or None when rendering failed, with error set. Exactly one of the two is set.
  • You can yield any number of times; each yield <content> replaces the output, renders it, and receives the new (result, error). A bare yield after the first answers immediately with the current result unchanged.
  • End with return <content> to set the final output, raise to make that the component's error, or plain return to keep the current result (an unhandled error keeps bubbling).
View source

provide function

provide(key: str, value: Any = MISSING, **data: Any = {}) -> None

Make one value available to this component's descendants.

Any component rendered below this one (including components inside slot content rendered below it) can read the data with self.inject(key). The data does NOT enter the template variables; descendants opt in explicitly.

Pass a direct positional value when the caller already owns the value object. Or pass keyword fields and Citry will freeze them into an immutable payload whose fields are read as attributes::

class Page(Component):
    template = '<c-user-card />'

    def template_data(self, kwargs, slots):
        self.provide("user_data", user=kwargs["user"])
        return {}

class UserCard(Component):
    template = '<div>{{ name }}</div>'

    def template_data(self, kwargs, slots):
        return {"name": self.inject("user_data").user}

class LocaleRoot(Component):
    def template_data(self, kwargs, slots):
        self.provide("citry_i18n", kwargs.locale_context)
        return {}

In templates, the same thing is written with the <c-provide> built-in component: <c-provide key="user_data" c-user="user">.

Parameters

  • key str - Name the data is provided under (a non-empty identifier). Positional-only, so a data field named key is allowed.
  • value Any - One direct value. It is passed through unchanged.
  • **data Any - Fields Citry freezes into one immutable payload. A call cannot pass both a direct value and keyword fields.
View source

inject function

inject(key: str, default: Any = MISSING) -> Any

Read data a component above this one provided under key.

The data must have been provided by a component on the render path above this one (via Component.provide or the <c-provide> built-in); the nearest provider wins when the same key is provided twice. A component's own provide calls are visible to its descendants only, never to its own inject.

A direct value is returned unchanged. Keyword fields passed to provide() return an immutable payload with those fields as attributes: self.inject("user_data").user. Injection works during template_data and keeps working after the render for as long as the component instance is kept.

Parameters

  • key str - The name the data was provided under.
  • default Any - Returned when nothing was provided under key. An explicit None works. Without a default, a missing key raises KeyError.
View source

unprovide function

unprovide(key: str) -> None

Hide an inherited provide from this component's descendants.

The component may still inject the inherited value itself. Components rendered below it observe the key as missing unless a nearer component provides a new value under the same key. Call this from template_data when content below a component boundary must establish a fresh context before using a compound child.

Parameters

  • key str - The provide key to hide below this component.
View source

ancestors attribute

ancestors: Iterator[Component]

All ancestor components, nearest first: the parent, then the parent's parent, up to and including the root. Empty for a root component.

Useful to check where a component sits, e.g.::

is_themed = any(isinstance(c, Theme) for c in self.ancestors)

The chain follows who wrote the component, the same as parent: a component written inside a <c-fill> has the fill's author as its parent, not the component whose slot rendered it. So the check above holds when Theme's own template renders this component; for "am I rendered inside a Theme, slots included", have Theme provide a value and inject it here, which travels the render path and crosses slot boundaries.

View source

get_template function

get_template() -> CitryTemplate | None

The loaded template (a CitryTemplate), or None for a template-less component. Resolved from template / template_file once per class; on_template_loaded applied.

View source

get_js function

get_js() -> str | None

The loaded primary JS content, or None. Resolved from js / js_file once per class; on_js_loaded applied.

View source

get_messages function

get_messages() -> str | None

Return the loaded messages / messages_file source, or None.

View source

get_css function

get_css() -> str | None

The loaded primary CSS content, or None. Resolved from css / css_file once per class; on_css_loaded applied.

View source

get_dependencies function

get_dependencies() -> CitryDependencies

The merged secondary assets from this component's (and, per Dependencies.extend, its bases') nested Dependencies class. Owned by the built-in dependencies extension.

View source

reset_template function

reset_template() -> None

Clear this class's loaded template (and its compiled form and cached Const optimization results), so the next render re-reads it. Subclasses that inherit this template cache their own copies; reset them too (Citry.get_components_for_file lists every class using a given file).

View source

reset_files function

reset_files() -> None

Clear this class's loaded messages/JS/CSS (and, via the on_files_reset hook, extension state such as the merged Dependencies), so the next access re-reads them.

View source

NestedSchemaReplacedWarning class

Bases: UserWarning

Warn that a subclass's nested data class drops fields its parent declared.

A nested data class on a subclass, such as class Kwargs:, replaces the parent's class of the same name, the same as any nested Python class. Citry emits this warning once, when the subclass is defined (or, for a library component, when its library is registered), if the new class leaves out fields that the parent's class declared. For State it also warns when the new class stops setting _public, _model, or _max_age that the parent's State set.

To keep the parent's fields, name the parent's class as a base. To drop them on purpose, silence this warning class with the standard warnings filters.

Example

class Message(Component):
    class Kwargs:
        text: str

# Warns: Kwargs replaces Message.Kwargs and leaves out 'text'.
class SignedMessage(Message):
    class Kwargs:
        signature: str

# Keeps 'text' and adds 'signature'; no warning.
class SignedMessageFixed(Message):
    class Kwargs(Message.Kwargs):
        signature: str
Citry version: 0.9.0