Skip to main content

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.

PyPI version Python Support License Documentation

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-routes with decorator-based routing and auth tag filtering
  • Serves static files with StaticRouter and hierarchical resource loading with fallback chains
  • Handles WebSocket with the WSX extension protocol for structured request/response messaging
  • Exposes MCP endpoints via McpApplication for AI tool integration (Streamable HTTP transport)
  • Configures plugins at runtime via the plugin_config web 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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

genro_asgi-0.13.0.tar.gz (1.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

genro_asgi-0.13.0-py3-none-any.whl (349.3 kB view details)

Uploaded Python 3

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

Hashes for genro_asgi-0.13.0.tar.gz
Algorithm Hash digest
SHA256 720101fb785107be2d2466062101a2bd9ac356559a31598179c4d448cb2dd141
MD5 c36ebc68eba094c9d2f5ebf929ce15b0
BLAKE2b-256 73be31995f686e99129ecc3fbe9f7f9ed7d0eaa1c0bc5d71b3584add8b235688

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_asgi-0.13.0.tar.gz:

Publisher: publish.yml on genropy/genro-asgi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for genro_asgi-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5bd4928e6520ff82b3d69f207aa51ec18803f884f21f379e07f75baea976bd69
MD5 dad6bfb4a2d46f20a5c60f643daa7e1a
BLAKE2b-256 a9093192d1440e62b002bb42c6b7bff484f056086f640d64c9fa6d5a8864a19c

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_asgi-0.13.0-py3-none-any.whl:

Publisher: publish.yml on genropy/genro-asgi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.37.0

2 files

0.36.0

2 files

0.35.0

2 files

0.34.0

2 files

0.33.0

2 files

0.32.0

2 files

0.31.0

2 files

0.30.0

2 files

0.29.0

2 files

0.28.0

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.0

2 files

0.23.0

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

This release

0.13.0 This release

2 files

0.10.0

2 files

0.9.0

2 files

0.7.0

2 files

0.6.7

2 files

0.6.6

2 files

0.6.5

2 files

0.2.0

2 files

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