Skip to content

Routing

Route definition, path parameter extraction, and URL reversing.

Route

Represents a registered route with path parameter support.

Routes can be named for URL reversing:

# Auto-named from function name
@app.gemini("/user/{id}")
def user_profile(request, id):  # name="user_profile"
    pass

# Explicit name
@app.gemini("/u/{id}", name="user_detail")
def handler(request, id):
    pass

Route

Route(
    path: str,
    handler: Callable[..., Any],
    *,
    name: str | None = None,
    input_prompt: str | None = None,
    sensitive_input: bool = False,
)

Represents a registered route.

Routes match URL paths and extract parameters based on the path template.

Example

route = Route("/user/{username}", handler_func) if route.matches("/user/alice"): params = route.extract_params("/user/alice") # params = {"username": "alice"}

Create a new route.

Parameters:

Name Type Description Default
path str

Path template with optional parameters (e.g., "/user/{id}").

required
handler Callable[..., Any]

The handler function to call.

required
name str | None

Route name for URL reversing. Defaults to handler function name.

None
input_prompt str | None

If set, request input with this prompt before calling handler.

None
sensitive_input bool

If True, use status 11 (sensitive input) instead of 10.

False
Source code in src/xitzin/routing.py
def __init__(
    self,
    path: str,
    handler: Callable[..., Any],
    *,
    name: str | None = None,
    input_prompt: str | None = None,
    sensitive_input: bool = False,
) -> None:
    """Create a new route.

    Args:
        path: Path template with optional parameters (e.g., "/user/{id}").
        handler: The handler function to call.
        name: Route name for URL reversing. Defaults to handler function name.
        input_prompt: If set, request input with this prompt before calling handler.
        sensitive_input: If True, use status 11 (sensitive input) instead of 10.
    """
    self.path = path
    self.handler = handler
    self.name = (
        name if name is not None else getattr(handler, "__name__", "<anonymous>")
    )
    self.input_prompt = input_prompt
    self.sensitive_input = sensitive_input

    self._param_pattern, self._param_names = self._compile_path(path)
    self._type_hints = self._get_handler_type_hints(handler)
    self._is_async = asyncio.iscoroutinefunction(handler)

call_handler async

call_handler(
    request: Request, params: dict[str, Any]
) -> Any

Call the handler with the request and extracted parameters.

Parameters:

Name Type Description Default
request Request

The current request.

required
params dict[str, Any]

Extracted path parameters.

required

Returns:

Type Description
Any

The handler's return value.

Source code in src/xitzin/routing.py
async def call_handler(self, request: Request, params: dict[str, Any]) -> Any:
    """Call the handler with the request and extracted parameters.

    Args:
        request: The current request.
        params: Extracted path parameters.

    Returns:
        The handler's return value.
    """
    if self._is_async:
        return await self.handler(request, **params)
    # Wrap sync handler in executor to avoid blocking
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, lambda: self.handler(request, **params))

extract_params

extract_params(path: str) -> dict[str, Any]

Extract and type-convert path parameters.

Parameters:

Name Type Description Default
path str

URL path to extract parameters from.

required

Returns:

Type Description
dict[str, Any]

Dictionary of parameter names to values.

Source code in src/xitzin/routing.py
def extract_params(self, path: str) -> dict[str, Any]:
    """Extract and type-convert path parameters.

    Args:
        path: URL path to extract parameters from.

    Returns:
        Dictionary of parameter names to values.
    """
    match = self._param_pattern.match(path)
    if not match:
        return {}

    params: dict[str, Any] = {}
    for name, value in match.groupdict().items():
        # Apply type conversion based on handler annotations
        target_type = self._type_hints.get(name, str)
        try:
            if target_type is int:
                params[name] = int(value)
            elif target_type is float:
                params[name] = float(value)
            elif target_type is bool:
                params[name] = value.lower() in ("true", "1", "yes")
            else:
                params[name] = value
        except (ValueError, TypeError):
            # Keep as string if conversion fails
            params[name] = value

    return params

matches

matches(path: str) -> bool

Check if this route matches the given path.

Parameters:

Name Type Description Default
path str

URL path to match.

required

Returns:

Type Description
bool

True if the path matches this route's pattern.

Source code in src/xitzin/routing.py
def matches(self, path: str) -> bool:
    """Check if this route matches the given path.

    Args:
        path: URL path to match.

    Returns:
        True if the path matches this route's pattern.
    """
    return self._param_pattern.match(path) is not None

reverse

reverse(**params: Any) -> str

Build URL from this route's path template.

Parameters:

Name Type Description Default
**params Any

Path parameters to substitute.

{}

Returns:

Type Description
str

URL path string.

Raises:

Type Description
ValueError

If required parameters are missing.

Example

route = Route("/user/{username}", handler) route.reverse(username="alice") # Returns "/user/alice"

Source code in src/xitzin/routing.py
def reverse(self, **params: Any) -> str:
    """Build URL from this route's path template.

    Args:
        **params: Path parameters to substitute.

    Returns:
        URL path string.

    Raises:
        ValueError: If required parameters are missing.

    Example:
        route = Route("/user/{username}", handler)
        route.reverse(username="alice")  # Returns "/user/alice"
    """
    missing = set(self._param_names) - set(params.keys())
    if missing:
        missing_params = ", ".join(sorted(missing))
        raise ValueError(
            f"Route '{self.name}' missing required parameters: {missing_params}"
        )

    url = self.path
    for name in self._param_names:
        value = str(params[name])
        # Handle both {name} and {name:path} patterns
        url = url.replace(f"{{{name}}}", value)
        url = url.replace(f"{{{name}:path}}", value)

    return url

