Пример #1
0
    def returntype(self):
        lhs_val: DataType = self.args[0]
        rhs_val: DataType = self.args[1]

        if isinstance(lhs_val, Selector):
            lhs = get_instantiated_type(lhs_val.returntype())
        else:
            lhs = get_instantiated_type(lhs_val)

        if isinstance(rhs_val, Selector):
            rhs = get_instantiated_type(rhs_val.returntype())
        else:
            rhs = get_instantiated_type(rhs_val)

        if isinstance(lhs, (String, File, Directory)) or isinstance(
                rhs, (String, File, Directory)):
            return String()
        if isinstance(lhs, Float) or isinstance(rhs, Float):
            return Double()
        if isinstance(lhs, Float) or isinstance(rhs, Float):
            return Float()
        if isinstance(lhs, Int) and isinstance(rhs, Int):
            return Int()

        raise TypeError(
            f"Unsure how to derive returntype from {lhs.id()} + {rhs.id()}")
Пример #2
0
    def find_connection(
            self, source_dt: ParseableType,
            desired_dt: ParseableType) -> List[JanisTransformation]:

        from inspect import getmro

        source = get_instantiated_type(source_dt)
        desired = get_instantiated_type(desired_dt)

        if desired.can_receive_from(source):
            return []

        types = getmro(type(source))

        for T in types:
            if not issubclass(T, DataType) or T == DataType:
                continue

            transformation = self.find_connection_inner(T, desired)
            if transformation is not None:
                return transformation

        raise Exception(
            f"There's no transformation that can satisfy {source.name()} -> {desired.name()}"
        )
Пример #3
0
 def returntype(self):
     args = []
     for a in self.args[1:]:
         if isinstance(a, Selector):
             args.append(get_instantiated_type(a.returntype()))
         else:
             args.append(get_instantiated_type(a))
     return UnionType(*args)
Пример #4
0
    def add_source(self, operator: Selector, should_scatter) -> Edge:
        """
        Add a connection
        :param start:
        :param stag:
        :param should_scatter:
        :return:
        """

        from janis_core.workflow.workflow import StepNode

        # start: Node, stag: Optional[str]

        # stype = (start.outputs()[stag] if stag is not None else first_value(start.outputs())).outtype
        stype = get_instantiated_type(operator.returntype())
        ftype = (self.finish.inputs()[self.ftag] if self.ftag is not None else
                 first_value(self.finish.inputs())).intype

        # start_is_scattered = isinstance(start, StepNode) and start.scatter is not None
        #
        # if start_is_scattered:
        #     Logger.log(
        #         f"This edge merges the inputs from '{full_dot(start, stag)}' for "
        #         f"'{full_dot(self.finish, self.ftag)}'"
        #     )
        #     stype = Array(stype)

        if should_scatter:
            if not stype.is_array():
                raise Exception(
                    f"Scatter was required for '{operator} → '{self.finish.id()}.{self.ftag}' but "
                    f"the input type was {type(stype).__name__} and not an array"
                )
            stype = get_instantiated_type(stype.subtype())

        if len(self.source_map) == 1:  # and start.id() not in self.source_map:
            self.multiple_inputs = True

            if not ftype.is_array():
                raise Exception(
                    f"Adding multiple inputs to '{self.finish.id()}' and '{ftype.id()}' is not an array"
                )

        if not stype.is_array() and ftype.is_array():
            # https://www.commonwl.org/user_guide/misc/#connect-a-solo-value-to-an-input-that-expects-an-array-of-that-type
            self.multiple_inputs = True

        e = Edge(operator,
                 self.finish,
                 self.ftag,
                 should_scatter=should_scatter)
        # todo: deal with source_map
        self.source_map.append(e)
        return e
Пример #5
0
    def returntype(self):
        from copy import copy

        a = self.args[0]
        if isinstance(a, Selector):
            ret = get_instantiated_type(a.returntype())
        else:
            ret = get_instantiated_type(a)

        ret = copy(ret)
        ret.optional = False
        return ret
Пример #6
0
    def output(
        self,
        identifier: str,
        datatype: Optional[ParseableType] = None,
        source: Union[StepNode, ConnectionSource] = None,
        output_tag: Union[
            str,
            InputSelector,
            ConnectionSource,
            List[Union[str, InputSelector, ConnectionSource]],
        ] = None,
        output_prefix: Union[str, InputSelector, ConnectionSource] = None,
    ):
        """
        Create an output on a workflow

        :param identifier: The identifier for the output
        :param datatype: Optional data type of the output to check. This will be automatically inferred if not provided.
        :param source: The source of the output, must be an output to a step node
        :param output_tag: A janis annotation for grouping outputs by this value.  If a list is passed, it represents
        a structure of nested directories, the first element being the root directory.
        At most, one InputSelector can resolve to an array, and this behaviour is only defined if the output
        scattered source, and the number of elements is equal.
        :param output_prefix: Decides the prefix that an output will have, or acts as a map if the InputSelector
        resolves to an array with equal length to the number of shards (scatters). Any other behaviour is defined and
        may result in an unexpected termination.
        :return:
        """
        self.verify_identifier(identifier, repr(datatype))

        if source is None:
            raise Exception("Output source must not be 'None'")

        node, tag = verify_or_try_get_source(source)
        if not datatype:
            datatype: DataType = node.outputs()[tag].output_type.received_type()

            if isinstance(node, StepNode) and node.scatter:
                datatype = Array(datatype)

        if output_prefix:
            op = output_prefix if isinstance(output_prefix, list) else [output_prefix]
            output_prefix = self.verify_output_source_type(
                identifier, op, "output_prefix"
            )
        if output_tag:
            ot = output_tag if isinstance(output_tag, list) else [output_tag]
            output_tag = self.verify_output_source_type(identifier, ot, "output_tag")

        otp = OutputNode(
            self,
            identifier=identifier,
            datatype=get_instantiated_type(datatype),
            source=(node, tag),
            output_tag=output_tag,
            output_prefix=output_prefix,
        )
        self.nodes[identifier] = otp
        self.output_nodes[identifier] = otp
        return otp
Пример #7
0
    def input(
        self,
        identifier: str,
        datatype: ParseableType,
        default: any = None,
        value: any = None,
        doc: Union[str, InputDocumentation] = None,
    ):
        """
        Create an input node on a workflow
        :return:
        """

        self.verify_identifier(identifier, repr(datatype))

        datatype = get_instantiated_type(datatype)
        if default is not None:
            datatype.optional = True

        doc = (doc if isinstance(doc, InputDocumentation) else
               InputDocumentation(doc=doc))

        inp = InputNode(
            self,
            identifier=identifier,
            datatype=datatype,
            default=default,
            doc=doc,
            value=value,
        )
        self.nodes[identifier] = inp
        self.input_nodes[identifier] = inp
        return inp
Пример #8
0
    def __init__(
        self, input_to_select, remove_file_extension=None, type_hint=File, **kwargs
    ):
        """
        :param input_to_select: The name of the input to select
        :param remove_file_extension: Call basename() and remove the file extension
        :param type_hint: Janis can't determine the type of the input to select until translation time,
            so providing a hint type might suppress false warnings. This is similar to using .as_type(dt)
        """

        if not isinstance(input_to_select, str):
            raise Exception(
                f"Expected input_to_select to be string, not {type(input_to_select)}: {str(input_to_select)}"
            )

        # maybe worth validating the input_to_select identifier
        self.input_to_select = input_to_select
        self.type_hint = get_instantiated_type(type_hint) or File()

        if "use_basename" in kwargs:
            use_basename = kwargs["use_basename"]
            if remove_file_extension is None:
                remove_file_extension = use_basename
            Logger.warn(
                f"The 'use_basename' key is deprecated, please use 'remove_file_extension' instead: "
                f'InputSelector("{self.input_to_select}", remove_file_extension={str(use_basename)})'
            )

        self.remove_file_extension = remove_file_extension
Пример #9
0
    def __init__(
        self,
        tag: str,
        output_type: ParseableType,
        glob: Optional[Union[Selector, str]] = None,
        doc: Optional[str] = None,
    ):
        """
        A ToolOutput instructs the the engine how to collect an output and how
        it may be referenced in a workflow.

        :param tag: The identifier of a output, must be unique in the inputs and outputs.
        :param output_type: The type of output that is being collected.
        :param glob: How to collect this output, can accept any :class:`janis.Selector`.
        :param doc: Documentation on what the output is, used to generate docs.
        """

        if not Validators.validate_identifier(tag):
            raise Exception(
                f"The identifier '{tag}' was invalid because {Validators.reason_for_failure(tag)}"
            )

        self.tag = tag
        self.output_type: ParseableType = get_instantiated_type(output_type)
        self.glob = glob
        self.doc = doc
Пример #10
0
    def input(
        self,
        identifier: str,
        datatype: ParseableType,
        default: any = None,
        value: any = None,
        doc: str = None,
    ):
        """
        Create an input node on a workflow
        :return:
        """

        self.verify_identifier(identifier, repr(datatype))

        datatype = get_instantiated_type(datatype)
        if default:
            datatype.optional = True

        inp = InputNode(
            self,
            identifier=identifier,
            datatype=datatype,
            default=default,
            doc=doc,
            value=value,
        )
        self.nodes[identifier] = inp
        self.input_nodes[identifier] = inp
        return inp
Пример #11
0
    def inputs(self):
        if self._cached_input_signature is None:

            import inspect

            argspec = inspect.signature(self.code_block)
            docstrings = parse_docstring(self.code_block.__doc__)
            paramlist = docstrings.get("params", [])
            paramdocs = {
                p["name"]: p.get("doc").strip()
                for p in paramlist if "name" in p
            }

            missing_annotations = set()
            unsupported_types = {}

            ins = []
            for inp in argspec.parameters.values():
                if inp.name in inspect_ignore_keys:
                    continue
                fdefault = inp.default
                optional = (fdefault
                            is not inspect.Parameter.empty) or fdefault is None
                default = fdefault if optional else None

                defaulttype = type(fdefault) if fdefault is not None else None
                annotation = (defaulttype
                              if inp.annotation is inspect.Parameter.empty else
                              inp.annotation)
                if not annotation:
                    missing_annotations.add(inp.name)
                    continue

                dt_type: Optional[DataType] = get_instantiated_type(
                    annotation, optional=optional)
                if not dt_type:
                    unsupported_types[inp.name] = annotation
                    continue

                ins.append(
                    TInput(
                        tag=inp.name,
                        intype=dt_type,
                        default=default,
                        doc=InputDocumentation(paramdocs.get(inp.name)),
                    ))

            if missing_annotations:
                raise Exception(
                    f"The following types on the PythonTool '{self.id()}' were missing type annotations (REQUIRED): "
                    + ", ".join(missing_annotations))

            if unsupported_types:
                raise Exception(f"Unsupported types for inputs: " + ", ".join(
                    f"{k}: {v}" for k, v in unsupported_types.items()))
            self._cached_input_signature = ins

        return self._cached_input_signature
Пример #12
0
    def __init__(
        self,
        tag: str,
        output_type: ParseableType,
        glob: Optional[Union[Selector, str]] = None,
        presents_as: str = None,
        secondaries_present_as: Dict[str, str] = None,
        doc: Optional[Union[str, OutputDocumentation]] = None,
    ):
        """
        A ToolOutput instructs the the engine how to collect an output and how
        it may be referenced in a workflow.

        :param tag: The identifier of a output, must be unique in the inputs and outputs.
        :param output_type: The type of output that is being collected.
        :param glob: How to collect this output, can accept any :class:`janis.Selector`.
        :param doc: Documentation on what the output is, used to generate docs.
        """

        if not Validators.validate_identifier(tag):
            raise Exception(
                f"The identifier '{tag}' was invalid because {Validators.reason_for_failure(tag)}"
            )

        self.tag = tag
        self.output_type: ParseableType = get_instantiated_type(output_type)

        if not glob and not (
            isinstance(self.output_type, Stdout) or isinstance(self.output_type, Stderr)
        ):
            raise Exception(
                "ToolOutput expects a glob when the output type is not Stdout / Stderr"
            )

        self.glob = glob
        self.presents_as = presents_as
        self.secondaries_present_as = secondaries_present_as
        self.doc = (
            doc
            if isinstance(doc, OutputDocumentation)
            else OutputDocumentation(doc=doc)
        )

        if self.secondaries_present_as:
            if not self.output_type.secondary_files():
                raise Exception(
                    f"The ToolOutput '{self.id()}' requested a rewrite of secondary file extension through "
                    f"'secondaries_present_as', but the type {self.output_type.id()} not have any secondary files."
                )
            secs = set(self.output_type.secondary_files())
            to_remap = set(self.secondaries_present_as.keys())
            invalid = to_remap - secs
            if len(invalid) > 0:
                raise Exception(
                    f"Error when constructing output '{self.id()}', the secondaries_present_as contained secondary "
                    f"files ({', '.join(invalid)}) that were not found in the output "
                    f"type '{self.output_type.id()}' ({', '.join(secs)})"
                )
