Theme
Version
GitHub PyPI Discord
On this page

Contrib integrations

Adapters mounting citry into web frameworks, and cache adapters for shared stores (the citry.contrib.<name> modules).

View source

mount function

mount(app: Any, citry_instance: Citry, prefix: str = '/citry') -> None

Mount citry_instance's routes into a FastAPI/Starlette app at prefix, and record the prefix on the instance.

A trailing / on prefix is dropped. When app lists its routes, as FastAPI and Starlette apps do, citry check and the editor can also ask it whether it serves each link path written in your templates.

For an app served below a sub-path, Citry's URLs (its scripts, styles, and Events endpoints) start with that sub-path. mount() adds a middleware to app that reads each request's ASGI root_path, which uvicorn --root-path or FastAPI(root_path=...) sets. It runs inside your own middleware, so one that sets root_path from a proxy header is seen. Outside a request, such as in a script or a test, the URLs use app.root_path, or the site root for a plain Starlette app. Call mount() before the server starts the app, for example at module level, not inside lifespan: Starlette accepts middleware only until then, so a later mount() skips it, and the URLs use app.root_path only. A page that one of your own middlewares renders runs outside Citry's middleware, so its URLs use app.root_path too.

Raises

  • ValueError - If prefix does not start with /, or if a route's full path (its parents' paths followed by its own) has two parameters side by side (such as {a}{b}) or uses one parameter name twice (see URLRoute). The app is left unchanged.
View source

mount function

mount(app: Any, citry_instance: Citry, prefix: str = '/citry') -> None

Mount citry_instance's routes into a Flask app at prefix, and record the prefix on the instance.

A trailing / on prefix is dropped, so "/citry/" serves the same paths as "/citry". For a Flask app, citry check and the editor can also ask the app whether it serves each link path written in your templates.

Raises

  • ValueError - If prefix does not start with /, or is the root "/", or if a route's full path (its parents' paths followed by its own) has two parameters side by side (such as {a}{b}) or uses one parameter name twice (see URLRoute). The app is left unchanged.
View source

urlpatterns function

urlpatterns(citry_instance: Citry, prefix: str | None = None) -> list[Any]

Citry.urls as Django URL patterns, for include()-ing.

Pass prefix (where you include the patterns, e.g. "/citry") to also record it on the instance, so URL building (fragment manifests, the runtime src) points at the right place; leaving it None means you call set_mounted_prefix yourself.

The generated views are synchronous, so every route handler must be a plain function; an async def handler raises TypeError here, pointing at the ASGI adapter as the fix. urlpatterns() also raises ValueError when a route's full path (its parents' paths followed by its own) has two parameters side by side (such as {a}{b}) or uses one parameter name twice.

Calling it also lets citry check and the editor ask the project whether its URL patterns or static files serve each link path written in your templates.

View source

enable_hot_reload function

enable_hot_reload(citry_instance: Citry, mode: Literal['hot', 'restart'] = 'hot') -> Callable[..., bool | None]

Pick up edits to component files in development, using Django's autoreloader.

Call it once at startup, for example from your AppConfig.ready(). When runserver starts its reloader, this asks the reloader to also watch every file under the engine's dirs (Citry(dirs=[...])). Django already watches Python files and its own template directories. When a watched file that a component has loaded changes, Citry clears that component's stored template, JS, and CSS (see invalidate_file()), so the next render reads the file again.

Other changed files are handled like this:

  • A Python file goes to Django, which restarts the server.
  • Any other file under the engine's dirs needs no action, because no component has read it yet, so the server keeps running.
  • Any other file outside the engine's dirs goes to Django's normal handling.

Calling it again for the same engine replaces the earlier call, so the last mode wins. Point the engine's dirs at your component folders, not the project root: the reloader checks every file under them, so a large folder makes it slow.

Example

::

# apps.py
from django.apps import AppConfig

from citry.contrib.django import enable_hot_reload
from myapp import engine


class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        enable_hot_reload(engine)

Parameters

  • citry_instance Citry - The engine whose components should pick up edits.
  • mode Literal['hot', 'restart'] - What happens after Citry clears a changed component file. "hot" (the default) keeps the server running, and the next render reads the new file. "restart" restarts the server process, the same way Django restarts it after a Python edit.

Returns

Callable[..., bool | None]: The receiver connected to Django's ``file_changed`` signal.

Raises

  • ValueError - When mode is not "hot" or "restart".
View source

secret function

secret() -> str | list[str]

Django's SECRET_KEY and SECRET_KEY_FALLBACKS, for passing as Citry(secret=...).

Citry signs values with the engine-level secret setting. In a Django project the natural signing keys are the ones the project already manages, so pass them through::

from citry import Citry
from citry.contrib.django import secret

app = Citry(secret=secret())

