genro-asgi Architecture Overview
Version: 2.0.0 Status: SOURCE OF TRUTH Last Updated: 2026-06-28
What is genro-asgi
genro-asgi is a minimal ASGI framework that provides:
AsgiServer: a demultiplexer that mounts one application per first-level URL segment and relays the ASGI call to it
Configuration: a Python
config.pybuilder recipe rendered onto the serverRequest System: transport-agnostic request handling (HTTP, WebSocket/WSX)
Response System: a single
Responseclass with auto content-type detectionExecutors: thread/process pools for blocking and CPU-bound work
WSX Protocol: RPC message format over WebSocket
Core Principles
1. Instance isolation, no globals
The server is an instance with its own state. Every child object holds a
semantic reference to its parent (self.server, self.application, …). There
are no module-level singletons; del server collects everything.
2. The server demultiplexes, apps route
AsgiServer does no routing of its own. The Dispatcher picks the app from the
first path segment and relays the whole ASGI call; each app does its own routing
in handle_request.
3. Config-driven
The server boots from a config.py whose ServerConfiguration (a subclass of
AsgiConfigBuilder) is rendered onto it. The builder recipe is the single source
of the server’s topology (apps, middleware, auth, databases, openapi).
4. Transport agnostic
Handlers receive a BaseRequest; the same handler works over HTTP or WebSocket
(WSX), which uses MsgRequest(BaseRequest).
Module Structure
src/genro_asgi/
├── __init__.py # Public exports (52 symbols)
├── __main__.py # CLI entry point
├── types.py # ASGI type definitions
├── exceptions.py # HTTPException, WebSocket exceptions, Redirect
├── request.py # BaseRequest, HttpRequest, MsgRequest, RequestRegistry
├── response.py # Response (set_result / set_error), make_cookie
├── websocket.py # WebSocket connection wrapper
├── lifespan.py # Lifespan, ServerLifespan
├── loader.py # AppLoader (isolated module loading)
├── resources.py # ResourceLoader (hierarchical fallback)
├── storage.py # LocalStorage, StorageNode
├── db.py # AsgiDbHandlerBase
├── config/ # config.py builder model (the live configuration)
├── server/ # server.py, dispatcher.py, auth_mixin.py, worker.py, server_app/
├── applications/ # AsgiApplication, OpenApiApplication, McpApplication
├── authentication/ # Backward-compatible shim (logic in server/auth_mixin.py)
├── datastructures/ # Headers, URL, QueryParams, State, Address
├── executors/ # BaseExecutor, LocalExecutor, ThreadExecutor, ExecutorRegistry
├── middleware/ # Auto-discovery + 7 middleware classes
├── routers/ # StaticRouter
├── session/ # Session, Avatar, MemorySessionStore
├── sys_applications/ # genro_api, login_page, plugin_config, swagger
├── utils/ # split_and_strip and small helpers
└── wsx/ # WSX protocol (handler, protocol, registry)
Architecture Documents
Document |
Description |
|---|---|
AsgiServer, config.py boot, mount, dispatch |
|
BaseRequest, HttpRequest, MsgRequest, RequestRegistry |
|
The single Response class |
|
Blocking/CPU task execution |
|
Startup/shutdown lifecycle |
|
WSX message format |
|
Streaming and protections |
|
Demultiplex + per-app routing |
|
Authentication and context |
Quick Start
The server boots from a config.py. You do not subclass AsgiServer; you write
a ServerConfiguration recipe and point the server at it.
# config.py
from genro_asgi.config import AsgiConfigBuilder
from my_app import Application as MyApp # an AsgiApplication subclass
class ServerConfiguration(AsgiConfigBuilder):
def main(self, root):
root.server(host="127.0.0.1", port=8000)
root.middleware(cors=True)
apps = root.applications(default="main")
apps.application(code="main", app_class=MyApp)
from genro_asgi import AsgiServer
server = AsgiServer("config.py") # the config.py directory is the server dir
server.run() # uvicorn on the configured host/port
Key Design Decisions
No envelope abstraction
Requests are the trackable unit; there is no RequestEnvelope/ResponseEnvelope
wrapper. RequestRegistry tracks the in-flight request.
Single request.py and single Response
All request classes live in request.py; there is a single Response class in
response.py (no JSONResponse/HTMLResponse/… hierarchy). set_result()
picks the content type from the result.
WSX is the message protocol; handling lives in request.py
wsx/ holds the WSX message format (protocol.py), the connection handler
(handler.py) and the connection registry (registry.py). WSX request handling
uses MsgRequest(BaseRequest) from request.py.
Copyright: Softwell S.r.l. (2025-2026) License: Apache License 2.0