PageRenderTime 53ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/wxgeometrie/pylib/decorator.py

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