Theme
Version
GitHub PyPI Discord
On this page

Extensions

The plugin system: the extension base, its commands, and the hook context objects.

View source

Extension class

Base class for all extensions.

Subclass this, set name (a lowercase Python identifier), and implement the on_* hooks you care about. Every hook has an empty default, so an extension only overrides what it needs (the manager calls only the hooks an extension actually overrides). The on_* methods below are the full hook catalog.

View source

name attribute

name: str

Name of the extension. Lowercase, a valid Python identifier. Determines the attribute the per-component config is reachable under (component.<name>) and, via :attr:class_name, the nested class name.

View source

class_name attribute

class_name: str

PascalCase name of the per-component nested config class, derived from :attr:name at subclass creation (my_extension -> MyExtension).

View source

Config attribute

Base class the per-component nested config inherits from.

View source

commands attribute

CLI commands this extension provides (see :class:ExtensionCommand).

View source

introspection_version attribute

introspection_version: int | None

Positive schema version when this extension publishes component metadata.

View source

render_cache_mode attribute

render_cache_mode: Literal['deny', 'stateless', 'payload']

Whether settled render state from this extension can be replayed.

View source

render_cache_version attribute

render_cache_version: int | None

Positive compatibility version for stateless or payload replay.

View source

citry attribute

citry: Citry

The Citry instance this extension instance belongs to. Set by the manager when the extension is attached (extensions are per-instance, so the back-reference is unambiguous).

View source

urls attribute

urls: list[URLRoute]

HTTP routes this extension provides (see citry/util/routing.py).

Mounted by the web-integration adapters as part of Citry.urls: a user extension's routes live under ext/<extension name>/; built-in extensions own their paths directly. Override as an attribute or property; handlers can reach engine state through self.citry.

View source

validate_config_fields function

validate_config_fields(fields: Mapping[str, Any], component: type[Component] | None = None) -> None

Check the config fields declared for this extension, at declaration time.

Users configure an extension in two places: a component's nested config class (class View: for an extension named "view"), and the engine-wide extensions_defaults setting. Citry calls this method once per declaration:

  • At engine construction, with the extension's entry in the extensions_defaults setting (component is None).
  • At component class definition, with the fields declared on the component's nested config class, including fields from its user-written base classes (component is the component class).

The base implementation accepts everything: by default an extension's config may hold any fields, even methods. Override it to reject bad fields early, so a typo in a field name fails at startup or at class definition instead of surfacing later as a confusing downstream error. Because both declaration sites are checked, the fields are known-valid by the time the config class is instantiated.

Example

An extension whose config accepts exactly one field:

from citry import Extension

class CacheExtension(Extension):
    name = "cache"

    def validate_config_fields(self, fields, *, component=None):
        for name in fields:
            if name != "ttl":
                msg = f"unknown config field {name!r}; the only field is 'ttl'"
                raise ValueError(msg)

Parameters

  • fields Mapping[str, Any] - The declared fields, mapping field name to declared value. For a nested config class, dunder names, the ``Config`` base's members, and citry's own bookkeeping attributes are already filtered out.
  • component type[Component] | None - The component class the fields were declared on, or ``None`` when the fields come from the ``extensions_defaults`` setting.

Raises

  • ValueError - When a field is not valid for this extension. Raise it with a message naming the offending field and what is valid; citry prefixes the declaration site (the component and its nested class, or the setting).
View source

inspect_component function

inspect_component(ctx: ComponentIntrospectionContext) -> dict[str, object] | None

Publish this extension's allowlisted metadata for one component.

Citry calls this direct query method only when a caller explicitly requests the extension by name. Override it together with a positive :attr:introspection_version. Return an exact built-in dict made only from strict JSON values, or None when this component has no entry. The method must be observational, deterministic, reentrant, and thread-safe; it must not render, load assets, mutate registration, or depend on request state.

Parameters

  • ctx ComponentIntrospectionContext - The owning engine, temporary live component class, and its already-built core metadata record. ``ctx.info.extensions`` is always empty.

Returns

dict[str, object] | None: An extension-owned JSON object, or ``None``.

View source

on_extension_created function

on_extension_created(ctx: OnExtensionCreatedContext) -> None

Called once when this extension instance is created.

View source

on_component_class_created function

on_component_class_created(ctx: OnComponentClassCreatedContext) -> None

Called after a Component class is defined, before it is registered.

View source

on_component_registered function

on_component_registered(ctx: OnComponentRegisteredContext) -> None

Called after a Component class is registered.

View source

on_component_unregistered function

on_component_unregistered(ctx: OnComponentUnregisteredContext) -> None

Called after a Component class is unregistered.

View source

on_component_input function

on_component_input(ctx: OnComponentInputContext) -> None

Called when a component starts rendering, before template_data.

Inspect or mutate ctx.kwargs / ctx.slots in place. These are the authoritative raw mappings. Citry normalizes Slots and constructs the component's final typed kwargs and slots once all input hooks finish.

View source

on_component_data function

on_component_data(ctx: OnComponentDataContext) -> None

Called after template_data; mutate ctx.template_data to add or change template variables.

View source

on_component_rendered function

on_component_rendered(ctx: OnComponentRenderedContext) -> CitryRender | str | None

Called after a component (and its children) rendered. Return a new CitryRender / str to replace the output, raise to replace the error, or return None to keep the original.

View source

on_slot_rendered function

on_slot_rendered(ctx: OnSlotRenderedContext) -> RenderPart | None

Called after a <c-slot> site rendered (a fill, or the fallback).

Return a new render part (str or CitryRender) to replace the output, or None to keep the original. Raising propagates.

View source

on_attrs_resolved function

on_attrs_resolved(ctx: OnAttrsResolvedContext) -> dict[str, Any] | None

Called after an HTML element's dynamic attributes resolved to their final dict, before it is formatted into the output. Return a new dict to replace the attributes, or None to keep them.

Fires per element per render, only for elements with at least one dynamic attribute (a c-* value or a c-bind spread).

View source

on_render_context_merge function

on_render_context_merge(ctx: OnRenderContextMergeContext) -> None

Called when a nested render's output is consumed by an enclosing render (a child component settling into its parent, or an already-rendered value embedded via an expression or slot).

Merge your extension's slice of ctx.child_context.extra into ctx.parent_context.extra, with your own policy (the dependencies extension, for example, appends records preserving order). The core does not merge anything itself.

View source

export_render_cache function

export_render_cache(ctx: OnRenderCacheExportContext) -> dict[str, object]

Export this payload extension's selected strict-JSON contribution.

View source

stage_render_cache function

stage_render_cache(ctx: OnRenderCacheStageContext) -> StagedRenderCacheContribution

Validate a detached payload and return a mutation-free replay contribution.

View source

on_serialize function

on_serialize(ctx: OnSerializeContext) -> str | None

Called at the end of CitryRender.serialize() with the joined HTML.

Return a new string to replace the output (threaded across extensions), or None to keep it. This is where serialize-time work that needs the whole page happens; the dependencies extension places the collected JS/CSS here, using ctx.placeholders for the <c-js>/<c-css> positions.

View source

on_template_loaded function

on_template_loaded(ctx: OnTemplateLoadedContext) -> str | None

Called once per class with the template string before it is parsed. Return a new string to modify it.

View source

on_template_compiled function

on_template_compiled(ctx: OnTemplateCompiledContext) -> list[BodyItem] | None

Called once per compiled body, with the generated node list. Mutate it in place or return a new list.

View source

on_template_reset function

on_template_reset(ctx: OnTemplateResetContext) -> None

Called after a component class's loaded template is reset.

View source

on_js_loaded function

on_js_loaded(ctx: OnJsLoadedContext) -> str | None

Called once per class with the component's primary JS content (inline or read from js_file). Return a new string to modify it.

View source

on_css_loaded function

on_css_loaded(ctx: OnCssLoadedContext) -> str | None

Called once per class with the component's primary CSS content (inline or read from css_file). Return a new string to modify it.

View source

ExtensionConfig class

Base for the per-component nested config class (reached as Extension.Config).

An extension named "view" (class_name == "View") lets a user define a nested class View: on a component. The manager rebuilds that nested class as a subclass of this base (binding component_class), then instantiates it per render and attaches it as component.view.

The component back-reference is a weakref, and the component may be None for extensions that run outside a component lifecycle (for example a future Storybook extension).

View source

component_class attribute

component_class: type[Component]

The Component class this config is defined on (bound by the manager).

View source

component attribute

component: Component

The owning Component instance.

Raises RuntimeError if this config runs outside a component lifecycle (no component), or if the component has been garbage-collected.

View source

ExtensionManager class

Fans each lifecycle hook out across a Citry instance's extensions.

Owned by :class:~citry.citry.Citry and built once in its __init__. Unlike DJC's module-level singleton, there is no deferred-event machinery: a component class is bound to its Citry (and thus these extensions) at definition time, so the extensions are always present when a hook fires.

Dispatch is smart: for each hook name, only the extensions that actually override that hook are called (an extension that does not implement a hook costs nothing). The same name-keyed dispatch underlies :meth:emit, which extensions use for their own custom hooks (e.g. on_dependencies).

