Rendering
The render pipeline, its output structs, Citry's trusted-HTML marker, and the Secret marker that keeps a value out of the data sent to the browser. citry.Markup is exactly markupsafe.Markup. Markup(value) trusts the complete value without sanitizing or validating anything. safe_eval runs an expression in the sandbox, unsafe marks a function that sandboxed expressions must not call, and SecurityError is what a blocked access raises. Two parts of this page are experimental and may change in a minor release: RenderFrame, and the API for embedding Citry tags in another template engine (CompiledBody, render_compiled_body and collect_compiled_body_fills).
CitryElement class
Intermediate representation of a component invocation.
Created by Component(). Holds the component class and the kwargs/slots that were passed. Rendering is deferred until .render() is called.
Attributes
comp_cls- The Component subclass to render.kwargs- The keyword arguments passed to the component.slots- The slot fills passed to the component. Filled from either channel: the reservedslots=kwarg when composing from Python (MyComp(title="x", slots={...})), or the collected<c-fill>tags / implicit default body when composed by a parent template. Values are raw inputs here (strings, functions, elements, Slots); they normalize toSlotinstances when the component instance is created at render time.component_tag_client_bindings- The final source-ordered bindings captured from a component tag, including native Vue props and event listeners and Citry server events. They are framework metadata, not Python kwargs.element_morph_metadata- Private metadata materialized only by the dynamic ordinary-element built-in.
render function
render(template_globals: Mapping[str, Any] | None = None, provides: Mapping[str, Any] | None = None) -> CitryRenderRender this component into a CitryRender.
Each call mints fresh per-instance state (render_id, etc.), so the same CitryElement can be rendered multiple times with distinct identities.
Parameters
template_globalsMapping[str, Any] | None- Template variables added for this render. They reach the whole tree, sit above the Citry instance's globals, and sit below each component's owntemplate_data.providesMapping[str, Any] | None- Values the root component and its rendered descendants may read withinject(). A nested directrender()call starts a new root and must pass its required values again.
Returns
CitryRender: A ``CitryRender`` with the complete rendered tree. Call
CitryRender class
The result of rendering a CitryElement (the render-phase output).
Experimental
The
frameattribute, theframe=argument and theRenderFrameclass they use are experimental. They may change in a minor release without a deprecation period. The rest ofCitryRenderis stable.
Attributes
parts- Ordered list ofstror nestedCitryRenderfragments.context- TheCitryContextused to produce this render.is_component_rootbool- True for a nontransparent component's whole output. Transparent whole outputs and interior renders (a<c-if>/<c-for>block, a nested template, slot-fill content rendered in the enclosing scope) are False. Serialization uses this to tell a completed child-component subtree (which becomes its own marked frame) from content that joins into the surrounding frame; the component on the context cannot tell these apart, because slot-fill content carries the context of the component that wrote it, not the one it renders inside.
is_component_root attribute
is_component_root: boolWhether this render is the whole output frame of one component.
serialize function
serialize(deps_strategy: DepsStrategy = 'document', deps_position: DepsPosition = 'smart', csp_nonce: str | None = None, security_csp: SecurityCspMode | None = None, security_javascript: SecurityJavascriptMode | None = None, security_script_integrity: SecurityScriptIntegrityMode | None = None, ssr: bool | None = None) -> strTurn this render into a final HTML string.
In static output, each component's root element gets a data-cid-<id> attribute. When a rendered component needs Vue in the browser, Citry keeps the surrounding page markup, puts the rendered content (the page body, for a full document) in one element where Vue mounts, and adds the data and scripts Vue needs to mount it. Citry places the collected JS/CSS according to deps_strategy and deps_position.
With security_csp="warn", Citry reports content that would break under a Content Security Policy but leaves the output unchanged. With "strict", it raises instead of warning, and it requires csp_nonce when Citry emits an executable script or a <style> element. With security_javascript="warn", it reports which components need JavaScript; "omit" drops Citry's scripts but keeps the HTML and CSS; "forbid" raises when a rendered component needs browser behavior.
Parameters
deps_strategyDepsStrategy-How to handle the collected JS/CSS.
"document"(default): emit the tags, plus Citry's Vue browser runtime and the data it needs to mount the page when a component needs per-instance browser behavior, such asjs_data()values on its Vue instance or$componentcallbacks."simple": the tags only, no JavaScript runtime. For static pages and emails; per-instance JS does not run (CSS variables still work, they are pure CSS)."fragment": HTML meant to be inserted into an already-loaded page (an HTMX swap,fetch+innerHTML, ...). A fragment without browser behavior carries its tags directly. An interactive fragment carries a script that loads Citry's browser runtime if the page does not have it yet, plus JSON that tells the runtime which assets to load before it mounts the fragment. A fragment that carries JS, CSS, or browser behavior requires a mounted web integration."ignore": no tags inserted.
deps_positionDepsPosition-Where the tags go (
document/simpleonly)."smart"(default): into the<c-js>/<c-css>placeholders when present, else CSS before the first</head>and JS before the last</body>, else CSS is prepended and JS appended."prepend"/"append": all tags before/after the whole output.
csp_noncestr | None- Raw request nonce to add to structured scripts and inline styles. The host owns nonce generation and the matching Content-Security-Policy response header.security_cspSecurityCspMode | None- Override this render's engine-level CSP policy.security_javascriptSecurityJavascriptMode | None- Override this render's engine-level JavaScript delivery policy.security_script_integritySecurityScriptIntegrityMode | None- Overridesecurity_script_integrityfor this render.ssrbool | None- Override the engine's initial Vue HTML hydration setting.Noneuses thessrsetting.
Raises
RuntimeError- If a child component was left unrendered, which can only happen when this render did not come fromrender(); if the same render is placed more than once in the output; or ifdeps_strategy="fragment"needs a mounted web integration and none is mounted.ValueError- If an argument is invalid; ifsecurity_csp="strict"rejects the output or the output needscsp_nonceand none was given; or ifsecurity_javascript="forbid"finds a component that needs browser behavior; or if a component usesEventsand no web integration is mounted.TypeError- Ifssris not a bool orNone.
serialize_result function
serialize_result(deps_strategy: DepsStrategy = 'document', deps_position: DepsPosition = 'smart', csp_nonce: str | None = None, security_csp: SecurityCspMode | None = None, security_javascript: SecurityJavascriptMode | None = None, security_script_integrity: SecurityScriptIntegrityMode | None = None, ssr: bool | None = None) -> SerializedRenderReturn final HTML together with security metadata for those exact bytes.
Arguments and validation match serialize(); this method also returns the security metadata a host needs, while serialize() returns only result.html.
SerializedRender class
Final HTML plus host-facing security metadata for those exact bytes.
SerializedSecurity class
Security contributions produced by one serialization call.
csp_script_hashes is the deduplicated document-order tuple of quoted hash sources that a host can add to script-src. It and scripts are empty when digest-producing security features are disabled.
SerializedScriptSecurity class
Security metadata for one structured script in serialized output.
digests uses the unquoted SRI form, such as "sha384-...". provenance states whether Citry computed or verified those bytes. origin_class_id identifies the component class when one owns the tag.
provenance attribute
provenance: Literal['citry-computed', 'declared-verified', 'declared-unverified'] CitryContext class
Render-scoped state for a single component render.
Attributes
variables- The per-component template variables (thetemplate_dataoutput). Read by nodes when evaluating expressions. Values are ordinary Python objects; the private mapping stores any Const promise separately by variable name.component- TheComponentinstance currently rendering. Gives a node access to the component tree (itscitryregistry for resolving child component names, and itsparent/rootlinkage). The current component is stored on the context, so each component render gets its ownCitryContext.extra- Tree-wide scratch space for extensions (for example the collected JS/CSS dependency records). Citry does not copy these entries itself: when a child render is placed into its parent, Citry calls theon_render_context_mergehook, and each extension copies its own entries from the child'sextrainto the parent's. By convention, each extension stores its data under a top-level key named after itself (extra["dependencies"]). Citry uses some other top-level keys too, so choose a distinctive name.provides- The provide/inject entries active at this point of the render. Entries may hold a direct caller value, a frozen keyword- field payload, or a private blocked marker. Read-only by convention;Component.provideandComponent.unprovidebuild a new mapping rather than mutating this one.
CitryTemplate class
A component's loaded template: the source string, its origin, and its compiled form (once first rendered).
Attributes
sourcestr- The template string, afteron_template_loadedhooks ran.originstr- Where the template came from, for error messages and debugging. The absolute file path for a file template, or"<module file>::<ClassName>"for an inline one.filepathPath | None- The resolved template file, orNonewhen the template was inlined on the class.generateCallable[[], list[BodyItem]] | None- Internal. The compiled body-generating function; calling it yields a fresh node list.Noneuntil the render pipeline compiles the template on first render.prepared_generateCallable[[], list[BodyItem]] | None- Internal. The separate typed-output generator used only by the private Vue preparation path.used_varsfrozenset[str]- Internal. Every variable name the template uses, including in nested tags (the parse-timeTemplate.used_variables). Empty until compiled. TheConstoptimization keys its cache only on these.declared_slotstuple[DeclaredSlot, ...]- Internal. The<c-slot>tags the template declares (static names only), used to check the component against itsSlotsschema. Empty until compiled.
prepared_standalone_bodies attribute
prepared_standalone_bodies: OrderedDict[object, list[Any]] CompiledBody class
Opaque handle to one already-compiled independent Citry body list.
Experimental
Part of the experimental API for embedding Citry tags in another template engine. It may change in a minor release without a deprecation period.
render_compiled_body function
render_compiled_body(body: CompiledBody, context: CitryContext, variables_overlay: Mapping[str, Any] | None = None) -> CitryRenderRender a protected compiled body with a live host-variable overlay.
Experimental
Part of the experimental API for embedding Citry tags in another template engine. It may change in a minor release without a deprecation period.
collect_compiled_body_fills function
collect_compiled_body_fills(body: CompiledBody, context: CitryContext, sink: FillSink, variables_overlay: Mapping[str, Any] | None = None) -> NoneCollect fills from a host-selected compiled body against a live overlay.
Experimental
Part of the experimental API for embedding Citry tags in another template engine. It may change in a minor release without a deprecation period.
Placeholder class
A spot in the output whose final text is supplied at serialize time.
Rendered output is normally text and nested renders, fixed once rendered. A Placeholder marks a position whose content is only known when the whole page is serialized: the <c-js> / <c-css> built-ins render one each, and the dependencies extension fills them with the collected script/style tags via the on_serialize hook.
Attributes
key- What belongs at this spot (e.g."deps:js"). The serializer reports each occurrence to theon_serializehook under this key plus a counter and a private per-serialization identity. An extension that knows the key supplies the text; an occurrence no extension fills serializes to nothing. The private identity keeps cleanup from matching an authored<template c-render-id>with the same key and counter.
RenderFrame class
Immutable identity needed to traverse and serialize one render frame.
Experimental
RenderFrame, theCitryRender.frameattribute and theframe=argument ofCitryRenderare experimental. They may change in a minor release without a deprecation period.
is_transparent_root attribute
is_transparent_root: boolTrue for a transparent component's whole output, excluding caller-owned interiors.
from_context function
from_context(context: CitryContext, is_component_root: bool, is_transparent_root: bool = False) -> RenderFrameSnapshot the identity-bearing portion of one live render context.
const_value function
Unwrap outer marker layers; leave plain values unchanged and reject marker cycles.
Markup class
Bases: str
citry.Markup is exactly markupsafe.Markup, re-exported unchanged. Markup(value) trusts the complete value without sanitizing, validating, or escaping anything.
Secret class
Bases: Generic
Wrap a server-only value, such as an API token, so Citry never sends it to the browser.
Citry sends everything Component.js_data() returns to the browser, where anyone can read it in the page source. Wrap a value in Secret to have Citry refuse to send it.
Rendering a component raises a TypeError that names the component and the key, before any HTML is sent, when a Secret is anywhere in the dicts and lists js_data() returns. A Secret in a Component.css_data() value or a State field raises ValueError, whether or not the component has CSS. citry --app ... check also warns when it can see the Secret: a module-level Secret that js_data() puts in its result, such as {"token": API_TOKEN}, or reads from an attribute stored on a module or a plain class, such as config.API_TOKEN (not through a property or a lazy settings object), or a JsData field annotated Secret[str].
A Secret that js_data() returns in another place, such as inside a dataclass instance nested in the result, as a dict key, or in a set, is refused too, but by the TypeError for any value JSON cannot carry, which does not name the key.
On the server a Secret keeps its value hidden. Call get_secret_value() where you need the value, for example to call an API in an event handler. Everywhere Python turns it into text it shows *** instead: str(), repr(), f-strings, logs, and {{ token }} in a template. So passing a Secret to a template or to a child component's Kwargs is allowed and never prints the value.
A child that uses the render cache cannot take a Secret this way. Without Cache.vary(), its cache key is built from all of its kwargs, and a Secret cannot be part of a key, so rendering raises CacheKeyError. Return values that are not secret, such as a user id, from that child's Cache.vary(), or leave the child uncached.
Annotate a Kwargs or TemplateData field as Secret[str] (or Secret[bytes], and so on) so a plain value passed there is rejected. The type in brackets is only for type checkers: check_input_types tests that the value is a Secret, not what it holds. When Kwargs is a Pydantic model, Pydantic's own error for a plain value prints that value; add model_config = ConfigDict(hide_input_in_errors=True) to the model to keep it out of the message.
A Secret never equals a plain value: API_TOKEN == sent is always False. To check a value a user sent, use hmac.compare_digest, which takes the same time however much of the input matches:
token = API_TOKEN.get_secret_value().encode()
matches = hmac.compare_digest(token, sent.encode())
Compare bytes, as here: compare_digest raises TypeError for text that is not ASCII. Two secrets are equal when their values are equal. When both values are str, or both are bytes, they are compared with hmac.compare_digest. Any other values are compared with ==.
A Secret cannot be changed after it is created. It can be a dict key when its value is hashable, and equal secrets hash alike. bool() is the truth value of the wrapped value, so if token: tells whether a token is set. Copying or pickling a Secret keeps the value, so a pickle still holds it in readable form.
Citry detects only values you wrap. A secret kept in a plain str is sent like any other string.
Example
Read a token on the server, and send the browser only the result:
import os from citry import Component, Secret API_TOKEN = Secret(os.environ["API_TOKEN"]) class Board(Component): def template_data(self, kwargs, slots): token = API_TOKEN.get_secret_value() return {"rows": load_rows(token)} def js_data(self, kwargs, slots): # Returning API_TOKEN here raises TypeError. return {"apiBase": "/api"} template = ''' <ul> <c-for each="row in rows"> <li>{{ row }}</li> </c-for> </ul> '''
Parameters
valueT- The value to keep on the server. Any type works.
Raises
TypeError- Whenvalueis already aSecret. Pass thatSecreton as it is instead of wrapping it again.
get_secret_value function
get_secret_value() -> TReturn the wrapped value.
Call it only in server code that needs the value itself. Returning the result from js_data(), or writing it into a template, sends it to the browser as an ordinary value.
Example
token = API_TOKEN.get_secret_value()
Returns
T: The value given to `Secret(...)`.
SecurityError class
Bases: Exception
An expression attempted an operation blocked by the evaluator's sandbox.
The evaluator raises this at evaluation time when a checked variable, attribute, key, callable, or assignment is unsafe.
safe_eval function
safe_eval(source: str, validate_variable: Callable[[str], bool] | None = None, validate_attribute: Callable[[Any, str], bool] | None = None, validate_subscript: Callable[[Any, Any], bool] | None = None, validate_callable: Callable[[Callable], bool] | None = None, validate_assign: Callable[[str, Any], bool] | None = None) -> Callable[[Mapping[str, Any]], Any]Compile a Python expression into a function that runs it inside the sandbox Citry uses for template expressions. Call the result with a mapping of the names the expression may read. A blocked call, attribute, or name raises SecurityError when the expression runs. Pass only expression text that you or the template author wrote, never text from a request or from stored user data, because the sandbox does not block everything. See Sandbox template code for what the sandbox blocks and what it misses.
Parameters
sourcestr- The Python expression string to transform.validate_variableCallable[[str], bool] | None- Optional extra validation for variable lookups.validate_attributeCallable[[Any, str], bool] | None- Optional extra validation for attribute access.validate_subscriptCallable[[Any, Any], bool] | None- Optional extra validation for subscript access.validate_callableCallable[[Callable], bool] | None- Optional extra validation for function calls.validate_assignCallable[[str, Any], bool] | None- Optional extra validation for variable assignments.
Returns
Callable[[Mapping[str, Any]], Any]: A compiled function that takes a context mapping and evaluates the expression.
Raises
SyntaxError- If the input is not valid Python syntax, contains forbidden constructs, or nests too deeply (more than about 97 chained operators or 65 nested brackets).
unsafe function
unsafe(f: F) -> FMark a function or method so that sandboxed expressions cannot call it. While the expression sandbox is on (the default), a template that calls it fails with SecurityError. See Block your functions.