diff --git a/README.md b/README.md index 7cd4e11..6782621 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ sbg_cwl_upgrader -i admin/sbg-public-data/whole-exome-sequencing-bwa-gatk-4-0 -o ``` You will additionally be asked if you want to decompose the workflow after installation. +### Save upgraded CWL in version v1.1 +Version of the upgraded app can be controlled with `-c` parameter +``` +sbg_cwl_upgrader -i admin/sbg-public-data/whole-exome-sequencing-bwa-gatk-4-0 -c v1.1-o username/usernames-demo-project/wes +``` + ### Decompose a Platform workflow Sometimes, you want all workflow components to be available in the same project as the workflow. This can be done using the `sbg_cwl_decomposer` tool. This tool will: diff --git a/requirements.txt b/requirements.txt index 96edf68..9cb93a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ setuptools==41.0.1 -sevenbridges-python==0.20.2 +sevenbridges-python==1.0.1 humanfriendly==7.1.1 termcolor==1.1.0 typing==3.6.6 diff --git a/sbg_cwl_upgrader/converter/cwl_converter.py b/sbg_cwl_upgrader/converter/cwl_converter.py index 8d1615e..605e6e1 100644 --- a/sbg_cwl_upgrader/converter/cwl_converter.py +++ b/sbg_cwl_upgrader/converter/cwl_converter.py @@ -20,6 +20,8 @@ logger = logging.getLogger(__name__) +DEFAULT_CWL_VERSION = 'v1.0' + def dict_to_yaml(data: dict, out_path: str): y = YAML() @@ -43,6 +45,7 @@ class CWLConverterFacade: def __init__(self, input_: str, output: str = None, + cwl_version: str = 'v1.0', token: str = None, platform: str = None, app_revision: str = None, @@ -64,6 +67,7 @@ def __init__(self, self.username = None self.input_ = input_ self.output = output + self.cwl_version = cwl_version self.validate = validate self.update = update self.decompose = decompose @@ -235,12 +239,15 @@ def _load_input_cwl(self): return raw - @staticmethod - def _parse(data): + def _parse(self, data): if 'class' in data and isinstance(data['class'], str): if data['class'] == 'CommandLineTool': - return CWLToolConverter().convert_dict(data) + return CWLToolConverter( + cwl_version=self.cwl_version + ).convert_dict(data) elif data['class'] == 'Workflow': - return CWLWorkflowConverter().convert_dict(data) + return CWLWorkflowConverter( + cwl_version=self.cwl_version + ).convert_dict(data) else: raise ValueError('Invalid cwl class.') diff --git a/sbg_cwl_upgrader/converter/sbg_draft2_to_cwl_1_0.py b/sbg_cwl_upgrader/converter/sbg_draft2_to_cwl_1_0.py index ff56d07..d4519fb 100644 --- a/sbg_cwl_upgrader/converter/sbg_draft2_to_cwl_1_0.py +++ b/sbg_cwl_upgrader/converter/sbg_draft2_to_cwl_1_0.py @@ -4,27 +4,31 @@ configure_logging, add_logging_to_args) from sbg_cwl_upgrader.converter.cwl_converter import CWLConverterFacade +from sbg_cwl_upgrader.converter.cwl_converter import DEFAULT_CWL_VERSION def create_arg_parser(): parser = argparse.ArgumentParser( description=' This tool converts CWL draft2 applications ' - '(workflows, command line tools) to CWL v1.0.') + '(workflows, command line tools) to CWL v1.0 or v1.1') parser.add_argument('-i', '--input', required=True, help='can be either draft2 file (YAML, JSON, CWL)' ' path or application ID.') parser.add_argument('-o', '--output', required=True, - help='can be either cwl v1.0 file (YAML, JSON, CWL)' + help='can be either cwl v1 file (YAML, JSON, CWL)' ' path or application ID.') + parser.add_argument('-c', '--cwl-version', default=DEFAULT_CWL_VERSION, + choices=['v1.0', 'v1.1'], + help='set cwl version for output') parser.add_argument('-r', '--revision', type=int, help='platform application revision. default: latest') parser.add_argument('-v', '--validate', action='store_true', - help='validate JS in the converted CWL v1.0 app.') + help='validate JS in the converted CWL v1 app.') parser.add_argument('-u', '--update', dest='update', action='store_true', help='update/install if output is a platform app.') parser.add_argument('-d', '--decompose', action='store_true', - help='decompose the converted CWL v1.0 workflow.') + help='decompose the converted CWL v1 workflow.') add_logging_to_args(parser) add_sbg_auth_to_args(parser) @@ -48,6 +52,7 @@ def main(args=sys.argv[1:]): app_revision=args['revision'], input_=args['input'], output=args['output'], + cwl_version=args['cwl_version'], validate=args['validate'], update=args['update'], decompose=args['decompose']) diff --git a/sbg_cwl_upgrader/converter/tool.py b/sbg_cwl_upgrader/converter/tool.py index dc0f92b..6699425 100644 --- a/sbg_cwl_upgrader/converter/tool.py +++ b/sbg_cwl_upgrader/converter/tool.py @@ -221,6 +221,10 @@ def __init__(self, sbg_draft2=None): class CWLToolConverter(CWL): + + def __init__(self, cwl_version=None): + self.cwl_version = cwl_version + @staticmethod def _is_staged_file(sbg_draft2_input): if ('sbg:stageInput' in sbg_draft2_input @@ -520,7 +524,7 @@ def _handle_bash(literal): def convert_dict(self, data: dict): """Main method for converting draft2 tool to CWL1.0""" - # Just reuse if tool is already CWLv1.0 + # Just reuse if tool is already CWLv1 if data.get('cwlVersion') != 'sbg:draft-2': return data @@ -535,8 +539,8 @@ def convert_dict(self, data: dict): 'sbg:revision', 'sbg:revisionsInfo', 'sbg:createdOn']} - new_data['cwlVersion'] = 'v1.0' - new_data['sbg:appVersion'] = ['v1.0'] + new_data['cwlVersion'] = self.cwl_version + new_data['sbg:appVersion'] = [self.cwl_version] if 'stdin' in data: new_data['stdin'] = self._handle_stream(data['stdin']) if 'stdout' in data: diff --git a/sbg_cwl_upgrader/converter/workflow.py b/sbg_cwl_upgrader/converter/workflow.py index dad1ab8..1082ee5 100644 --- a/sbg_cwl_upgrader/converter/workflow.py +++ b/sbg_cwl_upgrader/converter/workflow.py @@ -11,6 +11,9 @@ class CWLWorkflowConverter(CWL): + def __init__(self, cwl_version=None): + self.cwl_version = cwl_version + @staticmethod def handle_source(source: list): source = deepcopy(source) @@ -139,29 +142,29 @@ def default_requirements(): {'class': 'StepInputExpressionRequirement'}] def convert_dict(self, data: dict) -> dict: - v1_0_data = {k: deepcopy(v) for k, v in data.items() + v1_data = {k: deepcopy(v) for k, v in data.items() if k not in ['x', 'y', 'appUrl']} - if v1_0_data.get('cwlVersion') != 'sbg:draft-2': - - return v1_0_data - v1_0_data['cwlVersion'] = 'v1.0' - v1_0_data['sbg:appVersion'] = [v1_0_data['cwlVersion']] - v1_0_data['inputs'] = self.handle_inputs(v1_0_data['inputs']) - v1_0_data['outputs'] = self.handle_outputs(v1_0_data['outputs']) - v1_0_data['requirements'] = self.default_requirements() - if 'description' in v1_0_data: - v1_0_data['doc'] = deepcopy(v1_0_data['description']) - del v1_0_data['description'] - for o in v1_0_data['outputs']: + if v1_data.get('cwlVersion') != 'sbg:draft-2': + + return v1_data + v1_data['cwlVersion'] = self.cwl_version + v1_data['sbg:appVersion'] = [self.cwl_version] + v1_data['inputs'] = self.handle_inputs(v1_data['inputs']) + v1_data['outputs'] = self.handle_outputs(v1_data['outputs']) + v1_data['requirements'] = self.default_requirements() + if 'description' in v1_data: + v1_data['doc'] = deepcopy(v1_data['description']) + del v1_data['description'] + for o in v1_data['outputs']: o['id'] = self.handle_id(o['id']) o['outputSource'] = self.handle_source(o['source']) del o['source'] - if 'steps' in v1_0_data and isinstance(v1_0_data['steps'], list): - v1_0_data['steps'] = self.handle_steps(v1_0_data['steps']) + if 'steps' in v1_data and isinstance(v1_data['steps'], list): + v1_data['steps'] = self.handle_steps(v1_data['steps']) connection_checker = ConnectionChecker() - v1_0_data = connection_checker.fix_terminal_output_types(v1_0_data) - v1_0_data = connection_checker.fix_connection_matching(v1_0_data) - return v1_0_data + v1_data = connection_checker.fix_terminal_output_types(v1_data) + v1_data = connection_checker.fix_connection_matching(v1_data) + return v1_data def get_pool(pool=None): diff --git a/setup.py b/setup.py index 9a662b1..55e157d 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ NAME = 'sbg_cwl_upgrader' -VERSION = '0.2.2' +VERSION = '0.3.0' DIR = os.path.abspath(os.path.dirname(__file__)) NOW = datetime.utcnow() diff --git a/tests/converter/test_cwl_converter.py b/tests/converter/test_cwl_converter.py index 91b1466..834cb18 100644 --- a/tests/converter/test_cwl_converter.py +++ b/tests/converter/test_cwl_converter.py @@ -31,6 +31,7 @@ def test_all_steps(self): 'wes_cwl1.json'), 'r') as f: result = json.load(f) c = object.__new__(CWLConverterFacade) + c.cwl_version = 'v1.0' converted = c._parse(in_data) for cwl_key in ['hints', 'steps', 'inputs', 'outputs', 'requirements']: self.assertEqual(len(converted[cwl_key]), diff --git a/tests/converter/test_sbg_draft2_to_cwl1.py b/tests/converter/test_sbg_draft2_to_cwl1.py index b94eb41..89f1124 100644 --- a/tests/converter/test_sbg_draft2_to_cwl1.py +++ b/tests/converter/test_sbg_draft2_to_cwl1.py @@ -18,6 +18,7 @@ def test_optional_defaults(self, mock_facade): endpoint=None, input_='a.cwl', output='b.cwl', + cwl_version='v1.0', platform='igor', profile='default', token=None, @@ -30,13 +31,15 @@ def test_optional_defaults(self, mock_facade): 'sbg_cwl_upgrader.converter.sbg_draft2_to_cwl_1_0.CWLConverterFacade' ) def test_shorthand_inputs(self, mock_facade): - main(['-i', 'a.cwl', '-o', 'b.cwl', '-u', '-d', '-v', '-r', '2']) + main(['-i', 'a.cwl', '-o', 'b.cwl', '-c', 'v1.1', + '-u', '-d', '-v', '-r', '2']) mock_facade.assert_called_once_with( app_revision=2, decompose=True, endpoint=None, input_='a.cwl', output='b.cwl', + cwl_version='v1.1', platform='igor', profile='default', token=None,