Coverage for src/lexigram/web/routing/groups.py: 27%
30 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""Route groups for organizing controllers with shared configuration.
3Provides RouteGroup for grouping controllers with shared prefix, guards, and interceptors.
4"""
6from __future__ import annotations
8from typing import TYPE_CHECKING, Any
10if TYPE_CHECKING:
11 from collections.abc import Callable
13 from lexigram.web.routing.controllers import Controller
16class RouteGroup:
17 """Groups controllers with shared configuration.
19 Usage:
20 admin_group = RouteGroup(
21 prefix="/api/v1/admin",
22 controllers=[UserController, RoleController],
23 guards=[AuthGuard, RoleGuard("admin")],
24 interceptors=[AuditLogInterceptor],
25 )
27 # Register with WebProvider
28 provider = WebProvider(
29 route_groups=[admin_group],
30 )
31 """
33 def __init__(
34 self,
35 prefix: str,
36 controllers: list[type[Controller]],
37 guards: list[Any] | None = None,
38 interceptors: list[Any] | None = None,
39 middleware: list[Any] | None = None,
40 ):
41 self.prefix = prefix
42 self.controllers = controllers
43 self.guards = guards or []
44 self.interceptors = interceptors or []
45 self.middleware = middleware or []
47 def apply_guards(self, controller_cls: type[Controller]) -> type[Controller]:
48 """Apply group's guards to controller by creating a subclass."""
49 existing_guards = getattr(controller_cls, "_guards", [])
50 return type(
51 f"{controller_cls.__name__}_with_guards",
52 (controller_cls,),
53 {"_guards": existing_guards + self.guards},
54 )
56 def apply_interceptors(self, controller_cls: type[Controller]) -> type[Controller]:
57 """Apply group's interceptors to controller by creating a subclass."""
58 existing_interceptors = getattr(controller_cls, "_interceptors", [])
59 return type(
60 f"{controller_cls.__name__}_with_interceptors",
61 (controller_cls,),
62 {"_interceptors": existing_interceptors + self.interceptors},
63 )
65 def finalize(self) -> list[type[Controller]]:
66 """Apply group configuration to all controllers by creating subclasses."""
67 results = []
68 for controller in self.controllers:
69 controller = self.apply_guards(controller)
70 controller = self.apply_interceptors(controller)
72 # Get existing prefix and combine
73 existing_prefix = getattr(controller, "prefix", "")
74 if existing_prefix:
75 # Combine prefixes
76 combined_prefix = self.prefix.rstrip("/") + existing_prefix
77 else:
78 combined_prefix = self.prefix
80 # Create final subclass with prefix
81 results.append(
82 type(
83 f"{controller.__name__}_finalized",
84 (controller,),
85 {"prefix": combined_prefix},
86 ),
87 )
89 return results
92def group(
93 prefix: str,
94 guards: list[Any] | None = None,
95 interceptors: list[Any] | None = None,
96 middleware: list[Any] | None = None,
97) -> Callable[[list[type[Controller]]], RouteGroup]:
98 """Create a route group.
100 Usage:
101 @group("/api/v1/admin", guards=[AuthGuard])
102 class AdminController(Controller):
103 ...
104 """
106 def decorator(controllers: list[type[Controller]]) -> RouteGroup:
107 return RouteGroup(
108 prefix=prefix,
109 controllers=controllers,
110 guards=guards,
111 interceptors=interceptors,
112 middleware=middleware,
113 )
115 return decorator