Пример #13
0
 def __init__(self,
              tag: str,
              intype: DataType,
              default=None,
              doc: InputDocumentation = None):
     self.tag = tag
     self.intype = get_instantiated_type(intype)
     self.default = default
     self.doc = doc
Пример #14
0
    def to_wdl(self, unwrap_operator, *args):
        iterable, separator = [unwrap_operator(a) for a in self.args]
        iterable_arg = self.args[0]
        if isinstance(iterable_arg, list):
            is_optional = any(
                get_instantiated_type(a.returntype()).optional for a in iterable_arg
            )
        else:
            rettype = get_instantiated_type(iterable_arg.returntype())
            if rettype.is_array():
                is_optional = rettype.subtype().optional
            else:
                is_optional = rettype.optional

        if is_optional:
            return f"sep({separator}, select_first([{iterable}, []]))"
        else:
            return f"sep({separator}, {iterable})"
Пример #15
0
    def returntype(self):
        if isinstance(self.args[0], list):
            rettype = self.args[0][0].returntype()
        else:
            outer_rettype = get_instantiated_type(self.args[0].returntype())
            if not isinstance(outer_rettype, Array):
                # hmmm, this could be a bad input selector
                rettype = outer_rettype
                if not isinstance(self.args[0], InputSelector):
                    Logger.warn(
                        f'Expected return type of "{self.args[0]}" to be an array, '
                        f'but found {outer_rettype}, will return this as a returntype.'
                    )
            else:
                rettype = outer_rettype.subtype()

        rettype = copy(get_instantiated_type(rettype))
        rettype.optional = False
        return Array(rettype)
Пример #16
0
    def __init__(
        self,
        tag: str,
        input_type: ParseableType,
        position: Optional[int] = None,
        prefix: Optional[str] = None,
        separate_value_from_prefix: bool = None,
        prefix_applies_to_all_elements: bool = None,
        separator: str = None,
        shell_quote: bool = None,
        localise_file: bool = None,
        default: Any = None,
        doc: Optional[str] = None,
    ):
        """
        A ``ToolInput`` represents an input to a tool, with parameters that allow it to be bound on the command line.
        The ToolInput must have either a position or prefix set to be bound onto the command line.

        :param tag: The identifier of the input (unique to inputs and outputs of a tool)
        :param input_type: The data type that this input accepts
        :type input_type: ``janis.ParseableType``
        :param position: The position of the input to be applied. (Default = 0, after the base_command).
        :param prefix: The prefix to be appended before the element. (By default, a space will also be applied, see ``separate_value_from_prefix`` for more information)
        :param separate_value_from_prefix: (Default: True) Add a space between the prefix and value when ``True``.
        :param prefix_applies_to_all_elements: Applies the prefix to each element of the array (Array inputs only)
        :param shell_quote: Stops shell quotes from being applied in all circumstances, useful when joining multiple commands together.
        :param separator: The separator between each element of an array (defaults to ' ')
        :param localise_file: Ensures that the file(s) are localised into the execution directory.
        :param default: The default value to be applied if the input is not defined.
        :param doc: Documentation string for the ToolInput, this is used to generate the tool documentation and provide
        hints to the user.
        """
        super().__init__(
            value=None,
            prefix=prefix,
            position=position,
            separate_value_from_prefix=separate_value_from_prefix,
            doc=doc,
            shell_quote=shell_quote,
        )

        # if default is not None:
        #     input_type.optional = True

        if not Validators.validate_identifier(tag):
            raise Exception(
                f"The identifier '{tag}' was not validated because {Validators.reason_for_failure(tag)}"
            )

        self.tag: str = tag
        self.input_type: ParseableType = get_instantiated_type(input_type)
        self.default = default
        self.prefix_applies_to_all_elements = prefix_applies_to_all_elements
        self.separator = separator
        self.localise_file = localise_file
