Genro ASGI
A minimal ASGI server and application toolkit for building web services with routing, authentication, sessions, and middleware — configured by a Python config.py builder recipe.
What it does
- Serves web applications mounted on URL prefixes (
/api/,/shop/, etc.) with config-driven loading - Authenticates requests via bearer tokens, basic auth, or JWT — O(1) credential lookup, lazy auth for zero overhead on unprotected routes
- Manages sessions with in-memory store, cookie-based reconnection, and user avatars (identity + tags + extensible Bag data)
- Applies middleware at two levels: global (CORS, errors, auth) and per-app (session, cache) with independent chains
- Routes requests via
genro-routeswith decorator-based routing and auth tag filtering - Serves static files with
StaticRouterand hierarchical resource loading with fallback chains - Handles WebSocket with the WSX extension protocol for structured request/response messaging
- Exposes MCP endpoints via
McpApplicationfor AI tool integration (Streamable HTTP transport) - Configures plugins at runtime via the
plugin_configweb UI (mounted under/_sys/plugin_config/) with persistence to JSON
Installation
pip install genro-asgi # core
pip install genro-asgi[json] # + orjson for fast JSON
pip install genro-asgi[dev] # + test/lint tools
Hello world (no config file)
One file, one import:
# hello.py
from genro_asgi import AsgiApplication, route
class Hello(AsgiApplication):
@route()
def index(self) -> str:
return "Hello, world!"
genro-asgi serve application=hello.py:Hello
Open http://127.0.0.1:8000/ — and http://127.0.0.1:8000/_server/monitor for
the built-in server monitor, free with the standard configuration. The target
also takes the module spelling (package.module:Class), plus --reload,
--debug and --workers N (uvicorn multi-process, like any ASGI framework).
Register the app under a name and manage it:
genro-asgi serve application=hello.py:Hello --name hello # serve AND register
genro-asgi serve hello # relaunch by name
genro-asgi apps # list (with status)
genro-asgi stop hello # stop a running app
genro-asgi remove hello # drop the registration
The registry (~/.genroasgi) stores a pointer per name — target and options —
never a copy of the app: relaunching by name always runs the current code.
When the app grows past one file, the next step is an explicit config.py
(below): multiple apps on mounts, middleware, auth, multi-worker pools.
REST + OpenAPI + Swagger + MCP from one class
Subclass OpenApiApplication instead of AsgiApplication and the same
@route methods get an OpenAPI 3.1 schema and a Swagger UI generated from
their signatures — parameter names, types, defaults:
# shop.py
from genro_asgi import route
from genro_asgi.applications.openapi_application import OpenApiApplication
class Shop(OpenApiApplication):
openapi_info = {"title": "Shop API", "version": "1.0.0"}
@route(media_type="application/json")
def search(self, q: str = "", max_price: float = 100.0) -> dict:
return {"query": q, "hits": []}
genro-asgi serve application=shop.py:Shop
/search?q=moka&max_price=30— the endpoint, query params typed and coerced/_meta/docs— Swagger UI/_meta/schema_json— OpenAPI 3.1 schema
If you know FastAPI, this is the same decorate-a-method workflow. The
difference is where the route description lives: genro-routes keeps it
protocol-neutral, so other transports can read the same tree. Switch the base
class to McpOpenApiApplication and the app grows an MCP face on /mcp —
the endpoints you declare become tools agents can call, with the same
parameter handling as REST, no separate MCP layer to keep in sync:
class Shop(McpOpenApiApplication):
@route(media_type="application/json", channel_channels="mcp,rest")
def search(self, q: str = "", max_price: float = 100.0) -> dict: # REST + MCP tool
...
@route(media_type="application/json")
def restock(self, item_id: int = 0, quantity: int = 0) -> dict: # REST only
...
tools/list on /mcp shows search and not restock: a method becomes a
tool only where its decorator says so. Working examples, each one file,
runnable with genro-asgi serve: examples/openapi_demo.py,
examples/openapi_nested_demo.py (the API
composed from nested RoutingClass sections),
examples/mcp_openapi_demo.py.
Quick Start
The server boots from a config.py whose ServerConfiguration (a subclass of
AsgiConfigBuilder) is rendered onto it. The recipe builds the server tree with
the builder API; application classes are imported and passed as objects.
from genro_asgi import AsgiServer
server = AsgiServer("config.py") # the config.py directory is the server dir
server.run() # starts uvicorn on the configured host/port
config.py:
from genro_asgi.config import AsgiConfigBuilder
from shop_app import Application as Shop # your AsgiApplication subclass
class ServerConfiguration(AsgiConfigBuilder):
def main(self, root):
root.server(host="127.0.0.1", port=8000)
root.middleware(cors=True, auth=True) # one bool kwarg per middleware
root.authMiddleware(
bearer={"api_key": {"token": "sk_live_abc123", "tags": "api,read"}}
)
apps = root.applications(default="shop") # "shop" is served on the empty mount
apps.application(code="shop", app_class=Shop,
middleware={"session": True})
root.openapi(title="Shop API", version="1.0.0")
Each app derives its URL mount from its code (or "" when it is the
collection default), unless it declares an explicit mount. The server
demultiplexes on the first path segment; each app does its own routing.
Architecture at a glance
The server is an instance with its own state — no global variables. Every component is an isolated instance connected via semantic parent-child references.
Request flow:
uvicorn → AsgiServer → global middleware (errors → cors → auth)
→ Dispatcher → per-app middleware (session, cache, ...)
→ handler(**query) → Response → ASGI send
Core components
| Component | Purpose |
|---|---|
| AsgiServer | ASGI entry point, loads config, mounts apps, composes BasicAuthMixin |
| Dispatcher | Resolves handler via router, applies per-app middleware chains |
| BasicAuthMixin | Server-side auth: server.authenticate(scope), server.verify_credentials() |
| SessionMiddleware | Per-app session lifecycle: cookie extraction, reconnection, Set-Cookie |
| Session / Avatar | Session with meta + Bag data + Avatar (identity, tags, extensible data) |
| MemorySessionStore | In-memory session store with dump/restore for persistence |
| AsgiApplication | Base class for mountable applications |
| McpApplication | MCP Streamable HTTP transport for AI tools |
| StaticRouter | Filesystem-backed static file serving |
| LocalStorage | Filesystem storage with mount system |
Middleware
| Middleware | Name | Order | Default |
|---|---|---|---|
| ErrorMiddleware | errors | 100 | on |
| LoggingMiddleware | logging | 200 | off |
| CORSMiddleware | cors | 300 | off |
| AuthMiddleware | auth | 400 | off |
| SessionMiddleware | session | 450 | off |
| CacheMiddleware | cache | 900 | off |
| CompressionMiddleware | compression | 900 | off |
Scope is not a property of a middleware class: a middleware runs globally
when the server's middleware(...) section names it, and per-app when an
application's own middleware config names it. The same class can serve in
either chain; Order sorts each chain independently.
Documentation
Full documentation: https://genro-asgi.readthedocs.io
Development
git clone https://github.com/genropy/genro-asgi.git
cd genro-asgi
pip install -e .[dev]
pytest # run tests
ruff check . # lint
mypy src # type check
License
Copyright 2025-2026 Softwell S.r.l.
Licensed under the Apache License 2.0. See NOTICE for additional attribution.
Links
- GitHub: https://github.com/genropy/genro-asgi
- PyPI: https://pypi.org/project/genro-asgi/
- Documentation: https://genro-asgi.readthedocs.io
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file genro_asgi-0.13.0.tar.gz.
File metadata
- Download URL: genro_asgi-0.13.0.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
720101fb785107be2d2466062101a2bd9ac356559a31598179c4d448cb2dd141
|
|
| MD5 |
c36ebc68eba094c9d2f5ebf929ce15b0
|
|
| BLAKE2b-256 |
73be31995f686e99129ecc3fbe9f7f9ed7d0eaa1c0bc5d71b3584add8b235688
|
Provenance
The following attestation bundles were made for genro_asgi-0.13.0.tar.gz:
Publisher:
publish.yml on genropy/genro-asgi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
genro_asgi-0.13.0.tar.gz -
Subject digest:
720101fb785107be2d2466062101a2bd9ac356559a31598179c4d448cb2dd141 - Sigstore transparency entry: 2151482372
- Sigstore integration time:
-
Permalink:
genropy/genro-asgi@946bf878d073b2d57a6ea461fa76d50f0ddae346 -
Branch / Tag:
refs/tags/v0.13.0 - Owner: https://github.com/genropy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@946bf878d073b2d57a6ea461fa76d50f0ddae346 -
Trigger Event:
release
-
Statement type:
File details
Details for the file genro_asgi-0.13.0-py3-none-any.whl.
File metadata
- Download URL: genro_asgi-0.13.0-py3-none-any.whl
- Upload date:
- Size: 349.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bd4928e6520ff82b3d69f207aa51ec18803f884f21f379e07f75baea976bd69
|
|
| MD5 |
dad6bfb4a2d46f20a5c60f643daa7e1a
|
|
| BLAKE2b-256 |
a9093192d1440e62b002bb42c6b7bff484f056086f640d64c9fa6d5a8864a19c
|
Provenance
The following attestation bundles were made for genro_asgi-0.13.0-py3-none-any.whl:
Publisher:
publish.yml on genropy/genro-asgi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
genro_asgi-0.13.0-py3-none-any.whl -
Subject digest:
5bd4928e6520ff82b3d69f207aa51ec18803f884f21f379e07f75baea976bd69 - Sigstore transparency entry: 2151482523
- Sigstore integration time:
-
Permalink:
genropy/genro-asgi@946bf878d073b2d57a6ea461fa76d50f0ddae346 -
Branch / Tag:
refs/tags/v0.13.0 - Owner: https://github.com/genropy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@946bf878d073b2d57a6ea461fa76d50f0ddae346 -
Trigger Event:
release
-
Statement type: