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/. See building the documentation for
local builds and the current Read the Docs verification status.
- 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)
Install the git hooks once per clone (pre-commit runs ruff; pre-push runs the
tests only when the pushed range changes Python, and only the suite the change
belongs to — tests/core, tests/spa, tests/server_app, or everything
when more than one of them changed):
cp hooks/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
cp hooks/pre-push .git/hooks/pre-push && chmod +x .git/hooks/pre-push
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
Metadata
Release files for genro-asgi 0.46.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| genro_asgi-0.46.3.tar.gz | 1.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| genro_asgi-0.46.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.1 MB
Release files / genro_asgi-0.46.3.tar.gz
| Download URL | genro_asgi-0.46.3.tar.gz |
|---|---|
| Size | 1.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1b73c51ddab0dd0f61a446778c1d8b7d0693dac1db9fea8c3b202e775c48ad46
|
|
BLAKE2b-256 checksum How to use checksums |
c08c5fa81faca683a0c32f1cea4da78e9865e8a122c145c651079454cb0d3446
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logRelease files / genro_asgi-0.46.3-py3-none-any.whl
| Download URL | genro_asgi-0.46.3-py3-none-any.whl |
|---|---|
| Size | 493.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c961115e4eed1157cb1e73905aaacd7734d4950c4216b15160f053958bddafbe
|
|
BLAKE2b-256 checksum How to use checksums |
e26f7290272f1528493d7d4d307ba167241e4351d40eeabe615e317a0abf6659
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency log