Server

Core ASGI server classes.

AsgiServer

ASGI Server - main entry point for genro-asgi applications.

Classes:
AsgiServer – central coordinator: reads a config.py (a

ServerConfiguration builder), is the render target that configures itself, mounts apps, builds the middleware chain, handles lifespan, demultiplexes requests to the mounted apps via the Dispatcher (each app routes itself).

Inherits BasicAuthMixin and RoutingClass. Run with AsgiServer(config_path).run().

WebSocket connections are handled by WsxHandler (WSX protocol over WebSocket).

class genro_asgi.server.server.AsgiServer(config_path, host=None, port=None, reload=None, parent=None)[source]

Bases: BasicAuthMixin, RoutingClass

ASGI server: a demultiplexer over the mounted applications.

The server does no routing of its own. It boots from a config.py whose ServerConfiguration builder is rendered onto it, mounting one app per URL prefix in apps; the Dispatcher selects the app from the first path segment and the app does its own routing.

config_path

Path to the config.py that configures this server.

config_handler

ConfigurationHandler that renders the builder onto it.

dispatcher

Dispatcher; demultiplexes requests to the target app.

lifespan

ServerLifespan for startup/shutdown.

server_application

ServerApplication with system endpoints (_server).

apps

Dict of mounted apps keyed by mount (the empty mount is the main app).

wsx_handler

WsxHandler for WebSocket (WSX protocol) connections.

storage

LocalStorage service shared by the apps.

user_store

FileUserStore local identity source when users=True, else None.

resource_loader

ResourceLoader with hierarchical app/server fallback.

session_store

Server-managed session store.

request_registry

RequestRegistry tracking the current request.

logger

Server logger instance.

__init__(config_path, host=None, port=None, reload=None, parent=None)[source]

Initialize AsgiServer from a config.py path or a configuration class.

Composes BasicAuthMixin (auth) and RoutingClass (routing). Builds the fixed parts of the server, then loads the configuration (a module with a ServerConfiguration builder, or directly an AsgiConfigBuilder subclass) and renders it onto this server: the render is what mounts apps, builds the middleware chain and plugs the router. The server is the render target (see apply_configuration).

Parameters:
  • config_path (str | Path | type[AsgiConfigBuilder]) – Path to the config.py (its directory is the server dir), or an AsgiConfigBuilder subclass for a programmatic configuration (the server dir is the working directory).

  • host (str | None) – Runtime override for the host (wins over the configuration).

  • port (int | None) – Runtime override for the port (wins over the configuration).

  • reload (bool | None) – Runtime override for auto-reload (wins over the configuration).

  • parent (Any) – Optional parent object with shared resources and logic.

apply_configuration(nodes)[source]

Apply the rendered configuration nodes to this server.

Each top-level ConfigNode is dispatched by tag to _apply_<tag>. An unknown tag raises: grammar and server must stay in sync.

Return type:

None

apply_persisted_config()[source]

Apply plugin_config.json to the current router tree.

Called at boot after plugins are attached.

Return type:

None

apps: dict[str, AsgiApplication]
base_dir: Path
build_db_handler(code, descriptor)[source]

Build a db handler from a database descriptor (code removed).

db_class (required, imported by the config.py) builds the db from the remaining attributes; db_handler_class (optional, default AsgiDbHandlerBase) wraps it. The handler is what the registry holds.

Return type:

Any

config_handler
config_path
configuration_class: type[AsgiConfigBuilder] | None
create_session(auth=None)[source]

Create a new session in the session store.

Parameters:

auth (dict[str, Any] | None) – Auth dict snapshot to store in session.

Return type:

Session

Returns:

New Session instance with unique token.

property db: Any

Default database handler (shortcut for get_db(“default”)).

db_registry: dict[str, Any]
dispatcher: Callable[[MutableMapping[str, Any], Callable[[], Awaitable[MutableMapping[str, Any]]], Callable[[MutableMapping[str, Any]], Awaitable[None]]], Awaitable[None]]
export_factory_env()[source]

Expose this server’s configuration to server_factory via env.

A file configuration travels as its path (GENRO_ASGI_CONFIG); a programmatic one as the dotted module:Class of the configuration class (GENRO_ASGI_CONFIG_CLASS), which must therefore be importable.

Return type:

None

get_db(name)[source]

Return a registered database handler by name.

Parameters:

name (str) – Logical name used in register_db().

Raises:

KeyError – If no database registered with that name.

Return type:

Any

get_plugin_config(router_path='', entry='')[source]

Read live plugin configuration for a router/entry.

Uses routing.configure(“?”) for introspection via genro-routes public API.

Parameters:
  • router_path (str) – Slash-separated path to the router (empty = all).

  • entry (str) – Specific entry/handler name to filter (empty = all).

Return type:

dict[str, Any]

Returns:

Dict with router_path, plugin_info. Error dict if router not found.

host
lifespan
logger
login_enabled
mount(name, app)[source]

Mount an application on the server.

Parameters:
  • name (str) – Mount name (becomes the URL prefix).

  • app (Any) – Application instance (AsgiApplication or RoutingClass).

Return type:

None

openapi_info: dict[str, Any]
parent
property plugin_config_file: Path

Plain path of plugin_config.json in server_dir (no-encryption mode).

property plugin_config_node: LocalStorageNode

Storage node for plugin_config.json.

Routes to the encrypted secure: mount when key material is installed (storage.encryption_active); otherwise stays plain on the site: mount at the historical path.

port
register_db(name, db)[source]

Register a database handler in the registry.

Parameters:
  • name (str) – Logical name for this database (e.g. “sourcerer”, “default”).

  • db (Any) – A db handler (AsgiDbHandlerBase) wrapping the real db.

Return type:

None

reload
property request: BaseRequest | None

Current request from registry.

request_registry
resource_loader
property response: Response | None

Current response from request.

revert_plugin_config()[source]

Revert runtime config to what is saved in plugin_config.json.

Returns:

True. Includes “message” if no saved config exists.

Return type:

dict[str, Any]

run(workers=None)[source]

Run the server using Uvicorn.

workers > 1 runs that many uvicorn processes on one shared socket (each rebuilds the server through the module factory, like reload does); mutually exclusive with reload — uvicorn’s own constraint.

Return type:

None

save_plugin_config()[source]

Persist current runtime plugin config to plugin_config.json.

Uses routing.configure(“?”) to snapshot the full tree via genro-routes public API. Writes through plugin_config_node: encrypted at rest when keys are installed, plain otherwise (with a warning).

Returns:

True and “file” fullpath on success.

Return type:

dict[str, Any]

server_application
session_store
set_plugin_config(router_path='', plugin_name='', target='_all_', **config_values)[source]

Apply a plugin config change at runtime without persisting.

Delegates to routing.configure() via genro-routes public API.

Parameters:
  • router_path (str) – Slash-separated path to the router (empty = root).

  • plugin_name (str) – Name of the plugin to configure.

  • target (str) – Target slot (“_all_” or specific entry name).

  • **config_values (Any) – Plugin configuration key-value pairs.

Returns:

True on success, or “error” key on failure.

Return type:

dict[str, Any]

storage
user_store: FileUserStore | None
wsx_handler

Configuration

The server is configured by a config.py whose ServerConfiguration (a subclass of AsgiConfigBuilder) is rendered onto it.

AsgiConfigBuilder — builder dialect for genro-asgi server configuration.

The user subclasses AsgiConfigBuilder in a config.py and overrides main(self, root) (the recipe) and optionally setup(self, data) (the values behind ^pointers). The server mounts the builder on a ConfigurationHandler and renders it: rendering applies the configuration to the live server (see AsgiConfigRenderer).

Example config.py:

from genro_asgi.config import AsgiConfigBuilder
from myshop.app import Application as Shop

class ServerConfiguration(AsgiConfigBuilder):
    def main(self, root):
        root.server(host="127.0.0.1", port=8000, reload=True)
        root.middleware(cors=True)
        apps = root.applications(default="shop")
        apps.application(code="shop", app_class=Shop)
        root.openapi(title="Demo Shop API", version="1.0.0")
class genro_asgi.config.asgi_config_builder.AsgiConfigBuilder(name=None)[source]

Bases: BuilderBase, AsgiConfigElements

Server-configuration dialect. Grammar from AsgiConfigElements, application to the live server from AsgiConfigRenderer.

property renderer_asgi: AsgiConfigRenderer

Fresh AsgiConfigRenderer bound to this builder.

AsgiConfigRenderer — object renderer for the server configuration dialect.

Unlike the markup dialects (HTML, SVG) whose render emits a string, this is an object renderer: a node renders to a ConfigNode carrying its tag, its already-resolved attributes (the walk resolves ^pointers and data logic) and its rendered children. The render result is the list of top-level ConfigNodes, delivered to the server, which configures itself from them.

class genro_asgi.config.asgi_config_renderer.AsgiConfigRenderer(builder, handler=None)[source]

Bases: RendererBase

Object renderer: a node renders to a ConfigNode with resolved attrs.

finalize(result, target, **_opts)[source]

Deliver the list of top-level ConfigNodes to the target.

An object dialect does not join fragments into text. target is the server: it receives the ConfigNodes via apply_configuration and configures itself. target=None returns the list as-is.

Return type:

Any

mode: str | None = 'asgi'

The render mode this renderer serves ("html", "svg", …). A class attribute: each renderer_<mode> property maps one mode to one renderer class, so the mode is intrinsic, not injected. The base has none; concrete dialects set it. Used to look up the mode’s default target and to resolve sub-builder renderers.

render_type: str = 'object'

"string" for markup dialects (HTML, SVG, CSS, XML) whose fragments are serialized strings, "object" for dialects that produce live objects (widgets, flowables). Governs whether target=False (return-as-value) is allowed: meaningful for "string", an error for "object".

Type:

Output type of this renderer

rendered_item(node, item, runtime_attrs, *, tag, **opts)[source]

Build a ConfigNode from the resolved tag/attrs and the children.

runtime_attrs are resolved by the walk (pointers, data logic); item is the list of child ConfigNodes when the node has children.

Return type:

Any

