Router

Routers are based on Werkzeug’s URL Map, but dispatch to handler functions directly. All features from Werkzeug’s URL routing are inherited, including the rule string format and type converters.

@route

The @route decorator works similar to Flask or FastAPI, but they are not tied to an Application object. Instead, you can define routes on functions or methods, and then add them directly to the router.

from rolo import Router, route, Response
from werkzeug import Request
from werkzeug.serving import run_simple

@route("/users")
def list_users(_request: Request, args):
    assert not args
    return Response("user")

@route("/users/<int:user_id>")
def get_user_by_id(_request: Request, args):
    assert args
    return Response(f"{args['user_id']}")

router = Router()
router.add(list_users)
router.add(get_user_by_id)

# convert Router to a WSGI app and serve it through werkzeug
run_simple('localhost', 8080, router.wsgi(), use_reloader=True)

Depending on the dispatcher your Router uses, the signature of your endpoints will look differently.

Handler dispatcher

Routers use dispatchers to dispatch the request to functions. In the previous example, the default dispatcher calls the function with the Request object and the request arguments as dictionary. The “handler dispatcher” can transform functions into more Flask or FastAPI-like functions, that also allow you to return values that are automatically transformed.

from rolo import Router, route
from rolo.routing import handler_dispatcher

from werkzeug import Request
from werkzeug.serving import run_simple

@route("/users")
def list_users(request: Request):
    # query from db using the ?q= query string
    query = request.args["q"]
    # ...
    return [{"user_id": ...}, ...]

@route("/users/<int:user_id>")
def get_user_by_id(_request: Request, user_id: int):
    return {"user_id": user_id, "name": ...}

router = Router(dispatcher=handler_dispatcher())
router.add(list_users)
router.add(get_user_by_id)

# convert Router to a WSGI app and serve it through werkzeug
run_simple('localhost', 8080, router.wsgi(), use_reloader=True)

Using classes

Unlike Flask or FastAPI, Rolo allows you to use classes to organize your routes. The above example can also be written as follows

from rolo import Router, route, Request
from rolo.routing import handler_dispatcher

class UserResource:

    @route("/users/")
    def list_users(self, _request: Request):
        return "user"

    @route("/users/<int:user_id>")
    def get_user_by_id(self, _request: Request, user_id: int):
        return f"{user_id}"

router = Router(dispatcher=handler_dispatcher())
router.add(UserResource())

The router will scan the instantiated UserResource for @route decorators, and add them automatically.

Resource classes

If you prefer the RESTful style that Falcon <https://falcon.readthedocs.io/en/stable/>_ implements, you can use the @resource decorator on a class. This will automatically create routes for all on_<verb> methods. Here is an example

from rolo import Router, resource, Request

@resource("/users/<int:user_id>")
class UserResource:

    def on_get(self, request: Request, user_id: int):
        return {"user_id": user_id, "user": ...}

    def on_post(self, request: Request, user_id: int):
        data = request.json
        # ... do something

router = Router()
router.add(UserResource())

Pydantic integration

Here’s how the default example from the FastAPI documentation would look like with rolo:

import pydantic

from rolo import Request, Router, route


class Item(pydantic.BaseModel):
    name: str
    price: float
    is_offer: bool | None = None


@route("/", methods=["GET"])
def read_root(request: Request):
    return {"Hello": "World"}


@route("/items/<int:item_id>", methods=["GET"])
def read_item(request: Request, item_id: int):
    return {"item_id": item_id, "q": request.query_string}


@route("/items/<int:item_id>", methods=["PUT"])
def update_item(request: Request, item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}


router = Router()
router.add(read_root)
router.add(read_item)
router.add(update_item)