Development of an internal social media platform with personalised dashboards for students
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 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. """This module implements decorators for implementing other decorators
  2. as well as some commonly used decorators.
  3. """
  4. import sys
  5. PY2 = sys.version_info[0] == 2
  6. PY3 = sys.version_info[0] == 3
  7. if PY3:
  8. string_types = str,
  9. import builtins
  10. exec_ = getattr(builtins, "exec")
  11. del builtins
  12. else:
  13. string_types = basestring,
  14. def exec_(_code_, _globs_=None, _locs_=None):
  15. """Execute code in a namespace."""
  16. if _globs_ is None:
  17. frame = sys._getframe(1)
  18. _globs_ = frame.f_globals
  19. if _locs_ is None:
  20. _locs_ = frame.f_locals
  21. del frame
  22. elif _locs_ is None:
  23. _locs_ = _globs_
  24. exec("""exec _code_ in _globs_, _locs_""")
  25. from functools import partial
  26. from inspect import ismethod, isclass, formatargspec
  27. from collections import namedtuple
  28. from threading import Lock, RLock
  29. try:
  30. from inspect import signature
  31. except ImportError:
  32. pass
  33. from .wrappers import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy,
  34. CallableObjectProxy)
  35. # Adapter wrapper for the wrapped function which will overlay certain
  36. # properties from the adapter function onto the wrapped function so that
  37. # functions such as inspect.getargspec(), inspect.getfullargspec(),
  38. # inspect.signature() and inspect.getsource() return the correct results
  39. # one would expect.
  40. class _AdapterFunctionCode(CallableObjectProxy):
  41. def __init__(self, wrapped_code, adapter_code):
  42. super(_AdapterFunctionCode, self).__init__(wrapped_code)
  43. self._self_adapter_code = adapter_code
  44. @property
  45. def co_argcount(self):
  46. return self._self_adapter_code.co_argcount
  47. @property
  48. def co_code(self):
  49. return self._self_adapter_code.co_code
  50. @property
  51. def co_flags(self):
  52. return self._self_adapter_code.co_flags
  53. @property
  54. def co_kwonlyargcount(self):
  55. return self._self_adapter_code.co_kwonlyargcount
  56. @property
  57. def co_varnames(self):
  58. return self._self_adapter_code.co_varnames
  59. class _AdapterFunctionSurrogate(CallableObjectProxy):
  60. def __init__(self, wrapped, adapter):
  61. super(_AdapterFunctionSurrogate, self).__init__(wrapped)
  62. self._self_adapter = adapter
  63. @property
  64. def __code__(self):
  65. return _AdapterFunctionCode(self.__wrapped__.__code__,
  66. self._self_adapter.__code__)
  67. @property
  68. def __defaults__(self):
  69. return self._self_adapter.__defaults__
  70. @property
  71. def __kwdefaults__(self):
  72. return self._self_adapter.__kwdefaults__
  73. @property
  74. def __signature__(self):
  75. if 'signature' not in globals():
  76. return self._self_adapter.__signature__
  77. else:
  78. # Can't allow this to fail on Python 3 else it falls
  79. # through to using __wrapped__, but that will be the
  80. # wrong function we want to derive the signature
  81. # from. Thus generate the signature ourselves.
  82. return signature(self._self_adapter)
  83. if PY2:
  84. func_code = __code__
  85. func_defaults = __defaults__
  86. class _BoundAdapterWrapper(BoundFunctionWrapper):
  87. @property
  88. def __func__(self):
  89. return _AdapterFunctionSurrogate(self.__wrapped__.__func__,
  90. self._self_parent._self_adapter)
  91. if PY2:
  92. im_func = __func__
  93. class AdapterWrapper(FunctionWrapper):
  94. __bound_function_wrapper__ = _BoundAdapterWrapper
  95. def __init__(self, *args, **kwargs):
  96. adapter = kwargs.pop('adapter')
  97. super(AdapterWrapper, self).__init__(*args, **kwargs)
  98. self._self_surrogate = _AdapterFunctionSurrogate(
  99. self.__wrapped__, adapter)
  100. self._self_adapter = adapter
  101. @property
  102. def __code__(self):
  103. return self._self_surrogate.__code__
  104. @property
  105. def __defaults__(self):
  106. return self._self_surrogate.__defaults__
  107. @property
  108. def __kwdefaults__(self):
  109. return self._self_surrogate.__kwdefaults__
  110. if PY2:
  111. func_code = __code__
  112. func_defaults = __defaults__
  113. @property
  114. def __signature__(self):
  115. return self._self_surrogate.__signature__
  116. class AdapterFactory(object):
  117. def __call__(self, wrapped):
  118. raise NotImplementedError()
  119. class DelegatedAdapterFactory(AdapterFactory):
  120. def __init__(self, factory):
  121. super(DelegatedAdapterFactory, self).__init__()
  122. self.factory = factory
  123. def __call__(self, wrapped):
  124. return self.factory(wrapped)
  125. adapter_factory = DelegatedAdapterFactory
  126. # Decorator for creating other decorators. This decorator and the
  127. # wrappers which they use are designed to properly preserve any name
  128. # attributes, function signatures etc, in addition to the wrappers
  129. # themselves acting like a transparent proxy for the original wrapped
  130. # function so the wrapper is effectively indistinguishable from the
  131. # original wrapped function.
  132. def decorator(wrapper=None, enabled=None, adapter=None):
  133. # The decorator should be supplied with a single positional argument
  134. # which is the wrapper function to be used to implement the
  135. # decorator. This may be preceded by a step whereby the keyword
  136. # arguments are supplied to customise the behaviour of the
  137. # decorator. The 'adapter' argument is used to optionally denote a
  138. # separate function which is notionally used by an adapter
  139. # decorator. In that case parts of the function '__code__' and
  140. # '__defaults__' attributes are used from the adapter function
  141. # rather than those of the wrapped function. This allows for the
  142. # argument specification from inspect.getargspec() and similar
  143. # functions to be overridden with a prototype for a different
  144. # function than what was wrapped. The 'enabled' argument provides a
  145. # way to enable/disable the use of the decorator. If the type of
  146. # 'enabled' is a boolean, then it is evaluated immediately and the
  147. # wrapper not even applied if it is False. If not a boolean, it will
  148. # be evaluated when the wrapper is called for an unbound wrapper,
  149. # and when binding occurs for a bound wrapper. When being evaluated,
  150. # if 'enabled' is callable it will be called to obtain the value to
  151. # be checked. If False, the wrapper will not be called and instead
  152. # the original wrapped function will be called directly instead.
  153. if wrapper is not None:
  154. # Helper function for creating wrapper of the appropriate
  155. # time when we need it down below.
  156. def _build(wrapped, wrapper, enabled=None, adapter=None):
  157. if adapter:
  158. if isinstance(adapter, AdapterFactory):
  159. adapter = adapter(wrapped)
  160. if not callable(adapter):
  161. ns = {}
  162. if not isinstance(adapter, string_types):
  163. adapter = formatargspec(*adapter)
  164. exec_('def adapter{0}: pass'.format(adapter), ns, ns)
  165. adapter = ns['adapter']
  166. return AdapterWrapper(wrapped=wrapped, wrapper=wrapper,
  167. enabled=enabled, adapter=adapter)
  168. return FunctionWrapper(wrapped=wrapped, wrapper=wrapper,
  169. enabled=enabled)
  170. # The wrapper has been provided so return the final decorator.
  171. # The decorator is itself one of our function wrappers so we
  172. # can determine when it is applied to functions, instance methods
  173. # or class methods. This allows us to bind the instance or class
  174. # method so the appropriate self or cls attribute is supplied
  175. # when it is finally called.
  176. def _wrapper(wrapped, instance, args, kwargs):
  177. # We first check for the case where the decorator was applied
  178. # to a class type.
  179. #
  180. # @decorator
  181. # class mydecoratorclass(object):
  182. # def __init__(self, arg=None):
  183. # self.arg = arg
  184. # def __call__(self, wrapped, instance, args, kwargs):
  185. # return wrapped(*args, **kwargs)
  186. #
  187. # @mydecoratorclass(arg=1)
  188. # def function():
  189. # pass
  190. #
  191. # In this case an instance of the class is to be used as the
  192. # decorator wrapper function. If args was empty at this point,
  193. # then it means that there were optional keyword arguments
  194. # supplied to be used when creating an instance of the class
  195. # to be used as the wrapper function.
  196. if instance is None and isclass(wrapped) and not args:
  197. # We still need to be passed the target function to be
  198. # wrapped as yet, so we need to return a further function
  199. # to be able to capture it.
  200. def _capture(target_wrapped):
  201. # Now have the target function to be wrapped and need
  202. # to create an instance of the class which is to act
  203. # as the decorator wrapper function. Before we do that,
  204. # we need to first check that use of the decorator
  205. # hadn't been disabled by a simple boolean. If it was,
  206. # the target function to be wrapped is returned instead.
  207. _enabled = enabled
  208. if type(_enabled) is bool:
  209. if not _enabled:
  210. return target_wrapped
  211. _enabled = None
  212. # Now create an instance of the class which is to act
  213. # as the decorator wrapper function. Any arguments had
  214. # to be supplied as keyword only arguments so that is
  215. # all we pass when creating it.
  216. target_wrapper = wrapped(**kwargs)
  217. # Finally build the wrapper itself and return it.
  218. return _build(target_wrapped, target_wrapper,
  219. _enabled, adapter)
  220. return _capture
  221. # We should always have the target function to be wrapped at
  222. # this point as the first (and only) value in args.
  223. target_wrapped = args[0]
  224. # Need to now check that use of the decorator hadn't been
  225. # disabled by a simple boolean. If it was, then target
  226. # function to be wrapped is returned instead.
  227. _enabled = enabled
  228. if type(_enabled) is bool:
  229. if not _enabled:
  230. return target_wrapped
  231. _enabled = None
  232. # We now need to build the wrapper, but there are a couple of
  233. # different cases we need to consider.
  234. if instance is None:
  235. if isclass(wrapped):
  236. # In this case the decorator was applied to a class
  237. # type but optional keyword arguments were not supplied
  238. # for initialising an instance of the class to be used
  239. # as the decorator wrapper function.
  240. #
  241. # @decorator
  242. # class mydecoratorclass(object):
  243. # def __init__(self, arg=None):
  244. # self.arg = arg
  245. # def __call__(self, wrapped, instance,
  246. # args, kwargs):
  247. # return wrapped(*args, **kwargs)
  248. #
  249. # @mydecoratorclass
  250. # def function():
  251. # pass
  252. #
  253. # We still need to create an instance of the class to
  254. # be used as the decorator wrapper function, but no
  255. # arguments are pass.
  256. target_wrapper = wrapped()
  257. else:
  258. # In this case the decorator was applied to a normal
  259. # function, or possibly a static method of a class.
  260. #
  261. # @decorator
  262. # def mydecoratorfuntion(wrapped, instance,
  263. # args, kwargs):
  264. # return wrapped(*args, **kwargs)
  265. #
  266. # @mydecoratorfunction
  267. # def function():
  268. # pass
  269. #
  270. # That normal function becomes the decorator wrapper
  271. # function.
  272. target_wrapper = wrapper
  273. else:
  274. if isclass(instance):
  275. # In this case the decorator was applied to a class
  276. # method.
  277. #
  278. # class myclass(object):
  279. # @decorator
  280. # @classmethod
  281. # def decoratorclassmethod(cls, wrapped,
  282. # instance, args, kwargs):
  283. # return wrapped(*args, **kwargs)
  284. #
  285. # instance = myclass()
  286. #
  287. # @instance.decoratorclassmethod
  288. # def function():
  289. # pass
  290. #
  291. # This one is a bit strange because binding was actually
  292. # performed on the wrapper created by our decorator
  293. # factory. We need to apply that binding to the decorator
  294. # wrapper function which which the decorator factory
  295. # was applied to.
  296. target_wrapper = wrapper.__get__(None, instance)
  297. else:
  298. # In this case the decorator was applied to an instance
  299. # method.
  300. #
  301. # class myclass(object):
  302. # @decorator
  303. # def decoratorclassmethod(self, wrapped,
  304. # instance, args, kwargs):
  305. # return wrapped(*args, **kwargs)
  306. #
  307. # instance = myclass()
  308. #
  309. # @instance.decoratorclassmethod
  310. # def function():
  311. # pass
  312. #
  313. # This one is a bit strange because binding was actually
  314. # performed on the wrapper created by our decorator
  315. # factory. We need to apply that binding to the decorator
  316. # wrapper function which which the decorator factory
  317. # was applied to.
  318. target_wrapper = wrapper.__get__(instance, type(instance))
  319. # Finally build the wrapper itself and return it.
  320. return _build(target_wrapped, target_wrapper, _enabled, adapter)
  321. # We first return our magic function wrapper here so we can
  322. # determine in what context the decorator factory was used. In
  323. # other words, it is itself a universal decorator.
  324. return _build(wrapper, _wrapper)
  325. else:
  326. # The wrapper still has not been provided, so we are just
  327. # collecting the optional keyword arguments. Return the
  328. # decorator again wrapped in a partial using the collected
  329. # arguments.
  330. return partial(decorator, enabled=enabled, adapter=adapter)
  331. # Decorator for implementing thread synchronization. It can be used as a
  332. # decorator, in which case the synchronization context is determined by
  333. # what type of function is wrapped, or it can also be used as a context
  334. # manager, where the user needs to supply the correct synchronization
  335. # context. It is also possible to supply an object which appears to be a
  336. # synchronization primitive of some sort, by virtue of having release()
  337. # and acquire() methods. In that case that will be used directly as the
  338. # synchronization primitive without creating a separate lock against the
  339. # derived or supplied context.
  340. def synchronized(wrapped):
  341. # Determine if being passed an object which is a synchronization
  342. # primitive. We can't check by type for Lock, RLock, Semaphore etc,
  343. # as the means of creating them isn't the type. Therefore use the
  344. # existence of acquire() and release() methods. This is more
  345. # extensible anyway as it allows custom synchronization mechanisms.
  346. if hasattr(wrapped, 'acquire') and hasattr(wrapped, 'release'):
  347. # We remember what the original lock is and then return a new
  348. # decorator which accesses and locks it. When returning the new
  349. # decorator we wrap it with an object proxy so we can override
  350. # the context manager methods in case it is being used to wrap
  351. # synchronized statements with a 'with' statement.
  352. lock = wrapped
  353. @decorator
  354. def _synchronized(wrapped, instance, args, kwargs):
  355. # Execute the wrapped function while the original supplied
  356. # lock is held.
  357. with lock:
  358. return wrapped(*args, **kwargs)
  359. class _PartialDecorator(CallableObjectProxy):
  360. def __enter__(self):
  361. lock.acquire()
  362. return lock
  363. def __exit__(self, *args):
  364. lock.release()
  365. return _PartialDecorator(wrapped=_synchronized)
  366. # Following only apply when the lock is being created automatically
  367. # based on the context of what was supplied. In this case we supply
  368. # a final decorator, but need to use FunctionWrapper directly as we
  369. # want to derive from it to add context manager methods in case it is
  370. # being used to wrap synchronized statements with a 'with' statement.
  371. def _synchronized_lock(context):
  372. # Attempt to retrieve the lock for the specific context.
  373. lock = vars(context).get('_synchronized_lock', None)
  374. if lock is None:
  375. # There is no existing lock defined for the context we
  376. # are dealing with so we need to create one. This needs
  377. # to be done in a way to guarantee there is only one
  378. # created, even if multiple threads try and create it at
  379. # the same time. We can't always use the setdefault()
  380. # method on the __dict__ for the context. This is the
  381. # case where the context is a class, as __dict__ is
  382. # actually a dictproxy. What we therefore do is use a
  383. # meta lock on this wrapper itself, to control the
  384. # creation and assignment of the lock attribute against
  385. # the context.
  386. meta_lock = vars(synchronized).setdefault(
  387. '_synchronized_meta_lock', Lock())
  388. with meta_lock:
  389. # We need to check again for whether the lock we want
  390. # exists in case two threads were trying to create it
  391. # at the same time and were competing to create the
  392. # meta lock.
  393. lock = vars(context).get('_synchronized_lock', None)
  394. if lock is None:
  395. lock = RLock()
  396. setattr(context, '_synchronized_lock', lock)
  397. return lock
  398. def _synchronized_wrapper(wrapped, instance, args, kwargs):
  399. # Execute the wrapped function while the lock for the
  400. # desired context is held. If instance is None then the
  401. # wrapped function is used as the context.
  402. with _synchronized_lock(instance or wrapped):
  403. return wrapped(*args, **kwargs)
  404. class _FinalDecorator(FunctionWrapper):
  405. def __enter__(self):
  406. self._self_lock = _synchronized_lock(self.__wrapped__)
  407. self._self_lock.acquire()
  408. return self._self_lock
  409. def __exit__(self, *args):
  410. self._self_lock.release()
  411. return _FinalDecorator(wrapped=wrapped, wrapper=_synchronized_wrapper)