Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/utils/decorators.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

117 statements  

1"Functions that help with dynamically creating decorators for views." 

2 

3from functools import partial, update_wrapper, wraps 

4 

5from asgiref.sync import iscoroutinefunction, markcoroutinefunction 

6 

7 

8class classonlymethod(classmethod): 

9 def __get__(self, instance, cls=None): 

10 if instance is not None: 

11 raise AttributeError( 

12 "This method is available only on the class, not on instances." 

13 ) 

14 return super().__get__(instance, cls) 

15 

16 

17def _update_method_wrapper(_wrapper, decorator): 

18 # _multi_decorate()'s bound_method isn't available in this scope. Cheat by 

19 # using it on a dummy function. 

20 @decorator 

21 def dummy(*args, **kwargs): 

22 pass 

23 

24 update_wrapper(_wrapper, dummy) 

25 

26 

27def _multi_decorate(decorators, method): 

28 """ 

29 Decorate `method` with one or more function decorators. `decorators` can be 

30 a single decorator or an iterable of decorators. 

31 """ 

32 if hasattr(decorators, "__iter__"): 

33 # Apply a list/tuple of decorators if 'decorators' is one. Decorator 

34 # functions are applied so that the call order is the same as the 

35 # order in which they appear in the iterable. 

36 decorators = decorators[::-1] 

37 else: 

38 decorators = [decorators] 

39 

40 def _wrapper(self, *args, **kwargs): 

41 # bound_method has the signature that 'decorator' expects i.e. no 

42 # 'self' argument, but it's a closure over self so it can call 

43 # 'func'. Also, wrap method.__get__() in a function because new 

44 # attributes can't be set on bound method objects, only on functions. 

45 bound_method = wraps(method)(partial(method.__get__(self, type(self)))) 

46 for dec in decorators: 

47 bound_method = dec(bound_method) 

48 return bound_method(*args, **kwargs) 

49 

50 # Copy any attributes that a decorator adds to the function it decorates. 

51 for dec in decorators: 

52 _update_method_wrapper(_wrapper, dec) 

53 # Preserve any existing attributes of 'method', including the name. 

54 update_wrapper(_wrapper, method) 

55 

56 if iscoroutinefunction(method): 

57 markcoroutinefunction(_wrapper) 

58 

59 return _wrapper 

60 

61 

62def method_decorator(decorator, name=""): 

63 """ 

64 Convert a function decorator into a method decorator 

65 """ 

66 

67 # 'obj' can be a class or a function. If 'obj' is a function at the time it 

68 # is passed to _dec, it will eventually be a method of the class it is 

69 # defined on. If 'obj' is a class, the 'name' is required to be the name 

70 # of the method that will be decorated. 

71 def _dec(obj): 

72 if not isinstance(obj, type): 

73 return _multi_decorate(decorator, obj) 

74 if not (name and hasattr(obj, name)): 

75 raise ValueError( 

76 "The keyword argument `name` must be the name of a method " 

77 "of the decorated class: %s. Got '%s' instead." % (obj, name) 

78 ) 

79 method = getattr(obj, name) 

80 if not callable(method): 

81 raise TypeError( 

82 "Cannot decorate '%s' as it isn't a callable attribute of " 

83 "%s (%s)." % (name, obj, method) 

84 ) 

85 _wrapper = _multi_decorate(decorator, method) 

86 setattr(obj, name, _wrapper) 

87 return obj 

88 

89 # Don't worry about making _dec look similar to a list/tuple as it's rather 

90 # meaningless. 

91 if not hasattr(decorator, "__iter__"): 

92 update_wrapper(_dec, decorator) 

93 # Change the name to aid debugging. 

94 obj = decorator if hasattr(decorator, "__name__") else decorator.__class__ 

95 _dec.__name__ = "method_decorator(%s)" % obj.__name__ 

96 return _dec 

97 

98 

