Skip to content

Commit b9de378

Browse files
GlassOfWhiskeymr-c
andauthored
Add CWLParameterContext as a TypedDict (#392)
This commit further modernizes the CWL type system. It also adds a `TypedDict` class to represent the CWL parameter context used for expression evaluation. Co-authored-by: Michael R. Crusoe <michael.crusoe@gmail.com>
1 parent 35a6e4a commit b9de378

File tree

6 files changed

+116
-97
lines changed

6 files changed

+116
-97
lines changed

cwl_utils/cwl_v1_0_expression_refactor.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
import cwl_utils.parser.cwl_v1_0_utils as utils
1818
from cwl_utils.errors import JavascriptException, WorkflowException
1919
from cwl_utils.expression import do_eval, interpolate
20-
from cwl_utils.types import CWLObjectType, CWLOutputType
20+
from cwl_utils.types import (
21+
CWLObjectType,
22+
CWLOutputType,
23+
CWLParameterContext,
24+
CWLRuntimeParameterContext,
25+
)
2126

2227

2328
def expand_stream_shortcuts(process: cwl.CommandLineTool) -> cwl.CommandLineTool:
@@ -85,14 +90,14 @@ def get_expression(
8590
if string.strip().startswith("${"):
8691
return string
8792
if "$(" in string:
88-
runtime: CWLObjectType = {
89-
"cores": 0,
90-
"ram": 0,
91-
"outdir": "/root",
92-
"tmpdir": "/tmp", # nosec
93-
"outdirSize": 0,
94-
"tmpdirSize": 0,
95-
}
93+
runtime = CWLRuntimeParameterContext(
94+
cores=0,
95+
ram=0,
96+
outdir="/root",
97+
tmpdir="/tmp", # nosec
98+
outdirSize=0,
99+
tmpdirSize=0,
100+
)
96101
try:
97102
do_eval(
98103
string,
@@ -114,11 +119,9 @@ def get_expression(
114119
str,
115120
interpolate(
116121
scan=string,
117-
rootvars={
118-
"inputs": inputs,
119-
"context": self,
120-
"runtime": runtime,
121-
},
122+
rootvars=CWLParameterContext(
123+
inputs=inputs, self=self, runtime=runtime
124+
),
122125
fullJS=True,
123126
escaping_behavior=2,
124127
convert_to_expression=True,

cwl_utils/cwl_v1_1_expression_refactor.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
import cwl_utils.parser.cwl_v1_1_utils as utils
1818
from cwl_utils.errors import JavascriptException, WorkflowException
1919
from cwl_utils.expression import do_eval, interpolate
20-
from cwl_utils.types import CWLObjectType, CWLOutputType
20+
from cwl_utils.types import (
21+
CWLObjectType,
22+
CWLOutputType,
23+
CWLParameterContext,
24+
CWLRuntimeParameterContext,
25+
)
2126

2227

2328
def expand_stream_shortcuts(process: cwl.CommandLineTool) -> cwl.CommandLineTool:
@@ -85,14 +90,14 @@ def get_expression(
8590
if string.strip().startswith("${"):
8691
return string
8792
if "$(" in string:
88-
runtime: CWLObjectType = {
89-
"cores": 0,
90-
"ram": 0,
91-
"outdir": "/root",
92-
"tmpdir": "/tmp", # nosec
93-
"outdirSize": 0,
94-
"tmpdirSize": 0,
95-
}
93+
runtime = CWLRuntimeParameterContext(
94+
cores=0,
95+
ram=0,
96+
outdir="/root",
97+
tmpdir="/tmp", # nosec
98+
outdirSize=0,
99+
tmpdirSize=0,
100+
)
96101
try:
97102
do_eval(
98103
string,
@@ -114,11 +119,9 @@ def get_expression(
114119
str,
115120
interpolate(
116121
scan=string,
117-
rootvars={
118-
"inputs": inputs,
119-
"context": self,
120-
"runtime": runtime,
121-
},
122+
rootvars=CWLParameterContext(
123+
inputs=inputs, self=self, runtime=runtime
124+
),
122125
fullJS=True,
123126
escaping_behavior=2,
124127
convert_to_expression=True,

cwl_utils/cwl_v1_2_expression_refactor.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
import cwl_utils.parser.cwl_v1_2_utils as utils
1818
from cwl_utils.errors import JavascriptException, WorkflowException
1919
from cwl_utils.expression import do_eval, interpolate
20-
from cwl_utils.types import CWLObjectType, CWLOutputType
20+
from cwl_utils.types import (
21+
CWLObjectType,
22+
CWLOutputType,
23+
CWLParameterContext,
24+
CWLRuntimeParameterContext,
25+
)
2126

2227

2328
def expand_stream_shortcuts(process: cwl.CommandLineTool) -> cwl.CommandLineTool:
@@ -85,14 +90,14 @@ def get_expression(
8590
if string.strip().startswith("${"):
8691
return string
8792
if "$(" in string:
88-
runtime: CWLObjectType = {
89-
"cores": 0,
90-
"ram": 0,
91-
"outdir": "/root",
92-
"tmpdir": "/tmp", # nosec
93-
"outdirSize": 0,
94-
"tmpdirSize": 0,
95-
}
93+
runtime = CWLRuntimeParameterContext(
94+
cores=0,
95+
ram=0,
96+
outdir="/root",
97+
tmpdir="/tmp", # nosec
98+
outdirSize=0,
99+
tmpdirSize=0,
100+
)
96101
try:
97102
do_eval(
98103
string,
@@ -114,11 +119,9 @@ def get_expression(
114119
str,
115120
interpolate(
116121
scan=string,
117-
rootvars={
118-
"inputs": inputs,
119-
"context": self,
120-
"runtime": runtime,
121-
},
122+
rootvars=CWLParameterContext(
123+
inputs=inputs, self=self, runtime=runtime
124+
),
122125
fullJS=True,
123126
escaping_behavior=2,
124127
convert_to_expression=True,

cwl_utils/expression.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
import json
77
from collections.abc import Awaitable, MutableMapping
88
from enum import Enum
9-
from typing import Any, Union, cast
9+
from typing import Any, Literal, Union, cast
1010

1111
from schema_salad.utils import json_dumps
1212

1313
from cwl_utils.errors import JavascriptException, SubstitutionError, WorkflowException
1414
from cwl_utils.loghandler import _logger
1515
from cwl_utils.sandboxjs import JSEngine, default_timeout, get_js_engine, param_re
16-
from cwl_utils.types import CWLObjectType, CWLOutputType
16+
from cwl_utils.types import CWLObjectType, CWLOutputType, CWLParameterContext
1717
from cwl_utils.utils import bytes2str_in_dicts
1818

1919

@@ -108,7 +108,7 @@ def scanner(scan: str) -> tuple[int, int] | None:
108108
def evaluator(
109109
js_engine: JSEngine,
110110
ex: str,
111-
obj: CWLObjectType,
111+
obj: CWLParameterContext,
112112
jslib: str,
113113
fullJS: bool,
114114
**kwargs: Any,
@@ -123,31 +123,35 @@ def evaluator(
123123
if first_symbol_end + 1 == len(ex) and first_symbol == "null":
124124
return None
125125
try:
126-
if first_symbol not in obj:
127-
raise WorkflowException("%s is not defined" % first_symbol)
128-
129-
if inspect.iscoroutinefunction(js_engine.regex_eval):
130-
return asyncio.get_event_loop().run_until_complete(
131-
cast(
132-
Awaitable[CWLOutputType],
126+
if first_symbol in ("inputs", "self", "runtime"):
127+
symbol = cast(
128+
Literal["inputs"] | Literal["self"] | Literal["runtime"],
129+
first_symbol,
130+
)
131+
if inspect.iscoroutinefunction(js_engine.regex_eval):
132+
return asyncio.get_event_loop().run_until_complete(
133+
cast(
134+
Awaitable[CWLOutputType],
135+
js_engine.regex_eval(
136+
first_symbol,
137+
ex[first_symbol_end:-1],
138+
cast(CWLOutputType, obj[symbol]),
139+
**kwargs,
140+
),
141+
)
142+
)
143+
else:
144+
return cast(
145+
CWLOutputType,
133146
js_engine.regex_eval(
134147
first_symbol,
135148
ex[first_symbol_end:-1],
136-
cast(CWLOutputType, obj[first_symbol]),
149+
cast(CWLOutputType, obj[symbol]),
137150
**kwargs,
138151
),
139152
)
140-
)
141153
else:
142-
return cast(
143-
CWLOutputType,
144-
js_engine.regex_eval(
145-
first_symbol,
146-
ex[first_symbol_end:-1],
147-
cast(CWLOutputType, obj[first_symbol]),
148-
**kwargs,
149-
),
150-
)
154+
raise WorkflowException(f"{first_symbol} is unexpected.")
151155
except WorkflowException as werr:
152156
expression_parse_exception = werr
153157
if fullJS:
@@ -174,7 +178,7 @@ def evaluator(
174178

175179
def interpolate(
176180
scan: str,
177-
rootvars: CWLObjectType,
181+
rootvars: CWLParameterContext,
178182
jslib: str = "",
179183
fullJS: bool = False,
180184
strip_whitespace: bool = True,
@@ -258,7 +262,7 @@ def dump(string: str) -> str:
258262
return "".join(parts)
259263

260264

261-
def jshead(engine_config: list[str], rootvars: CWLObjectType) -> str:
265+
def jshead(engine_config: list[str], rootvars: CWLParameterContext) -> str:
262266
"""Make sure all the byte strings are converted to str in `rootvars` dict."""
263267
return "\n".join(
264268
engine_config
@@ -293,7 +297,7 @@ def do_eval(
293297
runtime["outdir"] = outdir or None
294298

295299
rootvars = cast(
296-
CWLObjectType,
300+
CWLParameterContext,
297301
bytes2str_in_dicts({"inputs": jobinput, "self": context, "runtime": runtime}),
298302
)
299303

cwl_utils/parser/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from schema_salad.exceptions import ValidationException
1111
from schema_salad.utils import yaml_no_ts
1212

13-
from . import cwl_v1_0, cwl_v1_1, cwl_v1_2
1413
from ..errors import GraphTargetMissingException
14+
from . import cwl_v1_0, cwl_v1_1, cwl_v1_2
1515

1616

1717
class NoType(ABC):

cwl_utils/types.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# From https://github.com/rabix/sbpack/blob/b8404a0859ffcbe1edae6d8f934e51847b003320/sbpack/lib.py
33
"""Shared Python type definitions for commons JSON like CWL objects."""
44
from collections.abc import MutableMapping, MutableSequence
5-
from typing import Any, Optional, Union
5+
from typing import TypeAlias, TypedDict
66

7-
built_in_types = [
7+
built_in_types: list[str] = [
88
"null",
99
"boolean",
1010
"int",
@@ -21,31 +21,37 @@
2121
]
2222

2323

24-
CWLOutputAtomType = Union[
25-
None,
26-
bool,
27-
str,
28-
int,
29-
float,
30-
MutableSequence[
31-
Union[
32-
None, bool, str, int, float, MutableSequence[Any], MutableMapping[str, Any]
33-
]
34-
],
35-
MutableMapping[
36-
str,
37-
Union[
38-
None, bool, str, int, float, MutableSequence[Any], MutableMapping[str, Any]
39-
],
40-
],
41-
]
42-
CWLOutputType = Union[
43-
bool,
44-
str,
45-
int,
46-
float,
47-
MutableSequence[CWLOutputAtomType],
48-
MutableMapping[str, CWLOutputAtomType],
49-
]
50-
CWLObjectType = MutableMapping[str, Optional[CWLOutputType]]
51-
SinkType = Union[CWLOutputType, CWLObjectType]
24+
CWLOutputAtomType: TypeAlias = (
25+
None
26+
| bool
27+
| str
28+
| int
29+
| float
30+
| MutableSequence["CWLOutputAtomType"]
31+
| MutableMapping[str, "CWLOutputAtomType"]
32+
)
33+
CWLOutputType: TypeAlias = (
34+
bool
35+
| str
36+
| int
37+
| float
38+
| MutableSequence[CWLOutputAtomType]
39+
| MutableMapping[str, CWLOutputAtomType]
40+
)
41+
CWLObjectType: TypeAlias = MutableMapping[str, CWLOutputType | None]
42+
SinkType: TypeAlias = CWLOutputType | CWLObjectType
43+
44+
45+
class CWLRuntimeParameterContext(TypedDict, total=False):
46+
outdir: str
47+
tmpdir: str
48+
cores: float | str
49+
ram: float | str
50+
outdirSize: float | str
51+
tmpdirSize: float | str
52+
53+
54+
class CWLParameterContext(TypedDict, total=False):
55+
inputs: CWLObjectType
56+
self: CWLOutputType | None
57+
runtime: CWLRuntimeParameterContext

0 commit comments

Comments
 (0)