Theme
Version
GitHub PyPI Discord
On this page

Citry instance and config

The Citry instance that scopes components, settings, and caches.

View source

Citry class

Global instance that scopes all component state.

A Citry instance owns:

  • A private component-name registry reached through the engine's methods
  • Settings (to be expanded as the engine grows)
  • Transient rendering state

All Component classes are assigned to a Citry instance at class definition time. If no instance is specified, the default instance is used.

Call :meth:initialize after startup-time registration and before a server starts request threads. Lazy initialization remains available, but a thread that encounters lifecycle work owned by another thread receives CitryLifecycleInProgress.

View source

mode attribute

mode: Literal['production', 'development']
View source

engine_id attribute

engine_id: str

Return this engine's opaque process-lifetime identity token.

The value is stable for this Citry instance, including across clear(), and differs for another instance in the same process. Component-introspection consumers combine it with Component.class_id and Component.definition_id when they need to confirm that retained metadata still describes an exact live component generation.

Returns

str: A non-time-derived token intended only for same-process identity

View source

atomic_registration function

atomic_registration() -> Iterator[None]

Publish a group of component registrations together.

Component classes defined inside the block register normally and fire the normal class and registration hooks. If the block raises, Citry restores its component names, class-ID and file indexes, tag rules, and discovery state to their values at entry. Another thread receives CitryLifecycleInProgress rather than observing or changing the group before it commits.

This context manager is additive: it publishes new classes and aliases; unregister() rejects removals inside the block. Start the block outside other component lifecycle operations; nested atomic-registration blocks are rejected. Ordinary component definitions and their hooks remain reentrant inside the block.

Rollback covers the Citry registration and installation indexes listed above. Rendered-output caches, side effects that extension hooks or ordinary Python write elsewhere, and registrations made to another Citry instance are outside the transaction.

Yields

  • None - None. Component factories return their own created classes; the
  • None - context manager only owns publication and rollback.

Example

with app.atomic_registration():
    class AcmeButton(Component):
        citry = app

Raises

  • CitryLifecycleInProgress - If another thread owns component lifecycle work for this Citry instance.
  • RuntimeError - If called inside another component lifecycle operation on the same thread.
View source

register_library function

register_library(library: ComponentLibrary | ModuleType) -> LibraryInstallation

Materialize and publish an engine-neutral component library.

A library package may be passed directly when it exposes its manifest as __citry_library__. Registration creates a distinct concrete Component class for every definition and this Citry instance. The classes and immutable installation record become visible together.

Repeating the same manifest and exact definition generation returns the existing installation without rerunning component hooks. Clear the Citry instance before installing a reloaded or changed manifest with the same library name.

Example

import citry_ui
from citry import Citry

app = Citry()
installed = app.register_library(citry_ui)

