Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/backcall/backcall.py: 27%
66 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1# -*- coding: utf-8 -*-
2"""
3Created on Mon Jan 13 18:17:15 2014
5@author: takluyver
6"""
7import sys
8PY3 = (sys.version_info[0] >= 3)
10try:
11 from inspect import signature, Parameter # Python >= 3.3
12except ImportError:
13 from ._signatures import signature, Parameter
15if PY3:
16 from functools import wraps
17else:
18 from functools import wraps as _wraps
19 def wraps(f):
20 def dec(func):
21 _wraps(f)(func)
22 func.__wrapped__ = f
23 return func
25 return dec
27def callback_prototype(prototype):
28 """Decorator to process a callback prototype.
30 A callback prototype is a function whose signature includes all the values
31 that will be passed by the callback API in question.
33 The original function will be returned, with a ``prototype.adapt`` attribute
34 which can be used to prepare third party callbacks.
35 """
36 protosig = signature(prototype)
37 positional, keyword = [], []
38 for name, param in protosig.parameters.items():
39 if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
40 raise TypeError("*args/**kwargs not supported in prototypes")
42 if (param.default is not Parameter.empty) \
43 or (param.kind == Parameter.KEYWORD_ONLY):
44 keyword.append(name)
45 else:
46 positional.append(name)
48 kwargs = dict.fromkeys(keyword)
49 def adapt(callback):
50 """Introspect and prepare a third party callback."""
51 sig = signature(callback)
52 try:
53 # XXX: callback can have extra optional parameters - OK?
54 sig.bind(*positional, **kwargs)
55 return callback
56 except TypeError:
57 pass
59 # Match up arguments
60 unmatched_pos = positional[:]
61 unmatched_kw = kwargs.copy()
62 unrecognised = []
63 # TODO: unrecognised parameters with default values - OK?
64 for name, param in sig.parameters.items():
65 # print(name, param.kind) #DBG
66 if param.kind == Parameter.POSITIONAL_ONLY:
67 if len(unmatched_pos) > 0:
68 unmatched_pos.pop(0)
69 else:
70 unrecognised.append(name)
71 elif param.kind == Parameter.POSITIONAL_OR_KEYWORD:
72 if (param.default is not Parameter.empty) and (name in unmatched_kw):
73 unmatched_kw.pop(name)
74 elif len(unmatched_pos) > 0:
75 unmatched_pos.pop(0)
76 else:
77 unrecognised.append(name)
78 elif param.kind == Parameter.VAR_POSITIONAL:
79 unmatched_pos = []
80 elif param.kind == Parameter.KEYWORD_ONLY:
81 if name in unmatched_kw:
82 unmatched_kw.pop(name)
83 else:
84 unrecognised.append(name)
85 else: # VAR_KEYWORD
86 unmatched_kw = {}
88 # print(unmatched_pos, unmatched_kw, unrecognised) #DBG
90 if unrecognised:
91 raise TypeError("Function {!r} had unmatched arguments: {}".format(callback, unrecognised))
93 n_positional = len(positional) - len(unmatched_pos)
95 @wraps(callback)
96 def adapted(*args, **kwargs):
97 """Wrapper for third party callbacks that discards excess arguments"""
98# print(args, kwargs)
99 args = args[:n_positional]
100 for name in unmatched_kw:
101 # XXX: Could name not be in kwargs?
102 kwargs.pop(name)
103# print(args, kwargs, unmatched_pos, cut_positional, unmatched_kw)
104 return callback(*args, **kwargs)
106 return adapted
108 prototype.adapt = adapt
109 return prototype