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
« 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.
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
11from django.forms.utils import pretty_name
13from rest_framework.views import APIView
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
23 def decorator(func):
25 WrappedAPIView = type(
26 'WrappedAPIView',
27 (APIView,),
28 {'__doc__': func.__doc__}
29 )
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
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'
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__
46 allowed_methods = set(http_method_names) | {'options'}
47 WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
49 def handler(self, *args, **kwargs):
50 return func(*args, **kwargs)
52 for method in http_method_names:
53 setattr(WrappedAPIView, method.lower(), handler)
55 WrappedAPIView.__name__ = func.__name__
56 WrappedAPIView.__module__ = func.__module__
58 WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
59 APIView.renderer_classes)
61 WrappedAPIView.parser_classes = getattr(func, 'parser_classes',
62 APIView.parser_classes)
64 WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes',
65 APIView.authentication_classes)
67 WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
68 APIView.throttle_classes)
70 WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
71 APIView.permission_classes)
73 WrappedAPIView.schema = getattr(func, 'schema',
74 APIView.schema)
76 return WrappedAPIView.as_view()
78 return decorator
81def renderer_classes(renderer_classes):
82 def decorator(func):
83 func.renderer_classes = renderer_classes
84 return func
85 return decorator
88def parser_classes(parser_classes):
89 def decorator(func):
90 func.parser_classes = parser_classes
91 return func
92 return decorator
95def authentication_classes(authentication_classes):
96 def decorator(func):
97 func.authentication_classes = authentication_classes
98 return func
99 return decorator
102def throttle_classes(throttle_classes):
103 def decorator(func):
104 func.throttle_classes = throttle_classes
105 return func
106 return decorator
109def permission_classes(permission_classes):
110 def decorator(func):
111 func.permission_classes = permission_classes
112 return func
113 return decorator
116def schema(view_inspector):
117 def decorator(func):
118 func.schema = view_inspector
119 return func
120 return decorator
123def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
124 """
125 Mark a ViewSet method as a routable action.
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.
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]
148 assert detail is not None, (
149 "@action() missing required argument: 'detail'"
150 )
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.")
156 def decorator(func):
157 func.mapping = MethodMapper(func, methods)
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('_', '-')
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
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
173 return func
174 return decorator
177class MethodMapper(dict):
178 """
179 Enables mapping HTTP methods to different ViewSet methods for a single,
180 logical action.
182 Example usage:
184 class MyViewSet(ViewSet):
186 @action(detail=False)
187 def example(self, request, **kwargs):
188 ...
190 @example.mapping.post
191 def create_example(self, request, **kwargs):
192 ...
193 """
195 def __init__(self, action, methods):
196 self.action = action
197 for method in methods:
198 self[method] = self.action.__name__
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.")
207 self[method] = func.__name__
209 return func
211 def get(self, func):
212 return self._map('get', func)
214 def post(self, func):
215 return self._map('post', func)
217 def put(self, func):
218 return self._map('put', func)
220 def patch(self, func):
221 return self._map('patch', func)
223 def delete(self, func):
224 return self._map('delete', func)
226 def head(self, func):
227 return self._map('head', func)
229 def options(self, func):
230 return self._map('options', func)
232 def trace(self, func):
233 return self._map('trace', func)