Getting Startedο
Status: π΄ DA REVISIONARE
Welcome to genro-asgi. This page takes you from installation to a running server that answers real HTTP requests, then explains the hello-world line by line so you know why each piece is there before you reach for anything more advanced.
What genro-asgi isο
genro-asgi is a minimal ASGI server core. You build one server object, mount
your applications on it, and it routes incoming requests to @route-decorated
methods on those applications. Everything else β authentication, sessions,
background tasks, OpenAPI, MCP, streaming β grows on the same core by
composition, turned on through constructor keyword arguments.
Two ideas shape the whole design:
The server is an object. You construct an
AsgiServer, call.serve(), and throw it away. There are no global variables and no module-level state; a test can spin up a fresh isolated server on every run.Configuration is data. Features do not appear and disappear β the objects always exist. What changes is the config you hand them (a backend, a set of credentials, a middleware option). You never flip a feature on by monkey- patching; you describe it.
If you come from Starlette or FastAPI, the decorate-a-handler workflow will feel familiar; the differences are covered in Coming from Starlette / FastAPI.
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):
@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(primary=Hello())
server.serve(host="127.0.0.1", port=8000)
Run it:
python hello.py
And call it:
$ curl http://127.0.0.1:8000/index
{"hello": "world"}
$ curl "http://127.0.0.1:8000/greet?name=genro"
{"hello": "genro"}
$ curl "http://127.0.0.1:8000/greet"
{"hello": "world"}
$ curl -i http://127.0.0.1:8000/nowhere
HTTP/1.1 404 Not Found
Line by lineο
Every line above earns its place. Here is what each one does.
The primary applicationο
class Hello(RoutedApplication):
RoutedApplication is the base class for an application whose behaviour is
described by @route-decorated methods. Your subclass is the application: its
methods are its endpoints.
The @route decoratorο
from genro_routes import route
@route()
def index(self) -> dict[str, str]:
return {"hello": "world"}
route is imported from genro-routes, the protocol-neutral routing library
genro-asgi builds on. It is not re-exported from genro_asgi, so import it
directly from genro_routes.
Marking a method with @route() publishes it as an endpoint. The method name
becomes the URL segment: index answers GET /index, greet answers
GET /greet. Returning a dict produces a JSON response automatically.
Query parameters bind to the signatureο
@route()
def greet(self, name: str = "world") -> dict[str, str]:
return {"hello": name}
Parameters in the method signature bind to the requestβs query string, typed and
with defaults. GET /greet?name=genro calls greet(name="genro"); GET /greet
with no query string uses the default "world". The annotation drives coercion β
declare max_price: float and the incoming string is converted to a float
before your method runs.
Building and servingο
server = AsgiServer(primary=Hello())
server.serve(host="127.0.0.1", port=8000)
AsgiServer(primary=...) builds the server. The primary application is
mandatory β it answers / and every path no mounted app claims. Omitting it is
an error.
server.serve(host=..., port=...) boots a programmatic uvicorn loop and
blocks until the process stops. The server object is the ASGI application
handed to uvicorn β there is no separate app callable to wire up.
There is no
.run()method and there is no command-line launcher. You start the server by calling.serve()from your own Python entry point. Passingport=0lets the OS assign a free port, which is handy in tests.
Responding with JSON or HTMLο
A handler that returns a dict answers JSON. To answer HTML, declare the
media type on the route and return a string:
class Pages(RoutedApplication):
@route()
def data(self) -> dict:
return {"ok": True} # application/json
@route(media_type="text/html")
def home(self) -> str:
return "<h1>Welcome</h1>" # text/html
The automatic 404ο
You did not write a handler for /nowhere, yet the server answered 404 cleanly
rather than crashing. That is the error middleware, which is on by default. It
maps an unmatched path to HTTPNotFound and turns raised HTTP exceptions into
proper responses. You will meet the rest of the middleware chain in the
middleware guide.
The always-present _server appο
Every server, even a hand-built one like the hello-world above, automatically
mounts an internal _server application at /_server/. It exposes system
endpoints β login, task management, and (on request) a Swagger view of those
endpoints. You do not configure it; it is always there. See the
authentication and tasks guides
for what lives under it.
Next stepsο
Core concepts β the server/application model, the demux rule, routing, and the design principles behind them.
How-to guides β task-focused recipes: authentication, sessions, OpenAPI & Swagger, MCP, background tasks, streaming & SSE, middleware.
Coming from Starlette / FastAPI β a concept mapping and side-by-side example if you already know those frameworks.