Source code for dags.signature

import functools
import inspect


[docs]def create_signature(args=None, kwargs=None): """Create a inspect.Signature object based on args and kwargs. Args: args (list or None): The names of positional or keyword arguments. kwargs (list or None): The keyword only arguments. Returns: inspect.Signature """ args = [] if args is None else args kwargs = {} if kwargs is None else kwargs parameter_objects = [] for arg in args: param = inspect.Parameter( name=arg, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, ) parameter_objects.append(param) for arg in kwargs: param = inspect.Parameter( name=arg, kind=inspect.Parameter.KEYWORD_ONLY, ) parameter_objects.append(param) sig = inspect.Signature(parameters=parameter_objects) return sig
[docs]def with_signature(func=None, *, args=None, kwargs=None, enforce=True): """Decorator that adds a signature to a function of type ``f(*args, **kwargs)`` Caveats: The created signature only contains the names of arguments and whether they are keyword only. There is no way of setting default values, type hints or other things. Args: func (callable): The function to be decorated. Should take ``*args`` and ``**kwargs`` as only arguments. args (list or None): The names of positional or keyword arguments. kwargs (list or None): The keyword only arguments. enforce (bool): Whether the signature should be enforced or just added to the function for introspection. This creates runtime overhead. Returns: function: The function with signature. """ def decorator_with_signature(func): _args = [] if args is None else args _kwargs = [] if kwargs is None else kwargs signature = create_signature(_args, _kwargs) if enforce: valid_kwargs = set(_kwargs) | set(_args) funcname = getattr(func, "__name__", "function") @functools.wraps(func) def wrapper_with_signature(*args, **kwargs): _fail_if_too_many_positional_arguments(args, _args, funcname) present_args = set(_args[: len(args)]) present_kwargs = set(kwargs) _fail_if_duplicated_arguments(present_args, present_kwargs, funcname) _fail_if_invalid_keyword_arguments( present_kwargs, valid_kwargs, funcname ) return func(*args, **kwargs) else: def wrapper_with_signature(*args, **kwargs): return func(*args, **kwargs) wrapper_with_signature.__signature__ = signature return wrapper_with_signature if callable(func): return decorator_with_signature(func) else: return decorator_with_signature
[docs]def _fail_if_too_many_positional_arguments(present_args, argnames, funcname): if len(present_args) > len(argnames): raise TypeError( f"{funcname}() takes {len(argnames)} positional arguments " f"but {len(present_args)} were given" )
[docs]def _fail_if_duplicated_arguments(present_args, present_kwargs, funcname): problematic = present_args & present_kwargs if problematic: s = "s" if len(problematic) >= 2 else "" problem_str = ", ".join(list(problematic)) raise TypeError( f"{funcname}() got multiple values for argument{s} {problem_str}" )
[docs]def _fail_if_invalid_keyword_arguments(present_kwargs, valid_kwargs, funcname): problematic = present_kwargs - valid_kwargs if problematic: s = "s" if len(problematic) >= 2 else "" problem_str = ", ".join(list(problematic)) raise TypeError( f"{funcname}() got unexpected keyword argument{s} {problem_str}" )
[docs]def rename_arguments(func=None, *, mapper=None): """Rename positional and keyword arguments of func. Args: func (callable): The function of which the arguments are renamed. mapper (dict): Dict of strings where keys are old names and values are new of arguments. Returns: function: The function with renamed arguments. """ def decorator_rename_arguments(func): old_parameters = dict(inspect.signature(func).parameters) parameters = [] for name, param in old_parameters.items(): if name in mapper: parameters.append(param.replace(name=mapper[name])) else: parameters.append(param) signature = inspect.Signature(parameters=parameters) reverse_mapper = {v: k for k, v in mapper.items()} @functools.wraps(func) def wrapper_rename_arguments(*args, **kwargs): internal_kwargs = {} for name, value in kwargs.items(): if name in reverse_mapper: internal_kwargs[reverse_mapper[name]] = value elif name not in mapper: internal_kwargs[name] = value return func(*args, **internal_kwargs) wrapper_rename_arguments.__signature__ = signature return wrapper_rename_arguments if callable(func): return decorator_rename_arguments(func) else: return decorator_rename_arguments