Routing

Status: SOURCE OF TRUTH Last Updated: 2025-12-14 genro-routes version: 0.10.0


Overview

genro-asgi delegates all routing to genro-routes, a separate library that provides tree-based routing with introspection capabilities.

Full documentation: genro-routes


Core Components

Component

Description

Router

Tree-based router, organizes handlers hierarchically

RoutingClass

Base class for classes with routed methods

@route(name)

Decorator to register methods as routes

RouterInterface

Protocol for custom routers (e.g., StaticRouter)


Key Methods

Method

Description

router.get(selector)

Resolve path to handler

router.nodes(**kwargs)

List all nodes (entries + sub-routers)

router.attach(router, name)

Attach a sub-router

router.attach_instance(obj, name)

Attach a routed instance

router.openapi()

Generate OpenAPI schema

router.plug(plugin)

Register a plugin


Path Resolution

Paths use / as separator (URL-style, same as HTTP paths):

# URL path is used directly as selector
"/"  "index"
"/docs"  "docs"
"/docs/api/users"  "docs/api/users"
"/_sys/status"  "_sys/status"

No conversion needed between URL paths and selectors.


Basic Usage

Routed Methods

from genro_routes import RoutingClass, Router, route

class MyApp(RoutingClass):
    def __init__(self):
        self.router = Router(self, name="app")

    @route("app")
    def index(self):
        return {"status": "ok"}

    @route("app")
    def users(self, id: str = None):
        if id:
            return {"user": id}
        return {"users": ["alice", "bob"]}

Attaching Sub-Routers

from genro_asgi.routers import StaticRouter

# Attach a StaticRouter for serving files
static = StaticRouter("./public", name="static")
self.router.attach(static, name="static")

# Now /static/style.css serves ./public/style.css

Attaching Instances

class DocsApp(RoutingClass):
    def __init__(self):
        self.router = Router(self, name="docs")

    @route("docs")
    def index(self):
        return {"docs": "welcome"}

# In server
docs = DocsApp()
server.router.attach_instance(docs, name="docs")

# Now /docs/index calls docs.index()

Introspection with nodes()

The nodes() method returns the router tree structure:

# Get all nodes
nodes = router.nodes()

# With mode parameter for different output formats
nodes = router.nodes(mode="flat")    # flat list of all entries
nodes = router.nodes(mode="tree")    # nested tree structure
nodes = router.nodes(mode="openapi") # OpenAPI-compatible format

# Lazy loading for large trees
nodes = router.nodes(lazy=True)

FilterPlugin

Tag-based filtering with boolean expressions:

from genro_routes import FilterPlugin

# Register plugin
router.plug(FilterPlugin())

# Tag routes using decorators or metadata
@route("app", tags=["api", "public"])
def public_endpoint(self):
    pass

@route("app", tags=["api", "internal"])
def internal_endpoint(self):
    pass

# Filter nodes
public_nodes = router.nodes(filter="api & public")
non_internal = router.nodes(filter="api & !internal")

Future: User-Based Filtering

FilterPlugin can be used to enable/disable routes based on current user’s permissions:

# TODO: Future implementation
# Filter routes based on user tags/roles
user_tags = request.user.tags  # e.g., ["admin", "editor"]
visible_nodes = router.nodes(filter=build_filter_from_tags(user_tags))

This allows dynamic route visibility based on user permissions without modifying route definitions.


OpenAPI Generation

# Generate OpenAPI schema
schema = router.openapi()

# Returns dict compatible with OpenAPI 3.0 spec
# Can be serialized to JSON/YAML

StaticRouter

genro-asgi provides StaticRouter that implements RouterInterface for serving static files:

from genro_asgi.routers import StaticRouter

# Create router for a directory
static = StaticRouter("./public", name="assets")

# Resolves paths to files
handler = static.get("css/style.css")  # returns file handler

# List contents
nodes = static.nodes()  # returns files and subdirectories

See StaticRouter documentation for details.


Integration with AsgiServer

AsgiServer inherits from RoutingClass and uses genro-routes for dispatch:

from genro_asgi import AsgiServer
from genro_routes import route

class MyServer(AsgiServer):
    @route("root")
    def index(self):
        return {"message": "Hello"}

    @route("root")
    def api(self, path: str = None):
        return {"api": path}

The Dispatcher class handles the routing:

  1. Receives HTTP request with path (e.g., /api/users)

  2. Calls router.get("api/users") to find handler

  3. Executes handler and converts result to Response


Copyright: Softwell S.r.l. (2025) License: Apache License 2.0