Example #1
0
    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