Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/get_routes.py: 87%
45 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2Utility class for getting routes from a FastAPI app.
3"""
5from collections.abc import Sequence
6from typing import Final, Protocol
8from starlette.routing import BaseRoute
9from typing_extensions import NotRequired, ReadOnly, TypedDict
11from litellm._logging import verbose_logger
14class NamedEndpoint(Protocol):
15 __name__: str
18class RouteInfo(TypedDict):
19 path: ReadOnly[str | None]
20 methods: ReadOnly[Sequence[str] | None]
21 name: ReadOnly[str | None]
22 endpoint: ReadOnly[str | None]
23 mounted_app: NotRequired[ReadOnly[bool]]
26class GetRoutes:
27 @staticmethod
28 def get_app_routes(
29 route: BaseRoute,
30 endpoint_route: NamedEndpoint,
31 ) -> list[RouteInfo]:
32 """
33 Get routes for a regular route.
34 """
35 route_info: Final[RouteInfo] = {
36 "path": getattr(route, "path", None),
37 "methods": getattr(route, "methods", None),
38 "name": getattr(route, "name", None),
39 "endpoint": (endpoint_route.__name__ if getattr(route, "endpoint", None) else None),
40 }
41 return [route_info]
43 @staticmethod
44 def get_routes_for_mounted_app(
45 route: BaseRoute,
46 ) -> list[RouteInfo]:
47 """
48 Get routes for a mounted sub-application.
49 """
50 mount_path: Final[str] = getattr(route, "path", "")
51 sub_app: Final[object] = getattr(route, "app", None)
52 if not sub_app or not hasattr(sub_app, "routes"):
53 return []
54 sub_routes: Final[Sequence[object]] = getattr(sub_app, "routes", ())
55 return [
56 sub_route_info
57 for sub_route in sub_routes
58 if (sub_route_info := GetRoutes._mounted_sub_route_info(mount_path, sub_route)) is not None
59 ]
61 @staticmethod
62 def _mounted_sub_route_info(mount_path: str, sub_route: object) -> RouteInfo | None:
63 endpoint_func: Final[object] = getattr(sub_route, "endpoint", None) or getattr(sub_route, "app", None)
64 if endpoint_func is None: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true
65 return None
66 sub_route_path: Final[str] = getattr(sub_route, "path", "")
67 return {
68 "path": mount_path.rstrip("/") + sub_route_path,
69 "methods": getattr(sub_route, "methods", ["GET", "POST"]),
70 "name": getattr(sub_route, "name", None),
71 "endpoint": GetRoutes._safe_get_endpoint_name(endpoint_func),
72 "mounted_app": True,
73 }
75 @staticmethod
76 def _safe_get_endpoint_name(endpoint_function: object) -> str | None:
77 """
78 Safely get the name of the endpoint function.
79 """
80 try:
81 if hasattr(endpoint_function, "__name__"):
82 endpoint_name: Final[str] = getattr(endpoint_function, "__name__", "")
83 return endpoint_name
84 elif hasattr(endpoint_function, "__class__") and hasattr(endpoint_function.__class__, "__name__"): 84 ↛ 87line 84 didn't jump to line 87 because the condition on line 84 was always true
85 return endpoint_function.__class__.__name__
86 else:
87 return None
88 except Exception:
89 verbose_logger.exception("Error getting endpoint name for route: %s", endpoint_function)
90 return None