Operations#
Build an Engine once with Setup, then render
with Page, Fragment and Stream.
- class Setup(root: str | Path, signing: SignOptions = ..., watch: bool = False, fragments: list[str] = ..., escape: bool = False, max_template_bytes: int = 1048576)[source]#
Where the templates are and how to render and sign them.
Build one at startup and call
run()to get theEngine.Example
>>> from anza import Setup, Page >>> engine = Setup(root="templates").run() >>> Page("/", {"title": "Hello"}).run(engine).html '<html><body><h1>Hello</h1></body></html>'
Warning
With the default
escape=Falseslot values are inserted without HTML escaping. Turnescapeon, or pass values throughescape_html(), when they can contain user input.- signing: SignOptions#
How fragments are signed (off by default); keys are validated by
run().
- watch: bool = False#
Reload templates when files under
rootchange (a polling thread; stop it withEngine.close()).
- fragments: list[str]#
Fragment allowlist. Empty (the default) lets fragments use any template; otherwise an entry ending in
/allows that prefix (e.g."fragments/") and any other entry allows that exact name.
- escape: bool = False#
HTML-escape
{{slot}}values;{{{slot}}}stays raw. Off by default: values are inserted as is.
- validate() None[source]#
Check that
rootexists and is a directory.- Raises:
AnzaError –
VALIDATIONwhen it is missing or not a directory.
- run() Engine[source]#
Validate the root and the signing keys, load every template and start the watcher when
watchis set.- Returns:
Engine – The engine to render with. Call
Engine.close()to stop the watcher.- Raises:
AnzaError –
VALIDATIONfor a bad root;CRYPTOfor a missing, short or mismatched signing key (seeSignOptions);TEMPLATEfor a template that is not UTF-8, is too large or has a malformed placeholder;NOT_FOUNDfor a template that resolves (through a symlink) outsideroot.
- class Page(route: str, params: Any = None)[source]#
Render the page for a request route into a full HTML document.
/renderspages/home.htmland/a/b(or/a/b/) renderspages/a/b.html. An unknown route falls back topages/index.htmlwhen it exists. The page goes intolayout/shell.htmlas{{content}}(the shell is bound withparamspluscontent), or into a minimal default document with a<dock-main>open shadow root when there is no shell. See Rendering.Example
>>> from anza import Page, Setup >>> engine = Setup(root="templates").run() >>> doc = Page("/", {"title": "Home"}).run(engine) >>> doc.html '<html><body><h1>Home</h1></body></html>' >>> Page("/missing/../route").run(engine) Traceback (most recent call last): ... anza.errors.AnzaError: [Anza:NOT_FOUND] Page not found
- params: Any = None#
Values to bind (a dict,
(key, value)pairs, or any object whose public attributes are read, such as a dataclass or Pydantic model).
- validate() None[source]#
Check the operation before rendering.
- Raises:
AnzaError –
VALIDATIONwhenrouteis empty.
- run(engine: Engine) Document[source]#
Render the page with
engine.- Parameters:
engine – The engine from
Setup.run().- Returns:
Document – The rendered HTML document.
- Raises:
AnzaError –
VALIDATIONfor an empty route;NOT_FOUND(messagePage not found) for an unknown or malformed route (..,\, NUL) when there is nopages/index.html.
- class Fragment(template: str, slot: str, params: Any = None)[source]#
Render one template into an
Envelopefor a slot.The envelope (wire format v2) carries the slot, a Unix timestamp, the rendered HTML and, when the engine signs, a signature over all of them. Serialize it with
Envelope.to_json()and send it as a fetch response, an SSE event or a WebSocket message.Example
>>> from anza import Fragment, Setup, SignOptions >>> signed = Setup( ... root="templates", ... signing=SignOptions(mode="hmac", secret="a-secret-of-at-least-32-bytes!!!"), ... ).run() >>> env = Fragment("feed/card.html", "feed", {"title": "New"}).run(signed) >>> env.v, env.slot, env.html (2, 'feed', '<article>New</article>') >>> len(env.sig) # HMAC-SHA256, lowercase hex 64
Warning
Values are inserted without HTML escaping unless the engine was set up with
escape=True.- params: Any = None#
The values to bind (see
Page.params).
- validate() None[source]#
Check the operation before rendering.
- Raises:
AnzaError –
VALIDATIONwhentemplateorslotis empty.
- run(engine: Engine) Envelope[source]#
Render the fragment with
engine.- Parameters:
engine – The engine from
Setup.run().- Returns:
Envelope – The envelope, signed when the engine has a signing key.
- Raises:
AnzaError –
VALIDATIONfor an empty or malformed template name or an empty slot;NOT_FOUNDfor an unknown template or one outside thefragmentsallowlist.
- class Stream(template: str, slot: str, params: Any = None)[source]#
Render one template as a Server-Sent Event in one call.
Equivalent to
format_event(Fragment(template, slot, params).run(engine)), seeformat_event()and Streaming.Example
>>> from anza import Setup, Stream >>> engine = Setup(root="templates").run() >>> text = Stream("feed/card.html", "feed", {"title": "Push"}).run(engine) >>> text.startswith('event: template\ndata: {"v":2,"slot":"feed",') True >>> text.endswith('\n\n') True
- params: Any = None#
The values to bind (see
Page.params).
- validate() None[source]#
Check the operation before rendering.
- Raises:
AnzaError –
VALIDATIONwhentemplateorslotis empty.
- run(engine: Engine) str[source]#
Render the fragment and frame it as an SSE event.
- Parameters:
engine – The engine from
Setup.run().- Returns:
str –
"event: template\ndata: <envelope JSON>\n\n".- Raises:
AnzaError – The same errors as
Fragment.run().