Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/decorators.py: 43%

98 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2The most important decorator in this module is `@api_view`, which is used 

3for writing function-based views with REST framework. 

4 

5There are also various decorators for setting the API policies on function 

6based views, as well as the `@action` decorator, which is used to annotate 

7methods on viewsets that should be included by routers. 

8""" 

9import types 

10 

11from django.forms.utils import pretty_name 

12 

13from rest_framework.views import APIView 

14 

15 

16def api_view(http_method_names=None): 

17 """ 

18 Decorator that converts a function-based view into an APIView subclass. 

19 Takes a list of allowed methods for the view as an argument. 

20 """ 

21 http_method_names = ['GET'] if (http_method_names is None) else http_method_names 

22 

23 def decorator(func): 

24 

25 WrappedAPIView = type( 

26 'WrappedAPIView', 

27 (APIView,), 

28 {'__doc__': func.__doc__} 

29 ) 

30 

31 # Note, the above allows us to set the docstring. 

32 # It is the equivalent of: 

33 # 

34 # class WrappedAPIView(APIView): 

35 # pass 

36 # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this 

37 

38 # api_view applied without (method_names) 

39 assert not(isinstance(http_method_names, types.FunctionType)), \ 

40 '@api_view missing list of allowed HTTP methods' 

41 

42 # api_view applied with eg. string instead of list of strings 

43 assert isinstance(http_method_names, (list, tuple)), \ 

44 '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 

45 

46 allowed_methods = set(http_method_names) | {'options'} 

47 WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 

48 

49 def handler(self, *args, **kwargs): 

50 return func(*args, **kwargs) 

51 

52 for method in http_method_names: 

53 setattr(WrappedAPIView, method.lower(), handler) 

54 

55 WrappedAPIView.__name__ = func.__name__ 

56 WrappedAPIView.__module__ = func.__module__ 

57 

58 WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 

59 APIView.renderer_classes) 

60 

61 WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 

62 APIView.parser_classes) 

63 

64 WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 

65 APIView.authentication_classes) 

66 

67 WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 

68 APIView.throttle_classes) 

69 

70 WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 

71 APIView.permission_classes) 

72 

73 WrappedAPIView.schema = getattr(func, 'schema', 

74 APIView.schema) 

75 

76 return WrappedAPIView.as_view() 

77 

78 return decorator 

79 

80 

81def renderer_classes(renderer_classes): 

82 def decorator(func): 

83 func.renderer_classes = renderer_classes 

84 return func 

85 return decorator 

86 

87 

88def parser_classes(parser_classes): 

89 def decorator(func): 

90 func.parser_classes = parser_classes 

91 return func 

92 return decorator 

93 

94 

95def authentication_classes(authentication_classes): 

96 def decorator(func): 

97 func.authentication_classes = authentication_classes 

98 return func 

99 return decorator 

100 

101 

102def throttle_classes(throttle_classes): 

103 def decorator(func): 

104 func.throttle_classes = throttle_classes 

105 return func 

106 return decorator 

107 

108 

109def permission_classes(permission_classes): 

110 def decorator(func): 

111 func.permission_classes = permission_classes 

112 return func 

113 return decorator 

114 

115 

116def schema(view_inspector): 

117 def decorator(func): 

118 func.schema = view_inspector 

119 return func 

120 return decorator 

121 

122 

123def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs): 

124 """ 

125 Mark a ViewSet method as a routable action. 

126 

127 `@action`-decorated functions will be endowed with a `mapping` property, 

128 a `MethodMapper` that can be used to add additional method-based behaviors 

129 on the routed action. 

130 

131 :param methods: A list of HTTP method names this action responds to. 

132 Defaults to GET only. 

133 :param detail: Required. Determines whether this action applies to 

134 instance/detail requests or collection/list requests. 

135 :param url_path: Define the URL segment for this action. Defaults to the 

136 name of the method decorated. 

137 :param url_name: Define the internal (`reverse`) URL name for this action. 

138 Defaults to the name of the method decorated with underscores 

139 replaced with dashes. 

140 :param kwargs: Additional properties to set on the view. This can be used 

141 to override viewset-level *_classes settings, equivalent to 

142 how the `@renderer_classes` etc. decorators work for function- 

143 based API views. 

144 """ 

145 methods = ['get'] if (methods is None) else methods 

146 methods = [method.lower() for method in methods] 

147 

148 assert detail is not None, ( 

149 "@action() missing required argument: 'detail'" 

150 ) 

151 

152 # name and suffix are mutually exclusive 

153 if 'name' in kwargs and 'suffix' in kwargs: 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true

154 raise TypeError("`name` and `suffix` are mutually exclusive arguments.") 

155 

156 def decorator(func): 

157 func.mapping = MethodMapper(func, methods) 

158 

159 func.detail = detail 

160 func.url_path = url_path if url_path else func.__name__ 

161 func.url_name = url_name if url_name else func.__name__.replace('_', '-') 

162 

163 # These kwargs will end up being passed to `ViewSet.as_view()` within 

164 # the router, which eventually delegates to Django's CBV `View`, 

165 # which assigns them as instance attributes for each request. 

166 func.kwargs = kwargs 

167 

168 # Set descriptive arguments for viewsets 

169 if 'name' not in kwargs and 'suffix' not in kwargs: 169 ↛ 171line 169 didn't jump to line 171, because the condition on line 169 was never false

170 func.kwargs['name'] = pretty_name(func.__name__) 

171 func.kwargs['description'] = func.__doc__ or None 

172 

173 return func 

174 return decorator 

175 

176 

177class MethodMapper(dict): 

178 """ 

179 Enables mapping HTTP methods to different ViewSet methods for a single, 

180 logical action. 

181 

182 Example usage: 

183 

184 class MyViewSet(ViewSet): 

185 

186 @action(detail=False) 

187 def example(self, request, **kwargs): 

188 ... 

189 

190 @example.mapping.post 

191 def create_example(self, request, **kwargs): 

192 ... 

193 """ 

194 

195 def __init__(self, action, methods): 

196 self.action = action 

197 for method in methods: 

198 self[method] = self.action.__name__ 

199 

200 def _map(self, method, func): 

201 assert method not in self, ( 

202 "Method '%s' has already been mapped to '.%s'." % (method, self[method])) 

203 assert func.__name__ != self.action.__name__, ( 

204 "Method mapping does not behave like the property decorator. You " 

205 "cannot use the same method name for each mapping declaration.") 

206 

207 self[method] = func.__name__ 

208 

209 return func 

210 

211 def get(self, func): 

212 return self._map('get', func) 

213 

214 def post(self, func): 

215 return self._map('post', func) 

216 

217 def put(self, func): 

218 return self._map('put', func) 

219 

220 def patch(self, func): 

221 return self._map('patch', func) 

222 

223 def delete(self, func): 

224 return self._map('delete', func) 

225 

226 def head(self, func): 

227 return self._map('head', func) 

228 

229 def options(self, func): 

230 return self._map('options', func) 

231 

232 def trace(self, func): 

233 return self._map('trace', func)