Skip to main content

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.

PyPI version Python versions License

Status: Beta. Feature-complete core, API stabilizing. This repository is a spec-first redesign — see SPECIFICATION.md for 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-route auth_rule filtering, default-deny.
  • Manages sessions — in-memory or file-backed store, cookie reconnection, Avatar identity (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 OpenApiApplication and the same @route methods yield an OpenAPI 3.1 schema and a Swagger UI.
  • Speaks MCP — subclass McpApplication (or McpOpenApiApplication) and your routes become tools an AI agent can call, over MCP Streamable HTTP.
  • Streams — StreamingResponse for chunked bodies, SseStream for 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.

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.

Metadata

Release files for genro-asgi 0.41.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for genro-asgi 0.41.0
File Size Uploaded
genro_asgi-0.41.0.tar.gz 1.3 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for genro-asgi 0.41.0
File Interpreter ABI Platform
genro_asgi-0.41.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.7 MB

Release files / genro_asgi-0.41.0.tar.gz

Download URL genro_asgi-0.41.0.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
9ef511c4b8ffe07cf8f503059079dd9e46c41ed99694bb7769bd06f4b22b782d
BLAKE2b-256 checksum
How to use checksums
2535d4fc77dd5c3aa677f5679838c5687791af0e4609a5ccadf74d3b2d28d8d3
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 4, 2026.

Transparency log

Release files / genro_asgi-0.41.0-py3-none-any.whl

Download URL genro_asgi-0.41.0-py3-none-any.whl
Size 429.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
84bc867146db041ad97661f04458304a4ff521cd0bb6e4bccf6960068fc01e99
BLAKE2b-256 checksum
How to use checksums
6a1c9ee903433edc3b85d2ea9c47a8c5d5d0b5cc1a55bc4f0c3e70de42b500bd
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 4, 2026.

Transparency log

Release history Release notifications | RSS feed

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page