def _verify_decorator_correctness( sig: Signature, parameter_selector: List[Union[int, str]], exception_factory: Union[Type[BaseException], Callable[..., BaseException]] ) -> None: """ Verifies that all selected parameters actually exist for a method. :param sig: A method signature :param parameter_selector: a selector that indicates parameter(s) of the method. This is a list with a combination of ints for positional parameters or strings for keyword parameters :raises MalformedDecoratorError: If one or more of the selected parameters does not exist in the method """ for parameter in parameter_selector: if isinstance(parameter, int): if parameter < 0: raise MalformedDecoratorError( f'Positional parameter {parameter} out of range') try: sig.bind_partial(*([None] * (parameter + 1))) except TypeError: raise MalformedDecoratorError( f'Positional parameter {parameter} out of range') elif isinstance(parameter, str): try: sig.bind_partial(**{parameter: None}) except TypeError: raise MalformedDecoratorError( f'Unknown keyword parameter {parameter}') else: raise MalformedDecoratorError( f'Parameter selector {parameter} has unknown type {type(parameter)}' ) if isinstance(exception_factory, type) and issubclass( exception_factory, BaseException): pass elif isinstance(exception_factory, FunctionType): pass else: raise MalformedDecoratorError( f'Incorrect type for exception_factory: {type(exception_factory)}')
def get_args_kwargs(cls, loader: yaml.Loader, node: yaml.Node, signature: Signature = free_signature) -> tp.Optional[BoundArguments]: if isinstance(node, yaml.ScalarNode): try: # This sometimes fails because of ruamel/yaml/resolver.py line 370 node.tag = loader.resolver.resolve(yaml.ScalarNode, node.value, [True, False]) except IndexError: node.tag = loader.DEFAULT_SCALAR_TAG val = loader.construct_object(node) if val is None: val = loader.yaml_constructors[node.tag](loader, node) return None if val is None else signature.bind(val) elif isinstance(node, yaml.SequenceNode): bound = signature.bind(*node.value) args = [] subnodes = node.value elif isinstance(node, yaml.MappingNode): # Construct the keys kwargs = {loader.construct_object(key, deep=True): val for key, val in node.value} args = kwargs.setdefault('__args', yaml.SequenceNode('tag:yaml.org,2002:seq', [])) args_is_seq = isinstance(args, yaml.SequenceNode) and args.tag == 'tag:yaml.org,2002:seq' if args_is_seq: kwargs['__args'] = args.value # Extract nodes in order (nodes are not iterable, so only "flattens" __args) subnodes = list(flatten(kwargs.values())) __args = kwargs.pop('__args') bound = signature.bind_partial(*(__args if args_is_seq else ()), **kwargs) else: raise ValueError(f'Invalid node type, {node}') # Experimental cls.fix_signature(bound) # Construct nodes in yaml order subnode_values = {n: loader.construct_object(n, deep=True) for n in subnodes} for key, val in bound.arguments.items(): bound.arguments[key] = ( signature.parameters[key].kind == Parameter.VAR_POSITIONAL and (subnode_values[n] for n in val) or signature.parameters[key].kind == Parameter.VAR_KEYWORD and {name: subnode_values[n] for name, n in val.items()} or subnode_values[val] ) if args and args in subnode_values: return bound.signature.bind(*subnode_values[args], **bound.kwargs) return bound
def gen_args(sig: inspect.Signature) -> inspect.BoundArguments: args = sig.bind_partial() space = context_statespace() for param in sig.parameters.values(): smt_name = param.name + space.uniq() proxy_maker = lambda typ, **kw: proxy_for_type( typ, smt_name, allow_subtypes=True, **kw) has_annotation = (param.annotation != inspect.Parameter.empty) value: object if param.kind == inspect.Parameter.VAR_POSITIONAL: if has_annotation: varargs_type = List[param.annotation] # type: ignore value = proxy_maker(varargs_type) else: value = proxy_maker(List[Any]) elif param.kind == inspect.Parameter.VAR_KEYWORD: if has_annotation: varargs_type = Dict[str, param.annotation] # type: ignore value = cast(dict, proxy_maker(varargs_type)) # Using ** on a dict requires concrete string keys. Force # instiantiation of keys here: value = {k.__str__(): v for (k, v) in value.items()} else: value = proxy_maker(Dict[str, Any]) else: is_self = param.name == 'self' # Object parameters should meet thier invariants iff they are not the # class under test ("self"). meet_class_invariants = not is_self allow_subtypes = not is_self if has_annotation: value = proxy_for_type(param.annotation, smt_name, meet_class_invariants, allow_subtypes) else: value = proxy_for_type(cast(type, Any), smt_name, meet_class_invariants, allow_subtypes) debug('created proxy for', param.name, 'as type:', type(value)) args.arguments[param.name] = value return args
def to_bound_arguments( self, signature: inspect.Signature, partial: bool = False, ) -> inspect.BoundArguments: """ Generates an instance of :class:inspect.BoundArguments` for a given :class:`inspect.Signature`. Does not raise if invalid or incomplete arguments are provided, as the underlying implementation uses :meth:`inspect.Signature.bind_partial`. :param signature: an instance of :class:`inspect.Signature` to which :paramref:`.CallArguments.args` and :paramref:`.CallArguments.kwargs` will be bound. :param partial: does not raise if invalid or incomplete arguments are provided, as the underlying implementation uses :meth:`inspect.Signature.bind_partial` :returns: an instance of :class:`inspect.BoundArguments` to which :paramref:`.CallArguments.args` and :paramref:`.CallArguments.kwargs` are bound. """ return signature.bind_partial(*self.args, **self.kwargs) \ if partial \ else signature.bind(*self.args, **self.kwargs)
def __init__(self, sig: Signature, args, kwargs, has_returns_from_previous_task=True): args = tuple(args) kwargs = dict(kwargs) self.sig = sig params = sig.parameters # Check if the method signature contains # any VAR_KEYWORD (i.e., **kwargs) self.varkeyword = False for param in params.values(): if param.kind == param.VAR_KEYWORD: self.varkeyword = True add_later = {} try: # If the first positional argument is a # dict (i.e., result of a previous task), we need to process it. if isinstance(args[0], dict) and has_returns_from_previous_task: original_args = args[0] # Remove the RETURN_KEYS_KEY entry if RETURN_KEYS_KEY in original_args: del original_args[RETURN_KEYS_KEY] # Partially bind the remaining arguments ba = sig.bind_partial(*args[1:]).arguments for k, v in original_args.items(): # Add k,v pairs from the dictionary that are not in the # partially bound args if k not in ba: # But only if the keys exist in the arguments of the # method signature, or if a varkeyword(e.g. **kwargs) # appeared in the signature if (k in params) or self.varkeyword: if k not in kwargs: kwargs[k] = v else: # Otherwise, we still need to add this parameter # later, adter the method is called add_later[k] = v # Remove the dict from the positional arguments args = args[1:] except IndexError: pass remove_from_kwargs = {} if not self.varkeyword: remove_from_kwargs = { k: v for k, v in kwargs.items() if k not in params.keys() } # Pass in the kwargs that don't appear in the original app signature # to be later used possibly by other apps add_later.update(remove_from_kwargs) # and remove them from the kwargs for k in remove_from_kwargs.keys(): del kwargs[k] # remove keys from kwargs that are bound by the positional args bound_args = sig.bind_partial(*args).arguments for k in bound_args.keys(): if k in kwargs: del kwargs[k] self.args = args self.kwargs = kwargs self.return_args = dict(kwargs) self.return_args.update(bound_args) self.return_args.update(add_later) self._apply_indirect()
def fill_signature(self, sig: inspect.Signature) -> inspect.BoundArguments: # TODO: fill in **options arg # TODO: log more about untyped values, mismatch types, etc. # TODO: support automatically convert str to Path. params_type_args = [] kwargs = {} missing_params = [] var_positional_name = None for name, param in sig.parameters.items(): if param.annotation == Args: params_type_args.append(name) elif name in self.named: # Matching arg name and param name: try to fill in this param dtype = None as_list = False if param.annotation in [int, float, bool]: dtype = param.annotation elif param.annotation == list or get_origin( param.annotation) == list: as_list = True if get_args(param.annotation) is not None and len( get_args(param.annotation)) > 0: if get_args(param.annotation)[0] in [int, float, bool]: dtype = get_args(param.annotation)[0] if not as_list: kwargs[name] = self.named[name].as_dtype(dtype) else: kwargs[name] = self.named[name].as_list(dtype) elif param.kind == inspect.Parameter.VAR_POSITIONAL: var_positional_name = name elif param.default == inspect.Parameter.empty: missing_params.append(name) if len(params_type_args) == 1: # If exactly one parameter has the type of Args, bound this object to that parameter logger.info(f"Binding the entire args to {params_type_args[0]}") ret = sig.bind_partial(**{params_type_args[0]: self}) else: # Otherwise, try to assign fields from arguments to the parameters with appropriate types # Warn about missing params if len(missing_params) > 0: logger.warning( f"The following parameters may be missing: {missing_params}" ) # Debug about unused args unused_args = [k for k in self.named if k not in kwargs] if len(unused_args) > 0: logger.info( f"The following named arguments are not used: {unused_args}" ) # If there is a *param parameter not already filled, put the free arguments in if var_positional_name is not None: kwargs[var_positional_name] = [arg.auto_ for arg in self.free] else: logger.info( f"The following free arguments are not used: {self.free}") ret = sig.bind_partial(**kwargs) ret.apply_defaults() return ret