Router

Collection of routes with matching logic and URL reversing.

Router

Router()

Collection of routes with matching logic.

Routes are matched in registration order; first match wins. Mounted routes are checked before regular routes.

Source code in src/xitzin/routing.py
def __init__(self) -> None:
    self._routes: list[Route] = []
    self._routes_by_name: dict[str, Route] = {}
    self._mounted_routes: list[MountedRoute] = []
    self._titan_routes: list[TitanRoute] = []

add_mounted_route

add_mounted_route(route: MountedRoute) -> None

Add a mounted route to the router.

Mounted routes are checked before regular routes.

Parameters:

Name Type Description Default
route MountedRoute

The mounted route to add.

required
Source code in src/xitzin/routing.py
def add_mounted_route(self, route: MountedRoute) -> None:
    """Add a mounted route to the router.

    Mounted routes are checked before regular routes.

    Args:
        route: The mounted route to add.
    """
    self._mounted_routes.append(route)

add_route

add_route(route: Route) -> None

Add a route to the router.

Raises:

Type Description
ValueError

If a route with the same name already exists.

Source code in src/xitzin/routing.py
def add_route(self, route: Route) -> None:
    """Add a route to the router.

    Raises:
        ValueError: If a route with the same name already exists.
    """
    if route.name in self._routes_by_name:
        existing = self._routes_by_name[route.name]
        msg = (
            f"Route name '{route.name}' already registered "
            f"for path '{existing.path}'. "
            f"Use the name= parameter to provide a unique name."
        )
        raise ValueError(msg)
    self._routes.append(route)
    self._routes_by_name[route.name] = route

add_titan_route

add_titan_route(route: TitanRoute) -> None

Add a Titan upload route to the router.

Parameters:

Name Type Description Default
route TitanRoute

The Titan route to add.

required
Source code in src/xitzin/routing.py
def add_titan_route(self, route: TitanRoute) -> None:
    """Add a Titan upload route to the router.

    Args:
        route: The Titan route to add.
    """
    self._titan_routes.append(route)

has_titan_routes

has_titan_routes() -> bool

Check if any Titan routes are registered.

Source code in src/xitzin/routing.py
def has_titan_routes(self) -> bool:
    """Check if any Titan routes are registered."""
    return len(self._titan_routes) > 0

match

match(path: str) -> tuple[Route, dict[str, Any]] | None

Find a matching route and extract parameters.

Parameters:

Name Type Description Default
path str

URL path to match.

required

Returns:

Type Description
tuple[Route, dict[str, Any]] | None

Tuple of (route, params) if found, None otherwise.

Source code in src/xitzin/routing.py
def match(self, path: str) -> tuple[Route, dict[str, Any]] | None:
    """Find a matching route and extract parameters.

    Args:
        path: URL path to match.

    Returns:
        Tuple of (route, params) if found, None otherwise.
    """
    for route in self._routes:
        if route.matches(path):
            params = route.extract_params(path)
            return route, params
    return None

match_mount

match_mount(path: str) -> tuple[MountedRoute, str] | None

Find a matching mounted route and extract path info.

Parameters:

Name Type Description Default
path str

URL path to match.

required

Returns:

Type Description
tuple[MountedRoute, str] | None

Tuple of (mounted_route, path_info) if found, None otherwise.

Source code in src/xitzin/routing.py
def match_mount(self, path: str) -> tuple[MountedRoute, str] | None:
    """Find a matching mounted route and extract path info.

    Args:
        path: URL path to match.

    Returns:
        Tuple of (mounted_route, path_info) if found, None otherwise.
    """
    for mounted in self._mounted_routes:
        if mounted.matches(path):
            path_info = mounted.extract_path_info(path)
            return mounted, path_info
    return None

match_titan

match_titan(
    path: str,
) -> tuple[TitanRoute, dict[str, Any]] | None

Find a matching Titan route and extract parameters.

Parameters:

Name Type Description Default
path str

URL path to match.

required

Returns:

Type Description
tuple[TitanRoute, dict[str, Any]] | None

Tuple of (titan_route, params) if found, None otherwise.

Source code in src/xitzin/routing.py
def match_titan(self, path: str) -> tuple[TitanRoute, dict[str, Any]] | None:
    """Find a matching Titan route and extract parameters.

    Args:
        path: URL path to match.

    Returns:
        Tuple of (titan_route, params) if found, None otherwise.
    """
    for route in self._titan_routes:
        if route.matches(path):
            params = route.extract_params(path)
            return route, params
    return None

reverse

reverse(name: str, **params: Any) -> str

Build URL for a named route.

Parameters:

Name Type Description Default
name str

Route name.

required
**params Any

Path parameters.

{}

Returns:

Type Description
str

URL path string.

Raises:

Type Description
ValueError

If route name not found or parameters missing.

Example

router.reverse("user_profile", username="alice")

Returns "/user/alice"

Source code in src/xitzin/routing.py
def reverse(self, name: str, **params: Any) -> str:
    """Build URL for a named route.

    Args:
        name: Route name.
        **params: Path parameters.

    Returns:
        URL path string.

    Raises:
        ValueError: If route name not found or parameters missing.

    Example:
        router.reverse("user_profile", username="alice")
        # Returns "/user/alice"
    """
    if name not in self._routes_by_name:
        available = ", ".join(sorted(self._routes_by_name.keys()))
        raise ValueError(f"No route named '{name}'. Available routes: {available}")
    route = self._routes_by_name[name]
    return route.reverse(**params)