Skip to content

Commit 6a1fe4e

Browse files
committed
Upgrading decorator.py on py3
1 parent 4933f67 commit 6a1fe4e

File tree

1 file changed

+213
-69
lines changed

1 file changed

+213
-69
lines changed

source_py3/python_toolbox/third_party/decorator.py

Lines changed: 213 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# ######################### LICENCE ############################ #
1+
# ######################### LICENSE ############################ #
22

3-
# Copyright (c) 2005-2015, Michele Simionato
3+
# Copyright (c) 2005-2016, Michele Simionato
44
# All rights reserved.
55

66
# Redistribution and use in source and binary forms, with or without
@@ -31,14 +31,16 @@
3131
Decorator module, see http://pypi.python.org/pypi/decorator
3232
for the documentation.
3333
"""
34-
35-
__version__ = '3.4.2'
36-
37-
__all__ = ["decorator", "FunctionMaker", "contextmanager"]
34+
from __future__ import print_function
3835

3936
import re
4037
import sys
4138
import inspect
39+
import operator
40+
import itertools
41+
import collections
42+
43+
__version__ = '4.0.10'
4244

4345
if sys.version >= '3':
4446
from inspect import getfullargspec
@@ -60,9 +62,21 @@ def __iter__(self):
6062
yield self.varkw
6163
yield self.defaults
6264

65+
getargspec = inspect.getargspec
66+
6367
def get_init(cls):
6468
return cls.__init__.__func__
6569

70+
# getargspec has been deprecated in Python 3.5
71+
ArgSpec = collections.namedtuple(
72+
'ArgSpec', 'args varargs varkw defaults')
73+
74+
75+
def getargspec(f):
76+
"""A replacement for inspect.getargspec"""
77+
spec = getfullargspec(f)
78+
return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults)
79+
6680
DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(')
6781

6882

@@ -73,6 +87,10 @@ class FunctionMaker(object):
7387
It has attributes name, doc, module, signature, defaults, dict and
7488
methods update and make.
7589
"""
90+
91+
# Atomic get-and-increment provided by the GIL
92+
_compile_count = itertools.count()
93+
7694
def __init__(self, func=None, name=None, signature=None,
7795
defaults=None, doc=None, module=None, funcdict=None):
7896
self.shortsignature = signature
@@ -160,11 +178,16 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs):
160178
for n in names:
161179
if n in ('_func_', '_call_'):
162180
raise NameError('%s is overridden in\n%s' % (n, src))
163-
if not src.endswith('\n'): # add a newline just for safety
164-
src += '\n' # this is needed in old versions of Python
181+
182+
if not src.endswith('\n'): # add a newline for old Pythons
183+
src += '\n'
184+
185+
# Ensure each generated function has a unique filename for profilers
186+
# (such as cProfile) that depend on the tuple of (<filename>,
187+
# <definition line>, <function name>) being unique.
188+
filename = '<decorator-gen-%d>' % (next(self._compile_count),)
165189
try:
166-
code = compile(src, '<string>', 'single')
167-
# print >> sys.stderr, 'Compiling %s' % src
190+
code = compile(src, filename, 'single')
168191
exec(code, evaldict)
169192
except:
170193
print('Error in generated code:', file=sys.stderr)
@@ -199,75 +222,196 @@ def create(cls, obj, body, evaldict, defaults=None,
199222
evaldict, addsource, **attrs)
200223

201224

