Skip to content

Commit 224fee9

Browse files
authored
✨ Is515/runtime ports validation (ITISFoundation#2996)
1 parent 03a8b7f commit 224fee9

File tree

75 files changed

+1939
-876
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1939
-876
lines changed

packages/dask-task-models-library/src/dask_task_models_library/container_tasks/io.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class Config(DictModel.Config):
135135
},
136136
"optional_file_output": {
137137
"required": False,
138-
"url": "s3://some_file_url",
138+
"url": "s3://one_file_url",
139139
},
140140
},
141141
]
@@ -188,7 +188,7 @@ class Config(DictModel.Config):
188188
"int_output": -45,
189189
"float_output": 4564.45,
190190
"string_output": "nobody thinks like a string",
191-
"file_output": {"url": "s3://some_file_url"},
191+
"file_output": {"url": "s3://yet_another_file_url"},
192192
},
193193
]
194194
}

packages/dask-task-models-library/tests/container_tasks/test_io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def test_create_task_output_from_task_does_not_throw_when_there_are_optional_mis
118118
{
119119
"optional_file_output": {
120120
"required": False,
121-
"url": "s3://some_file_url",
121+
"url": "s3://another_file_url",
122122
"mapping": "the_output_filename",
123123
},
124124
}

packages/models-library/requirements/_base.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
#
44
--constraint ../../../requirements/constraints.txt
55

6+
jsonschema
67
pydantic[email]

packages/models-library/requirements/_base.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
#
55
# pip-compile --output-file=requirements/_base.txt --strip-extras requirements/_base.in
66
#
7+
attrs==21.4.0
8+
# via jsonschema
79
dnspython==2.2.1
810
# via email-validator
911
email-validator==1.1.3
1012
# via pydantic
1113
idna==3.3
1214
# via email-validator
15+
jsonschema==4.4.0
16+
# via -r requirements/_base.in
1317
pydantic==1.9.0
1418
# via
1519
# -c requirements/../../../requirements/constraints.txt
1620
# -r requirements/_base.in
21+
pyrsistent==0.18.1
22+
# via jsonschema
1723
typing-extensions==4.1.1
1824
# via pydantic

packages/models-library/requirements/_test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ async-timeout==4.0.2
1919
# via aiohttp
2020
attrs==21.4.0
2121
# via
22+
# -c requirements/_base.txt
2223
# aiohttp
2324
# pytest
2425
certifi==2021.10.8
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import Any, TypedDict, Union
2+
3+
Loc = tuple[Union[int, str], ...]
4+
5+
6+
class _ErrorDictRequired(TypedDict):
7+
"""
8+
loc: identifies path in nested model e.g. ("parent", "child", "field", 0)
9+
type: set to code defined pydantic.errors raised upon validation
10+
(i.e. inside @validator decorated functions)
11+
12+
Example:
13+
{
14+
"loc": (node_uuid, "complex", "real_part",)
15+
"msg": "Invalid real part, expected positive"
16+
"type": "value_error."
17+
}
18+
"""
19+
20+
loc: Loc
21+
msg: str
22+
type: str
23+
24+
# SEE tests_errors for more details
25+
26+
27+
class ErrorDict(_ErrorDictRequired, total=False):
28+
"""Structured dataset returned by pydantic's ValidationError.errors() -> List[ErrorDict]"""
29+
30+
ctx: dict[str, Any]
31+
32+
33+
# NOTE: Here we do not just import as 'from pydantic.error_wrappers import ErrorDict'
34+
# because that only works if TYPE_CHECKING=True.
35+
__all__ = ("ErrorDict",)

packages/models-library/src/models_library/function_services_catalog/services/demo_units.py

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
1-
from ...services import LATEST_INTEGRATION_VERSION, ServiceDockerData, ServiceType
1+
from ...services import (
2+
LATEST_INTEGRATION_VERSION,
3+
ServiceDockerData,
4+
ServiceInput,
5+
ServiceOutput,
6+
ServiceType,
7+
)
28
from .._key_labels import FUNCTION_SERVICE_KEY_PREFIX
39
from .._utils import OM, PC, FunctionServices, create_fake_thumbnail_url
410

