genro-asgi
A minimal ASGI server core — one instance-isolated server that mounts your applications, routes requests through genro-routes, and grows authentication, sessions, background tasks, OpenAPI and MCP by composition. No globals, no module state: the server is an object you build, run, and throw away.
Status: Beta. Feature-complete core, API stabilizing. This repository is a spec-first redesign — see
SPECIFICATION.mdfor the founding decision log.
What it does
- Serves applications — each app declares its
mount, the URL prefix it answers under (""is the site root); the server demultiplexes on the first path segment. - Routes requests — handlers are
@route-decorated methods on your application class; query and body parameters bind to the signature, typed. - Authenticates — basic / bearer / JWT credentials, API keys (
gak_…), and OIDC providers; per-routeauth_rulefiltering, default-deny. - Manages sessions — in-memory or file-backed store, cookie reconnection,
Avataridentity (tags + extensible Bag data). - Applies middleware — errors, CORS, logging, auth, session, well-known — composed as an ordered chain, each turned on by config.
- Runs background tasks — a spool + executor + cron/interval scheduler,
managed over
/_server/tasks. - Generates OpenAPI & Swagger — subclass
OpenApiApplicationand the same@routemethods yield an OpenAPI 3.1 schema and a Swagger UI. - Speaks MCP — subclass
McpApplication(orMcpOpenApiApplication) and your routes become tools an AI agent can call, over MCP Streamable HTTP. - Streams —
StreamingResponsefor chunked bodies,SseStreamfor Server-Sent Events.
Installation
pip install genro-asgi
Requires Python 3.11+.
Hello world
One file. A RoutedApplication subclass with two @route handlers, served by
an AsgiServer:
# hello.py
from genro_asgi import AsgiServer, RoutedApplication
from genro_routes import route
class Hello(RoutedApplication):
mount = "" # this app answers the site root
@route()
def index(self) -> dict[str, str]:
return {"hello": "world"}
@route()
def greet(self, name: str = "world") -> dict[str, str]:
return {"hello": name}
if __name__ == "__main__":
server = AsgiServer(applications=[Hello()])
server.serve(host="127.0.0.1", port=8000)
python hello.py
$ curl http://127.0.0.1:8000/index
{"hello": "world"}
$ curl "http://127.0.0.1:8000/greet?name=genro"
{"hello": "genro"}
A handler that returns a dict answers JSON; the query string binds to the
method signature (name above), with defaults and type coercion. An unknown
path answers 404 through the always-on error middleware.
REST + OpenAPI + Swagger from one class
Subclass OpenApiApplication instead of RoutedApplication, declare
openapi_info, and enable the plugins — the same @route methods now expose an
OpenAPI 3.1 schema and a Swagger UI, generated from their signatures:
from genro_asgi import AsgiServer, OpenApiApplication
from genro_routes import route
class Shop(OpenApiApplication):
mount = ""
openapi_info = {"title": "Shop API", "version": "1.0.0"}
@route()
def search(self, q: str = "", max_price: float = 100.0) -> dict:
return {"query": q, "hits": []}
server = AsgiServer(applications=[Shop()], plugins={"openapi": True, "pydantic": True})
server.serve(port=8000)
/search?q=moka&max_price=30— the endpoint, params typed and coerced/_meta/docs— the Swagger UI/_meta/schema_json— the OpenAPI 3.1 document
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 read the same tree. Switch the base class to
McpOpenApiApplication and the app grows an MCP face on /mcp — the routes you
mark with @route(channel_channels="mcp") become tools an agent can call, with
the same parameter handling as REST. See
Coming from Starlette / FastAPI for a full
concept mapping.
Architecture at a glance
The server is an instance with its own state — no global variables. Every
component is an isolated object connected by a semantic parent reference (an app
holds self.server, a request holds self.application).
uvicorn → AsgiServer → middleware chain (errors → cors → auth → session)
→ demultiplex on first path segment → application
→ @route handler(**params) → Response → ASGI send
AsgiServer is the shipped composition: it stacks the capability mixins
(communication, auth, session, middleware, plugins, storage, tasks) over
BaseServer in one MRO. You turn features on through constructor kwargs
(auth=…, middleware=…, tasks=…, plugins=…) — objects always exist,
their backends come from config.
Documentation
Full documentation (guides, architecture, API reference) is built with Sphinx
under docs/ and published on Read the Docs.
- Getting started
- Coming from Starlette / FastAPI
- Architecture overview
SPECIFICATION.md— the founding decision log
Build it locally:
pip install -e ".[docs]"
sphinx-build -b html docs docs/_build/html
Development
git clone https://github.com/genropy/genro-asgi.git
cd genro-asgi
pip install -e ".[dev]"
pytest # run tests
ruff check src/ # lint
mypy src/ # type check (advisory)
See CONTRIBUTING.md for the full workflow.
License
Copyright © 2025 Softwell S.r.l.
Licensed under the Apache License 2.0.
This project may include third-party components under separate open-source
licenses; see the NOTICE file for attribution.
Links
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.27.0.tar.gz.
File metadata
- Download URL: genro_asgi-0.27.0.tar.gz
- Upload date:
- Size: 487.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f8f48dd3dfb963865a361d4d7189ba2940e00b9cc77be882a63c89275b126b6
|
|
| MD5 |
c2f1b3528e8d27a708d0a031487fd16d
|
|
| BLAKE2b-256 |
54b9109f0dc6709f50b8e81ab5cf074cbb5434389c4be6cd45a1be701bb472ee
|
Provenance
The following attestation bundles were made for genro_asgi-0.27.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.27.0.tar.gz -
Subject digest:
9f8f48dd3dfb963865a361d4d7189ba2940e00b9cc77be882a63c89275b126b6 - Sigstore transparency entry: 2424443563
- Sigstore integration time:
-
Permalink:
genropy/genro-asgi@7186c882bbff25ef69595643a9f765c8a7d19495 -
Branch / Tag:
refs/tags/v0.27.0 - Owner: https://github.com/genropy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7186c882bbff25ef69595643a9f765c8a7d19495 -
Trigger Event:
push
-
Statement type:
File details
Details for the file genro_asgi-0.27.0-py3-none-any.whl.
File metadata
- Download URL: genro_asgi-0.27.0-py3-none-any.whl
- Upload date:
- Size: 322.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dab75190dbd1f7f93d6b34284dd2a903f62112b061545cace9c122aa07425def
|
|
| MD5 |
8a9ad1d4e5d2c4158d2a202dfe9e4a3d
|
|
| BLAKE2b-256 |
efd2a4fa6360824a9d99fb3dfbe849aa27cea7a722861671eb204236d3d39a87
|
Provenance
The following attestation bundles were made for genro_asgi-0.27.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.27.0-py3-none-any.whl -
Subject digest:
dab75190dbd1f7f93d6b34284dd2a903f62112b061545cace9c122aa07425def - Sigstore transparency entry: 2424443760
- Sigstore integration time:
-
Permalink:
genropy/genro-asgi@7186c882bbff25ef69595643a9f765c8a7d19495 -
Branch / Tag:
refs/tags/v0.27.0 - Owner: https://github.com/genropy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7186c882bbff25ef69595643a9f765c8a7d19495 -
Trigger Event:
push
-
Statement type: