Skip to content

Commit

Permalink
Enable conversion to CWL v1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Bogdan Gavrilovic committed Aug 5, 2020
2 parents ecc28aa + 9e23950 commit b0cb35c
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 32 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 11 additions & 4 deletions sbg_cwl_upgrader/converter/cwl_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

logger = logging.getLogger(__name__)

DEFAULT_CWL_VERSION = 'v1.0'


def dict_to_yaml(data: dict, out_path: str):
y = YAML()
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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.')
13 changes: 9 additions & 4 deletions sbg_cwl_upgrader/converter/sbg_draft2_to_cwl_1_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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'])
Expand Down
10 changes: 7 additions & 3 deletions sbg_cwl_upgrader/converter/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down
39 changes: 21 additions & 18 deletions sbg_cwl_upgrader/converter/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
1 change: 1 addition & 0 deletions tests/converter/test_cwl_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down
5 changes: 4 additions & 1 deletion tests/converter/test_sbg_draft2_to_cwl1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit b0cb35c

Please sign in to comment.