Routers

Routing implementations for genro-asgi.

StaticRouter

Filesystem-backed router for serving static files.

StaticRouter - Storage-backed router with best-match path resolution.

Maps URL paths to storage nodes (files or directories). Uses best-match strategy: walks the path as far as possible, returns the deepest valid node with unconsumed segments as extra_args.

Implements RouterInterface for use in routing hierarchies.

Best-Match Resolution:

Given path “alfa/beta/gamma/123?xx=3” and filesystem with only alfa/beta/:

  1. Walk: alfa (exists) → beta (exists) → gamma (not found) → STOP

  2. Return: StaticRouterNode pointing to alfa/beta/

  3. extra_args: [“gamma”, “123”]

  4. partial_kwargs: {“xx”: “3”}

The caller decides what to do with extra_args (e.g., pass to handler, use for sub-routing, return 404, etc.)

Usage:

# Create router from storage node router = StaticRouter(storage.node(“site:static”))

# Resolve exact file router_node = router.node(“css/style.css”) storage_node = router_node() # StorageNode for style.css content = storage_node.read_bytes()

# Resolve with best-match (partial path) router_node = router.node(“docs/api/v2/users?format=json”) # If only docs/api/ exists: storage_node = router_node() # StorageNode for docs/api/ extra = router_node.extra_args # [“v2”, “users”] params = router_node.partial_kwargs # {“format”: “json”}

# Check node type if router_node.metadata[“isfile”]:

# Serve file content pass

elif router_node.metadata[“isdir”]:

# Directory - caller decides (list, index.html, pass to handler) pass

StaticRouterNode Attributes:
  • type: “entry” (file) or “router” (directory)

  • name: basename of the storage node

  • path: resolved path (consumed segments)

  • callable: the node itself (for compatibility)

  • extra_args: list of unconsumed path segments

  • partial_kwargs: dict from parsed query string

  • metadata: {“mime_type”: str, “isdir”: bool, “isfile”: bool}

class genro_asgi.routers.static_router.StaticRouter(root, name=None, *, html_index=True)[source]

Bases: RouterInterface

Storage-backed router with best-match resolution.

Wraps a StorageNode (filesystem, S3, HTTP, etc.) and resolves paths using best-match strategy. Returns RouterNode whose callable yields the underlying StorageNode.

name

Router name for introspection (optional)

__init__(root, name=None, *, html_index=True)[source]

Initialize router with a storage root.

Parameters:
  • root (StorageNode) – StorageNode pointing to the directory to serve.

  • name (str | None) – Optional name for introspection and debugging.

  • html_index (bool) – Reserved for future use (index.html resolution).

name: str | None
node(path, **kwargs)[source]

Resolve path using best-match strategy.

Walks path segment by segment until a non-existent node is found. Returns the deepest valid node with unconsumed segments in extra_args.

Parameters:

path (str) – Relative path, optionally with query string. Examples: “css/style.css”, “api/v2/users?limit=10”

Returns:

  • callable() → StorageNode (the resolved file or directory)

  • extra_args: list of path segments after the resolved node

  • partial_kwargs: dict parsed from query string

  • type: “entry” (file) or “router” (directory)

  • metadata: {“mime_type”, “isdir”, “isfile”}

None if root doesn’t exist.

Return type:

StaticRouterNode | None

Examples

# Exact file match node = router.node(“style.css”) node.extra_args # [] node() # StorageNode for style.css

# Partial match with extra segments node = router.node(“docs/api/v2/users”) # If only docs/api/ exists: node.extra_args # [“v2”, “users”] node() # StorageNode for docs/api/

# With query string node = router.node(“data?format=json&limit=10”) node.partial_kwargs # {“format”: “json”, “limit”: “10”}

nodes(basepath=None, lazy=False, mode=None, pattern=None, **kwargs)[source]

Return a tree of files/directories respecting filters.

Parameters:
  • basepath (str | None) – Path to start from (e.g., “images/icons”). If provided, returns nodes starting from that point.

  • lazy (bool) – If True, child directories are returned as callables instead of recursively expanded.

  • mode (str | None) – Output format mode (reserved for compatibility).

  • pattern (str | None) – Regex pattern to filter entry names. Only files whose name matches are included.

Returns:

name, description, router, entries, routers. Empty dict if root doesn’t exist or is empty.

Return type:

dict[str, Any]

values()[source]

Return iterator of child routers. For static router, yields nothing.

Return type:

Iterator[RouterInterface]

class genro_asgi.routers.static_router.StaticRouterNode(storage_node, node_type, name, path, extra_args, partial_kwargs, metadata)[source]

Bases: object

Minimal RouterNode for StaticRouter. Returns StorageNode on __call__.

type

“entry” (file) or “router” (directory)

name

basename of the storage node

path

resolved path (consumed segments)

callable

self (for compatibility with tests expecting .callable)

extra_args

list of unconsumed path segments

partial_kwargs

dict from parsed query string

metadata

{“mime_type”: str, “isdir”: bool, “isfile”: bool}

property callable: StaticRouterNode

Return self for compatibility with code expecting .callable attribute.

extra_args
metadata
name
partial_kwargs
path
type