You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

decorators.py 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. "Functions that help with dynamically creating decorators for views."
  2. # For backwards compatibility in Django 2.0.
  3. from contextlib import ContextDecorator # noqa
  4. from functools import WRAPPER_ASSIGNMENTS, partial, update_wrapper, wraps
  5. class classonlymethod(classmethod):
  6. def __get__(self, instance, cls=None):
  7. if instance is not None:
  8. raise AttributeError("This method is available only on the class, not on instances.")
  9. return super().__get__(instance, cls)
  10. def _update_method_wrapper(_wrapper, decorator):
  11. # _multi_decorate()'s bound_method isn't available in this scope. Cheat by
  12. # using it on a dummy function.
  13. @decorator
  14. def dummy(*args, **kwargs):
  15. pass
  16. update_wrapper(_wrapper, dummy)
  17. def _multi_decorate(decorators, method):
  18. """
  19. Decorate `method` with one or more function decorators. `decorators` can be
  20. a single decorator or an iterable of decorators.
  21. """
  22. if hasattr(decorators, '__iter__'):
  23. # Apply a list/tuple of decorators if 'decorators' is one. Decorator
  24. # functions are applied so that the call order is the same as the
  25. # order in which they appear in the iterable.
  26. decorators = decorators[::-1]
  27. else:
  28. decorators = [decorators]
  29. def _wrapper(self, *args, **kwargs):
  30. # bound_method has the signature that 'decorator' expects i.e. no
  31. # 'self' argument, but it's a closure over self so it can call
  32. # 'func'. Also, wrap method.__get__() in a function because new
  33. # attributes can't be set on bound method objects, only on functions.
  34. bound_method = partial(method.__get__(self, type(self)))
  35. for dec in decorators:
  36. bound_method = dec(bound_method)
  37. return bound_method(*args, **kwargs)
  38. # Copy any attributes that a decorator adds to the function it decorates.
  39. for dec in decorators:
  40. _update_method_wrapper(_wrapper, dec)
  41. # Preserve any existing attributes of 'method', including the name.
  42. update_wrapper(_wrapper, method)
  43. return _wrapper
  44. def method_decorator(decorator, name=''):
  45. """
  46. Convert a function decorator into a method decorator
  47. """
  48. # 'obj' can be a class or a function. If 'obj' is a function at the time it
  49. # is passed to _dec, it will eventually be a method of the class it is
  50. # defined on. If 'obj' is a class, the 'name' is required to be the name
  51. # of the method that will be decorated.
  52. def _dec(obj):
  53. if not isinstance(obj, type):
  54. return _multi_decorate(decorator, obj)
  55. if not (name and hasattr(obj, name)):
  56. raise ValueError(
  57. "The keyword argument `name` must be the name of a method "
  58. "of the decorated class: %s. Got '%s' instead." % (obj, name)
  59. )
  60. method = getattr(obj, name)
  61. if not callable(method):
  62. raise TypeError(
  63. "Cannot decorate '%s' as it isn't a callable attribute of "
  64. "%s (%s)." % (name, obj, method)
  65. )
  66. _wrapper = _multi_decorate(decorator, method)
  67. setattr(obj, name, _wrapper)
  68. return obj
  69. # Don't worry about making _dec look similar to a list/tuple as it's rather
  70. # meaningless.
  71. if not hasattr(decorator, '__iter__'):
  72. update_wrapper(_dec, decorator)
  73. # Change the name to aid debugging.
  74. obj = decorator if hasattr(decorator, '__name__') else decorator.__class__
  75. _dec.__name__ = 'method_decorator(%s)' % obj.__name__
  76. return _dec
  77. def decorator_from_middleware_with_args(middleware_class):
  78. """
  79. Like decorator_from_middleware, but return a function
  80. that accepts the arguments to be passed to the middleware_class.
  81. Use like::
  82. cache_page = decorator_from_middleware_with_args(CacheMiddleware)
  83. # ...
  84. @cache_page(3600)
  85. def my_view(request):
  86. # ...
  87. """
  88. return make_middleware_decorator(middleware_class)
  89. def decorator_from_middleware(middleware_class):
  90. """
  91. Given a middleware class (not an instance), return a view decorator. This
  92. lets you use middleware functionality on a per-view basis. The middleware
  93. is created with no params passed.
  94. """
  95. return make_middleware_decorator(middleware_class)()
  96. # Unused, for backwards compatibility in Django 2.0.
  97. def available_attrs(fn):
  98. """
  99. Return the list of functools-wrappable attributes on a callable.
  100. This was required as a workaround for https://bugs.python.org/issue3445
  101. under Python 2.
  102. """
  103. return WRAPPER_ASSIGNMENTS
  104. def make_middleware_decorator(middleware_class):
  105. def _make_decorator(*m_args, **m_kwargs):
  106. middleware = middleware_class(*m_args, **m_kwargs)
  107. def _decorator(view_func):
  108. @wraps(view_func)
  109. def _wrapped_view(request, *args, **kwargs):
  110. if hasattr(middleware, 'process_request'):
  111. result = middleware.process_request(request)
  112. if result is not None:
  113. return result
  114. if hasattr(middleware, 'process_view'):
  115. result = middleware.process_view(request, view_func, args, kwargs)
  116. if result is not None:
  117. return result
  118. try:
  119. response = view_func(request, *args, **kwargs)
  120. except Exception as e:
  121. if hasattr(middleware, 'process_exception'):
  122. result = middleware.process_exception(request, e)
  123. if result is not None:
  124. return result
  125. raise
  126. if hasattr(response, 'render') and callable(response.render):
  127. if hasattr(middleware, 'process_template_response'):
  128. response = middleware.process_template_response(request, response)
  129. # Defer running of process_response until after the template
  130. # has been rendered:
  131. if hasattr(middleware, 'process_response'):
  132. def callback(response):
  133. return middleware.process_response(request, response)
  134. response.add_post_render_callback(callback)
  135. else:
  136. if hasattr(middleware, 'process_response'):
  137. return middleware.process_response(request, response)
  138. return response
  139. return _wrapped_view
  140. return _decorator
  141. return _make_decorator
  142. class classproperty:
  143. def __init__(self, method=None):
  144. self.fget = method
  145. def __get__(self, instance, cls=None):
  146. return self.fget(cls)
  147. def getter(self, method):
  148. self.fget = method
  149. return self