def create_commandlinetool( step: Dict[str, Union[str, List]]) -> cwlgen.CommandLineTool: name = step['name'] description = step['description'] # Initialize commandlinetool # TODO: Specify path tool_object = cwlgen.CommandLineTool(tool_id=name, base_command=f"./{name}.py", doc=description, cwl_version="v1.0") # Specify input parameters tool_object.inputs += [ cwlgen.CommandInputParameter(p, param_type=DEFAULT_TYPE) for p in step['input'] ] # Specify output parameters tool_object.outputs += [ cwlgen.CommandInputParameter(p, param_type=DEFAULT_TYPE) for p in step['output'] ] return tool_object
def setUp(self): self.frst_inp = cwlgen.CommandInputParameter('frst_id', param_type='File',\ label='a_label', \ default='def_value') binding = cwlgen.CommandLineBinding(position=2, prefix='--prefix') self.scnd_inp = cwlgen.CommandInputParameter('scnd_id', param_type='File',\ input_binding=binding)
def create_command_line_tool(node): """ Create a command line tool description file for a single step in a CWL workflow. NOTE: CWL only supports workflow steps that are bash shell applications Non-BashShellApp nodes are unable to be implemented in CWL """ # get inputs and outputs inputs = node.get('inputs', []) outputs = node.get('outputs', []) # strip command down to just the basic command, with no input or output parameters base_command = node.get('command', '') # TODO: find a better way of specifying command line program + arguments base_command = base_command[:base_command.index(" ")] base_command = common.u2s(base_command) # cwlgen's Serializer class doesn't support python 2.7's unicode types cwl_tool = cwlgen.CommandLineTool(tool_id=common.u2s(node['app']), label=common.u2s(node['nm']), base_command=base_command, cwl_version='v1.0') # add inputs for index, input in enumerate(inputs): file_binding = cwlgen.CommandLineBinding(position=index) input_file = cwlgen.CommandInputParameter('input_file_' + str(index), param_type='File', input_binding=file_binding, doc='input file ' + str(index)) cwl_tool.inputs.append(input_file) if len(inputs) == 0: cwl_tool.inputs.append( cwlgen.CommandInputParameter('dummy', param_type='null', doc='dummy')) # add outputs for index, output in enumerate(outputs): file_binding = cwlgen.CommandLineBinding() output_file = cwlgen.CommandOutputParameter( 'output_file_' + str(index), param_type='stdout', output_binding=file_binding, doc='output file ' + str(index)) cwl_tool.outputs.append(output_file) return cwl_tool.export_string()
def _convert_input(input, basedir): """Converts an input to a CWL input.""" entity = input.consumes type_ = 'Directory' if isinstance(entity, Collection) else 'File' sanitized_id = input.sanitized_id if input.mapped_to: sanitized_id = 'input_stdin' separate = None prefix = None if input.prefix: prefix = input.prefix separate = False if prefix.endswith(' '): prefix = prefix[:-1] separate = True return cwlgen.CommandInputParameter( sanitized_id, param_type=type_, input_binding=cwlgen.CommandLineBinding(position=input.position, prefix=prefix, separate=separate), default={ 'path': os.path.abspath(os.path.join(basedir, entity.path)), 'class': type_ })
def _convert_input(input, basedir): """Converts an input to a CWL input.""" entity = input.consumes type_ = "Directory" if isinstance(entity, Collection) else "File" sanitized_id = input.sanitized_id sanitized_id = sanitized_id.replace("/", "_") if input.mapped_to: sanitized_id = "input_stdin" separate = None prefix = None if input.prefix: prefix = input.prefix separate = False if prefix.endswith(" "): prefix = prefix[:-1] separate = True return cwlgen.CommandInputParameter( sanitized_id, param_type=type_, input_binding=cwlgen.CommandLineBinding(position=input.position, prefix=prefix, separate=separate), default={ "path": os.path.abspath(os.path.join(basedir, entity.path)), "class": type_ }, )
def add_input_file(self, input_obj): """ Add an input to the CWL tool. :param input_obj: Input object. :type input_obj: :class:`tooldog.biotool_model.Input` """ LOGGER.info("Adding input to CwlToolGen object...") # Build parameter self.input_ct += 1 # Give unique name to the input name = 'INPUT' + str(self.input_ct) # Get all different formats for this input list_formats = [] for format_obj in input_obj.formats: list_formats.append(format_obj.uri) formats = ', '.join(list_formats) # Create the parameter param_binding = cwlgen.CommandLineBinding(prefix='--' + name) param = cwlgen.CommandInputParameter(name, param_type='File', label=input_obj.data_type.term, param_format=formats, input_binding=param_binding) # Appends parameter to inputs self.tool.inputs.append(param)
def translate_tool_input(toolinput: ToolInput) -> cwlgen.CommandInputParameter: default, value_from = toolinput.default, None if isinstance(toolinput.input_type, Filename): default = toolinput.input_type.generated_filename() # value_from = get_input_value_from_potential_selector_or_generator(toolinput.input_type, code_environment=False, toolid=toolinput.id()) elif is_selector(default): default = None value_from = get_input_value_from_potential_selector_or_generator( toolinput.default, code_environment=False, toolid=toolinput.id()) data_type = toolinput.input_type.cwl_type(default is not None) input_binding = cwlgen.CommandLineBinding( # load_contents=toolinput.load_contents, position=toolinput.position, prefix=toolinput.prefix, separate=toolinput.separate_value_from_prefix, item_separator=toolinput.separator, value_from=value_from, shell_quote=toolinput.shell_quote, ) non_optional_dt_component = ([t for t in data_type if t != "null"][0] if isinstance(data_type, list) else data_type) # Binding array inputs onto the console # https://www.commonwl.org/user_guide/09-array-inputs/ if isinstance(toolinput.input_type, Array) and isinstance( non_optional_dt_component, cwlgen.CommandInputArraySchema): if toolinput.prefix_applies_to_all_elements: input_binding.prefix = None input_binding.separate = None nested_binding = cwlgen.CommandLineBinding( # load_contents=toolinput.load_contents, prefix=toolinput.prefix, separate=toolinput.separate_value_from_prefix, # item_separator=toolinput.item_separator, # value_from=toolinput.value_from, shell_quote=toolinput.shell_quote, ) non_optional_dt_component.inputBinding = nested_binding return cwlgen.CommandInputParameter( param_id=toolinput.tag, label=toolinput.tag, secondary_files=toolinput.input_type.secondary_files(), # streamable=None, doc=toolinput.doc, input_binding=input_binding, default=default, param_type=data_type, )
def _convert_output(output): """Converts an output to a CWL output.""" if output.mapped_to: return ( cwlgen.CommandOutputParameter( "output_{}".format(output.mapped_to.stream_type), param_type=output.mapped_to.stream_type, streamable=False, ), None, ) entity = output.produces type_ = "Directory" if isinstance(entity, Collection) else "File" sanitized_id = output.sanitized_id.replace("/", "_") if output.position: # output is specified as a parameter, create an input as well separate = None prefix = None if output.prefix: prefix = output.prefix separate = False if prefix.endswith(" "): prefix = prefix[:-1] separate = True arg = cwlgen.CommandInputParameter( "{}_arg".format(sanitized_id), param_type="string", input_binding=cwlgen.CommandLineBinding( position=output.position, prefix=prefix, separate=separate), default=entity.path, ) outp = cwlgen.CommandOutputParameter( sanitized_id, param_type=type_, output_binding=cwlgen.CommandOutputBinding( glob="$(inputs.{})".format(arg.id)), ) return outp, arg return ( cwlgen.CommandOutputParameter( sanitized_id, param_type=type_, output_binding=cwlgen.CommandOutputBinding(glob=entity.path)), None, )
def createStep(cwl_tool, cwl_tool_docker, implementation_file_binding, cases_file_binding, type, doc, input_doc, extension, output_doc, language="knime"): cwl_tool.namespaces.s = "http://phenomics.kcl.ac.uk/phenoflow/" metadata = {'type': type} cwl_tool.metadata = cwlgen.Metadata(**metadata) cwl_tool.doc = doc # Assume run in Docker cwl_tool.requirements.append(cwl_tool_docker) implementation_input_file = cwlgen.CommandInputParameter( "inputModule", param_type='File', input_binding=implementation_file_binding, doc=language[0].upper() + language[1:] + " implementation unit") cwl_tool.inputs.append(implementation_input_file) if ("external" not in type): data_input_file = cwlgen.CommandInputParameter( "potentialCases", param_type='File', input_binding=cases_file_binding, doc=input_doc) cwl_tool.inputs.append(data_input_file) workflow_output_binding = cwlgen.CommandOutputBinding(glob="*." + extension) output = cwlgen.CommandOutputParameter( 'output', doc=output_doc, param_type="File", output_binding=workflow_output_binding) cwl_tool.outputs.append(output) return cwl_tool
def load_inputs(self, inputs, inputs_el): """ Load the content of inputs into the inputs list. :param inputs: list to store inputs :type inputs: LIST :param inputs_el: Content of inputs :type inputs_el: LIST or DICT """ # For the moment, only deal with the format exported by cwlgen for key, value in dict_or_idlist_items(inputs_el): input_obj = cwlgen.CommandInputParameter(key) for key, element in value.items(): try: getattr(self, '_load_{}'.format(key))(input_obj, element) except AttributeError: _LOGGER.warning(key + " content for input is not processed (yet).") inputs.append(input_obj)
def _convert_output(output): """Converts an output to a CWL output.""" if output.mapped_to: return cwlgen.CommandOutputParameter( 'output_{}'.format(output.mapped_to.stream_type), param_type=output.mapped_to.stream_type, streamable=False), None entity = output.produces type_ = 'Directory' if isinstance(entity, Collection) else 'File' if output.position: # output is specified as a parameter, create an input as well separate = None prefix = None if output.prefix: prefix = output.prefix separate = False if prefix.endswith(' '): prefix = prefix[:-1] separate = True arg = cwlgen.CommandInputParameter( '{}_arg'.format(output.sanitized_id), param_type='string', input_binding=cwlgen.CommandLineBinding( position=output.position, prefix=prefix, separate=separate), default=entity.path) outp = cwlgen.CommandOutputParameter( output.sanitized_id, param_type=type_, output_binding=cwlgen.CommandOutputBinding( glob='$(inputs.{})'.format(arg.id))) return outp, arg return cwlgen.CommandOutputParameter( output.sanitized_id, param_type=type_, output_binding=cwlgen.CommandOutputBinding(glob=entity.path)), None
def _convert_argument(argument): """Converts an argument to a CWL input.""" value, type_ = _get_argument_type(argument.value) separate = None prefix = None if argument.prefix: prefix = argument.prefix separate = False if prefix.endswith(' '): prefix = prefix[:-1] separate = True return cwlgen.CommandInputParameter( argument.sanitized_id, param_type=type_, input_binding=cwlgen.CommandLineBinding(position=argument.position, prefix=prefix, separate=separate), default=value)
def generate_cwl(cls): """ Produces a CWL App object which can then be exported to yaml """ import cwlgen module = cls.get_module() # Basic definition of the tool cwl_tool = cwlgen.CommandLineTool(tool_id=cls.name, label=cls.name, base_command=f'python3 -m {module}') #TODO: Add documentation in ceci elements cwl_tool.doc = "Pipeline element from ceci" # Add the inputs of the tool for i,inp in enumerate(cls.input_tags()): input_binding = cwlgen.CommandLineBinding(position=(i+1)) input_param = cwlgen.CommandInputParameter(inp, param_type='File', input_binding=input_binding, doc='Some documentation about the input') cwl_tool.inputs.append(input_param) # Add the definition of the outputs for i,out in enumerate(cls.output_tags()): output_binding = cwlgen.CommandOutputBinding(glob=out) output = cwlgen.CommandOutputParameter(out, param_type='File', output_binding=output_binding, param_format='http://edamontology.org/format_2330', doc='Some results produced by the pipeline element') cwl_tool.outputs.append(output) # Potentially add more metadata metadata = {'name': cls.name, 'about': 'I let you guess', 'publication': [{'id': 'one_doi'}, {'id': 'another_doi'}], 'license': ['MIT']} cwl_tool.metadata = cwlgen.Metadata(**metadata) return cwl_tool
def generate_cwl(cls, log_dir=None): """ Produces a CWL App object which can then be exported to yaml """ import cwlgen module = cls.get_module() module = module.split(".")[0] # Basic definition of the tool cwl_tool = cwlgen.CommandLineTool( tool_id=cls.name, label=cls.name, base_command="python3", cwl_version="v1.0", doc=cls.__doc__, ) if log_dir is not None: cwl_tool.stdout = f"{cls.name}.out" cwl_tool.stderr = f"{cls.name}.err" # Adds the first input binding with the name of the module and pipeline stage input_arg = cwlgen.CommandLineBinding(position=-1, value_from=f"-m{module}") cwl_tool.arguments.append(input_arg) input_arg = cwlgen.CommandLineBinding(position=0, value_from=f"{cls.name}") cwl_tool.arguments.append(input_arg) type_dict = {int: "int", float: "float", str: "string", bool: "boolean"} # Adds the parameters of the tool for opt, def_val in cls.config_options.items(): # Handles special case of lists: if isinstance(def_val, list): v = def_val[0] param_type = { "type": "array", "items": type_dict[v] if isinstance(v, type) else type_dict[type(v)], } default = def_val if not isinstance(v, type) else None input_binding = cwlgen.CommandLineBinding( prefix=f"--{opt}=", item_separator=",", separate=False ) else: param_type = ( type_dict[def_val] if isinstance(def_val, type) else type_dict[type(def_val)] ) default = def_val if not isinstance(def_val, type) else None if param_type == "boolean": input_binding = cwlgen.CommandLineBinding(prefix=f"--{opt}") else: # pragma: no cover input_binding = cwlgen.CommandLineBinding( prefix=f"--{opt}=", separate=False ) input_param = cwlgen.CommandInputParameter( opt, label=opt, param_type=param_type, input_binding=input_binding, default=default, doc="Some documentation about this parameter", ) # We are bypassing the cwlgen builtin type check for the special case # of arrays until that gets added to the standard if isinstance(def_val, list): input_param.type = param_type cwl_tool.inputs.append(input_param) # Add the inputs of the tool for i, inp in enumerate(cls.input_tags()): input_binding = cwlgen.CommandLineBinding(prefix=f"--{inp}") input_param = cwlgen.CommandInputParameter( inp, label=inp, param_type="File", param_format=cls.inputs[i][1].format, # pylint: disable=no-member input_binding=input_binding, doc="Some documentation about the input", ) cwl_tool.inputs.append(input_param) # Adds the overall configuration file input_binding = cwlgen.CommandLineBinding(prefix="--config") input_param = cwlgen.CommandInputParameter( "config", label="config", param_type="File", param_format="http://edamontology.org/format_3750", input_binding=input_binding, doc="Configuration file", ) cwl_tool.inputs.append(input_param) # Add the definition of the outputs for i, out in enumerate(cls.output_tags()): output_name = cls.outputs[i][1].make_name(out) # pylint: disable=no-member output_binding = cwlgen.CommandOutputBinding(glob=output_name) output = cwlgen.CommandOutputParameter( out, label=out, param_type="File", output_binding=output_binding, param_format=cls.outputs[i][1].format, # pylint: disable=no-member doc="Some results produced by the pipeline element", ) cwl_tool.outputs.append(output) if log_dir is not None: output = cwlgen.CommandOutputParameter( f"{cls.name}@stdout", label="stdout", param_type="stdout", doc="Pipeline elements standard output", ) cwl_tool.outputs.append(output) error = cwlgen.CommandOutputParameter( f"{cls.name}@stderr", label="stderr", param_type="stderr", doc="Pipeline elements standard output", ) cwl_tool.outputs.append(error) # Potentially add more metadata # This requires a schema however... # metadata = {'name': cls.name, # 'about': 'Some additional info', # 'publication': [{'id': 'one_doi'}, {'id': 'another_doi'}], # 'license': ['MIT']} # cwl_tool.metadata = cwlgen.Metadata(**metadata) return cwl_tool
def generate_cwl(cls): """ Produces a CWL App object which can then be exported to yaml """ import cwlgen module = cls.get_module() module = module.split('.')[0] # Basic definition of the tool cwl_tool = cwlgen.CommandLineTool(tool_id=cls.name, label=cls.name, base_command='python3', cwl_version='v1.0', doc=cls.__doc__) # Adds the first input binding with the name of the module and pipeline stage input_arg = cwlgen.CommandLineBinding(position=-1, value_from=f'-m{module}') cwl_tool.arguments.append(input_arg) input_arg = cwlgen.CommandLineBinding(position=0, value_from=f'{cls.name}') cwl_tool.arguments.append(input_arg) type_dict = { int: 'int', float: 'float', str: 'string', bool: 'boolean' } # Adds the parameters of the tool for opt in cls.config_options: def_val = cls.config_options[opt] # Handles special case of lists: if type(def_val) is list: v = def_val[0] param_type = { 'type': 'array', 'items': type_dict[v] if type(v) == type else type_dict[type(v)] } default = def_val if type(v) != type else None input_binding = cwlgen.CommandLineBinding( prefix='--{}='.format(opt), item_separator=',', separate=False) else: param_type = type_dict[def_val] if type( def_val) == type else type_dict[type(def_val)] default = def_val if type(def_val) != type else None if param_type is 'boolean': input_binding = cwlgen.CommandLineBinding( prefix='--{}'.format(opt)) else: input_binding = cwlgen.CommandLineBinding( prefix='--{}='.format(opt), separate=False) input_param = cwlgen.CommandInputParameter( opt, label=opt, param_type=param_type, input_binding=input_binding, default=default, doc='Some documentation about this parameter') # We are bypassing the cwlgen builtin type check for the special case # of arrays until that gets added to the standard if type(def_val) is list: input_param.type = param_type cwl_tool.inputs.append(input_param) # Add the inputs of the tool for i, inp in enumerate(cls.input_tags()): input_binding = cwlgen.CommandLineBinding( prefix='--{}'.format(inp)) input_param = cwlgen.CommandInputParameter( inp, label=inp, param_type='File', param_format=cls.inputs[i][1].__name__, input_binding=input_binding, doc='Some documentation about the input') cwl_tool.inputs.append(input_param) # Adds the overall configuration file input_binding = cwlgen.CommandLineBinding(prefix='--config') input_param = cwlgen.CommandInputParameter('config', label='config', param_type='File', param_format='YamlFile', input_binding=input_binding, doc='Configuration file') cwl_tool.inputs.append(input_param) # Add the definition of the outputs for i, out in enumerate(cls.output_tags()): output_name = f'{out}.{cls.outputs[i][1].suffix}' output_binding = cwlgen.CommandOutputBinding(glob=output_name) output = cwlgen.CommandOutputParameter( out, label=out, param_type='File', output_binding=output_binding, param_format=cls.outputs[i][1].__name__, doc='Some results produced by the pipeline element') cwl_tool.outputs.append(output) # Potentially add more metadata # This requires a schema however... # metadata = {'name': cls.name, # 'about': 'Some additional info', # 'publication': [{'id': 'one_doi'}, {'id': 'another_doi'}], # 'license': ['MIT']} # cwl_tool.metadata = cwlgen.Metadata(**metadata) return cwl_tool
import cwlgen cwl_tool = cwlgen.CommandLineTool(tool_id='grep', label='print lines matching a pattern', base_command='grep', cwl_version='v1.0') file_binding = cwlgen.CommandLineBinding(position=2) input_file = cwlgen.CommandInputParameter( 'input_file', param_type='File', input_binding=file_binding, doc='input file from which you want to look for the pattern') cwl_tool.inputs.append(input_file) pattern_binding = cwlgen.CommandLineBinding(position=1) pattern = cwlgen.CommandInputParameter('pattern', param_type='string', input_binding=pattern_binding, doc='pattern to find in the input file') cwl_tool.inputs.append(pattern) cwl_tool.export()
def translate_tool_internal(cls, tool, with_docker=True, with_resource_overrides=False): metadata = tool.metadata if tool.metadata else ToolMetadata() stdouts = [ o.output_type for o in tool.outputs() if isinstance(o.output_type, Stdout) and o.output_type.stdoutname ] stdout = stdouts[0].stdoutname if len(stdouts) > 0 else None if isinstance(stdout, InputSelector): stdout = translate_input_selector(stdout, code_environment=False) tool_cwl = cwlgen.CommandLineTool( tool_id=tool.id(), base_command=tool.base_command(), label=tool.id(), doc=metadata.documentation, cwl_version=CWL_VERSION, stdin=None, stderr=None, stdout=stdout, ) # if any(not i.shell_quote for i in tool.inputs()): tool_cwl.requirements.append(cwlgen.ShellCommandRequirement()) tool_cwl.requirements.extend([cwlgen.InlineJavascriptReq()]) inputs_that_require_localisation = [ ti for ti in tool.inputs() if ti.localise_file and (issubclass(type(ti.input_type), File) or ( issubclass(type(ti.input_type), Array) ) and issubclass(type(ti.input_type.subtype()), File)) ] if inputs_that_require_localisation: tool_cwl.requirements.append( cwlgen.InitialWorkDirRequirement([ "$(inputs.%s)" % ti.id() for ti in inputs_that_require_localisation ])) if with_docker: tool_cwl.requirements.append( cwlgen.DockerRequirement( docker_pull=tool.container(), # docker_load=None, # docker_file=None, # docker_import=None, # docker_image_id=None, # docker_output_dir=None )) tool_cwl.inputs.extend(translate_tool_input(i) for i in tool.inputs()) tool_cwl.outputs.extend( translate_tool_output(o, tool=tool.id()) for o in tool.outputs()) args = tool.arguments() if args: tool_cwl.arguments.extend( translate_tool_argument(a) for a in tool.arguments()) if with_resource_overrides: # work out whether (the tool of) s is a workflow or tool tool_cwl.inputs.extend([ cwlgen.CommandInputParameter("runtime_memory", param_type="float?"), cwlgen.CommandInputParameter("runtime_cpu", param_type="int?"), # cwlgen.CommandInputParameter("runtime_disks", param_type="string?"), ]) tool_cwl.requirements.append( cwlgen.ResourceRequirement( cores_min="$(inputs.runtime_cpu ? inputs.runtime_cpu : 1)", ram_min= "$(inputs.runtime_memory ? Math.floor(1024 * inputs.runtime_memory) : 4096)", )) return tool_cwl
import cwlgen if __name__ == "__main__": # Initialize a command line tool west_tool = cwlgen.CommandLineTool( tool_id='west_tool', label='calculates pw.x, wstat.x and generates pw.out,wstat.out file', base_command='sh') # Add inputs (script_file, URLs, type, no_of_cores) script_binding = cwlgen.CommandLineBinding(position=1) script_file = cwlgen.CommandInputParameter( 'script_file', param_type='File', input_binding=script_binding, doc= 'West shell script which generates output based on inputs using docker' ) west_tool.inputs.append(script_file) file_binding = cwlgen.CommandLineBinding(position=2) input_file = cwlgen.CommandInputParameter( 'input_file', param_type='File', input_binding=file_binding, doc='Input file for west code. Can be pw.in, wstat.in') west_tool.inputs.append(input_file) url_binding = cwlgen.CommandLineBinding(position=3) urls = cwlgen.CommandInputParameter(
import cwlgen if __name__ == "__main__": # Create a tool cwl_tool = cwlgen.CommandLineTool(tool_id='my_tool', label='my_tool is magic',\ base_command='run_my_tool') # Add documentation cwl_tool.doc = "Magic is no magic without secrets..." # Add 2 inputs input_1_binding = cwlgen.CommandLineBinding(position=1) input_1 = cwlgen.CommandInputParameter('config_file', param_type='File',\ input_binding=input_1_binding,\ doc='config file',\ param_format='http://edamontology.org/format_2330') cwl_tool.inputs.append(input_1) input_2_binding = cwlgen.CommandLineBinding(prefix='-t') input_2 = cwlgen.CommandInputParameter('threads', param_type='int',\ input_binding=input_2_binding,\ doc='number of threads') cwl_tool.inputs.append(input_2) # Add 1 output output_1_binding = cwlgen.CommandOutputBinding(glob='counts.txt') output_1 = cwlgen.CommandOutputParameter('result_file', param_type='File',\ output_binding=output_1_binding,\ param_format='http://edamontology.org/format_2330',\ doc='magic results') cwl_tool.outputs.append(output_1)
import cwlgen tool_object = cwlgen.CommandLineTool(tool_id="echo-tool", base_command="echo", label=None, doc=None, cwl_version="v1.0", stdin=None, stderr=None, stdout=None, path=None) tool_object.inputs.append( cwlgen.CommandInputParameter("myParamId", label=None, secondary_files=None, param_format=None, streamable=None, doc=None, input_binding=None, default=None, param_type="string") ) # to get the dictionary representation: dict_to_export = tool_object.get_dict() # to get the string representation (YAML) yaml_export = tool_object.export_string() # print to console tool_object.export() tool_object.export("echotool.cwl")