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.

decorator.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. # ######################### LICENSE ############################ #
  2. # Copyright (c) 2005-2018, Michele Simionato
  3. # All rights reserved.
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. # Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # Redistributions in bytecode form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in
  11. # the documentation and/or other materials provided with the
  12. # distribution.
  13. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  15. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  16. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  17. # HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  18. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  19. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  20. # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  22. # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  23. # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  24. # DAMAGE.
  25. """
  26. Decorator module, see http://pypi.python.org/pypi/decorator
  27. for the documentation.
  28. """
  29. from __future__ import print_function
  30. import re
  31. import sys
  32. import inspect
  33. import operator
  34. import itertools
  35. import collections
  36. __version__ = '4.3.0'
  37. if sys.version >= '3':
  38. from inspect import getfullargspec
  39. def get_init(cls):
  40. return cls.__init__
  41. else:
  42. FullArgSpec = collections.namedtuple(
  43. 'FullArgSpec', 'args varargs varkw defaults '
  44. 'kwonlyargs kwonlydefaults annotations')
  45. def getfullargspec(f):
  46. "A quick and dirty replacement for getfullargspec for Python 2.X"
  47. return FullArgSpec._make(inspect.getargspec(f) + ([], None, {}))
  48. def get_init(cls):
  49. return cls.__init__.__func__
  50. try:
  51. iscoroutinefunction = inspect.iscoroutinefunction
  52. except AttributeError:
  53. # let's assume there are no coroutine functions in old Python
  54. def iscoroutinefunction(f):
  55. return False
  56. DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
  57. # basic functionality
  58. class FunctionMaker(object):
  59. """
  60. An object with the ability to create functions with a given signature.
  61. It has attributes name, doc, module, signature, defaults, dict and
  62. methods update and make.
  63. """
  64. # Atomic get-and-increment provided by the GIL
  65. _compile_count = itertools.count()
  66. # make pylint happy
  67. args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = ()
  68. def __init__(self, func=None, name=None, signature=None,
  69. defaults=None, doc=None, module=None, funcdict=None):
  70. self.shortsignature = signature
  71. if func:
  72. # func can be a class or a callable, but not an instance method
  73. self.name = func.__name__
  74. if self.name == '<lambda>': # small hack for lambda functions
  75. self.name = '_lambda_'
  76. self.doc = func.__doc__
  77. self.module = func.__module__
  78. if inspect.isfunction(func):
  79. argspec = getfullargspec(func)
  80. self.annotations = getattr(func, '__annotations__', {})
  81. for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
  82. 'kwonlydefaults'):
  83. setattr(self, a, getattr(argspec, a))
  84. for i, arg in enumerate(self.args):
  85. setattr(self, 'arg%d' % i, arg)
  86. allargs = list(self.args)
  87. allshortargs = list(self.args)
  88. if self.varargs:
  89. allargs.append('*' + self.varargs)
  90. allshortargs.append('*' + self.varargs)
  91. elif self.kwonlyargs:
  92. allargs.append('*') # single star syntax
  93. for a in self.kwonlyargs:
  94. allargs.append('%s=None' % a)
  95. allshortargs.append('%s=%s' % (a, a))
  96. if self.varkw:
  97. allargs.append('**' + self.varkw)
  98. allshortargs.append('**' + self.varkw)
  99. self.signature = ', '.join(allargs)
  100. self.shortsignature = ', '.join(allshortargs)
  101. self.dict = func.__dict__.copy()
  102. # func=None happens when decorating a caller
  103. if name:
  104. self.name = name
  105. if signature is not None:
  106. self.signature = signature
  107. if defaults:
  108. self.defaults = defaults
  109. if doc:
  110. self.doc = doc
  111. if module:
  112. self.module = module
  113. if funcdict:
  114. self.dict = funcdict
  115. # check existence required attributes
  116. assert hasattr(self, 'name')
  117. if not hasattr(self, 'signature'):
  118. raise TypeError('You are decorating a non function: %s' % func)
  119. def update(self, func, **kw):
  120. "Update the signature of func with the data in self"
  121. func.__name__ = self.name
  122. func.__doc__ = getattr(self, 'doc', None)
  123. func.__dict__ = getattr(self, 'dict', {})
  124. func.__defaults__ = self.defaults
  125. func.__kwdefaults__ = self.kwonlydefaults or None
  126. func.__annotations__ = getattr(self, 'annotations', None)
  127. try:
  128. frame = sys._getframe(3)
  129. except AttributeError: # for IronPython and similar implementations
  130. callermodule = '?'
  131. else:
  132. callermodule = frame.f_globals.get('__name__', '?')
  133. func.__module__ = getattr(self, 'module', callermodule)
  134. func.__dict__.update(kw)
  135. def make(self, src_templ, evaldict=None, addsource=False, **attrs):
  136. "Make a new function from a given template and update the signature"
  137. src = src_templ % vars(self) # expand name and signature
  138. evaldict = evaldict or {}
  139. mo = DEF.search(src)
  140. if mo is None:
  141. raise SyntaxError('not a valid function template\n%s' % src)
  142. name = mo.group(1) # extract the function name
  143. names = set([name] + [arg.strip(' *') for arg in
  144. self.shortsignature.split(',')])
  145. for n in names:
  146. if n in ('_func_', '_call_'):
  147. raise NameError('%s is overridden in\n%s' % (n, src))
  148. if not src.endswith('\n'): # add a newline for old Pythons
  149. src += '\n'
  150. # Ensure each generated function has a unique filename for profilers
  151. # (such as cProfile) that depend on the tuple of (<filename>,
  152. # <definition line>, <function name>) being unique.
  153. filename = '<decorator-gen-%d>' % (next(self._compile_count),)
  154. try:
  155. code = compile(src, filename, 'single')
  156. exec(code, evaldict)
  157. except Exception:
  158. print('Error in generated code:', file=sys.stderr)
  159. print(src, file=sys.stderr)
  160. raise
  161. func = evaldict[name]
  162. if addsource:
  163. attrs['__source__'] = src
  164. self.update(func, **attrs)
  165. return func
  166. @classmethod
  167. def create(cls, obj, body, evaldict, defaults=None,
  168. doc=None, module=None, addsource=True, **attrs):
  169. """
  170. Create a function from the strings name, signature and body.
  171. evaldict is the evaluation dictionary. If addsource is true an
  172. attribute __source__ is added to the result. The attributes attrs
  173. are added, if any.
  174. """
  175. if isinstance(obj, str): # "name(signature)"
  176. name, rest = obj.strip().split('(', 1)
  177. signature = rest[:-1] # strip a right parens
  178. func = None
  179. else: # a function
  180. name = None
  181. signature = None
  182. func = obj
  183. self = cls(func, name, signature, defaults, doc, module)
  184. ibody = '\n'.join(' ' + line for line in body.splitlines())
  185. caller = evaldict.get('_call_') # when called from `decorate`
  186. if caller and iscoroutinefunction(caller):
  187. body = ('async def %(name)s(%(signature)s):\n' + ibody).replace(
  188. 'return', 'return await')
  189. else:
  190. body = 'def %(name)s(%(signature)s):\n' + ibody
  191. return self.make(body, evaldict, addsource, **attrs)
  192. def decorate(func, caller, extras=()):
  193. """
  194. decorate(func, caller) decorates a function using a caller.
  195. """
  196. evaldict = dict(_call_=caller, _func_=func)
  197. es = ''
  198. for i, extra in enumerate(extras):
  199. ex = '_e%d_' % i
  200. evaldict[ex] = extra
  201. es += ex + ', '
  202. fun = FunctionMaker.create(
  203. func, "return _call_(_func_, %s%%(shortsignature)s)" % es,
  204. evaldict, __wrapped__=func)
  205. if hasattr(func, '__qualname__'):
  206. fun.__qualname__ = func.__qualname__
  207. return fun
  208. def decorator(caller, _func=None):
  209. """decorator(caller) converts a caller function into a decorator"""
  210. if _func is not None: # return a decorated function
  211. # this is obsolete behavior; you should use decorate instead
  212. return decorate(_func, caller)
  213. # else return a decorator function
  214. defaultargs, defaults = '', ()
  215. if inspect.isclass(caller):
  216. name = caller.__name__.lower()
  217. doc = 'decorator(%s) converts functions/generators into ' \
  218. 'factories of %s objects' % (caller.__name__, caller.__name__)
  219. elif inspect.isfunction(caller):
  220. if caller.__name__ == '<lambda>':
  221. name = '_lambda_'
  222. else:
  223. name = caller.__name__
  224. doc = caller.__doc__
  225. nargs = caller.__code__.co_argcount
  226. ndefs = len(caller.__defaults__ or ())
  227. defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs])
  228. if defaultargs:
  229. defaultargs += ','
  230. defaults = caller.__defaults__
  231. else: # assume caller is an object with a __call__ method
  232. name = caller.__class__.__name__.lower()
  233. doc = caller.__call__.__doc__
  234. evaldict = dict(_call=caller, _decorate_=decorate)
  235. dec = FunctionMaker.create(
  236. '%s(%s func)' % (name, defaultargs),
  237. 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n'
  238. 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs),
  239. evaldict, doc=doc, module=caller.__module__, __wrapped__=caller)
  240. if defaults:
  241. dec.__defaults__ = defaults + (None,)
  242. return dec
  243. # ####################### contextmanager ####################### #
  244. try: # Python >= 3.2
  245. from contextlib import _GeneratorContextManager
  246. except ImportError: # Python >= 2.5
  247. from contextlib import GeneratorContextManager as _GeneratorContextManager
  248. class ContextManager(_GeneratorContextManager):
  249. def __call__(self, func):
  250. """Context manager decorator"""
  251. return FunctionMaker.create(
  252. func, "with _self_: return _func_(%(shortsignature)s)",
  253. dict(_self_=self, _func_=func), __wrapped__=func)
  254. init = getfullargspec(_GeneratorContextManager.__init__)
  255. n_args = len(init.args)
  256. if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7
  257. def __init__(self, g, *a, **k):
  258. return _GeneratorContextManager.__init__(self, g(*a, **k))
  259. ContextManager.__init__ = __init__
  260. elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4
  261. pass
  262. elif n_args == 4: # (self, gen, args, kwds) Python 3.5
  263. def __init__(self, g, *a, **k):
  264. return _GeneratorContextManager.__init__(self, g, a, k)
  265. ContextManager.__init__ = __init__
  266. _contextmanager = decorator(ContextManager)
  267. def contextmanager(func):
  268. # Enable Pylint config: contextmanager-decorators=decorator.contextmanager
  269. return _contextmanager(func)
  270. # ############################ dispatch_on ############################ #
  271. def append(a, vancestors):
  272. """
  273. Append ``a`` to the list of the virtual ancestors, unless it is already
  274. included.
  275. """
  276. add = True
  277. for j, va in enumerate(vancestors):
  278. if issubclass(va, a):
  279. add = False
  280. break
  281. if issubclass(a, va):
  282. vancestors[j] = a
  283. add = False
  284. if add:
  285. vancestors.append(a)
  286. # inspired from simplegeneric by P.J. Eby and functools.singledispatch
  287. def dispatch_on(*dispatch_args):
  288. """
  289. Factory of decorators turning a function into a generic function
  290. dispatching on the given arguments.
  291. """
  292. assert dispatch_args, 'No dispatch args passed'
  293. dispatch_str = '(%s,)' % ', '.join(dispatch_args)
  294. def check(arguments, wrong=operator.ne, msg=''):
  295. """Make sure one passes the expected number of arguments"""
  296. if wrong(len(arguments), len(dispatch_args)):
  297. raise TypeError('Expected %d arguments, got %d%s' %
  298. (len(dispatch_args), len(arguments), msg))
  299. def gen_func_dec(func):
  300. """Decorator turning a function into a generic function"""
  301. # first check the dispatch arguments
  302. argset = set(getfullargspec(func).args)
  303. if not set(dispatch_args) <= argset:
  304. raise NameError('Unknown dispatch arguments %s' % dispatch_str)
  305. typemap = {}
  306. def vancestors(*types):
  307. """
  308. Get a list of sets of virtual ancestors for the given types
  309. """
  310. check(types)
  311. ras = [[] for _ in range(len(dispatch_args))]
  312. for types_ in typemap:
  313. for t, type_, ra in zip(types, types_, ras):
  314. if issubclass(t, type_) and type_ not in t.mro():
  315. append(type_, ra)
  316. return [set(ra) for ra in ras]
  317. def ancestors(*types):
  318. """
  319. Get a list of virtual MROs, one for each type
  320. """
  321. check(types)
  322. lists = []
  323. for t, vas in zip(types, vancestors(*types)):
  324. n_vas = len(vas)
  325. if n_vas > 1:
  326. raise RuntimeError(
  327. 'Ambiguous dispatch for %s: %s' % (t, vas))
  328. elif n_vas == 1:
  329. va, = vas
  330. mro = type('t', (t, va), {}).mro()[1:]
  331. else:
  332. mro = t.mro()
  333. lists.append(mro[:-1]) # discard t and object
  334. return lists
  335. def register(*types):
  336. """
  337. Decorator to register an implementation for the given types
  338. """
  339. check(types)
  340. def dec(f):
  341. check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
  342. typemap[types] = f
  343. return f
  344. return dec
  345. def dispatch_info(*types):
  346. """
  347. An utility to introspect the dispatch algorithm
  348. """
  349. check(types)
  350. lst = []
  351. for anc in itertools.product(*ancestors(*types)):
  352. lst.append(tuple(a.__name__ for a in anc))
  353. return lst
  354. def _dispatch(dispatch_args, *args, **kw):
  355. types = tuple(type(arg) for arg in dispatch_args)
  356. try: # fast path
  357. f = typemap[types]
  358. except KeyError:
  359. pass
  360. else:
  361. return f(*args, **kw)
  362. combinations = itertools.product(*ancestors(*types))
  363. next(combinations) # the first one has been already tried
  364. for types_ in combinations:
  365. f = typemap.get(types_)
  366. if f is not None:
  367. return f(*args, **kw)
  368. # else call the default implementation
  369. return func(*args, **kw)
  370. return FunctionMaker.create(
  371. func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
  372. dict(_f_=_dispatch), register=register, default=func,
  373. typemap=typemap, vancestors=vancestors, ancestors=ancestors,
  374. dispatch_info=dispatch_info, __wrapped__=func)
  375. gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
  376. return gen_func_dec