Parameters

  • library ComponentLibrary | ModuleType - A [`ComponentLibrary`](/reference/component-libraries/#citry-componentlibrary) or imported package exposing one as ``__citry_library__``.

Returns

LibraryInstallation: The exact active [`LibraryInstallation`](/reference/component-libraries/#citry-libraryinstallation).

Raises

  • LibraryManifestChanged - If the library name is already associated with another manifest or definition generation.
  • LibraryInstallationStale - If internal registry mutation damaged an active installation.
  • ValueError - If a required extension is not installed or a component identity collides with existing state.
  • AlreadyRegistered - If a registry name is already occupied.
  • CitryLifecycleInProgress - If another thread owns lifecycle work.
  • RuntimeError - If registration is attempted recursively.
View source

get_library_installation function

get_library_installation(name: str) -> LibraryInstallation

Return one exact active component-library installation.

Parameters

  • name str - The manifest's case-sensitive library name.

Returns

LibraryInstallation: The current immutable installation handle.

Raises

  • LibraryNotInstalled - If no library with ``name`` is active.
  • LibraryInstallationStale - If private registry mutation damaged the installation record.
  • CitryLifecycleInProgress - If another thread owns lifecycle work.
View source

register function

register(comp_cls: type[Component], name: str | None = None) -> None

Register an additional name for a component owned by this instance.

Component classes register automatically when they are defined. This method supports same-engine aliases and re-registering a class after it was removed. A class owned by another Citry instance is rejected.

Fires on_component_registered once per call, after the registry accepts the class.

Raises

  • AlreadyRegistered - If the requested name or class ID belongs to a different component, or the name is reserved.
  • ValueError - If the component belongs to another Citry instance, is a retired built-in generation, or the requested name is invalid.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

unregister function

unregister(comp_cls_or_name: type[Component] | str) -> None

Unregister one name or all names for a component owned by this instance.

Fires on_component_unregistered once per call, after the registry removes the class.

Raises

  • NotRegistered - If the requested class or name is not registered.
  • ValueError - If asked to remove a built-in's canonical name or unregister the built-in class.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
  • RuntimeError - If called inside `atomic_registration()` on this thread; atomic registration is additive.
View source

get function

get(name: str) -> type[Component]

Look up a component by name.

Parameters

  • name str - Registered component name, matched case-insensitively.

Returns

type[Component]: The registered component class.

Raises

  • NotRegistered - If no component has this name after discovery.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

has function

has(name: str) -> bool

Check whether a component name is registered.

Parameters

  • name str - Registered component name, matched case-insensitively.

Returns

bool: Whether the name is registered after discovery.

Raises

  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

components attribute

components: dict[str, type[Component]]

All registered components as a name-to-class mapping.

Raises

  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

inspect_components function

inspect_components(include_builtins: bool = False, resolve_assets: bool = False, include_default_values: bool = False, include_extensions: Iterable[str] = ()) -> ComponentCatalog

Return an immutable catalog of the currently registered components.

The method completes normal lazy discovery and built-in creation, then copies the registry once. Schema and asset metadata are built from that copy after lifecycle coordination is released. Asset inspection never reads source content or changes render and hot-reload caches.

Parameters

  • include_builtins bool - Include Citry's framework component classes.
  • resolve_assets bool - Check declared asset paths on the filesystem and report resolved, missing, and searched paths.
  • include_default_values bool - Copy portable literal schema defaults into field metadata. Default factories are never called.
  • include_extensions Iterable[str] - Installed extensions whose versioned component metadata inspectors should run. Names are deduplicated and sorted; no inspector runs unless explicitly requested.

Returns

ComponentCatalog: A canonically ordered [`ComponentCatalog`](/reference/component-introspection/#citry-componentcatalog)

Raises

  • TypeError - If a boolean option is not a bool or ``include_extensions`` is a string or non-iterable.
  • ComponentIntrospectionError - If a requested extension is missing, unsupported, fails, or publishes invalid metadata.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

inspect_component function

inspect_component(component: str | type[Component], resolve_assets: bool = False, include_default_values: bool = False, include_extensions: Iterable[str] = ()) -> ComponentInfo

Inspect one component selected from a copied registry snapshot.

A string is matched case-insensitively as a registered component name. A class must be owned by this engine and present under at least one name. Looking up an alias does not change the record's deterministic primary name.

Parameters

  • component str | type[Component] - A registered name or exact registered component class.
  • resolve_assets bool - Check declared asset paths on the filesystem.
  • include_default_values bool - Copy portable literal schema defaults.
  • include_extensions Iterable[str] - Installed extensions whose versioned metadata inspectors should run. Names are deduplicated and sorted.

Returns

ComponentInfo: A value-only [`ComponentInfo`](/reference/component-introspection/#citry-componentinfo) record.

Raises

  • TypeError - If ``component`` is neither a string nor a class, a boolean option is not a bool, or ``include_extensions`` is a string or non-iterable.
  • NotRegistered - If the name or exact class is absent from the copied registry snapshot.
  • ComponentIntrospectionError - If a requested extension is missing, unsupported, fails, or publishes invalid metadata.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

initialize function

initialize() -> None

Prepare component registration state before starting worker threads.

This imports configured component modules when autodiscovery is enabled, creates Citry's built-in components, and builds the current parse-time tag rules. Component template, JavaScript, and CSS asset files remain lazy and are loaded when rendering needs them.

Call this after startup-time component registration and before a server begins handling requests concurrently. Repeated successful calls are safe; a later registration invalidates tag rules, and another call rebuilds them.

Initialization is retryable rather than globally transactional. If a module import fails, components from earlier successful module imports may remain registered, while the incomplete work is retried later.

Returns

None: None.

Raises

  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
  • RuntimeError - If called recursively from an active lifecycle hook or initialization on this thread.
View source

autodiscover function

autodiscover(dirs: Sequence[str | Path] | None = None) -> list[str]

Import this instance's component modules so their classes register.

With no argument, imports every component module under the instance's dirs - the same scan the autodiscover setting performs automatically on first use - and marks that automatic scan done, so it will not run again. Pass dirs to import an extra set of directories on demand without affecting the automatic scan.

The directories must be importable: each one (or a parent of it) is on sys.path/PYTHONPATH, which is how a component file is mapped to the import name Python uses for it. A directory that holds component modules but is not importable raises ValueError.

Returns the dotted import paths of the modules that were imported. Safe to call more than once: an already-imported module has its components re-registered directly, so a call after clear() rebuilds the registry and a call that changes nothing is a no-op.

A scan is marked complete only after every module imports successfully. If one module raises, registrations it made to this Citry instance during that import are rolled back, and a later call can retry it. Earlier modules and dependency modules that imported successfully remain registered. Python side effects and registrations made to another Citry instance are outside this rollback. Calling autodiscover() recursively from a component module raises RuntimeError.

Raises

  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
  • RuntimeError - If a component module starts another autodiscovery scan on the same instance.
View source

urls attribute

urls: tuple[URLRoute, ...]

This instance's HTTP route table (framework-neutral URLRoutes).

The web-integration adapters (citry.contrib.asgi and friends) mount these into the host application; the routes serve cached component JS/CSS, the client runtime, and extension endpoints.

View source

commands attribute

commands: dict[str, tuple[type[ExtensionCommand], ...]]

This instance's CLI commands, keyed by extension name.

Each registered extension contributes the commands it declares in Extension.commands; the citry command-line tool reaches one as citry ext run <extension name> <command name>. See ExtensionManager.commands for ordering and the uniqueness guarantee.

View source

mounted_prefix attribute

mounted_prefix: str | None

Where this instance's routes are mounted (e.g. "/citry"), or None when nothing is mounted.

View source

set_mounted_prefix function

set_mounted_prefix(prefix: str) -> None

Record where this instance's routes are mounted in the host app.

The adapters' mount() call this; call it directly only in a process that builds URLs without mounting the routes itself (for example a worker that renders fragments served by another process). prefix must start with /; a trailing / is dropped.

View source

build_url function

build_url(path: str) -> str

An absolute URL path for one of this instance's routes.

path is the route's full path (no leading slash), e.g. "cache/Table_a1b2c3.js". Raises RuntimeError when no web integration is mounted, since the URL would point nowhere.

View source

get_component_by_class_id function

get_component_by_class_id(class_id: str) -> type[Component]

Look up a registered component class by its class_id.

class_id is the stable identifier (MyComp.class_id) used in cache keys and script URLs. Raises KeyError when no registered class has that id.

Raises

  • KeyError - If no registered component has this class ID.
  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
View source

get_components_for_file function

get_components_for_file(path: str | Path) -> list[type[Component]]

The component classes whose assets resolved to path.

Most callers want :meth:invalidate_file, which both finds these classes and resets them. This lower-level lookup is for a caller that wants the classes without resetting (a custom hot-reload handler, a test). Dead weakrefs are pruned on read.

View source

invalidate_file function

invalidate_file(path: str | Path) -> list[type[Component]]

Drop cached template/JS/CSS for every component that loaded an asset from path, so the next render re-reads it from disk.

Returns the component classes it reset. An empty list means the file backs no loaded component, which a hot-reload handler can read as "not mine" and, if it wants, fall through to a full restart. This is the host-neutral call a file watcher drives; see the watcher in :mod:citry.reload and docs/design/hot_reload.md.

View source

invalidate_all function

invalidate_all() -> list[type[Component]]

Reset cached template/JS/CSS for every component that has loaded a file, so the next render re-reads them all from disk. Returns the reset classes (in first-seen order).

For when a change cannot be mapped to a single path: a bulk edit, a branch switch, or a custom watcher reporting an event it cannot resolve to one file. Unlike :meth:clear, this leaves the registry and autodiscovery untouched.

View source

clear function

clear() -> None

Clear registrations and caches, and re-arm autodiscovery.

Raises

  • CitryLifecycleInProgress - If another thread is changing component lifecycle state.
  • RuntimeError - If called from another lifecycle operation on this thread.
View source

citry module

The Citry global instance - scopes all component state.

A Citry instance owns a component registry, settings, and transient rendering state. Every Component subclass is assigned to a Citry instance, either by declaring citry = my_citry in its class body or by using the default instance.

Example

Using the default instance (most common)::

from citry import Component

class MyTable(Component):
    template = "<table>...</table>"

Using a custom instance::

from citry import Citry, Component

my_citry = Citry()

class MyTable(Component):
    citry = my_citry
    template = "<table>...</table>"

Isolated instances for testing::

def test_my_component():
    test_citry = Citry()
    # Components registered here don't leak to other tests
    class MyTable(Component):
        citry = test_citry
        template = "..."
View source

AlreadyRegistered class

Bases: Exception

Raised when registering a component under a name that is already taken.

View source

NotRegistered class

Bases: Exception

Raised when looking up a component name that is not registered.

View source

CitryLifecycleInProgress class

Bases: RuntimeError

Raised when another thread is changing Citry's component lifecycle state.

Component discovery, registration, built-in creation, clearing, and tag-rule construction publish related state together. A competing thread receives this error rather than observing an incomplete registry or waiting in a way that can deadlock with Python's module-import locks.

Finish Citry.initialize() before starting request threads to avoid this error during normal server operation. An operation that encounters it may also be retried after the active lifecycle operation finishes.

View source

CitrySettings class

Immutable settings for a Citry instance.

Attributes

  • extensions Sequence[type[Extension] | Extension | str] - The extensions to install on the instance. Each entry is an ``Extension`` subclass, a ready-made instance, or an import string like ``"myapp.extensions.MyExtension"``. The set is fixed once the instance is constructed.
  • extensions_defaults Mapping[str, Mapping[str, Any]] - Default config values for extensions, keyed by extension name, e.g. ``{"events": {"_csrf": True}}``. When an extension reads a config field for a component, the component's own nested config class wins, a value given here fills in next, and the extension's built-in default comes last.
  • dirs tuple[Path, ...] - Directories searched when resolving a component's asset files (``template_file``, ``js_file``, ``css_file``, and ``Dependencies`` entries), after the directory of the component's own ``.py`` file. Entries are converted to ``Path`` and must be absolute; this is validated at construction (a relative entry raises ``ValueError``).
  • cache CitryCache | str | None - Where citry stores what it caches: a [`CitryCache`](/reference/citry/#citry-citrycache) object or an import string like ``"myapp.caching.MyCache"``. ``None`` gives the instance its own in-memory cache. The live backend built from this setting is ``Citry.cache``.
  • sandbox_expressions bool - Whether template expressions (``{{ ... }}`` and dynamic ``c-*`` attributes) are evaluated in the security sandbox. On by default. Turning it off evaluates expressions as plain Python, which is faster but removes security guardrails. Only do so when every template comes from a trusted source.
  • autodiscover bool - Whether to import the component modules under ``dirs`` the first time a component is looked up, so their classes register themselves without being imported by hand. On by default; when no ``dirs`` are set there is nothing to scan, so the default instance does nothing. The directories must be importable (on ``sys.path``/``PYTHONPATH``). See ``Citry.autodiscover`` and ``citry.autodiscovery``.
  • mode Mode - The build environment, ``"production"`` (the default) or ``"development"``. It is the single source of truth for whether the engine includes developer-only output: in ``"development"`` the built-in ``debug`` extension is auto-registered (visual component boundaries) and the client ownership graph carries source provenance. An unrecognized value raises ``ValueError`` at construction. See ``docs/design/dev_prod_mode.md``.
  • template_globals Mapping[str, Any] - Variables exposed to every component's template without being returned from each ``template_data()``. They are merged into every component's template variables on render, so a template can reference one directly (``{{ site_name }}``). A component's own ``template_data`` wins when it returns a key of the same name, so globals act as defaults. The value given here is the starting set; the live, editable copy is ``Citry.template_globals``, which is how you add or change a global after the instance exists (including the default instance, created at import before your code runs).
  • id_generator Callable[[], str] | str | None - A function returning the per-render id stamped on each component instance (``component.id``, which drives the ``data-cid-<id>`` markers that scope a component's CSS and JS on the page). Given as a callable or a ``"path.to.func"`` import string; passing a class also works: it is called once, and the resulting object is used as the generator (handy when the generator keeps state, like a counter). ``None`` uses the built-in generator. Override it for stable ids in snapshot tests. The generator must return ids that are unique among the components on one page and contain only lowercase ASCII letters, digits, hyphens, and underscores. The lowercase rule is required because the id is embedded in an HTML attribute name. This does not touch ``class_id``, which stays a stable hash of the component's import path.
  • secret str | list[str] | None - The signing secret for values citry hands to the browser and must recognize when they come back, such as the state the Events extension round-trips on each event call. A single string is the common form. A list means key rotation: the first entry signs new values, and a value signed by any entry still verifies, so already-issued values stay valid while a new key rolls out. A bare string is stored as a one-element list. ``None`` (the default) means no secret is set. Django projects can reuse their existing key by passing ``citry.contrib.django.secret()``.
  • event_result_resolvers Sequence[Any] - Result resolvers for the Events extension. When an event handler returns a value, citry converts it into the actions sent back to the browser (the instructions the client runtime applies: re-render this component, redirect, and so on). A resolver adds support for your own return types: it is given the handler's return value and either converts it into those actions or declines, letting the next resolver try. Resolvers run in order, after the built-in conversions; the first one to convert the value wins.
  • event_payload_codecs Sequence[Any] - Payload codecs for the Events extension's HTTP endpoints. A codec reads one request format (identified by its content type) into the event call the extension expects, so clients are not limited to the built-in JSON, form, and query formats. Codecs given here are tried before the built-in ones, in order.
View source

CitryCache class

Bases: Protocol

The cache backend interface.

Implement these four methods to plug in any store (Redis, diskcache, Django's cache framework, ...). Keys and values are strings.

View source

get function

get(key: str) -> str | None

Return the value for key, or None when absent or expired.

View source

set function

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

Store value under key. ttl is seconds until expiry; None means keep forever.

View source

delete function

delete(key: str) -> None

Remove key if present (no error when absent).

View source

has function

has(key: str) -> bool

Whether key is present (and not expired).

View source

InMemoryCache class

The default cache backend: a thread-safe in-process LRU store.

Unbounded by default. Pass max_entries to cap the size; when full, the entry that was read or written longest ago is dropped to make room.

Single-process only: each instance is its own store. For multi-worker deployments use a shared backend instead (see the module docstring).

View source

set function

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

clear function

clear() -> None

Drop all entries. Called by Citry.clear().

Citry version: 0.3.0