202-
def decorator(caller, func=None):
225+
def decorate(func, caller):
203226
"""
204-
decorator(caller) converts a caller function into a decorator;
205-
decorator(caller, func) decorates a function using a caller.
227+
decorate(func, caller) decorates a function using a caller.
206228
"""
207-
if func is not None: # returns a decorated function
208-
evaldict = func.__globals__.copy()
209-
evaldict['_call_'] = caller
210-
evaldict['_func_'] = func
211-
return FunctionMaker.create(
212-
func, "return _call_(_func_, %(shortsignature)s)",
213-
evaldict, __wrapped__=func)
214-
else: # returns a decorator
215-
if inspect.isclass(caller):
216-
name = caller.__name__.lower()
217-
callerfunc = get_init(caller)
218-
doc = 'decorator(%s) converts functions/generators into ' \
219-
'factories of %s objects' % (caller.__name__, caller.__name__)
220-
fun = getfullargspec(callerfunc).args[1] # second arg
221-
elif inspect.isfunction(caller):
222-
if caller.__name__ == '<lambda>':
223-
name = '_lambda_'
224-
else:
225-
name = caller.__name__
226-
callerfunc = caller
227-
doc = caller.__doc__
228-
fun = getfullargspec(callerfunc).args[0] # first arg
229-
else: # assume caller is an object with a __call__ method
230-
name = caller.__class__.__name__.lower()
231-
callerfunc = caller.__call__.__func__
232-
doc = caller.__call__.__doc__
233-
fun = getfullargspec(callerfunc).args[1] # second arg
234-
evaldict = callerfunc.__globals__.copy()
235-
evaldict['_call_'] = caller
236-
evaldict['decorator'] = decorator
237-
return FunctionMaker.create(
238-
'%s(%s)' % (name, fun),
239-
'return decorator(_call_, %s)' % fun,
240-
evaldict, call=caller, doc=doc, module=caller.__module__)
241-
229+
evaldict = dict(_call_=caller, _func_=func)
230+
fun = FunctionMaker.create(
231+
func, "return _call_(_func_, %(shortsignature)s)",
232+
evaldict, __wrapped__=func)
233+
if hasattr(func, '__qualname__'):
234+
fun.__qualname__ = func.__qualname__
235+
return fun
242236

243-
# ####################### contextmanager ####################### #
244237

245-
def __call__(self, func):
246-
'Context manager decorator'
238+
def decorator(caller, _func=None):
239+
"""decorator(caller) converts a caller function into a decorator"""
240+
if _func is not None: # return a decorated function
241+
# this is obsolete behavior; you should use decorate instead
242+
return decorate(_func, caller)
243+
# else return a decorator function
244+
if inspect.isclass(caller):
245+
name = caller.__name__.lower()
246+
doc = 'decorator(%s) converts functions/generators into ' \
247+
'factories of %s objects' % (caller.__name__, caller.__name__)
248+
elif inspect.isfunction(caller):
249+
if caller.__name__ == '<lambda>':
250+
name = '_lambda_'
251+
else:
252+
name = caller.__name__
253+
doc = caller.__doc__
254+
else: # assume caller is an object with a __call__ method
255+
name = caller.__class__.__name__.lower()
256+
doc = caller.__call__.__doc__
257+
evaldict = dict(_call_=caller, _decorate_=decorate)
247258
return FunctionMaker.create(
248-
func, "with _self_: return _func_(%(shortsignature)s)",
249-
dict(_self_=self, _func_=func), __wrapped__=func)
259+
'%s(func)' % name, 'return _decorate_(func, _call_)',
260+
evaldict, doc=doc, module=caller.__module__,
261+
__wrapped__=caller)
250262

251-
try: # Python >= 3.2
252263

264+
# ####################### contextmanager ####################### #
265+
266+
try: # Python >= 3.2
253267
from contextlib import _GeneratorContextManager
268+
except ImportError: # Python >= 2.5
269+
from contextlib import GeneratorContextManager as _GeneratorContextManager
254270

255-
class ContextManager(_GeneratorContextManager):
256-
__call__ = __call__
257271

258-
except ImportError: # Python >= 2.5
272+
class ContextManager(_GeneratorContextManager):
273+
def __call__(self, func):
274+
"""Context manager decorator"""
275+
return FunctionMaker.create(
276+
func, "with _self_: return _func_(%(shortsignature)s)",
277+
dict(_self_=self, _func_=func), __wrapped__=func)
259278

260-
try:
261-
from contextlib import GeneratorContextManager
262-
except ImportError: # Python 2.4
263-
class ContextManager(object):
264-
def __init__(self, g, *a, **k):
265-
raise RuntimeError(
266-
'You cannot used contextmanager in Python 2.4!')
267-
else:
268-
class ContextManager(GeneratorContextManager):
269-
def __init__(self, g, *a, **k):
270-
return GeneratorContextManager.__init__(self, g(*a, **k))
271-
__call__ = __call__
279+
init = getfullargspec(_GeneratorContextManager.__init__)
280+
n_args = len(init.args)
281+
if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7
282+
def __init__(self, g, *a, **k):
283+
return _GeneratorContextManager.__init__(self, g(*a, **k))
284+
ContextManager.__init__ = __init__
285+
elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4
286+
pass
287+
elif n_args == 4: # (self, gen, args, kwds) Python 3.5
288+
def __init__(self, g, *a, **k):
289+
return _GeneratorContextManager.__init__(self, g, a, k)
290+
ContextManager.__init__ = __init__
272291