5-
6-
def build_input(schema):
7-
description = schema.pop("description", schema["title"])
8-
9-
return {
10-
"label": schema["title"],
11-
"description": description,
12-
"type": "ref_contentSchema",
13-
"contentSchema": schema,
14-
}
15-
16-
1711
# SEE https://github.com/hgrecco/pint/blob/master/pint/default_en.txt
1812
#
1913
# NOTE: this service is also used as fixture in test_catalog_utils.py::test_can_connect_with_units
@@ -36,7 +30,7 @@ def build_input(schema):
3630
"contact": PC.email,
3731
"thumbnail": create_fake_thumbnail_url("demo-units"),
3832
"inputs": {
39-
"length": build_input(
33+
"length": ServiceInput.from_json_schema(
4034
{
4135
"title": "Distance",
4236
"minimum": 0,
@@ -45,7 +39,7 @@ def build_input(schema):
4539
"type": "number",
4640
}
4741
),
48-
"time": build_input(
42+
"time": ServiceInput.from_json_schema(
4943
{
5044
"title": "Time",
5145
"description": "Positive time",
@@ -54,21 +48,21 @@ def build_input(schema):
5448
"type": "number",
5549
}
5650
),
57-
"current": build_input(
51+
"current": ServiceInput.from_json_schema(
5852
{
5953
"title": "Current",
6054
"x_unit": "ampere",
6155
"type": "number",
6256
}
6357
),
64-
"luminosity": build_input(
58+
"luminosity": ServiceInput.from_json_schema(
6559
{
6660
"title": "Luminosity",
6761
"x_unit": "candela",
6862
"type": "number",
6963
}
7064
),
71-
"mass": build_input(
65+
"mass": ServiceInput.from_json_schema(
7266
{
7367
"title": "Mass",
7468
"description": "Positive mass",
@@ -77,44 +71,44 @@ def build_input(schema):
7771
"type": "number",
7872
}
7973
),
80-
"substance": build_input(
74+
"substance": ServiceInput.from_json_schema(
8175
{
8276
"title": "Substance",
8377
"minimum": 0,
8478
"x_unit": "milli-mole",
8579
"type": "number",
8680
}
8781
),
88-
"temperature": build_input(
82+
"temperature": ServiceInput.from_json_schema(
8983
{
9084
"title": "Temperature",
9185
"minimum": 0,
9286
"x_unit": "kelvin",
9387
"type": "number",
9488
}
9589
),
96-
"angle": build_input(
90+
"angle": ServiceInput.from_json_schema(
9791
{
9892
"title": "Angle",
9993
"x_unit": "degree",
10094
"type": "number",
10195
}
10296
),
103-
"velocity": build_input(
97+
"velocity": ServiceInput.from_json_schema(
10498
{
10599
"title": "Velo-city",
106100
"x_unit": "meter_per_second",
107101
"type": "number",
108102
}
109103
),
110-
"entropy": build_input(
104+
"entropy": ServiceInput.from_json_schema(
111105
{
112106
"title": "Entropy",
113107
"x_unit": "m**2 kg/s**2/K",
114108
"type": "number",
115109
}
116110
),
117-
"radiation": build_input(
111+
"radiation": ServiceInput.from_json_schema(
118112
{
119113
"title": "Radiation",
120114
"x_unit": "rutherford",
@@ -123,75 +117,75 @@ def build_input(schema):
123117
),
124118
},
125119
"outputs": {
126-
"mass": build_input(
120+
"mass": ServiceOutput.from_json_schema(
127121
{
128122
"title": "Mass",
129123
"minimum": 0,
130124
"x_unit": "kilo-gram",
131125
"type": "number",
132126
}
133127
),
134-
"luminosity": build_input(
128+
"luminosity": ServiceOutput.from_json_schema(
135129
{
136130
"title": "Luminosity",
137131
"x_unit": "candela",
138132
"type": "number",
139133
}
140134
),
141-
"current": build_input(
135+
"current": ServiceOutput.from_json_schema(
142136
{
143137
"title": "Current",
144138
"x_unit": "milli-ampere",
145139
"type": "number",
146140
}
147141
),
148-
"time": build_input(
142+
"time": ServiceOutput.from_json_schema(
149143
{
150144
"title": "Time",
151145
"minimum": 0,
152146
"x_unit": "minute",
153147
"type": "number",
154148
}
155149
),
156-
"length": build_input(
150+
"length": ServiceOutput.from_json_schema(
157151
{
158152
"title": "Distance",
159153
"description": "Distance value converted",
160154
"x_unit": "milli-meter",
161155
"type": "number",
162156
}
163157
),
164-
"substance": build_input(
158+
"substance": ServiceOutput.from_json_schema(
165159
{
166160
"title": "Substance",
167161
"minimum": 0,
168162
"x_unit": "mole",
169163
"type": "number",
170164
}
171165
),
172-
"temperature": build_input(
166+
"temperature": ServiceOutput.from_json_schema(
173167
{
174168
"title": "Temperature",
175169
"minimum": 0,
176170
"x_unit": "degree_Celsius",
177171
"type": "number",
178172
}
179173
),
180-
"angle": build_input(
174+
"angle": ServiceOutput.from_json_schema(
181175
{
182176
"title": "Angle",
183177
"x_unit": "radian",
184178
"type": "number",
185179
}
186180
),
187-
"velocity": build_input(
181+
"velocity": ServiceOutput.from_json_schema(
188182
{
189183
"title": "Velo-city",
190184
"x_unit": "kilometer_per_hour",
191185
"type": "number",
192186
}
193187
),
194-
"radiation": build_input(
188+
"radiation": ServiceOutput.from_json_schema(
195189
{
196190
"title": "Radiation",
197191
"x_unit": "curie",

packages/models-library/src/models_library/function_services_catalog/services/iter_range.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Iterator, Optional
22

3-
from ...projects_nodes import Outputs
3+
from ...projects_nodes import OutputsDict
44
from ...services import LATEST_INTEGRATION_VERSION, ServiceDockerData, ServiceType
55
from .._key_labels import FUNCTION_SERVICE_KEY_PREFIX
66
from .._utils import OM, FunctionServices, create_fake_thumbnail_url
@@ -60,7 +60,7 @@ def _linspace_func(
6060
yield value
6161

6262

63-
def _linspace_generator(**kwargs) -> Iterator[Outputs]:
63+
def _linspace_generator(**kwargs) -> Iterator[OutputsDict]:
6464
# Maps generator with iterable outputs.
6565
# Can have non-iterable outputs as well
6666
for value in _linspace_func(**kwargs):

packages/models-library/src/models_library/function_services_catalog/services/iter_sensitivity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from pydantic import schema_of
55

6-
from ...projects_nodes import Outputs
6+
from ...projects_nodes import OutputsDict
77
from ...services import LATEST_INTEGRATION_VERSION, ServiceDockerData, ServiceType
88
from .._key_labels import FUNCTION_SERVICE_KEY_PREFIX
99
from .._utils import EN, OM, FunctionServices, create_fake_thumbnail_url
@@ -96,7 +96,7 @@ def eval_sensitivity(
9696

9797
def _sensitivity_generator(
9898
paramrefs: List[float], paramdiff: List[float], diff_or_fact: bool
99-
) -> Iterator[Outputs]:
99+
) -> Iterator[OutputsDict]:
100100
for i, paramtestplus, paramtestminus in eval_sensitivity(
101101
paramrefs=paramrefs, paramdiff=paramdiff, diff_or_fact=diff_or_fact
102102
):

packages/models-library/src/models_library/projects_nodes.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@
4949
StrictBool,
5050
StrictInt,
5151
StrictFloat,
52-
Json, # FIXME: remove if OM sends object/array
52+
Json, # TODO: remove when OM sends object/array instead of json-formatted strings
5353
str,
5454
Union[SimCoreFileLink, DatCoreFileLink], # *FileLink to service
5555
DownloadLink,
5656
Union[List[Any], Dict[str, Any]], # arrays | object
5757
]
5858

5959
InputID = OutputID = constr(regex=PROPERTY_KEY_RE)
60-
Inputs = Dict[InputID, InputTypes]
61-
Outputs = Dict[OutputID, OutputTypes]
62-
InputsUnits = Dict[InputID, str]
60+
InputsDict = Dict[InputID, InputTypes]
61+
OutputsDict = Dict[OutputID, OutputTypes]
62+
UnitStr = constr(strip_whitespace=True)
6363

6464

6565
class NodeState(BaseModel):
@@ -136,12 +136,12 @@ class Node(BaseModel):
136136
)
137137

138138
# INPUT PORTS ---
139-
inputs: Optional[Inputs] = Field(
139+
inputs: Optional[InputsDict] = Field(
140140
default_factory=dict, description="values of input properties"
141141
)
142-
inputs_units: Optional[InputsUnits] = Field(
142+
inputs_units: Optional[Dict[InputID, UnitStr]] = Field(
143143
None,
144-
description="values of input unit",
144+
description="Overrides default unit (if any) defined in the service for each port",
145145
alias="inputsUnits",
146146
)
147147
input_access: Optional[Dict[InputID, AccessEnum]] = Field(
@@ -154,7 +154,7 @@ class Node(BaseModel):
154154
)
155155

156156
# OUTPUT PORTS ---
157-
outputs: Optional[Outputs] = Field(
157+
outputs: Optional[OutputsDict] = Field(
158158
default_factory=dict, description="values of output properties"
159159
)
160160
output_node: Optional[bool] = Field(None, deprecated=True, alias="outputNode")

0 commit comments

Comments
 (0)