Skip to content

Commit cb79d90

Browse files
authored
🎨 Extends list_projects_marked_as_jobs in the webserver's rpc API (#7577)
1 parent fb26261 commit cb79d90

File tree

23 files changed

+400
-169
lines changed

23 files changed

+400
-169
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class ProjectAtDB(BaseProjectModel):
113113

114114
published: Annotated[
115115
bool | None,
116-
Field(default=False, description="Defines if a study is available publicly"),
117-
]
116+
Field(description="Defines if a study is available publicly"),
117+
] = False
118118

119119
@field_validator("project_type", mode="before")
120120
@classmethod

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

Lines changed: 159 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Models Node as a central element in a project's pipeline
2+
Models Node as a central element in a project's pipeline
33
"""
44

55
from typing import Annotated, Any, TypeAlias, Union
@@ -17,6 +17,7 @@
1717
StringConstraints,
1818
field_validator,
1919
)
20+
from pydantic.config import JsonDict
2021

2122
from .basic_types import EnvVarKey, KeyIDStr
2223
from .projects_access import AccessEnum
@@ -71,26 +72,41 @@
7172

7273

7374
class NodeState(BaseModel):
74-
modified: bool = Field(
75-
default=True, description="true if the node's outputs need to be re-computed"
76-
)
77-
dependencies: set[NodeID] = Field(
78-
default_factory=set,
79-
description="contains the node inputs dependencies if they need to be computed first",
80-
)
81-
current_status: RunningState = Field(
82-
default=RunningState.NOT_STARTED,
83-
description="the node's current state",
84-
alias="currentStatus",
85-
)
86-
progress: float | None = Field(
87-
default=0,
88-
ge=0.0,
89-
le=1.0,
90-
description="current progress of the task if available (None if not started or not a computational task)",
91-
)
75+
modified: Annotated[
76+
bool,
77+
Field(
78+
description="true if the node's outputs need to be re-computed",
79+
),
80+
] = True
81+
82+
dependencies: Annotated[
83+
set[NodeID],
84+
Field(
85+
default_factory=set,
86+
description="contains the node inputs dependencies if they need to be computed first",
87+
),
88+
] = DEFAULT_FACTORY
89+
90+
current_status: Annotated[
91+
RunningState,
92+
Field(
93+
description="the node's current state",
94+
alias="currentStatus",
95+
),
96+
] = RunningState.NOT_STARTED
97+
98+
progress: Annotated[
99+
float | None,
100+
Field(
101+
ge=0.0,
102+
le=1.0,
103+
description="current progress of the task if available (None if not started or not a computational task)",
104+
),
105+
] = 0
106+
92107
model_config = ConfigDict(
93108
extra="forbid",
109+
populate_by_name=True,
94110
json_schema_extra={
95111
"examples": [
96112
{
@@ -113,24 +129,35 @@ class NodeState(BaseModel):
113129
)
114130

115131

132+
def _convert_old_enum_name(v) -> RunningState:
133+
if v == "FAILURE":
134+
return RunningState.FAILED
135+
return RunningState(v)
136+
137+
116138
class Node(BaseModel):
117-
key: ServiceKey = Field(
118-
...,
119-
description="distinctive name for the node based on the docker registry path",
120-
examples=[
121-
"simcore/services/comp/itis/sleeper",
122-
"simcore/services/dynamic/3dviewer",
123-
"simcore/services/frontend/file-picker",
124-
],
125-
)
126-
version: ServiceVersion = Field(
127-
...,
128-
description="semantic version number of the node",
129-
examples=["1.0.0", "0.0.1"],
130-
)
131-
label: str = Field(
132-
..., description="The short name of the node", examples=["JupyterLab"]
133-
)
139+
key: Annotated[
140+
ServiceKey,
141+
Field(
142+
description="distinctive name for the node based on the docker registry path",
143+
examples=[
144+
"simcore/services/comp/itis/sleeper",
145+
"simcore/services/dynamic/3dviewer",
146+
"simcore/services/frontend/file-picker",
147+
],
148+
),
149+
]
150+
version: Annotated[
151+
ServiceVersion,
152+
Field(
153+
description="semantic version number of the node",
154+
examples=["1.0.0", "0.0.1"],
155+
),
156+
]
157+
label: Annotated[
158+
str,
159+
Field(description="The short name of the node", examples=["JupyterLab"]),
160+
]
134161
progress: Annotated[
135162
float | None,
136163
Field(
@@ -204,9 +231,9 @@ class Node(BaseModel):
204231
Field(default_factory=dict, description="values of output properties"),
205232
] = DEFAULT_FACTORY
206233

207-
output_node: Annotated[
208-
bool | None, Field(deprecated=True, alias="outputNode")
209-
] = None
234+
output_node: Annotated[bool | None, Field(deprecated=True, alias="outputNode")] = (
235+
None
236+
)
210237

211238
output_nodes: Annotated[
212239
list[NodeID] | None,
@@ -255,24 +282,109 @@ def _convert_empty_str_to_none(cls, v):
255282
return None
256283
return v
257284

258-
@classmethod
259-
def _convert_old_enum_name(cls, v) -> RunningState:
260-
if v == "FAILURE":
261-
return RunningState.FAILED
262-
return RunningState(v)
263-
264285
@field_validator("state", mode="before")
265286
@classmethod
266287
def _convert_from_enum(cls, v):
267288
if isinstance(v, str):
289+
268290
# the old version of state was a enum of RunningState
269-
running_state_value = cls._convert_old_enum_name(v)
270-
return NodeState(currentStatus=running_state_value)
291+
running_state_value = _convert_old_enum_name(v)
292+
return NodeState(current_status=running_state_value)
271293
return v
272294

295+
@staticmethod
296+
def _update_json_schema_extra(schema: JsonDict) -> None:
297+
schema.update(
298+
{
299+
"examples": [
300+
# Minimal example with only required fields
301+
{
302+
"key": "simcore/services/comp/no_ports",
303+
"version": "1.0.0",
304+
"label": "Sleep",
305+
},
306+
# Complete example with optional fields
307+
{
308+
"key": "simcore/services/comp/only_inputs",
309+
"version": "1.0.0",
310+
"label": "Only INputs",
311+
"inputs": {
312+
"input_1": 1,
313+
"input_2": 2,
314+
"input_3": 3,
315+
},
316+
},
317+
# Complete example with optional fields
318+
{
319+
"key": "simcore/services/comp/only_outputs",
320+
"version": "1.0.0",
321+
"label": "Only Outputs",
322+
"outputs": {
323+
"output_1": 1,
324+
"output_2": 2,
325+
"output_3": 3,
326+
},
327+
},
328+
# Example with all possible input and output types
329+
{
330+
"key": "simcore/services/comp/itis/all-types",
331+
"version": "1.0.0",
332+
"label": "All Types Demo",
333+
"inputs": {
334+
"boolean_input": True,
335+
"integer_input": 42,
336+
"float_input": 3.14159,
337+
"string_input": "text value",
338+
"json_input": {"key": "value", "nested": {"data": 123}},
339+
"port_link_input": {
340+
"nodeUuid": "f2700a54-adcf-45d4-9881-01ec30fd75a2",
341+
"output": "out_1",
342+
},
343+
"simcore_file_link": {
344+
"store": "simcore.s3",
345+
"path": "123e4567-e89b-12d3-a456-426614174000/test.csv",
346+
},
347+
"datcore_file_link": {
348+
"store": "datcore",
349+
"dataset": "N:dataset:123",
350+
"path": "path/to/file.txt",
351+
},
352+
"download_link": {
353+
"downloadLink": "https://example.com/downloadable/file.txt"
354+
},
355+
"array_input": [1, 2, 3, 4, 5],
356+
"object_input": {"name": "test", "value": 42},
357+
},
358+
"outputs": {
359+
"boolean_output": False,
360+
"integer_output": 100,
361+
"float_output": 2.71828,
362+
"string_output": "result text",
363+
"json_output": {"status": "success", "data": [1, 2, 3]},
364+
"simcore_file_output": {
365+
"store": "simcore.s3",
366+
"path": "987e6543-e21b-12d3-a456-426614174000/result.csv",
367+
},
368+
"datcore_file_output": {
369+
"store": "datcore",
370+
"dataset": "N:dataset:456",
371+
"path": "results/output.txt",
372+
},
373+
"download_link_output": {
374+
"downloadLink": "https://example.com/results/download.txt"
375+
},
376+
"array_output": ["a", "b", "c", "d"],
377+
"object_output": {"status": "complete", "count": 42},
378+
},
379+
},
380+
],
381+
}
382+
)
383+
273384
model_config = ConfigDict(
274385
extra="forbid",
275386
populate_by_name=True,
387+
json_schema_extra=_update_json_schema_extra,
276388
)
277389

278390

packages/models-library/src/models_library/rpc/webserver/projects.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from datetime import datetime
22
from typing import Annotated, TypeAlias
3+
from uuid import uuid4
34

4-
from models_library.projects import ProjectID
5+
from models_library.projects import NodesDict, ProjectID
6+
from models_library.projects_nodes import Node
57
from models_library.rpc_pagination import PageRpc
68
from pydantic import BaseModel, ConfigDict, Field
9+
from pydantic.config import JsonDict
710

811

9-
class ProjectRpcGet(BaseModel):
12+
class ProjectJobRpcGet(BaseModel):
1013
"""
1114
Minimal information about a project that (for now) will fullfill
1215
the needs of the api-server. Specifically, the fields needed in
@@ -23,19 +26,44 @@ class ProjectRpcGet(BaseModel):
2326
]
2427
description: str
2528

29+
workbench: NodesDict
30+
2631
# timestamps
27-
creation_date: datetime
28-
last_change_date: datetime
32+
creation_at: datetime
33+
modified_at: datetime
34+
35+
# Specific to jobs
36+
job_parent_resource_name: str
37+
38+
@staticmethod
39+
def _update_json_schema_extra(schema: JsonDict) -> None:
40+
nodes_examples = Node.model_json_schema()["examples"]
41+
schema.update(
42+
{
43+
"examples": [
44+
{
45+
"uuid": "12345678-1234-5678-1234-123456789012",
46+
"name": "My project",
47+
"description": "My project description",
48+
"workbench": {f"{uuid4()}": n for n in nodes_examples[2:3]},
49+
"creation_at": "2023-01-01T00:00:00Z",
50+
"modified_at": "2023-01-01T00:00:00Z",
51+
"job_parent_resource_name": "solvers/foo/release/1.2.3",
52+
},
53+
]
54+
}
55+
)
2956

3057
model_config = ConfigDict(
3158
extra="forbid",
3259
populate_by_name=True,
60+
json_schema_extra=_update_json_schema_extra,
3361
)
3462

3563

36-
PageRpcProjectRpcGet: TypeAlias = PageRpc[
64+
PageRpcProjectJobRpcGet: TypeAlias = PageRpc[
3765
# WARNING: keep this definition in models_library and not in the RPC interface
3866
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
3967
# and will fail to serialize/deserialize these parameters when transmitted/received
40-
ProjectRpcGet
68+
ProjectJobRpcGet
4169
]

0 commit comments

Comments
 (0)