Coverage for src/lexigram/web/routing/decorators.py: 81%

27 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 04:37 +0800

1"""HTTP route decorators for defining endpoints. 

2 

3Provides decorator functions for each HTTP method to register route handlers 

4with the Lexigram router. These decorators store route configuration on the 

5decorated function for later discovery by the routing system. 

6 

7Example: 

8 >>> from lexigram.web.routing import get, post 

9 >>> 

10 >>> @get("/users") 

11 >>> async def list_users(request): 

12 ... return {"users": []} 

13 >>> 

14 >>> @post("/users") 

15 >>> async def create_user(request): 

16 ... data = await request.json() 

17 ... return {"id": 1, "data": data} 

18""" 

19 

20from __future__ import annotations 

21 

22from collections.abc import Callable 

23from typing import Any, TypeVar, cast 

24 

25F = TypeVar("F", bound=Callable[..., Any]) 

26 

27 

28def route(method: str, path: str, **kwargs: Any) -> Callable[[F], F]: 

29 """Create a route decorator for the specified HTTP method. 

30 

31 Args: 

32 method: HTTP method (GET, POST, PUT, DELETE, etc.). 

33 path: URL path pattern for the route. 

34 **kwargs: Additional route configuration options. 

35 

36 Returns: 

37 A decorator function that configures the route handler. 

38 """ 

39 

40 def decorator(func: F) -> F: 

41 # Use setattr to avoid ruff auto-converting to direct attribute access 

42 # which fails because RoutableProtocol is a Callable and ruff/mypy don't like 

43 # dynamic attributes on it without setattr. 

44 cast("Any", func)._route_config = {"method": method, "path": path, **kwargs} 

45 return func 

46 

47 return decorator 

48 

49 

50def get(path: str, **kwargs: Any) -> Callable[[F], F]: 

51 """Register a GET route handler. 

52 

53 Args: 

54 path: URL path pattern for the route. 

55 **kwargs: Additional route configuration. 

56 

57 Returns: 

58 Configured route handler function. 

59 

60 Example: 

61 >>> @get("/users") 

62 >>> async def list_users(request): 

63 ... return {"users": []} 

64 """ 

65 return route("GET", path, **kwargs) 

66 

67 

68def post(path: str, **kwargs: Any) -> Callable[[F], F]: 

69 """Register a POST route handler. 

70 

71 Parameters annotated with a Pydantic ``BaseModel`` or ``DomainModel`` 

72 subclass are automatically deserialised from the JSON request body by 

73 :class:`~lexigram.web.routing.parameter_binder.ParameterBinder` — no 

74 ``body()`` decorator is required. 

75 

76 Args: 

77 path: URL path pattern for the route. 

78 **kwargs: Additional route configuration. 

79 

80 Returns: 

81 Configured route handler function. 

82 

83 Example: 

84 >>> @post("/users") 

85 >>> async def create_user(request): 

86 ... data = await request.json() 

87 ... return {"id": 1, "data": data} 

88 """ 

89 return route("POST", path, **kwargs) 

90 

91 

92def put(path: str, **kwargs: Any) -> Callable[[F], F]: 

93 """Register a PUT route handler. 

94 

95 Args: 

96 path: URL path pattern for the route. 

97 **kwargs: Additional route configuration. 

98 

99 Returns: 

100 Configured route handler function. 

101 """ 

102 return route("PUT", path, **kwargs) 

103 

104 

105def delete(path: str, **kwargs: Any) -> Callable[[F], F]: 

106 """Register a DELETE route handler. 

107 

108 Args: 

109 path: URL path pattern for the route. 

110 **kwargs: Additional route configuration. 

111 

112 Returns: 

113 Configured route handler function. 

114 """ 

115 return route("DELETE", path, **kwargs) 

116 

117 

118def patch(path: str, **kwargs: Any) -> Callable[[F], F]: 

119 """Register a PATCH route handler. 

120 

121 Args: 

122 path: URL path pattern for the route. 

123 **kwargs: Additional route configuration. 

124 

125 Returns: 

126 Configured route handler function. 

127 """ 

128 return route("PATCH", path, **kwargs) 

129 

130 

131def head(path: str, **kwargs: Any) -> Callable[[F], F]: 

132 """Register a HEAD route handler. 

133 

134 Args: 

135 path: URL path pattern for the route. 

136 **kwargs: Additional route configuration. 

137 

138 Returns: 

139 Configured route handler function. 

140 """ 

141 return route("HEAD", path, **kwargs) 

142 

143 

144def options(path: str, **kwargs: Any) -> Callable[[F], F]: 

145 """Register an OPTIONS route handler. 

146 

147 Args: 

148 path: URL path pattern for the route. 

149 **kwargs: Additional route configuration. 

150 

151 Returns: 

152 Configured route handler function. 

153 """ 

154 return route("OPTIONS", path, **kwargs) 

155 

156 

157def trace(path: str, **kwargs: Any) -> Callable[[F], F]: 

158 """Register a TRACE route handler. 

159 

160 Args: 

161 path: URL path pattern for the route. 

162 **kwargs: Additional route configuration. 

163 

164 Returns: 

165 Configured route handler function. 

166 """ 

167 return route("TRACE", path, **kwargs) 

168 

169 

170def websocket(path: str, **kwargs: Any) -> Callable[[F], F]: 

171 """Register a WebSocket route handler. 

172 

173 Args: 

174 path: URL path pattern for the WebSocket endpoint. 

175 **kwargs: Additional route configuration. 

176 

177 Returns: 

178 Configured WebSocket handler function. 

179 """ 

180 return route("WEBSOCKET", path, **kwargs) 

181 

182 

183# NOTE: Parameter decorators were moved to `parameters.py` which provides a 

184# full implementation (metadata storage and support for annotations). 

185# Keep imports local to the routing package to preserve backward compatibility: