Skip to content

Commit 985b93e

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 985b93e

File tree

4 files changed

+161
-30
lines changed

4 files changed

+161
-30
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/file_formats.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
"""
77

88

9-
from rdflib import OWL, RDFS, Graph, URIRef
9+
from rdflib import Graph, OWL, RDFS, URIRef
1010
from schema_salad.exceptions import ValidationException
1111
from schema_salad.utils import aslist, json_dumps
1212

13-
from cwl_utils.types import CWLObjectType
13+
from cwl_utils.types import CWLFileType
1414

1515

1616
def formatSubclassOf(
@@ -49,7 +49,7 @@ def formatSubclassOf(
4949

5050

5151
def check_format(
52-
actual_file: CWLObjectType | list[CWLObjectType],
52+
actual_file: CWLFileType | list[CWLFileType],
5353
input_formats: list[str] | str,
5454
ontology: Graph | None,
5555
) -> None:

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: 112 additions & 12 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,22 +28,48 @@
2128
]
2229

2330

24-
CWLOutputAtomType: TypeAlias = (
25-
None
26-
| bool
27-
| str
28-
| int
29-
| float
30-
| MutableSequence["CWLOutputAtomType"]
31-
| MutableMapping[str, "CWLOutputAtomType"]
31+
CWLDirectoryType = TypedDict(
32+
"CWLDirectoryType",
33+
{
34+
"class": Required[Literal["Directory"]],
35+
"location": str,
36+
"path": str,
37+
"basename": str,
38+
"listing": MutableSequence["CWLFileType | CWLDirectoryType"],
39+
},
40+
total=False,
41+
)
42+
43+
44+
CWLFileType = TypedDict(
45+
"CWLFileType",
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["CWLFileType | CWLDirectoryType"],
57+
"format": str,
58+
"contents": str,
59+
},
60+
total=False,
3261
)
62+
63+
3364
CWLOutputType: TypeAlias = (
3465
bool
3566
| str
3667
| int
3768
| float
38-
| MutableSequence[CWLOutputAtomType]
39-
| MutableMapping[str, CWLOutputAtomType]
69+
| CWLFileType
70+
| CWLDirectoryType
71+
| MutableSequence["CWLOutputType | None"]
72+
| MutableMapping[str, "CWLOutputType | None"]
4073
)
4174
CWLObjectType: TypeAlias = MutableMapping[str, CWLOutputType | None]
4275
SinkType: TypeAlias = CWLOutputType | CWLObjectType
@@ -49,9 +82,76 @@ class CWLRuntimeParameterContext(TypedDict, total=False):
4982
ram: float | str
5083
outdirSize: float | str
5184
tmpdirSize: float | str
85+
exitCode: int
5286

5387

5488
class CWLParameterContext(TypedDict, total=False):
5589
inputs: CWLObjectType
5690
self: CWLOutputType | None
5791
runtime: CWLRuntimeParameterContext
92+
93+
94+
class DirentType(TypedDict, total=False):
95+
entry: Required[str]
96+
entryname: str
97+
writable: bool
98+
99+
100+
def is_cwl_parameter_context_key(
101+
key: Any,
102+
) -> TypeGuard[Literal["inputs", "self", "runtime"]]:
103+
return key in ("inputs", "self", "runtime")
104+
105+
106+
def is_directory(value: Any) -> TypeGuard[CWLDirectoryType]:
107+
return isinstance(value, Mapping) and value.get("class") == "Directory"
108+
109+
110+
def is_directory_key(
111+
key: Any,
112+
) -> TypeGuard[Literal["class", "location", "path", "basename", "listing"]]:
113+
return key in ("class", "location", "path", "basename", "listing")
114+
115+
116+
def is_file(value: Any) -> TypeGuard[CWLFileType]:
117+
return isinstance(value, Mapping) and value.get("class") == "File"
118+
119+
120+
def is_file_key(
121+
key: Any,
122+
) -> TypeGuard[
123+
Literal[
124+
"class",
125+
"location",
126+
"path",
127+
"basename",
128+
"dirname",
129+
"nameroot",
130+
"nameext",
131+
"checksum",
132+
"size",
133+
"secondaryFiles",
134+
"format",
135+
"contents",
136+
]
137+
]:
138+
return key in (
139+
"class",
140+
"location",
141+
"path",
142+
"basename",
143+
"dirname",
144+
"nameroot",
145+
"nameext",
146+
"checksum",
147+
"size",
148+
"secondaryFiles",
149+
"format",
150+
"contents",
151+
)
152+
153+
154+
def is_file_or_directory(
155+
value: Any,
156+
) -> TypeGuard[CWLFileType | CWLDirectoryType]:
157+
return isinstance(value, Mapping) and value.get("class") in ("File", "Directory")

0 commit comments

Comments
 (0)