Spaces:
No application file
No application file
| # -*- coding: utf-8 -*- | |
| """ | |
| Created on Mon Jan 13 18:17:15 2014 | |
| @author: takluyver | |
| """ | |
| import sys | |
| PY3 = (sys.version_info[0] >= 3) | |
| try: | |
| from inspect import signature, Parameter # Python >= 3.3 | |
| except ImportError: | |
| from ._signatures import signature, Parameter | |
| if PY3: | |
| from functools import wraps | |
| else: | |
| from functools import wraps as _wraps | |
| def wraps(f): | |
| def dec(func): | |
| _wraps(f)(func) | |
| func.__wrapped__ = f | |
| return func | |
| return dec | |
| def callback_prototype(prototype): | |
| """Decorator to process a callback prototype. | |
| A callback prototype is a function whose signature includes all the values | |
| that will be passed by the callback API in question. | |
| The original function will be returned, with a ``prototype.adapt`` attribute | |
| which can be used to prepare third party callbacks. | |
| """ | |
| protosig = signature(prototype) | |
| positional, keyword = [], [] | |
| for name, param in protosig.parameters.items(): | |
| if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): | |
| raise TypeError("*args/**kwargs not supported in prototypes") | |
| if (param.default is not Parameter.empty) \ | |
| or (param.kind == Parameter.KEYWORD_ONLY): | |
| keyword.append(name) | |
| else: | |
| positional.append(name) | |
| kwargs = dict.fromkeys(keyword) | |
| def adapt(callback): | |
| """Introspect and prepare a third party callback.""" | |
| sig = signature(callback) | |
| try: | |
| # XXX: callback can have extra optional parameters - OK? | |
| sig.bind(*positional, **kwargs) | |
| return callback | |
| except TypeError: | |
| pass | |
| # Match up arguments | |
| unmatched_pos = positional[:] | |
| unmatched_kw = kwargs.copy() | |
| unrecognised = [] | |
| # TODO: unrecognised parameters with default values - OK? | |
| for name, param in sig.parameters.items(): | |
| # print(name, param.kind) #DBG | |
| if param.kind == Parameter.POSITIONAL_ONLY: | |
| if len(unmatched_pos) > 0: | |
| unmatched_pos.pop(0) | |
| else: | |
| unrecognised.append(name) | |
| elif param.kind == Parameter.POSITIONAL_OR_KEYWORD: | |
| if (param.default is not Parameter.empty) and (name in unmatched_kw): | |
| unmatched_kw.pop(name) | |
| elif len(unmatched_pos) > 0: | |
| unmatched_pos.pop(0) | |
| else: | |
| unrecognised.append(name) | |
| elif param.kind == Parameter.VAR_POSITIONAL: | |
| unmatched_pos = [] | |
| elif param.kind == Parameter.KEYWORD_ONLY: | |
| if name in unmatched_kw: | |
| unmatched_kw.pop(name) | |
| else: | |
| unrecognised.append(name) | |
| else: # VAR_KEYWORD | |
| unmatched_kw = {} | |
| # print(unmatched_pos, unmatched_kw, unrecognised) #DBG | |
| if unrecognised: | |
| raise TypeError("Function {!r} had unmatched arguments: {}".format(callback, unrecognised)) | |
| n_positional = len(positional) - len(unmatched_pos) | |
| def adapted(*args, **kwargs): | |
| """Wrapper for third party callbacks that discards excess arguments""" | |
| # print(args, kwargs) | |
| args = args[:n_positional] | |
| for name in unmatched_kw: | |
| # XXX: Could name not be in kwargs? | |
| kwargs.pop(name) | |
| # print(args, kwargs, unmatched_pos, cut_positional, unmatched_kw) | |
| return callback(*args, **kwargs) | |
| return adapted | |
| prototype.adapt = adapt | |
| return prototype |