Skip to content
4 changes: 2 additions & 2 deletions packages/models-library/src/models_library/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ class ProjectAtDB(BaseProjectModel):

published: Annotated[
bool | None,
Field(default=False, description="Defines if a study is available publicly"),
]
Field(description="Defines if a study is available publicly"),
] = False

@field_validator("project_type", mode="before")
@classmethod
Expand Down
206 changes: 159 additions & 47 deletions packages/models-library/src/models_library/projects_nodes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Models Node as a central element in a project's pipeline
Models Node as a central element in a project's pipeline
"""

from typing import Annotated, Any, TypeAlias, Union
Expand All @@ -17,6 +17,7 @@
StringConstraints,
field_validator,
)
from pydantic.config import JsonDict

from .basic_types import EnvVarKey, KeyIDStr
from .projects_access import AccessEnum
Expand Down Expand Up @@ -71,26 +72,41 @@


class NodeState(BaseModel):
modified: bool = Field(
default=True, description="true if the node's outputs need to be re-computed"
)
dependencies: set[NodeID] = Field(
default_factory=set,
description="contains the node inputs dependencies if they need to be computed first",
)
current_status: RunningState = Field(
default=RunningState.NOT_STARTED,
description="the node's current state",
alias="currentStatus",
)
progress: float | None = Field(
default=0,
ge=0.0,
le=1.0,
description="current progress of the task if available (None if not started or not a computational task)",
)
modified: Annotated[
bool,
Field(
description="true if the node's outputs need to be re-computed",
),
] = True

dependencies: Annotated[
set[NodeID],
Field(
default_factory=set,
description="contains the node inputs dependencies if they need to be computed first",
),
] = DEFAULT_FACTORY

current_status: Annotated[
RunningState,
Field(
description="the node's current state",
alias="currentStatus",
),
] = RunningState.NOT_STARTED

progress: Annotated[
float | None,
Field(
ge=0.0,
le=1.0,
description="current progress of the task if available (None if not started or not a computational task)",
),
] = 0

model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
json_schema_extra={
"examples": [
{
Expand All @@ -113,24 +129,35 @@ class NodeState(BaseModel):
)


def _convert_old_enum_name(v) -> RunningState:
if v == "FAILURE":
return RunningState.FAILED
return RunningState(v)


class Node(BaseModel):
key: ServiceKey = Field(
...,
description="distinctive name for the node based on the docker registry path",
examples=[
"simcore/services/comp/itis/sleeper",
"simcore/services/dynamic/3dviewer",
"simcore/services/frontend/file-picker",
],
)
version: ServiceVersion = Field(
...,
description="semantic version number of the node",
examples=["1.0.0", "0.0.1"],
)
label: str = Field(
..., description="The short name of the node", examples=["JupyterLab"]
)
key: Annotated[
ServiceKey,
Field(
description="distinctive name for the node based on the docker registry path",
examples=[
"simcore/services/comp/itis/sleeper",
"simcore/services/dynamic/3dviewer",
"simcore/services/frontend/file-picker",
],
),
]
version: Annotated[
ServiceVersion,
Field(
description="semantic version number of the node",
examples=["1.0.0", "0.0.1"],
),
]
label: Annotated[
str,
Field(description="The short name of the node", examples=["JupyterLab"]),
]
progress: Annotated[
float | None,
Field(
Expand Down Expand Up @@ -204,9 +231,9 @@ class Node(BaseModel):
Field(default_factory=dict, description="values of output properties"),
] = DEFAULT_FACTORY

output_node: Annotated[
bool | None, Field(deprecated=True, alias="outputNode")
] = None
output_node: Annotated[bool | None, Field(deprecated=True, alias="outputNode")] = (
None
)

output_nodes: Annotated[
list[NodeID] | None,
Expand Down Expand Up @@ -255,24 +282,109 @@ def _convert_empty_str_to_none(cls, v):
return None
return v

@classmethod
def _convert_old_enum_name(cls, v) -> RunningState:
if v == "FAILURE":
return RunningState.FAILED
return RunningState(v)

@field_validator("state", mode="before")
@classmethod
def _convert_from_enum(cls, v):
if isinstance(v, str):

# the old version of state was a enum of RunningState
running_state_value = cls._convert_old_enum_name(v)
return NodeState(currentStatus=running_state_value)
running_state_value = _convert_old_enum_name(v)
return NodeState(current_status=running_state_value)
return v

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
# Minimal example with only required fields
{
"key": "simcore/services/comp/no_ports",
"version": "1.0.0",
"label": "Sleep",
},
# Complete example with optional fields
{
"key": "simcore/services/comp/only_inputs",
"version": "1.0.0",
"label": "Only INputs",
"inputs": {
"input_1": 1,
"input_2": 2,
"input_3": 3,
},
},
# Complete example with optional fields
{
"key": "simcore/services/comp/only_outputs",
"version": "1.0.0",
"label": "Only Outputs",
"outputs": {
"output_1": 1,
"output_2": 2,
"output_3": 3,
},
},
# Example with all possible input and output types
{
"key": "simcore/services/comp/itis/all-types",
"version": "1.0.0",
"label": "All Types Demo",
"inputs": {
"boolean_input": True,
"integer_input": 42,
"float_input": 3.14159,
"string_input": "text value",
"json_input": {"key": "value", "nested": {"data": 123}},
"port_link_input": {
"nodeUuid": "f2700a54-adcf-45d4-9881-01ec30fd75a2",
"output": "out_1",
},
"simcore_file_link": {
"store": "simcore.s3",
"path": "123e4567-e89b-12d3-a456-426614174000/test.csv",
},
"datcore_file_link": {
"store": "datcore",
"dataset": "N:dataset:123",
"path": "path/to/file.txt",
},
"download_link": {
"downloadLink": "https://example.com/downloadable/file.txt"
},
"array_input": [1, 2, 3, 4, 5],
"object_input": {"name": "test", "value": 42},
},
"outputs": {
"boolean_output": False,
"integer_output": 100,
"float_output": 2.71828,
"string_output": "result text",
"json_output": {"status": "success", "data": [1, 2, 3]},
"simcore_file_output": {
"store": "simcore.s3",
"path": "987e6543-e21b-12d3-a456-426614174000/result.csv",
},
"datcore_file_output": {
"store": "datcore",
"dataset": "N:dataset:456",
"path": "results/output.txt",
},
"download_link_output": {
"downloadLink": "https://example.com/results/download.txt"
},
"array_output": ["a", "b", "c", "d"],
"object_output": {"status": "complete", "count": 42},
},
},
],
}
)

model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
json_schema_extra=_update_json_schema_extra,
)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from datetime import datetime
from typing import Annotated, TypeAlias
from uuid import uuid4

from models_library.projects import ProjectID
from models_library.projects import NodesDict, ProjectID
from models_library.projects_nodes import Node
from models_library.rpc_pagination import PageRpc
from pydantic import BaseModel, ConfigDict, Field
from pydantic.config import JsonDict


class ProjectRpcGet(BaseModel):
class ProjectJobRpcGet(BaseModel):
"""
Minimal information about a project that (for now) will fullfill
the needs of the api-server. Specifically, the fields needed in
Expand All @@ -23,19 +26,44 @@ class ProjectRpcGet(BaseModel):
]
description: str

workbench: NodesDict

# timestamps
creation_date: datetime
last_change_date: datetime
creation_at: datetime
modified_at: datetime

# Specific to jobs
job_parent_resource_name: str

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
nodes_examples = Node.model_json_schema()["examples"]
schema.update(
{
"examples": [
{
"uuid": "12345678-1234-5678-1234-123456789012",
"name": "My project",
"description": "My project description",
"workbench": {f"{uuid4()}": n for n in nodes_examples[2:3]},
"creation_at": "2023-01-01T00:00:00Z",
"modified_at": "2023-01-01T00:00:00Z",
"job_parent_resource_name": "solvers/foo/release/1.2.3",
},
]
}
)

model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
json_schema_extra=_update_json_schema_extra,
)


PageRpcProjectRpcGet: TypeAlias = PageRpc[
PageRpcProjectJobRpcGet: TypeAlias = PageRpc[
# WARNING: keep this definition in models_library and not in the RPC interface
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
# and will fail to serialize/deserialize these parameters when transmitted/received
ProjectRpcGet
ProjectJobRpcGet
]
Loading
Loading