Пример #17
0
    def basename(self):
        from .standard import BasenameOperator

        outtype = get_instantiated_type(self.returntype()).received_type()
        if not isinstance(outtype, (File, Directory)):
            raise Exception(
                "Basename operator can only be applied to steps of File / Directory type, received: "
                + str(outtype)
            )

        return BasenameOperator(self)
Пример #18
0
    def __init__(
        self,
        start_type: ParseableType,
        finish_type: ParseableType,
        tool: Tool,
        relevant_tool_input: Optional[str] = None,
        relevant_tool_output: Optional[str] = None,
    ):
        self.type1 = get_instantiated_type(start_type)
        self.type2 = get_instantiated_type(finish_type)
        self.tool = tool

        connection_type = f"`{self.type1} -> {self.type2}`"

        Logger.log(
            f"Building transformation for {connection_type} using tool '{tool.id()}"
        )

        self.relevant_tool_input = self.evaluate_tool_input(
            relevant_tool_input)
        self.relevant_tool_output = self.evaluate_tool_output(
            relevant_tool_output)
Пример #19
0
    def validate(self, perform_typecheck=False):
        args = self.args
        argtypes = self.argtypes()

        if len(args) < len(argtypes):
            # missing params
            nmissing = len(argtypes) - len(args)
            raise TypeError(
                f"{self.__class__.__name__} missing {nmissing} required positional argument"
            )
        elif len(args) > len(argtypes):
            raise TypeError(
                f"{self.__class__.__name__} expects {len(argtypes)} arguments, but {len(args)} were given"
            )

        errors = []

        if perform_typecheck:
            for i in range(len(args)):
                expected = get_instantiated_type(argtypes[i])
                received_arg = args[i]
                if isinstance(received_arg, Selector):
                    received = get_instantiated_type(args[i].returntype())
                else:
                    received = get_instantiated_type(received_arg)
                if not expected.can_receive_from(received):
                    errors.append(
                        f"argument {i+1} '{received.id()}' was incompatible with the expected {expected.id()}"
                    )

        if errors:
            singularprefix = "were errors" if len(
                errors) != 1 else "was an error"
            raise TypeError(
                f"There {singularprefix} in the arguments when validating '{self.__class__.__name__}', "
                + ", ".join(errors))

        return True
Пример #20
0
    def check_types(self):
        from janis_core.workflow.workflow import InputNode, StepNode

        # stoolin: TOutput = self.start.outputs()[
        #     self.stag
        # ] if self.stag is not None else first_value(self.start.outputs())
        ftoolin: TInput = (self.finish.inputs()[self.ftag] if self.ftag
                           is not None else first_value(self.finish.inputs()))

        stype = get_instantiated_type(self.source.returntype())
        ftype = get_instantiated_type(ftoolin.intype)

        if self.scatter:
            if not stype.is_array():
                raise Exception(
                    f"Scatter was required for '{self.source} → '{self.finish.id()}.{self.ftag}' but "
                    f"the input type was {type(stype).__name__} and not an array"
                )
            stype = stype.subtype()

        # Scatters are handled automatically by the StepTagInput Array unwrapping
        # Merges are handled automatically by the `start_is_scattered` Array wrap

        self.compatible_types = ftype.can_receive_from(stype, False)
        if not self.compatible_types:
            if ftype.is_array() and ftype.subtype().can_receive_from(stype):
                self.compatible_types = True

        if not self.compatible_types:

            s = str(self.source)
            f = full_dot(self.finish, self.ftag)
            message = (f"Mismatch of types when joining '{s}' to '{f}': "
                       f"{stype.id()} -/→ {ftoolin.intype.id()}")
            if stype.is_array() and ftype.can_receive_from(stype.subtype()):
                message += " (did you forget to SCATTER?)"
            Logger.critical(message)
