Added streaming flag to bypass handler timeout for long-lived routes.
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 19s
CI / Tests (Python 3.13) (push) Successful in 19s
CI / Tests (Python 3.14) (push) Successful in 15s
CI / Type Checking (push) Successful in 8s
CI / Spelling (push) Successful in 4s
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 19s
CI / Tests (Python 3.13) (push) Successful in 19s
CI / Tests (Python 3.14) (push) Successful in 15s
CI / Type Checking (push) Successful in 8s
CI / Spelling (push) Successful in 4s
This commit is contained in:
+1
-1
Submodule docs updated: 2da7f1fc0f...458065420a
@@ -38,6 +38,7 @@ class RouteMark(TypedDict):
|
|||||||
|
|
||||||
path: str
|
path: str
|
||||||
methods: list[str] | None
|
methods: list[str] | None
|
||||||
|
streaming: bool
|
||||||
|
|
||||||
|
|
||||||
# Handler type: receives RouteContext, returns a response or dict (auto-JSON).
|
# Handler type: receives RouteContext, returns a response or dict (auto-JSON).
|
||||||
@@ -55,12 +56,14 @@ class RouteInfo:
|
|||||||
methods: frozenset[str] # HTTP methods (GET, POST, etc.).
|
methods: frozenset[str] # HTTP methods (GET, POST, etc.).
|
||||||
handler: RouteHandler
|
handler: RouteHandler
|
||||||
module_name: str
|
module_name: str
|
||||||
|
streaming: bool = False
|
||||||
|
|
||||||
|
|
||||||
def on_route(
|
def on_route(
|
||||||
path: str,
|
path: str,
|
||||||
*,
|
*,
|
||||||
methods: list[str] | None = None,
|
methods: list[str] | None = None,
|
||||||
|
streaming: bool = False,
|
||||||
) -> Callable[[RouteHandler], RouteHandler]:
|
) -> Callable[[RouteHandler], RouteHandler]:
|
||||||
"""Register an HTTP route handler.
|
"""Register an HTTP route handler.
|
||||||
|
|
||||||
@@ -68,6 +71,8 @@ def on_route(
|
|||||||
|
|
||||||
:param path: URL path (relative to module namespace, e.g., "/stats").
|
:param path: URL path (relative to module namespace, e.g., "/stats").
|
||||||
:param methods: List of HTTP methods to accept. Default: ["GET"].
|
:param methods: List of HTTP methods to accept. Default: ["GET"].
|
||||||
|
:param streaming: If True, the handler bypasses the dispatch timeout.
|
||||||
|
The handler is responsible for its own keepalives and cleanup.
|
||||||
:return: Decorator that marks the function for registration.
|
:return: Decorator that marks the function for registration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -75,7 +80,7 @@ def on_route(
|
|||||||
# Mark the function with route info for deferred registration.
|
# Mark the function with route info for deferred registration.
|
||||||
# methods=None is resolved to ["GET"] by RouteRegistry.register().
|
# methods=None is resolved to ["GET"] by RouteRegistry.register().
|
||||||
func._owlbot_route = RouteMark( # type: ignore[attr-defined]
|
func._owlbot_route = RouteMark( # type: ignore[attr-defined]
|
||||||
path=path, methods=methods
|
path=path, methods=methods, streaming=streaming
|
||||||
)
|
)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class RouteRegistry:
|
|||||||
*,
|
*,
|
||||||
methods: list[str] | None = None,
|
methods: list[str] | None = None,
|
||||||
module_name: str,
|
module_name: str,
|
||||||
|
streaming: bool = False,
|
||||||
) -> RouteInfo:
|
) -> RouteInfo:
|
||||||
"""Register a route handler.
|
"""Register a route handler.
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ class RouteRegistry:
|
|||||||
:param handler: Async function to handle the route.
|
:param handler: Async function to handle the route.
|
||||||
:param methods: List of HTTP methods. Default: ["GET"].
|
:param methods: List of HTTP methods. Default: ["GET"].
|
||||||
:param module_name: Name of the module registering this route.
|
:param module_name: Name of the module registering this route.
|
||||||
|
:param streaming: Whether this route streams its response and should
|
||||||
|
bypass the handler timeout. Default: False.
|
||||||
:return: RouteInfo for the registered route.
|
:return: RouteInfo for the registered route.
|
||||||
:raises ValueError: If any method overlaps with an existing handler
|
:raises ValueError: If any method overlaps with an existing handler
|
||||||
on the same path.
|
on the same path.
|
||||||
@@ -129,6 +132,7 @@ class RouteRegistry:
|
|||||||
methods=new_methods,
|
methods=new_methods,
|
||||||
handler=handler,
|
handler=handler,
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
|
streaming=streaming,
|
||||||
)
|
)
|
||||||
|
|
||||||
if group is None:
|
if group is None:
|
||||||
@@ -351,6 +355,7 @@ class RouteRegistry:
|
|||||||
handler=obj,
|
handler=obj,
|
||||||
methods=mark["methods"],
|
methods=mark["methods"],
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
|
streaming=mark["streaming"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -385,6 +390,7 @@ class RouteDispatcher:
|
|||||||
*,
|
*,
|
||||||
methods: list[str] | None = None,
|
methods: list[str] | None = None,
|
||||||
module_name: str,
|
module_name: str,
|
||||||
|
streaming: bool = False,
|
||||||
) -> RouteInfo:
|
) -> RouteInfo:
|
||||||
"""Register a route handler.
|
"""Register a route handler.
|
||||||
|
|
||||||
@@ -394,6 +400,7 @@ class RouteDispatcher:
|
|||||||
:param handler: Async function to handle the route.
|
:param handler: Async function to handle the route.
|
||||||
:param methods: List of HTTP methods. Default: ["GET"].
|
:param methods: List of HTTP methods. Default: ["GET"].
|
||||||
:param module_name: Name of the module registering this route.
|
:param module_name: Name of the module registering this route.
|
||||||
|
:param streaming: If True, handler runs without timeout. Default: False.
|
||||||
:return: RouteInfo for the registered route.
|
:return: RouteInfo for the registered route.
|
||||||
:raises ValueError: If route conflicts with existing route.
|
:raises ValueError: If route conflicts with existing route.
|
||||||
"""
|
"""
|
||||||
@@ -402,6 +409,7 @@ class RouteDispatcher:
|
|||||||
handler=handler,
|
handler=handler,
|
||||||
methods=methods,
|
methods=methods,
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
|
streaming=streaming,
|
||||||
)
|
)
|
||||||
|
|
||||||
def unregister(self, full_path: str, *, method: str | None = None) -> bool:
|
def unregister(self, full_path: str, *, method: str | None = None) -> bool:
|
||||||
@@ -537,9 +545,16 @@ class RouteDispatcher:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
result = await asyncio.wait_for(
|
if route_info.streaming:
|
||||||
route_info.handler(ctx), timeout=self._handler_timeout
|
mod_logger.debug(
|
||||||
)
|
"Streaming handler '%s' dispatched (no timeout).",
|
||||||
|
route_info.full_path,
|
||||||
|
)
|
||||||
|
result = await route_info.handler(ctx)
|
||||||
|
else:
|
||||||
|
result = await asyncio.wait_for(
|
||||||
|
route_info.handler(ctx), timeout=self._handler_timeout
|
||||||
|
)
|
||||||
elapsed = (time.perf_counter() - start) * 1000
|
elapsed = (time.perf_counter() - start) * 1000
|
||||||
|
|
||||||
mod_logger.debug(
|
mod_logger.debug(
|
||||||
@@ -614,6 +629,7 @@ class ModuleRoutes:
|
|||||||
handler: RouteHandler,
|
handler: RouteHandler,
|
||||||
*,
|
*,
|
||||||
methods: list[str] | None = None,
|
methods: list[str] | None = None,
|
||||||
|
streaming: bool = False,
|
||||||
) -> RouteInfo:
|
) -> RouteInfo:
|
||||||
"""Register a route handler for this module.
|
"""Register a route handler for this module.
|
||||||
|
|
||||||
@@ -622,6 +638,7 @@ class ModuleRoutes:
|
|||||||
:param path: URL path relative to module namespace (e.g., "/stats").
|
:param path: URL path relative to module namespace (e.g., "/stats").
|
||||||
:param handler: Async function to handle the route.
|
:param handler: Async function to handle the route.
|
||||||
:param methods: List of HTTP methods (e.g., ["GET"]). Default: ["GET"].
|
:param methods: List of HTTP methods (e.g., ["GET"]). Default: ["GET"].
|
||||||
|
:param streaming: If True, handler runs without timeout. Default: False.
|
||||||
:return: RouteInfo for the registered route.
|
:return: RouteInfo for the registered route.
|
||||||
:raises ValueError: If route conflicts with existing route.
|
:raises ValueError: If route conflicts with existing route.
|
||||||
"""
|
"""
|
||||||
@@ -630,6 +647,7 @@ class ModuleRoutes:
|
|||||||
handler=handler,
|
handler=handler,
|
||||||
methods=methods,
|
methods=methods,
|
||||||
module_name=self._module_name,
|
module_name=self._module_name,
|
||||||
|
streaming=streaming,
|
||||||
)
|
)
|
||||||
|
|
||||||
def unregister(self, path: str, *, method: str | None = None) -> bool:
|
def unregister(self, path: str, *, method: str | None = None) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user