View source

get_extension_command function

get_extension_command(name: str, command_name: str) -> type[ExtensionCommand]
View source

commands attribute

commands: dict[str, tuple[type[ExtensionCommand], ...]]

Every extension's CLI commands, keyed by extension name (read as Citry.commands).

Built-in extensions come first (they are prepended at construction), then the user's extensions in spec order; only extensions that declare commands appear. Extension names are unique (enforced at construction), so the keys never collide. The CLI reaches a command as citry ext run <extension name> <command name>.

View source

urls attribute

urls: tuple[URLRoute, ...]

The combined route table of every extension (read as Citry.urls).

Built-in extensions own their paths directly (e.g. the dependencies extension's cache/... and citry.js); a user extension's routes are namespaced under ext/<extension name>/ so they cannot collide with citry's own or each other's.

View source

emit function

emit(name: str, ctx: Any, result: _Result = 'none', field: str | None = None) -> Any

Dispatch hook name to the extensions that define it, combining the hooks' returned values per result:

  • "none": call every extension, ignore returns; return None.
  • "first": return the first non-None return (short-circuit).
  • "map": thread ctx.<field> - each non-None return replaces it (via dataclasses.replace) and is passed to the next extension; the final field value is returned.

An extension defines name by overriding it (see _extensions_with_hook). name need not be a hook declared on :class:Extension, so an extension can fire its own custom hook for others to implement.

Example

Most named hooks delegate here. on_component_data notifies every extension that defines it ("none")::

manager.emit("on_component_data", ctx)

on_template_loaded threads ctx.content through the extensions ("map") and returns the final string::

manager.emit("on_template_loaded", ctx, result="map", field="content")

A custom hook can let an extension short-circuit ("first" returns the first non-None value)::

manager.emit("on_my_event", ctx, result="first")
View source

on_component_class_created function

on_component_class_created(component_class: type[Component]) -> None
View source

on_component_registered function

on_component_registered(name: str, component_class: type[Component]) -> None
View source

on_component_unregistered function

on_component_unregistered(name: str, component_class: type[Component]) -> None
View source

on_component_input function

on_component_input(component: Component) -> None
View source

on_component_data function

on_component_data(component: Component, context: CitryContext, template_data: dict[str, Any], js_data: dict[str, Any], css_data: dict[str, Any]) -> None
View source

on_render_context_merge function

on_render_context_merge(parent_context: CitryContext, child_context: CitryContext) -> None
View source

on_serialize function

on_serialize(context: CitryContext, html: str, placeholders: dict[str, str], deps_strategy: str, deps_position: str) -> str
View source

on_component_rendered function

on_component_rendered(component: Component, render: CitryRender | str | None, error: Exception | None) -> tuple[CitryRender | str | None, Exception | None, bool]

Thread the rendered output through the extensions; a return replaces the render, a raise replaces the error.

View source

on_slot_rendered function

on_slot_rendered(component: Component, slot: Slot, slot_name: str, slot_node: SlotNode, slot_is_required: bool, result: RenderPart) -> RenderPart

Thread a slot's rendered output through the extensions; a return replaces the result, a raise propagates.

View source

has_hook function

has_hook(name: str) -> bool

Whether any installed extension implements the hook name.

View source

on_attrs_resolved function

on_attrs_resolved(component: Component, tag_name: str, attrs: dict[str, Any]) -> dict[str, Any]

Thread an element's resolved attribute dict through the extensions; a return replaces the dict, a raise propagates.

This sits on a per-element per-render hot path, so when no extension implements the hook the dict is returned without building a context.

View source

on_template_loaded function

on_template_loaded(component_class: type[Component], content: str) -> str
View source

on_template_compiled function

on_template_compiled(component_class: type[Component], nodes: list[BodyItem]) -> list[BodyItem]
View source

on_template_reset function

on_template_reset(component_class: type[Component]) -> None

Notify extensions after a component class's template is reset.

View source

on_js_loaded function

on_js_loaded(component_class: type[Component], content: str) -> str
View source

on_css_loaded function

on_css_loaded(component_class: type[Component], content: str) -> str
View source

on_files_reset function

on_files_reset(component_class: type[Component]) -> None

Notify extensions that a component class's loaded asset files were reset, so each drops its own per-class state (the dependencies built-in drops its merged result here).

Deliberately not declared on the :class:Extension base: this is the first consumer of the duck-typed custom-hook dispatch (an extension subscribes by defining a method named on_files_reset).

View source

ExtensionCommand class

Base class for an extension's CLI command.

Subclass this, set name (and usually help), declare any arguments, and define handle to do the work. A command that only groups subcommands leaves handle unset, and the runner prints its help instead of running anything. The declarations are turned into an argparse parser and dispatched by :mod:citry.command; an extension lists its command classes in Extension.commands and a user reaches one as citry ext run <extension> <command>. (Extension HTTP routes are a separate surface, Extension.urls.)

View source

name attribute

name: str

The command name (citry ext run <extension> <name>).

View source

help attribute

help: str

One-line description of the command, shown in --help output.

View source

arguments attribute

Positional arguments and options, declared with :class:~citry.command.CommandArg.

View source

subcommands attribute

Nested commands. A command with subcommands usually has no handle of its own.

View source

subparser_input attribute

subparser_input: CommandSubcommand | None

Optional customization of how this command appears when nested under a parent.

View source

handle attribute

handle: CommandHandler | None

Runs the command, called with the parsed options as keyword arguments. None (the default) marks a command that only groups subcommands; a real command overrides this with def handle(self, **kwargs).

View source

citry attribute

citry: Citry | None

The engine the command runs against, bound by the runner before handle is called (mirrors :attr:Extension.citry). A command's handle reads it to reach the component registry and the installed extensions.

View source

Debug class

Bases: Extension

Draw development-only boundaries around component and slot output.

Install this extension explicitly with Citry(extensions=[Debug]). Its per-component config has two exact boolean fields, highlight_components and highlight_slots. Set them globally in extensions_defaults["debug"] or override them in a component's nested class Debug.

The visual boundaries are real div elements. They are useful for inspecting ordinary page structure, but can affect layout, direct-child selectors, and restricted table or select content models. Do not enable them in production or use them for layout-sensitive verification.

Example

Enable both boundary types for one engine:

from citry import Citry
from citry.ext.debug import Debug

app = Citry(
    extensions=[Debug],
    extensions_defaults={
        "debug": {
            "highlight_components": True,
            "highlight_slots": True,
        },
    },
)
View source

CommandArg class

One positional argument or option, mirroring ArgumentParser.add_argument.

Every field maps to the matching add_argument keyword, and :func:build_parser passes them through unchanged, so the field names must stay aligned with argparse.

View source

name_or_flags attribute

name_or_flags: str | Sequence[str]

A positional name ("path") or a list of option flags (["--shout", "-s"]).

View source

action attribute

action: CommandAction | Action | None
View source

nargs attribute

nargs: int | Literal['*', '+', '?'] | None
View source

to_add_argument_kwargs function

to_add_argument_kwargs() -> dict[str, Any]

The add_argument keywords for this argument, minus name_or_flags.

name_or_flags is passed positionally by :func:build_parser, so it is not included here; unset (None) fields are dropped.

View source

CommandArgGroup class

A titled group of arguments, mirroring ArgumentParser.add_argument_group.

Place one in a command's arguments list to group related options together in the --help output.

View source

CommandSubcommand class

How a command appears when nested under a parent, mirroring add_subparsers().add_parser.

A command sets this as its subparser_input to customize its entry in the parent's subcommand list (for example a different help line or program name).

View source

to_add_parser_kwargs function

to_add_parser_kwargs() -> dict[str, Any]

The add_parser keywords for this subcommand entry (unset fields dropped).

View source

OnAttrsResolvedContext class

View source

citry attribute

citry: Citry

The Citry instance the component belongs to.

View source

component attribute

component: Component

The component whose template holds the element.

View source

tag_name attribute

tag_name: str

The HTML tag the attributes belong to (e.g. "div").

View source

attrs attribute

attrs: dict[str, Any]

The resolved attribute dict: class/style already normalized to strings, booleans still True, omitted attributes already absent.

View source

OnComponentClassCreatedContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The created Component class.

View source

nested_declarations function

nested_declarations(name: str) -> tuple[NestedClassDeclaration, ...]

Return the exact authored bindings for name in component C3 order.

A record whose value is None is an explicit reset, distinct from the name being absent. The classes are the original source objects, even after Citry replaces the component attribute with an effective runtime config class.

Parameters

  • name str - The nested declaration name, usually the extension's [`class_name`](/reference/extensions/#citry-extension-class-name).

Returns

tuple[NestedClassDeclaration, ...]: The declarations from the component through its bases in C3 order.

View source

OnComponentDataContext class

View source

citry attribute

citry: Citry

The Citry instance the component belongs to.

View source

component attribute

component: Component

The Component instance being rendered.

View source

context attribute

context: CitryContext

The render-scoped CitryContext for this component's render. Extensions stash tree-wide state in context.extra (for example the dependencies extension's render records); it bubbles up through on_render_context_merge as nested renders are consumed. context.provides is not yet populated when this hook fires.

View source

template_data attribute

template_data: dict[str, Any]

The template variables from Component.template_data() (mutable).

View source

js_data attribute

js_data: dict[str, Any]

The JS variables from Component.js_data() (mutable). Consumed by the built-in dependencies extension.

View source

css_data attribute

css_data: dict[str, Any]

The CSS variables from Component.css_data() (mutable). Consumed by the built-in dependencies extension.

View source

OnComponentInputContext class

View source

citry attribute

citry: Citry

The Citry instance the component belongs to.

View source

component attribute

component: Component

The Component instance being rendered.

View source

kwargs attribute

kwargs: dict[str, Any]

The keyword arguments passed to the component (mutable plain dict).

View source

slots attribute

slots: dict[str, Any]

The slot fills passed to the component (mutable plain dict).

View source

OnComponentRegisteredContext class

View source

citry attribute

citry: Citry

The Citry instance the component was registered with.

View source

name attribute

name: str

The name the component was registered under.

View source

component_class attribute

component_class: type[Component]

The registered Component class.

View source

OnComponentRenderedContext class

View source

citry attribute

citry: Citry

The Citry instance the component belongs to.

View source

component attribute

component: Component

The Component instance that was rendered.

View source

render attribute

render: CitryRender | str | None

The rendered output, or None if rendering failed.

