def _merge_params_and_taskinputs(cls, params, taskinputs): """ params: arguments passed as a dictionary to constructor As a special case, if a task has only one input, it does not need to be wrapped in a dict (i.e. `params` can be the value itself). taskinputs: arguments passed directly as keywords to constructor This function does the following: + Merge dictionary and keyword arguments. Keyword arguments take precedence. + Check that all arguments required by task `run()` signature are provided. + Retrieve any missing argument values from the defaults in `run()` signature. + Cast every input to its expected type. If an input defines multiple allowable types, the left-most one takes precedence. """ if params is None: params = {} elif isinstance(params, str): params = build_parameters(params) elif isinstance(params, dict): params = ParameterSet(params) else: if len(cls.inputs) == 1: # For tasks with only one input, don't require dict θname, θtype = next(iter(cls.inputs.items())) if len(taskinputs) > 0: raise TypeError(f"Argument given by name {θname} " "and position.") # if not isinstance(taskinputs, θtype): # # Cast to correct type # taskinputs = cast(taskinputs, θtype) taskinputs = ParameterSet({θname: taskinputs}) else: raise ValueError("`params` should be either a dictionary " "or a path to a parameter file, however it " "is of type {}.".format(type(params))) taskinputs = {**params, **taskinputs} sigparams = inspect.signature(cls._run).parameters required_inputs = [ p.name for p in sigparams.values() if p.default is inspect._empty ] default_inputs = { p.name: p.default for p in sigparams.values() if p.default is not inspect._empty } if type(cls.__dict__['_run']) is not staticmethod: # instance and class methods already provide 'self' or 'cls' firstarg = required_inputs.pop(0) # Only allowing 'self' and 'cls' ensures we don't accidentally # remove true input arguments assert firstarg in ("self", "cls") if not all((p in taskinputs) for p in required_inputs): raise TypeError("Missing required inputs '{}'.".format( set(required_inputs).difference(taskinputs))) # Add default inputs so they are recorded as task arguments taskinputs = {**default_inputs, **taskinputs} # Finally, cast all task inputs for name, θ in taskinputs.items(): θtype = cls.inputs[name] if isinstance(θ, LazyCastTypes): # Can't cast e.g. tasks: they haven't been executed yet continue elif not isinstance(θ, θtype): taskinputs[name] = cast(θ, θtype, 'input') return taskinputs