Пример #21
0
    def find_connection_inner(
            self, source_dt: ParseableType,
            desired_dt: ParseableType) -> Optional[List[JanisTransformation]]:

        source = get_instantiated_type(source_dt)
        desired = get_instantiated_type(desired_dt)

        if desired.can_receive_from(source):
            return []

        queue: List[JanisTransformation] = []
        parent_mapping: Dict[str, JanisTransformation] = {}

        desired_dt_name = desired.name()

        queue.extend(self._edges.get(source.name(), []))

        parent_mapping[source.name()] = None

        while len(queue) > 0:
            edge = queue.pop(0)

            end_name = edge.type2.name()

            if end_name in parent_mapping:
                # we've already got a parent (hence we've already seen it), so let's skip it
                continue

            parent_mapping[end_name] = edge

            if end_name == desired_dt_name:
                return JanisTransformationGraph.trace(parent_mapping, end_name)

            queue.extend(self._edges.get(end_name, []))

        return None
Пример #22
0
    def __init__(
        self,
        tag: str,
        output_type: ParseableType,
        selector: Optional[Union[Selector, str]] = None,
        presents_as: str = None,
        secondaries_present_as: Dict[str, str] = None,
        doc: Optional[Union[str, OutputDocumentation]] = None,
        glob: Optional[Union[Selector, str]] = None,
        _skip_output_quality_check=False,
    ):
        """
        A ToolOutput instructs the the engine how to collect an output and how
        it may be referenced in a workflow.

        :param tag: The identifier of a output, must be unique in the inputs and outputs.
        :param output_type: The type of output that is being collected.
        :param selector: How to collect this output, can accept any :class:`janis.Selector`.
        :param glob: (DEPRECATED) An alias for `selector`
        :param doc: Documentation on what the output is, used to generate docs.
        :param _skip_output_quality_check: DO NOT USE THIS PARAMETER, it's a scapegoat for parsing CWL ExpressionTools when an cwl.output.json is generated
        """

        if not Validators.validate_identifier(tag):
            raise Exception(
                f"The identifier '{tag}' was invalid because {Validators.reason_for_failure(tag)}"
            )

        self.tag = tag
        self.output_type: ParseableType = get_instantiated_type(output_type)
        self._skip_output_quality_check = _skip_output_quality_check

        if selector is None and glob is not None:
            selector = glob
        elif selector is not None and glob is not None:
            raise TypeError(
                f"ToolInput({tag}) received inputs for both selector and glob. Please only use glob"
            )

        if (not _skip_output_quality_check and selector is None
                and not (isinstance(self.output_type, Stdout)
                         or isinstance(self.output_type, Stderr))):
            raise Exception(
                "ToolOutput expects a 'selector=' param when the output type is not Stdout / Stderr"
            )

        self.selector = selector
        self.presents_as = presents_as
        self.secondaries_present_as = secondaries_present_as
        self.doc = (doc if isinstance(doc, OutputDocumentation) else
                    OutputDocumentation(doc=doc))

        if isinstance(selector, Operator) and self.presents_as:
            raise Exception(
                f"Error when constructing output '{self.id()}', Janis does not support 'presents_as' AND "
                "operators within a ToolOutput selector. Please raise an issue if you think this is in error."
            )

        if self.secondaries_present_as:
            if not self.output_type.secondary_files():
                raise Exception(
                    f"The ToolOutput '{self.id()}' requested a rewrite of secondary file extension through "
                    f"'secondaries_present_as', but the type {self.output_type.id()} not have any secondary files."
                )
            secs = set(self.output_type.secondary_files())
            to_remap = set(self.secondaries_present_as.keys())
            invalid = to_remap - secs
            if len(invalid) > 0:
                raise Exception(
                    f"Error when constructing output '{self.id()}', the secondaries_present_as contained secondary "
                    f"files ({', '.join(invalid)}) that were not found in the output "
                    f"type '{self.output_type.id()}' ({', '.join(secs)})")
Пример #23
0
 def __init__(self, tag, outtype, doc: OutputDocumentation = None):
     self.tag = tag
     self.outtype = get_instantiated_type(outtype)
     self.doc: Optional[OutputDocumentation] = doc
Пример #24
0
    def step(
        self,
        identifier: str,
        tool: Tool,
        scatter: Union[str, ScatterDescription] = None,
        ignore_missing=False,
    ):
        """
        Method to do
        :param identifier:
        :param tool:
        :param scatter: Indicate whether a scatter should occur, on what, and how
        :type scatter: Union[str, ScatterDescription]
        :param ignore_missing: Don't throw an error if required params are missing from this function
        :return:
        """

        self.verify_identifier(identifier, tool.id())

        if scatter is not None and isinstance(scatter, str):

            scatter = ScatterDescription(
                [scatter] if isinstance(scatter, str) else scatter
            )

        # verify scatter
        if scatter:
            ins = set(tool.inputs_map().keys())
            fields = set(scatter.fields)
            if any(f not in ins for f in fields):
                # if there is a field not in the input map, we have a problem
                extra_keys = ", ".join(f"'{f}'" for f in (fields - ins))
                raise Exception(
                    f"Couldn't scatter the field(s) for step '{identifier}' "
                    f"(tool: '{tool}') {extra_keys} as they were not found in the input map"
                )

        tool.workflow = self
        inputs = tool.inputs_map()

        connections = tool.connections

        provided_keys = set(connections.keys())
        all_keys = set(inputs.keys())
        required_keys = set(
            i for i, v in inputs.items() if not v.input_type.optional and not v.default
        )

        if not provided_keys.issubset(all_keys):
            unrecparams = ", ".join(provided_keys - all_keys)

            tags = ", ".join([f"in.{i}" for i in all_keys])

            raise Exception(
                f"Unrecognised parameters {unrecparams} when creating '{identifier}' ({tool.id()}). "
                f"Expected types: {tags}"
            )

        if not ignore_missing and not required_keys.issubset(provided_keys):
            missing = ", ".join(f"'{i}'" for i in (required_keys - provided_keys))
            raise Exception(
                f"Missing the parameters {missing} when creating '{identifier}' ({tool.id()})"
            )

        stp = StepNode(self, identifier=identifier, tool=tool, scatter=scatter)

        added_edges = []
        for (k, v) in connections.items():

            if is_python_primitive(v):
                inp_identifier = f"{identifier}_{k}"

                referencedtype = copy.copy(inputs[k].input_type)
                parsed_type = get_instantiated_type(v)

                if parsed_type and not referencedtype.can_receive_from(parsed_type):
                    raise TypeError(
                        f"The type {parsed_type.id()} inferred from the value '{v}' is not "
                        f"compatible with the '{identifier}.{k}' type: {referencedtype.id()}"
                    )

                referencedtype.optional = True

                v = self.input(inp_identifier, referencedtype, default=v)
            if v is None:
                inp_identifier = f"{identifier}_{k}"
                v = self.input(inp_identifier, inputs[k].input_type, default=v)

            verifiedsource = verify_or_try_get_source(v)
            if isinstance(verifiedsource, list):
                for vv in verifiedsource:
                    added_edges.append(stp._add_edge(k, vv))
            else:
                added_edges.append(stp._add_edge(k, verifiedsource))

        for e in added_edges:

            si = e.finish.sources[e.ftag] if e.ftag else first_value(e.finish.sources)
            self.has_multiple_inputs = self.has_multiple_inputs or si.multiple_inputs

        self.has_scatter = self.has_scatter or scatter is not None
        self.has_subworkflow = self.has_subworkflow or isinstance(tool, Workflow)
        self.nodes[identifier] = stp
        self.step_nodes[identifier] = stp

        return stp
Пример #25
0
 def test_parse_primitive_bool(self):
     t = get_instantiated_type(bool)
     self.assertIsInstance(t, Boolean)