When the project sets SECRET_KEY_FALLBACKS to rotate its key, Citry follows the rotation: SECRET_KEY signs new values, and a value signed with any of the fallback keys still verifies, so values Citry already put in visitors' pages keep verifying until you remove the old key. Empty and whitespace-only fallback entries are skipped.

The keys are read when this is called, so Django settings must be configured by then (in a normal Django startup they already are).

Returns

str | list[str]: ``SECRET_KEY`` as a plain string when ``SECRET_KEY_FALLBACKS`` is

Raises

  • RuntimeError - When Django settings are not configured; the message names the fix.
  • django.core.exceptions.ImproperlyConfigured - When settings are configured but the SECRET_KEY itself is unusable (for example empty); Django's own message explains the problem.
  • TypeError - When SECRET_KEY_FALLBACKS is a single string instead of a list, or holds an entry that is not a string.
View source

DjangoCache class

Adapt a Django cache (django.core.cache.caches[...]) to citry's CitryCache protocol, so citry's stored scripts live in whatever cache backend the Django project already runs (Redis, Memcached, database, ...).

View source

set function

set(key: str, value: str, ttl: float | None = None) -> None
View source

asgi_app function

asgi_app(citry_instance: Citry) -> Callable[[Scope, Receive, Send], Awaitable[None]]

Build the ASGI application serving citry_instance.urls.

The returned app handles lifespan events (so it also works served standalone) and routes each HTTP request to the matching citry handler, preferring the route's handler_async (its async version) when it has one. It builds a RouteRequest from the ASGI scope, awaits an async handler or runs a sync one in a worker thread, and sends the returned RouteResponse as ASGI messages.

Raises

  • ValueError - If a route's full path (its parents' paths followed by its own) has two parameters side by side (such as {a}{b}) or uses one parameter name twice. See URLRoute.
View source

reload_lifespan function

reload_lifespan(engine: Citry, roots: Iterable[str | Path] | None = None, watcher: FileWatcher | None = None, on_reload: Callable[[set[Path], list[type[Component]]], None] | None = None) -> Callable[[Any], AbstractAsyncContextManager[None]]

A Starlette/FastAPI lifespan that hot-reloads component files while the app runs.

Compose it into the app's root lifespan::

from contextlib import asynccontextmanager
from citry.contrib.asgi import reload_lifespan

watch_lifespan = reload_lifespan(citry_instance)

@asynccontextmanager
async def lifespan(app):
    citry_instance.initialize()
    async with watch_lifespan(app):
        yield

app = FastAPI(lifespan=lifespan)   # or Starlette(...)

It starts Citry's file watcher on startup and stops it on shutdown, so editing a component's template/JS/CSS shows up on the next render without restarting. It manages only the watcher and does not call Citry.initialize(); the root lifespan owns initialization. For development; in production simply do not add it. If you already have a lifespan, nest this one inside yours.

Parameters

  • engine Citry - The Citry instance whose component caches are reset when a watched file changes.
  • roots Iterable[str | Path] | None - Directories to watch. None watches the instance's dirs setting.
  • watcher FileWatcher | None - The file-watching backend. None uses watchfiles if installed, else watchdog, else a polling watcher.
  • on_reload Callable[[set[Path], list[type[Component]]], None] | None - Called after each batch of changes with the changed paths and the component classes that were reset.

Returns

Callable[[Any], AbstractAsyncContextManager[None]]: A function that takes the app and returns the lifespan context

Raises

  • ValueError - On startup, if none of the roots exists. Missing directories are skipped.
View source

wsgi_app function

wsgi_app(citry_instance: Citry) -> WSGIApp

Build the WSGI application serving citry_instance.urls.

The returned app routes each request to the matching citry handler. It builds a RouteRequest from the WSGI environ, calls the handler, and turns the returned RouteResponse into a WSGI response. Because WSGI is synchronous, a request that reaches an async def handler raises TypeError before the handler runs, and the message points to the ASGI adapter.

Raises

  • ValueError - If a route's full path (its parents' paths followed by its own) has two parameters side by side (such as {a}{b}) or uses one parameter name twice. See URLRoute.
View source

RedisCache class

Adapt a Redis client (redis.Redis or compatible) to citry's CitryCache protocol. Values are stored as UTF-8 strings; ttl becomes the key's expiry (ex).

prefix is prepended to every key, for sharing a Redis database with other uses. (Citry's own keys already start with citry:.)

View source

set function

set(key: str, value: str, ttl: float | None = None) -> None
View source

DiskCache class

Adapt a diskcache.Cache (or compatible) to citry's CitryCache protocol. The store is a directory on disk, shared by every worker process on the host.

View source

set function

set(key: str, value: str, ttl: float | None = None) -> None
Citry version: 0.9.0