273292
contextmanager = decorator(ContextManager)
293+
294+
295+
# ############################ dispatch_on ############################ #
296+
297+
def append(a, vancestors):
298+
"""
299+
Append ``a`` to the list of the virtual ancestors, unless it is already
300+
included.
301+
"""
302+
add = True
303+
for j, va in enumerate(vancestors):
304+
if issubclass(va, a):
305+
add = False
306+
break
307+
if issubclass(a, va):
308+
vancestors[j] = a
309+
add = False
310+
if add:
311+
vancestors.append(a)
312+
313+
314+
# inspired from simplegeneric by P.J. Eby and functools.singledispatch
315+
def dispatch_on(*dispatch_args):
316+
"""
317+
Factory of decorators turning a function into a generic function
318+
dispatching on the given arguments.
319+
"""
320+
assert dispatch_args, 'No dispatch args passed'
321+
dispatch_str = '(%s,)' % ', '.join(dispatch_args)
322+
323+
def check(arguments, wrong=operator.ne, msg=''):
324+
"""Make sure one passes the expected number of arguments"""
325+
if wrong(len(arguments), len(dispatch_args)):
326+
raise TypeError('Expected %d arguments, got %d%s' %
327+
(len(dispatch_args), len(arguments), msg))
328+
329+
def gen_func_dec(func):
330+
"""Decorator turning a function into a generic function"""
331+
332+
# first check the dispatch arguments
333+
argset = set(getfullargspec(func).args)
334+
if not set(dispatch_args) <= argset:
335+
raise NameError('Unknown dispatch arguments %s' % dispatch_str)
336+
337+
typemap = {}
338+
339+
def vancestors(*types):
340+
"""
341+
Get a list of sets of virtual ancestors for the given types
342+
"""
343+
check(types)
344+
ras = [[] for _ in range(len(dispatch_args))]
345+
for types_ in typemap:
346+
for t, type_, ra in zip(types, types_, ras):
347+
if issubclass(t, type_) and type_ not in t.__mro__:
348+
append(type_, ra)
349+
return [set(ra) for ra in ras]
350+
351+
def ancestors(*types):
352+
"""
353+
Get a list of virtual MROs, one for each type
354+
"""
355+
check(types)
356+
lists = []
357+
for t, vas in zip(types, vancestors(*types)):
358+
n_vas = len(vas)
359+
if n_vas > 1:
360+
raise RuntimeError(
361+
'Ambiguous dispatch for %s: %s' % (t, vas))
362+
elif n_vas == 1:
363+
va, = vas
364+
mro = type('t', (t, va), {}).__mro__[1:]
365+
else:
366+
mro = t.__mro__
367+
lists.append(mro[:-1]) # discard t and object
368+
return lists
369+
370+
def register(*types):
371+
"""
372+
Decorator to register an implementation for the given types
373+
"""
374+
check(types)
375+
376+
def dec(f):
377+
check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
378+
typemap[types] = f
379+
return f
380+
return dec
381+
382+
def dispatch_info(*types):
383+
"""
384+
An utility to introspect the dispatch algorithm
385+
"""
386+
check(types)
387+
lst = []
388+
for anc in itertools.product(*ancestors(*types)):
389+
lst.append(tuple(a.__name__ for a in anc))
390+
return lst
391+
392+
def _dispatch(dispatch_args, *args, **kw):
393+
types = tuple(type(arg) for arg in dispatch_args)
394+
try: # fast path
395+
f = typemap[types]
396+
except KeyError:
397+
pass
398+
else:
399+
return f(*args, **kw)
400+
combinations = itertools.product(*ancestors(*types))
401+
next(combinations) # the first one has been already tried
402+
for types_ in combinations:
403+
f = typemap.get(types_)
404+
if f is not None:
405+
return f(*args, **kw)
406+
407+
# else call the default implementation
408+
return func(*args, **kw)
409+
410+
return FunctionMaker.create(
411+
func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
412+
dict(_f_=_dispatch), register=register, default=func,
413+
typemap=typemap, vancestors=vancestors, ancestors=ancestors,
414+
dispatch_info=dispatch_info, __wrapped__=func)
415+
416+
gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
417+
return gen_func_dec

0 commit comments

Comments
 (0)