Skip to content

Commit 1ab8feb

Browse files
Add more TypedDict definitions
This commit adds more `TypedDict` definitions for `File`, `Dirent`, and `Directory` objects. In addition, it adds the `exitCode` entry to the `CWLRuntimeParameterContext`, which was missing.
1 parent b9de378 commit 1ab8feb

File tree

3 files changed

+159
-18
lines changed

3 files changed

+159
-18
lines changed

cwl_utils/expression.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
import json
77
from collections.abc import Awaitable, MutableMapping
88
from enum import Enum
9-
from typing import Any, Literal, Union, cast
9+
from typing import Any, 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, CWLParameterContext
16+
from cwl_utils.types import (
17+
CWLObjectType,
18+
CWLOutputType,
19+
CWLParameterContext,
20+
is_cwl_parameter_context_key,
21+
)
1722
from cwl_utils.utils import bytes2str_in_dicts
1823

1924

@@ -123,19 +128,15 @@ def evaluator(
123128
if first_symbol_end + 1 == len(ex) and first_symbol == "null":
124129
return None
125130
try:
126-
if first_symbol in ("inputs", "self", "runtime"):
127-
symbol = cast(
128-
Literal["inputs"] | Literal["self"] | Literal["runtime"],
129-
first_symbol,
130-
)
131+
if is_cwl_parameter_context_key(first_symbol):
131132
if inspect.iscoroutinefunction(js_engine.regex_eval):
132133
return asyncio.get_event_loop().run_until_complete(
133134
cast(
134135
Awaitable[CWLOutputType],
135136
js_engine.regex_eval(
136137
first_symbol,
137138
ex[first_symbol_end:-1],
138-
cast(CWLOutputType, obj[symbol]),
139+
cast(CWLOutputType, obj[first_symbol]),
139140
**kwargs,
140141
),
141142
)
@@ -146,7 +147,7 @@ def evaluator(
146147
js_engine.regex_eval(
147148
first_symbol,
148149
ex[first_symbol_end:-1],
149-
cast(CWLOutputType, obj[symbol]),
150+
cast(CWLOutputType, obj[first_symbol]),
150151
**kwargs,
151152
),
152153
)

cwl_utils/sandboxjs.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222

2323
from cwl_utils.errors import JavascriptException, WorkflowException
2424
from cwl_utils.loghandler import _logger
25-
from cwl_utils.types import CWLOutputType
25+
from cwl_utils.types import (
26+
CWLOutputType,
27+
is_directory,
28+
is_directory_key,
29+
is_file,
30+
is_file_key,
31+
)
2632
from cwl_utils.utils import singularity_supports_userns
2733

2834
default_timeout = 20
@@ -549,11 +555,35 @@ def regex_eval(
549555

550556
if isinstance(current_value, Mapping):
551557
try:
552-
return self.regex_eval(
553-
parsed_string + remaining_string,
554-
remaining_string[m.end(1) :],
555-
cast(CWLOutputType, current_value[cast(str, key)]),
556-
)
558+
if is_directory(current_value) and is_directory_key(key):
559+
return self.regex_eval(
560+
parsed_string + remaining_string,
561+
remaining_string[m.end(1) :],
562+
cast(
563+
CWLOutputType,
564+
current_value[key],
565+
),
566+
)
567+
elif is_file(current_value) and is_file_key(key):
568+
return self.regex_eval(
569+
parsed_string + remaining_string,
570+
remaining_string[m.end(1) :],
571+
cast(
572+
CWLOutputType,
573+
current_value[key],
574+
),
575+
)
576+
else:
577+
return self.regex_eval(
578+
parsed_string + remaining_string,
579+
remaining_string[m.end(1) :],
580+
cast(
581+
CWLOutputType,
582+
cast(MutableMapping[str, Any], current_value)[
583+
cast(str, key)
584+
],
585+
),
586+
)
557587
except KeyError as exc:
558588
raise WorkflowException(
559589
f"{parsed_string!r} doesn't have property {key!r}."

cwl_utils/types.py

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# From https://github.com/rabix/sbpack/blob/b8404a0859ffcbe1edae6d8f934e51847b003320/sbpack/lib.py
33
"""Shared Python type definitions for commons JSON like CWL objects."""
4-
from collections.abc import MutableMapping, MutableSequence
5-
from typing import TypeAlias, TypedDict
4+
import sys
5+
from collections.abc import Mapping, MutableMapping, MutableSequence
6+
from typing import Any, Literal, TypeAlias, TypeGuard, TypedDict
7+
8+
if sys.version_info >= (3, 11):
9+
from typing import Required
10+
else:
11+
from typing_extensions import Required
12+
613

714
built_in_types: list[str] = [
815
"null",
@@ -21,12 +28,46 @@
2128
]
2229

2330

31+
DirectoryDict = TypedDict(
32+
"DirectoryDict",
33+
{
34+
"class": Required[Literal["Directory"]],
35+
"location": str,
36+
"path": str,
37+
"basename": str,
38+
"listing": MutableSequence["FileDict | DirectoryDict"],
39+
},
40+
total=False,
41+
)
42+
43+
44+
FileDict = TypedDict(
45+
"FileDict",
46+
{
47+
"class": Required[Literal["File"]],
48+
"location": str,
49+
"path": str,
50+
"basename": str,
51+
"dirname": str,
52+
"nameroot": str,
53+
"nameext": str,
54+
"checksum": str,
55+
"size": int,
56+
"secondaryFiles": MutableSequence["FileDict | DirectoryDict"],
57+
"format": str,
58+
"contents": str,
59+
},
60+
total=False,
61+
)
62+
63+
2464
CWLOutputAtomType: TypeAlias = (
2565
None
2666
| bool
2767
| str
2868
| int
29-
| float
69+
| FileDict
70+
| DirectoryDict
3071
| MutableSequence["CWLOutputAtomType"]
3172
| MutableMapping[str, "CWLOutputAtomType"]
3273
)
@@ -35,6 +76,8 @@
3576
| str
3677
| int
3778
| float
79+
| FileDict
80+
| DirectoryDict
3881
| MutableSequence[CWLOutputAtomType]
3982
| MutableMapping[str, CWLOutputAtomType]
4083
)
@@ -49,9 +92,76 @@ class CWLRuntimeParameterContext(TypedDict, total=False):
4992
ram: float | str
5093
outdirSize: float | str
5194
tmpdirSize: float | str
95+
exitCode: int
5296

5397

5498
class CWLParameterContext(TypedDict, total=False):
5599
inputs: CWLObjectType
56100
self: CWLOutputType | None
57101
runtime: CWLRuntimeParameterContext
102+
103+
104+
class DirentDict(TypedDict, total=False):
105+
entry: Required[str]
106+
entryname: str
107+
writable: bool
108+
109+
110+
def is_cwl_parameter_context_key(
111+
key: Any,
112+
) -> TypeGuard[Literal["inputs", "self", "runtime"]]:
113+
return key in ("inputs", "self", "runtime")
114+
115+
116+
def is_directory(value: Any) -> TypeGuard[DirectoryDict]:
117+
return isinstance(value, Mapping) and value.get("class") == "Directory"
118+
119+
120+
def is_directory_key(
121+
key: Any,
122+
) -> TypeGuard[Literal["class", "location", "path", "basename", "listing"]]:
123+
return key in ("class", "location", "path", "basename", "listing")
124+
125+
126+
def is_file(value: Any) -> TypeGuard[FileDict]:
127+
return isinstance(value, Mapping) and value.get("class") == "File"
128+
129+
130+
def is_file_key(
131+
key: Any,
132+
) -> TypeGuard[
133+
Literal[
134+
"class",
135+
"location",
136+
"path",
137+
"basename",
138+
"dirname",
139+
"nameroot",
140+
"nameext",
141+
"checksum",
142+
"size",
143+
"secondaryFiles",
144+
"format",
145+
"contents",
146+
]
147+
]:
148+
return key in (
149+
"class",
150+
"location",
151+
"path",
152+
"basename",
153+
"dirname",
154+
"nameroot",
155+
"nameext",
156+
"checksum",
157+
"size",
158+
"secondaryFiles",
159+
"format",
160+
"contents",
161+
)
162+
163+
164+
def is_file_or_directory(
165+
value: Any,
166+
) -> TypeGuard[FileDict | DirectoryDict]:
167+
return isinstance(value, Mapping) and value.get("class") in ("File", "Directory")

0 commit comments

Comments
 (0)