class genro_asgi.config.asgi_config_renderer.ConfigNode(tag, label, attrs, children)[source]

Bases: object

A rendered configuration node: tag, label, resolved attrs, children.

attrs
children
label
tag

ConfigurationHandler — owns the configuration builder and renders it.

The server owns one ConfigurationHandler. It mounts the user’s ServerConfiguration builder and renders it toward the server: the render target is the server itself (never the handler), and the server carries the methods that turn the configuration nodes into server state.

The handler is the place where the reactive runtime will live (Fase 2): a live() section mutating the configuration will re-render the touched nodes toward the same target, the server, applying changes incrementally.

class genro_asgi.config.configuration_handler.ConfigurationHandler(server, application=None)[source]

Bases: BuilderHandler

Owns the ServerConfiguration builder; renders it onto the server.

configure(builder)[source]

Mount the configuration builder and render it onto the server.

add_builder runs the recipe (setup + main); the render delivers the configuration nodes to the server, which applies them.

Return type:

None

GenroAsgiWorker

A minimal single-app ASGI server (owns a ThreadExecutor).

GenroAsgiWorker - a minimal single-app ASGI server.

The unit a commander spawns to distribute load: one uvicorn process serving exactly one app. Unlike AsgiServer it owns no multi-app registry, middleware/plugin system, sessions, storage or config orchestration — only the bare minimum to serve one app:

  • a Dispatcher (which, with a single app on the empty mount, always resolves to that app),

  • a ServerLifespan (so the app’s on_startup/on_shutdown run),

  • a WsxHandler terminating the websocket connections the commander pipes down (issue #44): the standing connection lives in the process that owns the user’s pages, so server-initiated push is the worker’s own business,

  • the dual parent-child relationship with the app (app.server -> worker),

  • the app-facing server surface the app’s CANONICAL dispatch needs: a RequestRegistry, an empty db_registry and a None-returning authenticate (no server-level auth: whoever fronts the worker owns it).

The app is mounted on the empty mount (‘’) so the dispatcher’s root fallback always reaches it. It is the executor in the daemon-less worker model: the commander forwards requests to it over HTTP; it just runs the app.

Two serving paths:

  • run() — the blocking CLI path (uvicorn.run), unchanged;

  • start() / wait_stopped() / request_stop() — the async path for a parent that interleaves the serving with other tasks (the pool child entry): uvicorn runs as a task and, with port=0, the OS-assigned port is read back from the bound socket, so the worker can announce where it listens.

class genro_asgi.server.worker.GenroAsgiWorker(app, host='127.0.0.1', port=8000, max_workers=None)[source]

Bases: RoutingClass

Minimal ASGI server hosting a single app, run by uvicorn.

app

The single mounted application (an ASGI callable).

apps

One-entry dict {‘’: app}, so Dispatcher/ServerLifespan — which iterate server.apps — see the app on the root fallback mount.

host

Bind host.

port

Bind port.

logger

Logger for worker messages.

lifespan

ServerLifespan driving the app’s on_startup/on_shutdown.

dispatcher

Dispatcher delegating every request to the single app.

wsx_handler

WsxHandler terminating websocket connections (the dispatcher’s fallback for an app with no serve_websocket hook).

executor

Owned ThreadExecutor — the dispatch for blocking work, whose metrics report this worker’s pressure (busy/total/queue/occupancy). The app reaches it via the dual relationship (app.server.executor).

request_registry

RequestRegistry the app’s canonical dispatch creates and unregisters its requests on.

db_registry

Always empty — the minimal worker registers no databases; the app’s get_db falls back to its own registry.

server

The uvicorn Server of the async path (None until start()).

serve_task

The task running server.serve() on the async path.

__init__(app, host='127.0.0.1', port=8000, max_workers=None)[source]

Initialize the worker around a single app.

Parameters:
  • app (Any) – The application to serve (mounted on the empty mount).

  • host (str) – Bind host.

  • port (int) – Bind port.

  • max_workers (int | None) – Thread pool size for the dispatch executor (default: ThreadPoolExecutor’s own, min(32, cpu + 4)).

app
apps
authenticate(scope)[source]

No server-level auth on the minimal worker: every identity is None.

The app’s canonical dispatch delegates here (the app-facing server protocol); whoever fronts the worker (the commander, a gateway) owns the authentication of what it forwards.

Return type:

dict[str, Any] | None

db_registry: dict[str, Any]
dispatcher
executor
host
lifespan
logger
port
request_registry
request_stop()[source]

Ask the serving task to exit (uvicorn drains connections and returns).

Return type:

None

run()[source]

Run the worker using uvicorn (single process, no reload).

Return type:

None

serve_task: Task[None] | None
server: Server | None
async start()[source]

Serve as a task in the running loop; self.port becomes the bound port.

The async counterpart of run(): uvicorn’s serve() runs as a task and this returns once the server has started, with the OS-assigned port (port=0) read back from the bound socket — the same discovery ChannelHub.start() does for its TCP variant.

Return type:

None

async wait_stopped()[source]

Block until the serving task ends, then release the dispatch executor.

Return type:

None

wsx_handler