Skip to content
This repository was archived by the owner on Jun 9, 2020. It is now read-only.

Commit 841b7f1

Browse files
authored
Merge pull request #15 from illusional/add-array-types
Adds CommandInputArraySchema and can check array types
2 parents cdd436a + fecd37d commit 841b7f1

File tree

5 files changed

+142
-17
lines changed

5 files changed

+142
-17
lines changed

cwlgen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .version import __version__
1414

1515
from .utils import literal, literal_presenter
16-
from .elements import Parameter, CWL_VERSIONS, DEF_VERSION, CWL_SHEBANG
16+
from .elements import Parameter, CommandInputArraySchema, CWL_VERSIONS, DEF_VERSION, CWL_SHEBANG
1717
from .workflow import Workflow, File
1818
from .import_cwl import parse_cwl
1919

cwlgen/elements.py

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ def parse_param_type(param_type):
2222
Parses the parameter type as one of the required types:
2323
:: https://www.commonwl.org/v1.0/CommandLineTool.html#CommandInputParameter
2424
25-
26-
:param param_type:
25+
:param param_type: a CWL type that is _validated_
26+
:type param_type: CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string |
27+
array<CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string>
2728
:return: CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string |
2829
array<CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string>
2930
"""
@@ -34,12 +35,25 @@ def parse_param_type(param_type):
3435
if optional:
3536
_LOGGER.debug("Detected {param_type} to be optional".format(param_type=param_type))
3637
cwltype = param_type[:-1] if optional else param_type
38+
39+
# check for arrays
40+
if len(cwltype) > 2 and cwltype[-2:] == "[]":
41+
array_type = CommandInputArraySchema(items=cwltype[:-2])
42+
# How to make arrays optional input: https://www.biostars.org/p/233562/#234089
43+
return [DEF_TYPE, array_type] if optional else array_type
44+
3745
if cwltype not in CWL_TYPE:
3846
_LOGGER.warning("The type '{param_type}' is not a valid CWLType, expected one of: {types}"
3947
.format(param_type=param_type, types=", ".join(str(x) for x in CWL_TYPE)))
4048
_LOGGER.warning("type is set to {}.".format(DEF_TYPE))
4149
return DEF_TYPE
4250
return param_type
51+
52+
elif isinstance(param_type, list):
53+
return [parse_param_type(p) for p in param_type]
54+
55+
elif isinstance(param_type, CommandInputArraySchema):
56+
return param_type # validate if required
4357
else:
4458
_LOGGER.warning("Unable to detect type of param '{param_type}".format(param_type=param_type))
4559
return DEF_TYPE
@@ -85,12 +99,54 @@ def get_dict(self):
8599
:return: dictionnary of the object
86100
:rtype: DICT
87101
'''
88-
dict_param = {k: v for k, v in vars(self).items() if v is not None and v is not False}
89-
if dict_param['type'] != 'File':
90-
# Remove what is only for File
91-
for key in ['format', 'secondaryFiles', 'streamable']:
92-
try:
93-
del(dict_param[key])
94-
except KeyError:
95-
pass
102+
manual = ["type"]
103+
dict_param = {k: v for k, v in vars(self).items() if v is not None and v is not False and k not in manual}
104+
105+
should_have_file_related_keys = False
106+
107+
if isinstance(self.type, str):
108+
dict_param["type"] = self.type
109+
should_have_file_related_keys = self.type == "File"
110+
111+
elif isinstance(self.type, CommandInputArraySchema):
112+
dict_param["type"] = self.type.get_dict()
113+
should_have_file_related_keys = self.type.type == "File"
114+
115+
keys_to_remove = [k for k in ['format', 'secondaryFiles', 'streamable'] if k in dict_param]
116+
117+
if not should_have_file_related_keys:
118+
for key in keys_to_remove:
119+
del(dict_param[key])
96120
return dict_param
121+
122+
123+
class CommandInputArraySchema(object):
124+
'''
125+
Based on the parameter set out in the CWL spec:
126+
https://www.commonwl.org/v1.0/CommandLineTool.html#CommandInputArraySchema
127+
'''
128+
129+
def __init__(self, items=None, label=None, input_binding=None):
130+
'''
131+
:param items: Defines the type of the array elements.
132+
:type: `CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string | array<CWLType | CommandInputRecordSchema | CommandInputEnumSchema | CommandInputArraySchema | string>`
133+
:param label: A short, human-readable label of this object.
134+
:type label: STRING
135+
:param input_binding:
136+
:type input_binding: CommandLineBinding
137+
'''
138+
self.type = "array"
139+
self.items = parse_param_type(items)
140+
self.label = label
141+
self.inputBinding = input_binding
142+
143+
def get_dict(self):
144+
'''
145+
Transform the object to a [DICT] to write CWL.
146+
147+
:return: dictionnary of the object
148+
:rtype: DICT
149+
'''
150+
dict_binding = {k: v for k, v in vars(self).items() if v is not None}
151+
return dict_binding
152+

doc/source/classes.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ CommandOutputBinding
9494
:special-members:
9595
:exclude-members: __weakref__
9696

97+
CommandInputArraySchema
98+
"""""""""""""""""""""""
99+
100+
.. autoclass:: cwlgen.CommandInputArraySchema
101+
:members:
102+
:private-members:
103+
:special-members:
104+
:exclude-members: __weakref__
105+
97106
.. _requirements:
98107

99108
Requirements
@@ -137,7 +146,7 @@ CWLToolParser
137146
:private-members:
138147
:special-members:
139148
:exclude-members: __weakref_
140-
_
149+
141150
InputsParser
142151
""""""""""""
143152

test/test_unit_cwlgen.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ def setUp(self):
8080
self.param = cwlgen.Parameter('an_id', param_type='File', label='a_label',\
8181
doc='a_doc', param_format='a_format',\
8282
streamable=True, secondary_files='sec_files')
83+
self.nonfile_param = cwlgen.Parameter('non-file', param_type="string", label="a string",
84+
streamable=True, secondary_files=[".txt"], doc="documentation here")
85+
86+
self.array_param = cwlgen.Parameter('an array', param_type='string[]', label="an array of strings")
8387

8488
def test_init(self):
8589
self.assertEqual(self.param.id, 'an_id')
@@ -99,6 +103,21 @@ def test_get_dict(self):
99103
self.assertEqual(dict_test['secondaryFiles'], 'sec_files')
100104
self.assertTrue(dict_test['streamable'])
101105

106+
def test_nonfile_get_dict(self):
107+
dict_test = self.nonfile_param.get_dict()
108+
self.assertEqual(dict_test['type'], 'string')
109+
self.assertEqual(dict_test['doc'], self.nonfile_param.doc)
110+
self.assertNotIn('secondaryFiles', dict_test)
111+
self.assertNotIn('streamable', dict_test)
112+
self.assertNotIn('format', dict_test)
113+
114+
def test_array(self):
115+
dict_test = self.array_param.get_dict()
116+
td = dict_test['type']
117+
self.assertIsInstance(td, dict)
118+
self.assertEqual(td['type'], 'array')
119+
self.assertEqual(td['items'], self.array_param.type.items)
120+
102121

103122
class TestCommandInputParameter(unittest.TestCase):
104123

test/test_unit_typing.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from cwlgen.elements import parse_param_type, NON_NULL_CWL_TYPE, CWL_TYPE, DEF_TYPE
2+
from cwlgen.elements import parse_param_type, NON_NULL_CWL_TYPE, CWL_TYPE, DEF_TYPE, CommandInputArraySchema
33
import logging
44

55

@@ -10,12 +10,53 @@ def test_types(self):
1010
self.assertEqual(parse_param_type(cwl_type), cwl_type)
1111

1212
def test_incorrect_type(self):
13-
for cwl_type in NON_NULL_CWL_TYPE:
14-
should_be_def_type = parse_param_type(cwl_type.upper())
15-
self.assertNotEqual(should_be_def_type, cwl_type)
16-
self.assertEqual(should_be_def_type, DEF_TYPE)
13+
invalid_type = "invalid"
14+
should_be_def_type = parse_param_type(invalid_type)
15+
self.assertNotEqual(should_be_def_type, invalid_type)
16+
self.assertEqual(should_be_def_type, DEF_TYPE)
1717

1818
def test_optional_string(self):
1919
for cwl_type in NON_NULL_CWL_TYPE:
2020
optional_type = cwl_type + "?"
2121
self.assertEqual(parse_param_type(optional_type), optional_type)
22+
23+
def test_typed_array(self):
24+
array_string_type = "string[]"
25+
q = parse_param_type(array_string_type)
26+
self.assertIsInstance(q, CommandInputArraySchema)
27+
self.assertEqual(q.items, "string")
28+
29+
def test_incorrectly_typed_array(self):
30+
array_string_type = "invalid[]"
31+
q = parse_param_type(array_string_type)
32+
self.assertIsInstance(q, CommandInputArraySchema)
33+
self.assertNotEqual(q.items, "invalid")
34+
self.assertEqual(q.items, DEF_TYPE)
35+
36+
def test_optionally_typed_array(self):
37+
array_string_type = "string?[]"
38+
q = parse_param_type(array_string_type)
39+
self.assertIsInstance(q, CommandInputArraySchema)
40+
self.assertEqual(q.items, "string?")
41+
42+
def test_optional_typed_array(self):
43+
optional_array_string_type = "string[]?"
44+
q = parse_param_type(optional_array_string_type)
45+
self.assertIsInstance(q, list)
46+
self.assertEqual(len(q), 2)
47+
null_idx = q.index(DEF_TYPE)
48+
array_type = q[1 - null_idx]
49+
self.assertIsInstance(array_type, CommandInputArraySchema)
50+
self.assertEqual(array_type.items, "string")
51+
52+
def test_command_input_array_schema(self):
53+
ar = CommandInputArraySchema(items="string")
54+
self.assertIsInstance(ar, CommandInputArraySchema)
55+
self.assertEqual(parse_param_type(ar), ar)
56+
self.assertEqual(ar.items, "string")
57+
58+
def test_optional_type_input_array_schema(self):
59+
ar = CommandInputArraySchema(items="string?")
60+
self.assertIsInstance(ar, CommandInputArraySchema)
61+
self.assertEqual(parse_param_type(ar), ar)
62+
self.assertEqual(ar.items, "string?")

0 commit comments

Comments
 (0)