Web integration
The route table a web framework mounts, and the request/response types handlers use (see citry.contrib).
URLRoute class
One framework-neutral route: either a handler or nested children.
A child's full path is the parent's path followed by the child's, joined as plain text, so end a parent path with / when the child starts a new segment. A {name} parameter in the path becomes a keyword argument of the handler.
handler_async optionally carries an async def twin of handler (same signature and behavior). Adapters that run an event loop (ASGI) serve the route through the twin, awaiting it natively, while the plain handler stays what the sync hosts (WSGI, sync Django) mount and run. Only routes that need both worlds carry it; a route table meant solely for ASGI can simply pass an async def function as handler.
Example::
URLRoute(
"files/{folder}/{name}.{ext}",
handler=serve_file,
name="my_ext_file",
dotless_params=frozenset({"name"}),
)
URLRoute(
"ext/",
children=[URLRoute("my_ext/status", handler=status)],
)
The request path must match the full path exactly. A parameter takes one or more characters and never a /, so a parameter ends at the next / or at the fixed text that follows it. When that fixed text could also fall inside the value, as with {name}.{ext} and the path files/docs/report.v2.txt, each parameter takes as much as it can, the earlier one first: without dotless_params, the route would give name="report.v2" and ext="txt".
A parameter listed in dotless_params never holds a . and ends at the first one, which gives name="report" and ext="v2.txt" in the route above. Only the dotless_params of the route that has the handler applies, not a parent's, even for parameters in the parent's path.
Two parameters with nothing between them, such as {year}{month}, have no single correct split, so the web framework integrations raise ValueError for such a route when they mount the routes. That includes a parent path that ends in a parameter joined to a child path that starts with one. A path that uses one parameter name twice raises ValueError the same way.
methods is always a non-empty tuple of uppercase HTTP method names (("GET",), ("POST", "PUT")), so an adapter can iterate it to register or check the route. Any other value raises TypeError (not a tuple of strings) or ValueError (empty, or a name that is not an uppercase HTTP method token such as "get") when the route is built. Adapters answer any other method with 405. A route that lists GET also accepts HEAD (see :func:route_allows_method). When a route admits more methods than its handler serves, the handler answers 405 for the rest.
dotless_params must be a frozenset of parameter names; any other value raises TypeError when the route is built.
max_body_bytes is an optional function that returns the largest request body the route accepts. Citry calls it for each request, so the limit can follow a setting. The ASGI, WSGI, and Django integrations then read at most one byte more than the limit (under ASGI, at most one more message), whatever Content-Length says, so a client cannot make the server hold a large body in memory. request.body then holds at most the limit plus one byte, and the handler must reject a body longer than the limit, because that body was cut short. None (the default) reads the whole body. No adapter reads the body of a GET, HEAD or OPTIONS request, whatever the limit. The limit applies to this route only, not to its children. A function that returns something other than an int of 0 or more makes each request fail with an error, which the web framework answers with status 500.
handler attribute
handler: Callable[..., RouteResponse | Awaitable[RouteResponse]] | None RouteRequest class
A framework-neutral view of one HTTP request, passed to route handlers.
Each web adapter (citry.contrib.asgi, citry.contrib.wsgi, the Django patterns, ...) builds this from its host's request, so a handler reads the same fields no matter which framework serves it. Anything host-specific stays reachable through native.
Attributes
methodstr- The HTTP method, uppercase ("GET","POST", ...).pathstr- The full URL path of the request, including the prefix the route table is mounted under (e.g."/citry/citry.js").queryMapping[str, tuple[str, ...]]- The query-string parameters. Each key maps to all values it was sent with, in order, so repeated keys are preserved.headersRouteHeaders- The HTTP request headers; lookups ignore case.bodybytes- The raw request body. Empty for bodyless methods such as GET.content_typestr- TheContent-Typeheader value, or""when the request names none.nativeAny- The untouched host request object: the ASGI scope, the WSGI environ, or Django'sHttpRequest.
RouteResponse class
What a route handler returns; adapters translate it to the host's response type.
Attributes
contentstr | bytes- The response body, as text or raw bytes.content_typestr- TheContent-Typeheader value to send. Atext/*type with astrbody and nocharsetis sent with; charset=utf-8, because the adapters encode the string as UTF-8.statusint- The HTTP status code.headerstuple[tuple[str, str], ...]- Extra response headers, as(name, value)pairs; adapters send them in addition toContent-Type. Adapters also sendX-Content-Type-Options: nosniffunless this sets that header, so the browser usescontent_typeas given; set it to the body's real type. A name may repeat (e.g. twoSet-Cookielines): the ASGI and WSGI adapters send every pair, while the Django adapter raisesValueErroron a repeated name, because Django's response object holds one value per header name.
RouteHeaders class
Bases: Mapping
A read-only mapping of HTTP header names to values; lookups ignore case.
Built from (name, value) pairs. Iteration yields the names lowercased, and a header sent multiple times has its values joined with ", " (the HTTP rule for repeatable headers).
Example
headers = RouteHeaders([("Content-Type", "application/json")]) headers["content-type"] # "application/json" headers["CONTENT-TYPE"] # "application/json"