API Reference
Use this reference to look up class signatures, parameters, and methods. For examples that connect these APIs in an application, start with serving a site, rendering Markdown pages, or running named SQL queries.
Static Files
StaticFiles
StaticFiles(
*,
loader: BaseLoader,
html: bool = False,
check_dir: bool = True,
follow_symlink: bool = False,
max_age: int | None = 3600,
template_cache_control: str | None = None,
global_vars: dict[str, Any] | None = None,
filters: dict[str, Callable[..., Any]] | None = None,
extensions: Sequence[type[Extension] | str] = (),
fragments: Contexts | None = None,
page_context: Sequence[Route] = (),
query_runner: QueryRunner | None = None,
)
A raw ASGI app that serves files from a Jinja2 loader's directories.
Files ending in .jinja or .j2 are rendered as templates, markdown is rendered to HTML, and everything else is served as-is with ETag/Last-Modified support; see the module docstring for the full template surface. Owns the resources it renders through — the Jinja Environment, the MarkdownIt instance, and the optional QueryRunner — all constructed once in init.
The runner is held privately: templates can run a declared query through fetch() but cannot reach the runner itself. When no runner is configured, fetch() yields empty results rather than failing, so a site with no database still serves.
Two folders under a served directory are discovered rather than declared:
shortcodes/ contributes its template files as Jinja tags, and fragments/
contributes its region templates as htmx regions. Those regions are
self.fragments, which this app serves under its own /fragment/<name> and
an HTMXResponse anywhere in the app finds to cascade. The fragments
argument names the context functions of the regions that want one; a region
with none renders from its template alone.
Wire the serving app from a loader and its rendering resources.
loader supplies the search directories files are served from. html
enables directory index and 404.html resolution. check_dir makes the
first request verify the directories exist (raising rather than 404ing);
follow_symlink permits symlinked paths to resolve outside the realpath
of the served directory. max_age and template_cache_control set the
Cache-Control headers for static files and rendered templates respectively
(see the module docstring). global_vars and filters are merged into the
Jinja environment. query_runner executes template queries; when None,
fetch() returns empty results.
extensions are your own Jinja extensions (a class or an import path),
registered alongside the ones this class always wires: the {% sql %} tag,
the {% markdown %} tag, the shortcode tags, and the region tags.
page_context gives a page variables of its own: each Route names a
URL and an async function that returns the variables the page at that
URL renders with. See starlette_templates.context.
Every region template under a served directory's fragments/ becomes a
region and a Jinja tag of its own name, whether or not fragments is
given, and the app needs no other wiring for the cascade. fragments
supplies the context functions of the regions that want one: a module
holding them, a mapping of name to function, a function on its own, or
any mix of those in a sequence. See starlette_templates.htmx.
Raises:
-
ValueError–for any discovery problem in a
shortcodes/orfragments/folder — a shortcode file whose name cannot become a tag, a region template that declares no triggers, a region two sources both supply a function for, or a name two directories both claim. Also when apage_contextentry carries no context function to call. -
TypeError–if a
fragmentssource is neither a module, a mapping, nor a function.
QueryRunner
Executes a named query and returns its rows for binding into a template.
StaticFiles depends on this protocol, not a concrete engine (§4): the app supplies an implementation, and tests supply a fake. The single method is awaited inline by the template's fetch() call, so the rows are available where fetch() is used.
Methods:
-
run–Execute
query, binding its:nameplaceholders fromparams, return the rows.
run
async
run(query: Query, params: Mapping[str, Any]) -> list[Row]
Execute query, binding its :name placeholders from params, return the rows.
params carries the values for any named placeholders in the SQL (the
runner is responsible for binding them out-of-band, never interpolating).
run() is awaited inside the request's cancellation scope, so when the client disconnects the CancelledError propagates here through the await — a runner that holds an engine-side query open should cancel it off that, the same way any async call below a cancelled scope unwinds. StaticFiles passes no separate cancellation handle: it is a raw ASGI app and has none to give.
Raises:
-
QueryExecutionError–if the query cannot be executed.
Query
dataclass
Query(name: str, database: str | None, sql: str)
A named SQL query declared in a template, awaiting execution.
Declared by a {% sql name [from database] %} tag or an sql-info fenced code
block; collected into a QuerySet and run only when a template calls fetch()
on it. database is None when the declaration named no database. sql is
the raw query text with its :name placeholders left unbound — the runner
binds them out-of-band, never by interpolation.
Attributes:
name
instance-attribute
name: str
database
instance-attribute
database: str | None
sql
instance-attribute
sql: str
QuerySet
dataclass
QuerySet(queries: list[Query] = list())
The named queries declared in one template render, keyed by name.
Populated as {% sql %} tags and sql-fenced blocks are encountered, then read
back by fetch() when a query runs. Exposed to templates as queries, where a
query is reached by name as an attribute (queries.stations) or via get().
A name may appear more than once across databases; get() and by_database()
disambiguate by database.
Methods:
-
get– -
by_database–
Attributes:
QueryExecutionError
QueryExecutionError(query: Query, message: str)
Raised when a QueryRunner fails to execute a template's query.
Carries the offending query so the render boundary can report which named query failed without the runner's engine-specific exception leaking through.
Attributes:
-
query–
query
instance-attribute
query = query
jsonify
jsonify(data: Any, indent: int | None = None) -> Markup
Convert data to a Markup-safe JSON string for embedding in HTML templates.
Uses Pydantic's serialization to handle complex types like datetime, Decimal, and BaseModel instances.
markdown_extension
markdown_extension(
md: MarkdownIt,
) -> type[MarkdownExtension]
Build a MarkdownExtension subclass whose {% markdown %} tag renders through md.
MarkdownExtension
Jinja2 extension for {% markdown %}...{% endmarkdown %} blocks.
The body is Jinja first and markdown second. It renders like any other template body — variables, filters, shortcodes, region tags — and the result is converted to HTML. Under autoescape a variable is escaped before the conversion, so a value cannot inject markup or markdown of its own.
The template's own indentation comes off the body first (the common prefix of its lines), so a block indented to line up with the HTML around it is not read as an indented code block.
An sql fence inside the block declares into the same QuerySet the page uses, the way one in an included markdown file does, and produces no output.
Do not register this base class directly — it has no renderer. Use
:func:markdown_extension to build a subclass bound to a MarkdownIt.
Markdown Pages
MarkdownPage
dataclass
MarkdownPage(body: str, page: dict[str, Any] = dict())
A markdown source split into the variables it declares and the body that renders.
page is the frontmatter as written, reached in a template as page.title.
It is empty for a file that opens with no fence. body is everything below
the closing fence, and is the whole source when there is no frontmatter, so
rendering body never loses content.
Attributes:
-
body(str) – -
page(dict[str, Any]) – -
layout(str | None) –The template named by the
layoutkey, or None for a page that wraps itself.
body
instance-attribute
body: str
page
class-attribute
instance-attribute
page: dict[str, Any] = field(default_factory=dict)
layout
property
layout: str | None
The template named by the layout key, or None for a page that wraps itself.
split_frontmatter
split_frontmatter(
source: str, name: str = "<markdown>"
) -> MarkdownPage
Split source into its frontmatter variables and the markdown below them.
The fence has to be the first line, so only a file written to carry frontmatter is read as carrying it. name names the file in any error raised.
Raises:
-
ValueError–if the file opens a frontmatter block and never closes it, or if
load_frontmatterrejects the block.
load_frontmatter
load_frontmatter(block: str, name: str) -> dict[str, Any]
Read a frontmatter block as the mapping of variables it declares.
name names the file in any error raised, since the block itself carries nothing to identify it by.
Raises:
-
ValueError–if the block is not valid YAML, if it holds anything but a mapping, or if its
layoutis not the name of a template.
Page Context
ContextRoutes
ContextRoutes(routes: Sequence[Route] = ())
The context functions of a site's pages, keyed by the path each one answers.
Built once at wiring time by StaticFiles, from its page_context
argument, and read-only from the first request onward, so every concurrent
request shares one instance without a lock.
Hold the routes, after checking each one carries a context function.
Raises:
-
ValueError–if a route has no endpoint to call — a
Mountor aWebSocketRouterather than aRoute. Wiring fails here rather than at the render of whatever page the route was meant for.
Methods:
-
variables–Return the variables every route matching this request supplies.
Attributes:
-
routes–
routes
instance-attribute
routes = tuple(routes)
variables
async
variables(request: Request) -> dict[str, Any]
Return the variables every route matching this request supplies.
Fills in request.path_params from each match before that route's
function runs, so a function reads the parameters of its own path.
Merges in declaration order, so a later route wins a name an earlier
route also set.
Raises:
-
TypeError–if a matching route's context function is not async.
context_of
async
context_of(
route: Route, request: Request
) -> Mapping[str, Any]
Await the context function of route and return the variables it supplies.
Raises:
-
TypeError–if the function is not async. A synchronous one returns the mapping rather than an awaitable, which would otherwise fail deep in the render with nothing to say which route was at fault.
Shortcodes
shortcode_extension
shortcode_extension(
folder: str | Path,
suffixes: str | Sequence[str] = SHORTCODE_SUFFIXES,
) -> type[ShortcodeExtension]
Build a ShortcodeExtension subclass whose tags mirror the files in folder.
Raises:
-
ValueError–for any discovery problem — see :func:
discover_shortcode_templates.
discover_shortcode_templates
discover_shortcode_templates(
folder: Path,
suffixes: str | Sequence[str] = SHORTCODE_SUFFIXES,
) -> dict[str, Path]
Map normalized tag name -> template path for every template file in folder.
Raises:
-
ValueError–if the folder does not exist, contains no matching files, a file name normalizes to something that is not a usable tag (empty, not an identifier, a reserved Jinja tag), two file names normalize to the same tag (including two spellings of one name, such as
note.htmlbesidenote.jinja), or a tag shadows another tag's end tag.
normalize_shortcode_name
normalize_shortcode_name(name: str) -> str
Normalize a template file name to a tag name, the way slugify does but with underscores.
Compatibility-decomposes unicode and drops what has no ASCII equivalent, lowercases, replaces every run of non-alphanumeric characters with a single underscore, and strips leading/trailing underscores. Returns "" when nothing survives; the result is not guaranteed to be a valid identifier (e.g. it may start with a digit) — callers must check.
HTMX Fragments
HTMXResponse
HTMXResponse(
content: str = "",
trigger: str | Sequence[str] = (),
status_code: int = 200,
fragments: FragmentRegistry | None = None,
)
An HTML response that says what happened, and carries the cascade it implies.
content is the handler's own answer, what goes into the element that made
the request. trigger names what happened. Every region those triggers make
stale is rendered and appended to this response as an out-of-band swap, so
one click updates every part of the page that displays what changed:
return HTMXResponse("Added.", trigger="cart.changed")
The regions come from the StaticFiles mounted in the app this request
reached, found by registry_in. An app that serves a site with regions
therefore wires nothing: fragments=store on StaticFiles is the whole
setup. Pass fragments to name the registry instead — for a test, or an app
whose regions are not reachable from its routes.
The work happens in __call__, which is async, rather than in __init__,
which cannot be. Nothing is buffered and no head is rewritten: the body is
still this object's own attribute when the regions are appended to it.
A trigger no region wants leaves the response untouched, so a handler can still fire a purely client-side htmx event.
Methods:
-
cascade–Append the stale regions to this response's body, and correct its head.
cascade
async
cascade(
fragments: FragmentRegistry, request: Request
) -> None
Append the stale regions to this response's body, and correct its head.
Regions are independent by construction, so this costs the slowest render rather than the sum of them.
Raises:
-
ValueError–if a region renders markup that could not be swapped — no root element, or one whose id is not the region's name.
FragmentRegistry
FragmentRegistry(
env: Environment,
template_dir: str = TEMPLATE_DIR,
path_prefix: str = DEFAULT_PREFIX,
base_context: Context | None = None,
)
The page's re-renderable regions, keyed by what makes them stale.
Built once at wiring time — by StaticFiles, which discovers the regions and
publishes the registry as its fragments attribute — handed to the
middleware, and read-only from the first request onward, so every concurrent
request shares one instance without a lock. Registering a region after the
app starts serving is not supported.
Renders through the Jinja environment it is given, which must have
enable_async=True — regions are rendered concurrently and awaited. The one
StaticFiles builds is already set up for this.
A fragment's name names its template under template_dir, names its context
function when it has one, and must be the id of the element that template
returns — that id is what htmx matches against the live DOM, so a mismatch
means the swap silently lands nowhere. This is checked at render time and
raises rather than failing quietly.
base_context supplies the variables every region renders with whether or
not it has a context function of its own — queries is what StaticFiles
puts there, which is what lets a region declare and run its own query. It is
awaited once per region render, so each region gets its own, and it is
applied last: a context function does not overwrite the library's own names.
Methods:
-
add–Register a re-renderable region of the page.
-
stale–Return the regions the given triggers make stale, in registration order.
-
discover–Register every region template found under the served directories.
-
template_path–Return the loader name of a region template file:
fragments/cart_badge.html.jinja. -
template_for–Return the template a region renders, whichever suffix its author gave it.
-
render–Render one registered fragment, with no out-of-band marker on it.
-
url_for–Return where a fragment answers its own request.
-
name_at–Return the fragment a path names, or None if it names none.
-
wrap–Return a fragment's first-paint markup, ready to drop into a page.
Attributes:
-
env– -
template_dir– -
path_prefix– -
base_context– -
by_trigger(dict[str, list[Fragment]]) – -
by_name(dict[str, Fragment]) –
env
instance-attribute
env = env
template_dir
instance-attribute
template_dir = template_dir
path_prefix
instance-attribute
path_prefix = path_prefix
base_context
instance-attribute
base_context = base_context
add
add(
context: Context | None = None,
*triggers: str,
pull: bool = False,
name: str | None = None,
template: str | None = None,
) -> None
Register a re-renderable region of the page.
A context function stays an ordinary function — nothing wraps it — which is how the page's first paint reuses the same function that produces the out-of-band update.
Parameters:
-
context(Context | None, default:None) –the async function returning the template context, or None for a region whose template says all it needs. Its
__name__is the fragment's name, which names both the template and the id of the element that template must return, sonameis required without it. -
*triggers(str, default:()) –the trigger names that make this region stale. A region that displays two things can name both.
-
pull(bool, default:False) –fetch this region separately instead of appending it to the response. Use for a region slow enough that you do not want its latency on the critical path of the click.
-
name(str | None, default:None) –the region's name, when it is not the function's. Discovery passes the template's name, since the template is what renders.
-
template(str | None, default:None) –the template to render, when it is not the one
template_forfinds. Discovery passes the file it found.
Raises:
-
ValueError–if the name is already registered, since the name is a DOM id and a duplicate would make the swap ambiguous, or if neither a context function nor a name says what the region is called.
stale
stale(triggers: Sequence[str]) -> list[Fragment]
Return the regions the given triggers make stale, in registration order.
Deduplicated by name: a fragment watching two of the fired triggers appears once, and the order is stable so the same click always produces the same body.
discover
discover(
directories: Sequence[str], contexts: Contexts = ()
) -> None
Register every region template found under the served directories.
Point it at a folder and each region template in it becomes a region: the
file name — minus any of TEMPLATE_SUFFIXES — is the region's name, and
the template says what makes it stale. A region that wants variables
Python has to produce gets a function of its name from contexts — a
module holding them, a mapping of name to function, a function on its
own, or any mix of those in a sequence; a region with none renders with
empty_context. StaticFiles calls this for you.
Nothing lists the regions — adding one means adding a template, and no line anywhere else.
Raises:
-
ValueError–if two sources offer a context function of one name, if two templates are the same region under different suffixes, or if a template declares no triggers. A region that can never go stale is not a region, and either ambiguity would make the page's swap ambiguous, so discovery fails loudly rather than skipping.
-
TypeError–if a source is none of the three kinds a context comes from.
template_path
template_path(filename: str) -> str
Return the loader name of a region template file: fragments/cart_badge.html.jinja.
template_for
template_for(name: str) -> str
Return the template a region renders, whichever suffix its author gave it.
A region template may be spelled any of TEMPLATE_SUFFIXES, so the
environment's loader is asked which one exists — once, at registration,
rather than on every render. Falls back to the first spelling when the
loader has none of them, so the render raises a TemplateNotFound that
names what was looked for.
render
async
render(name: str, request: Request) -> str
Render one registered fragment, with no out-of-band marker on it.
Raises:
-
KeyError–if no fragment by that name is registered.
-
TemplateNotFound–if the fragment has no template of its name.
url_for
url_for(name: str, root_path: str = '') -> str
Return where a fragment answers its own request.
root_path is the prefix the serving app is mounted under, which the
page passes from its own request. A site mounted at /shop serves its
regions at /shop/fragment/<name>, so that is what a pull region's
markup must point at.
name_at
name_at(path: str) -> str | None
Return the fragment a path names, or None if it names none.
Returns None for an unregistered name too, so a path that merely looks like a fragment URL falls through to the app and gets its ordinary 404.
wrap
wrap(name: str, html: str, root_path: str = '') -> Markup
Return a fragment's first-paint markup, ready to drop into a page.
A push fragment is returned untouched. A pull fragment is wrapped in the
element that re-fetches it when any of its triggers fires, from where the
page's own root_path says regions are served.
HTMXMiddleware
HTMXMiddleware(app: ASGIApp, fragments: FragmentRegistry)
Cascades responses that are not HTMXResponses, for the apps that need it.
Most apps need none of this. An HTMXResponse carries its own cascade and
the mounted StaticFiles serves a pull region's own request, so a site with
regions wires nothing. Install this to cascade a response of some other class
that carries an HX-Trigger header — one another library built, or a header
set above the handler.
Doing it here means intercepting the response as ASGI messages: appending to
a body means rewriting its Content-Length, so the head is held back and the
chunks collected. That is the cost HTMXResponse avoids by appending to a
body it still owns, and the reason this is the exception rather than the path.
Install it innermost — before any middleware that rewrites the body, such as GZip — so that layer sees the finished body with the swaps already in it. A response that already cascaded itself is left alone, so the two layers never both append the same regions.
It also answers GET /fragment/<name> for any registered fragment, the way
StaticFiles does, so a pull region works in an app that mounts no site. A
path under that prefix naming nothing registered falls through to the app and
404s normally.
A context function is handed a request built from the scope. It can read the app, the path, and the headers; it must not read the request body, which the handler has already consumed.
Methods:
-
serve–Answer a pull fragment's own request, with no out-of-band marker on it.
Attributes:
app
instance-attribute
app = app
fragments
instance-attribute
fragments = fragments
serve
async
serve(
name: str, scope: Scope, receive: Receive, send: Send
) -> None
Answer a pull fragment's own request, with no out-of-band marker on it.
Fragment
dataclass
Fragment(
name: str,
context: Context,
triggers: tuple[str, ...],
pull: bool,
template: str,
)
A registered region. Built by FragmentRegistry.add; you never construct one.
Attributes:
name
instance-attribute
name: str
context
instance-attribute
context: Context
triggers
instance-attribute
triggers: tuple[str, ...]
pull
instance-attribute
pull: bool
template
instance-attribute
template: str
HTML in Python
ht
Factory for HTML elements, used as ht.<tag>(...).
Accessing any attribute (ht.div, ht.button) yields a new Element with
that tag, which is then called with children and attributes. Also exposes the
renderers render_element and render_document.
ht.div(id="my-div", classes=["container", "content"], style={"color": "red"})
ht.button(type="submit", classes=["btn", "btn-primary"])
ht.h1("Hello World")
Methods:
-
render_element–Render an element (or string, callable, or get_element object) to HTML.
-
render_document–Render a full HTML document (doctype, head, body) to a string.
render_element
classmethod
render_element(element: ElementChild) -> str
Render an element (or string, callable, or get_element object) to HTML.
The tree is walked iteratively with an explicit stack, so nesting depth is bounded by memory rather than by Python's recursion limit.
Callables are evaluated until a value remains; an object with get_element() is resolved to its Element; None renders to "". An error raised by a child is logged and treated as empty rather than aborting the whole document.
Raises:
-
TypeError–if the resolved value is not an Element, string, or number.
-
CycleError–if the element tree contains a cycle.
render_document
classmethod
render_document(
body: Element,
head: list[Element] | None = None,
title: str | None = None,
body_kwargs: dict[str, Any] | None = None,
head_kwargs: dict[str, Any] | None = None,
html_kwargs: dict[str, Any] | None = None,
) -> str
Render a full HTML document (doctype, head, body) to a string.
head is a list of elements appended after the default charset/viewport
meta tags. title, when given, adds a
*_kwargs mappings
set attributes on the html/head/body tags.
Raises:
-
TypeError–if
bodyis not an Element/str/get_element object, orheadis not a list.
ht.render_document(ht.div("Hello world"))
# <!DOCTYPE html><html><head>...</head><body><div>Hello world</div></body></html>
Element
Element(*args: ElementChild, **kwargs: Any)
A single HTML element: a tag, an ordered list of children, and attributes.
Build with ht.<tag>(...) rather than constructing directly. Children and
attributes are added through the +/+= operators, __call__, and the
append/extend/insert methods; every mutating method returns self so calls
chain. classes is always normalized to a list of strings.
Methods:
-
set_attrs–Set attributes, overwriting on duplicate keys;
classesis merged into the list. -
merge_attrs–Merge attributes, combining duplicate keys into a list rather than overwriting.
-
has_classes–Return True if the element carries every one of the given classes.
-
add_classes–Add classes to the element, de-duplicated in order.
-
remove_classes–Remove classes from the element, preserving the order of those that remain.
-
append–Add children to the element.
-
extend–Add several children to the element.
-
insert–Insert children at
index, in order, with the usual child coercion. -
to_string–Render the element to an HTML string.
-
render–Render the element to a Markup-wrapped HTML string.
-
pipe–Apply
function(self, *args, **kwargs)and return its result, for chaining.
Attributes:
-
tag(str) –The tag name of the element.
-
children(list[Any]) –List of child elements.
-
attributes(dict[str, Any]) –Dictionary of attributes.
tag
instance-attribute
tag: str = 'div'
The tag name of the element.
children
instance-attribute
children: list[Any] = []
List of child elements.
attributes
instance-attribute
attributes: dict[str, Any] = {}
Dictionary of attributes.
set_attrs
set_attrs(**kwargs: Any) -> Self
Set attributes, overwriting on duplicate keys; classes is merged into the list.
merge_attrs
merge_attrs(**kwargs: Any) -> Self
Merge attributes, combining duplicate keys into a list rather than overwriting.
ht.div(classes=["container"]).merge_attrs(id="my-div", classes=["content"])
has_classes
has_classes(*classes: str) -> bool
Return True if the element carries every one of the given classes.
add_classes
add_classes(*classes: Any) -> Self
Add classes to the element, de-duplicated in order.
A single callable replaces the class list wholesale and is resolved at render time, so it can reflect state that changes between renders.
ht.div().add_classes("container", "row")
ht.div().add_classes(lambda: "active" if is_active() else "")
remove_classes
remove_classes(*classes: str) -> Self
Remove classes from the element, preserving the order of those that remain.
ht.div().remove_classes("container", "row")
append
append(*args: ElementChild) -> Self
Add children to the element.
ht.div().append("Hello world")
extend
extend(*args: ElementChild) -> Self
Add several children to the element.
ht.div().extend("Hello", "world")
insert
insert(index: int, *args: ElementChild) -> Self
Insert children at index, in order, with the usual child coercion.
ht.div("World").insert(0, "Hello ")
to_string
to_string() -> str
Render the element to an HTML string.
ht.div("Hello world").to_string() # '<div>Hello world</div>'
render
render() -> Markup
Render the element to a Markup-wrapped HTML string.
pipe
pipe(
function: Callable[..., Any], *args: Any, **kwargs: Any
) -> Any
Apply function(self, *args, **kwargs) and return its result, for chaining.
def add_prefix(element, prefix):
return element.add_classes(f"{prefix}-class")
ht.div("Hello world").pipe(add_prefix, "my-prefix")
Document
Document(
*args: ElementChild,
page_title: str | None = None,
headers: Mapping[str, Any] | None = None,
status_code: int = 200,
**kwargs: Any,
)
An HTML document that is also an ASGI app, so a handler can return it directly.
A subclass of Element: children added to the Document become the body's
children, and its attributes become the body's attributes when rendered.
Owns pre-built title, head, body, and html elements.
Build a document with an optional title and the response metadata for ASGI use.
page_title sets the
headers and status_code are used when
the document is served as an ASGI response.
Methods:
-
get_element–Assemble and return the document as its
<html>Element. -
to_string–Render the document to an HTML string, prefixed with the doctype.
Attributes:
-
page_title(str | None) –The title of the page.
-
headers(Mapping[str, Any] | None) –Response headers.
-
status_code(int) –Response status code.
-
title–Document title element.
-
head(Element) –The head element.
-
body–The body element.
-
html–The html element.
page_title
instance-attribute
page_title: str | None = page_title
The title of the page.
headers
instance-attribute
headers: Mapping[str, Any] | None = headers
Response headers.
status_code
instance-attribute
status_code: int = status_code
Response status code.
title
instance-attribute
title = title()
Document title element.
head
instance-attribute
head: Element = head(
meta(charset="utf-8"),
meta(
name="viewport",
content="width=device-width, initial-scale=1",
),
meta(http_equiv="X-UA-Compatible", content="IE=edge"),
title,
)
The head element.
body
instance-attribute
body = body()
The body element.
html
instance-attribute
html = html(head, body)
The html element.
get_element
get_element() -> Element
Assemble and return the document as its <html> Element.
Moves the document's own children and attributes onto the body and sets the title, so the returned tree reflects everything added to the Document.
to_string
to_string() -> str
Render the document to an HTML string, prefixed with the doctype.
Errors
AppException
AppException(
detail: str,
status_code: int = 400,
code: ErrorCode | str | None = None,
source: ErrorSource | None = None,
meta: dict[str, Any] | None = None,
)
Base exception for all application errors with JSON:API compliance.
This exception provides structured error information that can be rendered as either JSON (for API requests) or HTML (for browser requests).
Parameters:
-
detail(str) –Human-readable description of the error
-
status_code(int, default:400) –HTTP status code (default: 400)
-
code(ErrorCode | str | None, default:None) –Machine-readable error code
-
source(ErrorSource | None, default:None) –Location of the error (JSON pointer, parameter name, etc.)
-
meta(dict[str, Any] | None, default:None) –Additional metadata about the error
Example
raise AppException(
detail="User with email 'john@example.com' already exists",
status_code=409,
code=ErrorCode.DUPLICATE_RESOURCE,
source=ErrorSource(parameter="email"),
meta={"email": "john@example.com"}
)
ErrorCode
Error codes for API responses following JSON:API specification.
Attributes:
-
INTERNAL_ERROR–Internal server error not caused by client request.
-
INVALID_REQUEST–The request is malformed or contains invalid parameters.
-
INVALID_PARAMETER–A specific parameter in the request is invalid.
-
NOT_FOUND–The requested resource could not be found.
-
VALIDATION_ERROR–The request data failed validation checks.
-
UNAUTHORIZED–Authentication is required and has failed or not been provided.
-
FORBIDDEN–Access to the requested resource is forbidden.
-
METHOD_NOT_ALLOWED–The HTTP method used is not allowed for the requested resource.
INTERNAL_ERROR
class-attribute
instance-attribute
INTERNAL_ERROR = 'internal_error'
Internal server error not caused by client request.
INVALID_REQUEST
class-attribute
instance-attribute
INVALID_REQUEST = 'invalid_request'
The request is malformed or contains invalid parameters.
INVALID_PARAMETER
class-attribute
instance-attribute
INVALID_PARAMETER = 'invalid_parameter'
A specific parameter in the request is invalid.
NOT_FOUND
class-attribute
instance-attribute
NOT_FOUND = 'not_found'
The requested resource could not be found.
VALIDATION_ERROR
class-attribute
instance-attribute
VALIDATION_ERROR = 'validation_error'
The request data failed validation checks.
UNAUTHORIZED
class-attribute
instance-attribute
UNAUTHORIZED = 'unauthorized'
Authentication is required and has failed or not been provided.
FORBIDDEN
class-attribute
instance-attribute
FORBIDDEN = 'forbidden'
Access to the requested resource is forbidden.
METHOD_NOT_ALLOWED
class-attribute
instance-attribute
METHOD_NOT_ALLOWED = 'method_not_allowed'
The HTTP method used is not allowed for the requested resource.
ErrorSource
JSON:API error source object indicating where the error originated.
Attributes:
pointer
class-attribute
instance-attribute
pointer: str | None = None
parameter
class-attribute
instance-attribute
parameter: str | None = None
header
class-attribute
instance-attribute
header: str | None = None
exception_handler
async
exception_handler(
request: Request,
exc: Exception,
jinja_env: Environment,
debug: bool = False,
) -> Response
Handle uncaught exceptions with JSON or HTML response.
Provides special handling for: - ValidationError: Detailed validation error messages - AppException: Custom application errors with structured data - General exceptions: Generic 500 error
Returns JSON:API format for API requests, HTML for browsers.
Parameters:
-
request(Request) –The request that caused the error
-
exc(Exception) –Exception that was raised
-
jinja_env(Environment) –Jinja2 environment for rendering templates
-
debug(bool, default:False) –Whether to include debug information
Returns:
-
Response–Response with error details
httpexception_handler
async
httpexception_handler(
request: Request,
exc: HTTPException,
jinja_env: Environment,
debug: bool = False,
) -> Response
Handle HTTP exceptions (404, 500, etc.) with JSON or HTML response.
Returns JSON:API error format for API requests, HTML error page for browsers.
Parameters:
-
request(Request) –The request that caused the error
-
exc(HTTPException) –HTTPException that was raised
-
jinja_env(Environment) –Jinja2 environment for rendering templates
-
debug(bool, default:False) –Whether to include debug information
Returns:
-
Response–Response with error details