99def decorator_from_middleware_with_args(middleware_class): 

100 """ 

101 Like decorator_from_middleware, but return a function 

102 that accepts the arguments to be passed to the middleware_class. 

103 Use like:: 

104 

105 cache_page = decorator_from_middleware_with_args(CacheMiddleware) 

106 # ... 

107 

108 @cache_page(3600) 

109 def my_view(request): 

110 # ... 

111 """ 

112 return make_middleware_decorator(middleware_class) 

113 

114 

115def decorator_from_middleware(middleware_class): 

116 """ 

117 Given a middleware class (not an instance), return a view decorator. This 

118 lets you use middleware functionality on a per-view basis. The middleware 

119 is created with no params passed. 

120 """ 

121 return make_middleware_decorator(middleware_class)() 

122 

123 

124def make_middleware_decorator(middleware_class): 

125 def _make_decorator(*m_args, **m_kwargs): 

126 def _decorator(view_func): 

127 middleware = middleware_class(view_func, *m_args, **m_kwargs) 

128 

129 def _pre_process_request(request, *args, **kwargs): 

130 if hasattr(middleware, "process_request"): 

131 result = middleware.process_request(request) 

132 if result is not None: 

133 return result 

134 if hasattr(middleware, "process_view"): 

135 result = middleware.process_view(request, view_func, args, kwargs) 

136 if result is not None: 

137 return result 

138 return None 

139 

140 def _process_exception(request, exception): 

141 if hasattr(middleware, "process_exception"): 

142 result = middleware.process_exception(request, exception) 

143 if result is not None: 

144 return result 

145 raise 

146 

147 def _post_process_request(request, response): 

148 if hasattr(response, "render") and callable(response.render): 

149 if hasattr(middleware, "process_template_response"): 

150 response = middleware.process_template_response( 

151 request, response 

152 ) 

153 # Defer running of process_response until after the template 

154 # has been rendered: 

155 if hasattr(middleware, "process_response"): 

156 

157 def callback(response): 

158 return middleware.process_response(request, response) 

159 

160 response.add_post_render_callback(callback) 

161 else: 

162 if hasattr(middleware, "process_response"): 

163 return middleware.process_response(request, response) 

164 return response 

165 

166 if iscoroutinefunction(view_func): 

167 

168 async def _view_wrapper(request, *args, **kwargs): 

169 result = _pre_process_request(request, *args, **kwargs) 

170 if result is not None: 

171 return result 

172 

173 try: 

174 response = await view_func(request, *args, **kwargs) 

175 except Exception as e: 

176 result = _process_exception(request, e) 

177 if result is not None: 

178 return result 

179 

180 return _post_process_request(request, response) 

181 

182 else: 

183 

184 def _view_wrapper(request, *args, **kwargs): 

185 result = _pre_process_request(request, *args, **kwargs) 

186 if result is not None: 

187 return result 

188 

189 try: 

190 response = view_func(request, *args, **kwargs) 

191 except Exception as e: 

192 result = _process_exception(request, e) 

193 if result is not None: 

194 return result 

195 

196 return _post_process_request(request, response) 

197 

198 return wraps(view_func)(_view_wrapper) 

199 

200 return _decorator 

201 

202 return _make_decorator 

203 

204 

205def sync_and_async_middleware(func): 

206 """ 

207 Mark a middleware factory as returning a hybrid middleware supporting both 

208 types of request. 

209 """ 

210 func.sync_capable = True 

211 func.async_capable = True 

212 return func 

213 

214 

215def sync_only_middleware(func): 

216 """ 

217 Mark a middleware factory as returning a sync middleware. 

218 This is the default. 

219 """ 

220 func.sync_capable = True 

221 func.async_capable = False 

222 return func 

223 

224 

225def async_only_middleware(func): 

226 """Mark a middleware factory as returning an async middleware.""" 

227 func.sync_capable = False 

228 func.async_capable = True 

229 return func