Source code for mmodel.signature

from inspect import signature, Parameter, Signature
from functools import wraps
from mmodel.utility import param_sorter
from collections import defaultdict


[docs]def split_arguments(sig, arguments): """Split the input argument into args and kwargs based on the signature. The kwargs need to match the inputs completely. The function takes care of the position-only variable in the old function. :param inspect.Signature sig: signature of the original function :param dict arguments: keyword argument values """ args = [] for param in sig.parameters.values(): if param.kind == 0: args.append(arguments.pop(param.name)) return args, arguments
[docs]def modify_signature(func, inputs): """Modify function signature to custom-defined inputs. The inputs replace the original signature in the same order. The resulting function is a keyword-only function. The conversion ignores VAR_POSITIONAL (args). For these functions, create a new function instead. :param callable func: function to change signature :param list inputs: new input parameters for the function. """ sig = signature(func) # check if input parameters are less than the parameters required # excludes parameters with defaults sig_dict = defaultdict(list) sig_default_dict = defaultdict(list) for param in sig.parameters.values(): sig_dict[param.kind].append(param.name) if param.default is not Parameter.empty: sig_default_dict[param.kind].append(param.name) sig_count = [len(sig_dict[key]) for key in [0, 1, 2, 3, 4]] nd_sig_count = [ len(sig_dict[key]) - len(sig_default_dict[key]) for key in [0, 1, 2, 3, 4] ] # there are four cases for max_length # case 1: var_kw - max = unlimited # case 1: kw_only, no var_kw - max = pos + pos_or_kw + kw_only # case 3: var_pos, no var_kw, no kw_only - max = unlimited # case 4: no var_pos, no kw_only, no var_kw - max = pos + pos_or_kw pos_expand = False if sig_count[4] > 0: max_length = None elif sig_count[3] > 0: max_length = sig_count[0] + sig_count[1] + sig_count[3] elif sig_count[2] > 0: max_length = None pos_expand = True else: max_length = sig_count[0] + sig_count[1] # there are three cases for min_length (nd = non-default) # case 1: nd kw_only - min = nd kw + pos_or_kw + pos # case 2: nd pos_or_kw, no nd kw_only - min = nd pos_or_kw + pos # case 3: no nd pos_or_kw, no nd kw_only - min = nd pos if nd_sig_count[3] > 0: min_length = nd_sig_count[3] + sig_count[1] + sig_count[0] elif nd_sig_count[1] > 0: min_length = nd_sig_count[1] + sig_count[0] else: min_length = nd_sig_count[0] # check if the inputs are enough for the function if min_length > len(inputs): raise ValueError("Not enough inputs for the function") elif max_length is not None and max_length < len(inputs): raise ValueError("Too many inputs for the function") new_param = [] for element in inputs: new_param.append(Parameter(element, kind=Parameter.POSITIONAL_OR_KEYWORD)) new_sig = Signature(new_param) # remove the *args and **kwargs param_list = sig_dict[0] + sig_dict[1] + sig_dict[3] param_pair = list(zip(param_list, inputs)) @wraps(func) def wrapped(**kwargs): # all parameters are positional if pos_expand: return func(*kwargs.values()) # if keyword argument exists in the original signature adjusted_args = [] adjusted_kwargs = {} for old_key, new_key in param_pair: if old_key in sig_dict[0]: adjusted_args.append(kwargs.pop(new_key)) else: adjusted_kwargs[old_key] = kwargs.pop(new_key) adjusted_kwargs.update(kwargs) return func(*adjusted_args, **adjusted_kwargs) wrapped.__signature__ = new_sig return wrapped
[docs]def add_signature(func, inputs): """Add signature to ufunc and builtin function. Numpy's ufunc and builtin type do not have signatures, therefore, a new signature is created, and the input arguments are mapped as positional-only values. :param callable func: function to change signature :param list inputs: new input parameters for the function. """ new_param = [] for element in inputs: new_param.append(Parameter(element, kind=Parameter.POSITIONAL_OR_KEYWORD)) new_sig = Signature(new_param) @wraps(func) def wrapped(**kwargs): return func(*kwargs.values()) wrapped.__signature__ = new_sig return wrapped
[docs]def convert_signature(func): """Convert function signature to pos_or_keywords. The method ignores "args", and "kwargs". Note the signature does not include parameters with default values. Use inputs to include the parameters. """ sig = signature(func) parameters = dict(sig.parameters) param_list = [] for param in parameters.values(): if (param.kind == 1 or param.kind == 3) and param.default is Parameter.empty: param_list.append(param) elif param.kind == 0: # defaults cannot be added to pos only parameter param_list.append( Parameter(param.name, kind=Parameter.POSITIONAL_OR_KEYWORD) ) new_sig = Signature(parameters=param_list) @wraps(func) def wrapped(**kwargs): # arguments = new_sig.bind(*args, **kwargs).arguments args, kwargs = split_arguments(sig, kwargs) return func(*args, **kwargs) wrapped.__signature__ = new_sig return wrapped
[docs]def restructure_signature( signature, default_dict, kind=Parameter.POSITIONAL_OR_KEYWORD ): """Add defaults to signature for Model. Here the parameter kinds are replaced with kind (defaults to POSITIONAL_OR_KEYWORD), and defaults are applied. The final signatures are sorted. The function is used in the Model signature definition, therefore no VAR_POSITIONAL or VAR_KEYWORD should be in the signature. :param inspect.Signature signature: signature to add defaults :param dict default_dict: default values for the parameters :param int kind: parameter kind """ param_list = [] for param in signature.parameters.values(): param_list.append( Parameter( param.name, kind=kind, default=default_dict.get(param.name, Parameter.empty), ) ) return Signature(sorted(param_list, key=param_sorter))
[docs]def has_signature(func): """Check if the function has a signature. The function checks if the function has a signature. If the function has a signature, the function returns True. If the function does not have a signature, the function returns False. """ try: signature(func) return True except ValueError: return False
[docs]def check_signature(func): """Check if the function signature has parameters with default values. If the function has position-only, or var-positional, or var-keyword, or default values, the function returns False. """ sig = signature(func) for param in sig.parameters.values(): if ( param.kind not in [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD] or param.default is not Parameter.empty ): return False return True