Пример #26
0
 def test_parse_instantiated_bool(self):
     t = get_instantiated_type(Boolean())
     self.assertIsInstance(t, Boolean)
Пример #27
0
 def test_parse_union_type(self):
     t = get_instantiated_type(Union[str, int])
     self.assertIsInstance(t.subtypes[0], String)
     self.assertIsInstance(t.subtypes[1], Int)
     self.assertEqual("Union<String, Integer>", t.id())
Пример #28
0
 def test_parse_optional_file(self):
     t = get_instantiated_type(Optional[File])
     self.assertTrue(t.optional)
     self.assertIsInstance(t, File)
Пример #29
0
    def __init__(
        self,
        tag: str,
        input_type: ParseableType,
        position: Optional[int] = None,
        prefix: Optional[str] = None,
        separate_value_from_prefix: bool = None,
        prefix_applies_to_all_elements: bool = None,
        presents_as: str = None,
        secondaries_present_as: Dict[str, str] = None,
        separator: str = None,
        shell_quote: bool = None,
        localise_file: bool = None,
        default: Any = None,
        doc: Optional[Union[str, InputDocumentation]] = None,
    ):
        """
        A ``ToolInput`` represents an input to a tool, with parameters that allow it to be bound on the command line.
        The ToolInput must have either a position or prefix set to be bound onto the command line.

        :param tag: The identifier of the input (unique to inputs and outputs of a tool)
        :param input_type: The data type that this input accepts
        :type input_type: ``janis.ParseableType``
        :param position: The position of the input to be applied. (Default = 0, after the base_command).
        :param prefix: The prefix to be appended before the element. (By default, a space will also be applied, see ``separate_value_from_prefix`` for more information)
        :param separate_value_from_prefix: (Default: True) Add a space between the prefix and value when ``True``.
        :param prefix_applies_to_all_elements: Applies the prefix to each element of the array (Array inputs only)
        :param shell_quote: Stops shell quotes from being applied in all circumstances, useful when joining multiple commands together.
        :param separator: The separator between each element of an array (defaults to ' ')
        :param localise_file: Ensures that the file(s) are localised into the execution directory.
        :param default: The default value to be applied if the input is not defined.
        :param doc: Documentation string for the ToolInput, this is used to generate the tool documentation and provide
        hints to the user.
        """
        super().__init__(
            value=None,
            prefix=prefix,
            position=position,
            separate_value_from_prefix=separate_value_from_prefix,
            doc=None,
            shell_quote=shell_quote,
        )

        self.doc: InputDocumentation = (doc if isinstance(
            doc, DocumentationMeta) else InputDocumentation(doc=doc))

        # if default is not None:
        #     input_type.optional = True

        if not Validators.validate_identifier(tag):
            raise Exception(
                f"The identifier '{tag}' was not validated because {Validators.reason_for_failure(tag)}"
            )

        self.tag: str = tag
        self.input_type: ParseableType = get_instantiated_type(input_type)
        self.default = default
        self.prefix_applies_to_all_elements = prefix_applies_to_all_elements
        self.separator = separator

        self.localise_file = localise_file
        self.presents_as = presents_as
        self.secondaries_present_as = secondaries_present_as

        if self.secondaries_present_as:
            if not self.input_type.secondary_files():
                raise Exception(
                    f"The ToolOutput '{self.id()}' requested a rewrite of secondary file extension through "
                    f"'secondaries_present_as', but the type {self.input_type.id()} not have any secondary files."
                )
            secs = set(self.input_type.secondary_files())
            to_remap = set(self.secondaries_present_as.keys())
            invalid = to_remap - secs
            if len(invalid) > 0:
                raise Exception(
                    f"Error when constructing output '{self.id()}', the secondaries_present_as contained secondary "
                    f"files ({', '.join(invalid)}) that were not found in the output "
                    f"type '{self.input_type.id()}' ({', '.join(secs)})")
Пример #30
0
 def test_parse_instantiated_float(self):
     t = get_instantiated_type(Float())
     self.assertIsInstance(t, Float)