Coverage for src/lexigram/web/routing/parameters.py: 48%

42 statements  

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

1"""Parameter decorators for request binding""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Callable 

6from typing import Any 

7 

8from lexigram.web.routing.types import RoutableProtocol 

9 

10 

11def _create_param_decorator( 

12 param_type: str, 

13 name: str | None = None, 

14 default: Any = ..., 

15 alias: str | None = None, 

16 validation: Callable[..., Any] | None = None, 

17 pipes: list[Any] | None = None, 

18) -> Any: 

19 """Create a parameter decorator that properly attaches metadata to parameters""" 

20 

21 metadata = { 

22 "type": param_type, 

23 "name": name, 

24 "default": default, 

25 "alias": alias, 

26 "validation": validation, 

27 "pipes": pipes, 

28 } 

29 

30 def decorator( 

31 param_annotation: Any = None, 

32 ) -> RoutableProtocol | Callable[[RoutableProtocol], RoutableProtocol]: 

33 # If used as @path, @query, etc. without parentheses 

34 if callable(param_annotation): 

35 # This is the function being decorated 

36 func: RoutableProtocol = param_annotation 

37 # For now, we'll store metadata on the function and match by position 

38 # A more sophisticated implementation would use AST parsing 

39 func_metadata = getattr(func, "_param_metadata", None) 

40 if func_metadata is None: 

41 func_metadata = [] 

42 func._param_metadata = func_metadata 

43 func_metadata.append(metadata) 

44 return func 

45 

46 # If used as @path(), @query(), etc. with parentheses 

47 def param_decorator(func: RoutableProtocol) -> RoutableProtocol: 

48 func_metadata = getattr(func, "_param_metadata", None) 

49 if func_metadata is None: 

50 func_metadata = [] 

51 func._param_metadata = func_metadata 

52 # Merge annotation into metadata for this usage 

53 usage_metadata = metadata.copy() 

54 usage_metadata["annotation"] = param_annotation 

55 func_metadata.append(usage_metadata) 

56 return func 

57 

58 # Attach metadata to param_decorator as well 

59 param_decorator._lexigram_param_info = metadata # type: ignore[attr-defined] 

60 return param_decorator 

61 

62 # Attach metadata to the decorator function itself 

63 decorator._lexigram_param_info = metadata # type: ignore[attr-defined] 

64 return decorator 

65 

66 

67def path( 

68 alias: str | None = None, 

69 validation: Callable[..., Any] | None = None, 

70 pipes: list[Any] | None = None, 

71) -> Any: 

72 """Path parameter decorator.""" 

73 return _create_param_decorator( 

74 "path", 

75 alias=alias, 

76 validation=validation, 

77 pipes=pipes, 

78 ) 

79 

80 

81# Query Parameters 

82def query( 

83 default: Any = ..., 

84 alias: str | None = None, 

85 validation: Callable[..., Any] | None = None, 

86 pipes: list[Any] | None = None, 

87) -> Any: 

88 """Query parameter decorator.""" 

89 return _create_param_decorator( 

90 "query", 

91 default=default, 

92 alias=alias, 

93 validation=validation, 

94 pipes=pipes, 

95 ) 

96 

97 

98# Body Parameters 

99def body( 

100 default: Any = ..., 

101 validation: Callable[..., Any] | None = None, 

102 pipes: list[Any] | None = None, 

103) -> Any: 

104 """Request body parameter decorator.""" 

105 return _create_param_decorator( 

106 "body", default=default, validation=validation, pipes=pipes 

107 ) 

108 

109 

110# Header Parameters 

111def header( 

112 default: Any = ..., 

113 alias: str | None = None, 

114 validation: Callable[..., Any] | None = None, 

115 pipes: list[Any] | None = None, 

116) -> Any: 

117 """Header parameter decorator.""" 

118 return _create_param_decorator( 

119 "header", 

120 default=default, 

121 alias=alias, 

122 validation=validation, 

123 pipes=pipes, 

124 ) 

125 

126 

127# Cookie Parameters 

128def cookie( 

129 default: Any = ..., 

130 alias: str | None = None, 

131 validation: Callable[..., Any] | None = None, 

132 pipes: list[Any] | None = None, 

133) -> Any: 

134 """Cookie parameter decorator.""" 

135 return _create_param_decorator( 

136 "cookie", 

137 default=default, 

138 alias=alias, 

139 validation=validation, 

140 pipes=pipes, 

141 ) 

142 

143 

144# Form Parameters 

145def form( 

146 default: Any = ..., 

147 alias: str | None = None, 

148 validation: Callable[..., Any] | None = None, 

149 pipes: list[Any] | None = None, 

150) -> Any: 

151 """Form parameter decorator.""" 

152 return _create_param_decorator( 

153 "form", 

154 default=default, 

155 alias=alias, 

156 validation=validation, 

157 pipes=pipes, 

158 ) 

159 

160 

161# File Parameters 

162def file( 

163 default: Any = ..., 

164 validation: Callable[..., Any] | None = None, 

165 pipes: list[Any] | None = None, 

166) -> Any: 

167 """File upload parameter decorator.""" 

168 return _create_param_decorator( 

169 "file", default=default, validation=validation, pipes=pipes 

170 )