View source

error attribute

error: Exception | None

The error raised during rendering, or None if it succeeded.

View source

OnComponentUnregisteredContext class

View source

citry attribute

citry: Citry

The Citry instance the component was unregistered from.

View source

name attribute

name: str

The name the component was registered under.

View source

component_class attribute

component_class: type[Component]

The unregistered Component class.

View source

OnCssLoadedContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose CSS was loaded.

View source

content attribute

content: str

The CSS content (inline or read from css_file).

View source

OnExtensionCreatedContext class

View source

citry attribute

citry: Citry

The Citry instance the extension belongs to.

View source

extension attribute

extension: Extension

The created extension instance.

View source

OnFilesResetContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose loaded asset files were reset.

View source

OnJsLoadedContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose JS was loaded.

View source

content attribute

content: str

The JS content (inline or read from js_file).

View source

OnRenderContextMergeContext class

View source

citry attribute

citry: Citry

The Citry instance the render belongs to.

View source

parent_context attribute

parent_context: CitryContext

The context of the render that consumed the nested one.

View source

child_context attribute

child_context: CitryContext

The context of the consumed nested render.

View source

OnSerializeContext class

View source

citry attribute

citry: Citry

The Citry instance the render belongs to.

View source

context attribute

context: CitryContext

The root render's CitryContext (its extra carries everything that bubbled up during the render).

View source

html attribute

html: str

The joined HTML (threaded: return a new string to replace it).

View source

placeholders attribute

placeholders: dict[str, str]

The placeholder parts found during serialization: unique placeholder id (the Placeholder.key plus a counter and a private serialization identity) to the exact text standing in for it in html. Match the key prefix rather than relying on the private suffix.

View source

deps_strategy attribute

deps_strategy: str

The serialize(deps_strategy=...) argument.

View source

deps_position attribute

deps_position: str

The serialize(deps_position=...) argument.

View source

OnSlotRenderedContext class

View source

citry attribute

citry: Citry

The Citry instance the component belongs to.

View source

component attribute

component: Component

The component whose template holds the <c-slot> that was rendered.

View source

slot attribute

slot: Slot

The Slot that was rendered: the fill, or the fallback when no fill was given.

View source

slot_name attribute

slot_name: str

The resolved slot name ("default" for an unnamed slot).

View source

slot_node attribute

slot_node: SlotNode

The runtime SlotNode at whose site the slot rendered.

View source

slot_is_required attribute

slot_is_required: bool

Whether the slot resolved as required.

View source

result attribute

result: RenderPart

The rendered output (a str or a CitryRender).

View source

OnTemplateCompiledContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose template was compiled.

View source

nodes attribute

nodes: list[BodyItem]

The generated body node list.

View source

OnTemplateLoadedContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose template was loaded.

View source

content attribute

content: str

The template string (before parsing).

View source

OnTemplateResetContext class

View source

citry attribute

citry: Citry

The Citry instance the component class belongs to.

View source

component_class attribute

component_class: type[Component]

The Component class whose loaded template was reset.

Citry version: 0.3.0