Skip to content
Merged
Show file tree
Hide file tree
Changes from 98 commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
272a7d1
remove wrong comments
sanderegg Jul 28, 2025
3a7f200
ruff
sanderegg Jul 28, 2025
4e1eb9b
ruff
sanderegg Jul 29, 2025
54170a2
added NodeLockState to NodeState
sanderegg Jul 29, 2025
90389e0
uniformize
sanderegg Jul 29, 2025
e5db533
added _get_node_lock_state method
sanderegg Jul 29, 2025
88be681
first draft
sanderegg Jul 29, 2025
899f640
ongoing
sanderegg Jul 29, 2025
7cda1db
fixed examples
sanderegg Jul 29, 2025
df978e8
worksforme
sanderegg Jul 29, 2025
6de28a4
removed template parameter
sanderegg Jul 29, 2025
31558b8
always add the state
sanderegg Jul 29, 2025
966a5d1
properly update
sanderegg Jul 29, 2025
1a03ef6
sonar
sanderegg Jul 29, 2025
ae35f97
sonar
sanderegg Jul 29, 2025
ff9d0a1
@copilot review
sanderegg Jul 30, 2025
c7e8bbf
typing
sanderegg Jul 30, 2025
28591b0
improve
sanderegg Jul 30, 2025
f6b70d2
improve
sanderegg Jul 30, 2025
632b878
this might break
sanderegg Jul 30, 2025
05b0ebd
no need to double catch
sanderegg Jul 30, 2025
09e4d5a
make lock_state optional
sanderegg Jul 30, 2025
856f7f7
revert changes
sanderegg Jul 30, 2025
2b7cf2c
improve logs
sanderegg Jul 30, 2025
9991687
revert
sanderegg Jul 30, 2025
b9ccf53
fixed test
sanderegg Jul 30, 2025
9104e0c
use model_dump
sanderegg Jul 31, 2025
c44c6ad
added exception for share state
sanderegg Jul 31, 2025
947db8e
missing optional entry
sanderegg Jul 31, 2025
f1cbfd6
fix mock project
sanderegg Jul 31, 2025
a44bd4b
mypy
sanderegg Jul 31, 2025
feba6d3
fix test
sanderegg Jul 31, 2025
bfd68d6
this is how to fix the tests
sanderegg Jul 31, 2025
04d690c
this is how to fix the tests
sanderegg Jul 31, 2025
ae42e49
this is how to fix the tests
sanderegg Jul 31, 2025
b49c20f
fix test
sanderegg Aug 11, 2025
f2184f5
tests are passing
sanderegg Aug 11, 2025
131ba3b
fixing tests
sanderegg Aug 11, 2025
1f805d5
tests are passing
sanderegg Aug 11, 2025
b7e8ebd
tests are passing
sanderegg Aug 11, 2025
47ac372
tests are passing
sanderegg Aug 11, 2025
9090653
make tests pass
sanderegg Aug 11, 2025
c85f85c
updated openapi
sanderegg Aug 11, 2025
7ada667
fixing tests
sanderegg Aug 11, 2025
0ee10cc
not working yet
sanderegg Aug 11, 2025
84ef32c
adding stuff
sanderegg Aug 11, 2025
abd40c3
change max time
sanderegg Aug 11, 2025
1fb260a
trying
sanderegg Aug 11, 2025
f71574c
fixed test
sanderegg Aug 11, 2025
e0b54cd
not yet fixed
sanderegg Aug 11, 2025
f3a3862
pylint
sanderegg Aug 11, 2025
b0b1e8a
refactor
sanderegg Aug 12, 2025
b64037d
having fun
sanderegg Aug 12, 2025
bc1b3f5
updated openapi
sanderegg Aug 12, 2025
abf6295
ensure project nodes is properly populated
sanderegg Aug 12, 2025
7f935bd
missing types
sanderegg Aug 12, 2025
744b8ef
use asyncpg
sanderegg Aug 12, 2025
33e084c
ruff
sanderegg Aug 12, 2025
ecef2cd
replacing test
sanderegg Aug 12, 2025
c2aa331
moving to asyncpg
sanderegg Aug 12, 2025
0e90837
test replaced and passing
sanderegg Aug 12, 2025
4d3e7d1
test with closed project
sanderegg Aug 12, 2025
076f6a0
removed unnecessary test
sanderegg Aug 13, 2025
e2d3d3e
ruff
sanderegg Aug 14, 2025
057c2a0
refactor
sanderegg Aug 14, 2025
2aa1a6e
revert
sanderegg Aug 14, 2025
c2df0a4
minor
sanderegg Aug 14, 2025
c3b7cfe
added some context
sanderegg Aug 14, 2025
444d618
improve test
sanderegg Aug 14, 2025
8f36044
refactor
sanderegg Aug 14, 2025
e30528e
revert
sanderegg Aug 14, 2025
b977e39
send notification when node is started/stopped
sanderegg Aug 14, 2025
2c6ee34
test is there
sanderegg Aug 14, 2025
66021b2
added examples
sanderegg Aug 14, 2025
c7f5fe3
typo
sanderegg Aug 14, 2025
1b457e1
use json_schema examples
sanderegg Aug 14, 2025
72ba345
that sucks
sanderegg Aug 14, 2025
95c69f4
typo
sanderegg Aug 14, 2025
0de5da0
revert
sanderegg Aug 14, 2025
cd0552d
typo
sanderegg Aug 14, 2025
3bd9cea
add some more checks
sanderegg Aug 15, 2025
055eca7
ongoing
sanderegg Aug 15, 2025
01f2c0a
revert test
sanderegg Aug 15, 2025
2742a90
ongoing
sanderegg Aug 15, 2025
5095032
fix test by changing ordering of calls
sanderegg Aug 18, 2025
ad688c4
this is not looking good
sanderegg Aug 18, 2025
abb0dab
linter
sanderegg Aug 18, 2025
209f7e8
only the first example
sanderegg Aug 18, 2025
a14e801
missing delete
sanderegg Aug 18, 2025
f4126f3
reverted
sanderegg Aug 18, 2025
93f894a
type
sanderegg Aug 18, 2025
63a39e8
revert
sanderegg Aug 18, 2025
d58f9ac
type
sanderegg Aug 18, 2025
baa603f
added check for mirrored WSL2
sanderegg Aug 18, 2025
3e081cc
ruff
sanderegg Aug 18, 2025
75d53a1
@GitHK review: remove old comment
sanderegg Aug 18, 2025
e9ee94c
@GitHK review: removed unused fixture
sanderegg Aug 18, 2025
1cec2bb
must be nullable
sanderegg Aug 18, 2025
5e6a238
fix test
sanderegg Aug 18, 2025
3ebed22
fix test
sanderegg Aug 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,31 @@ export DOCKER_REGISTRY ?= itisfoundation

MAKEFILES_WITH_OPENAPI_SPECS := $(shell find . -mindepth 2 -type f -name 'Makefile' -not -path '*/.*' -exec grep -l '^openapi-specs:' {} \; | xargs realpath)

# WSL 2 tricks
define _check_wsl_mirroring
$(shell \
if [ "$(IS_WSL2)" = "WSL2" ]; then \
win_user=$$(powershell.exe '$$env:UserName' | tr -d '\r' | tail -n 1 | xargs); \
config_path="/mnt/c/Users/$$win_user/.wslconfig"; \
if [ -f "$$config_path" ] && grep -q "networkingMode.*=.*mirrored" "$$config_path" 2>/dev/null; then \
echo "true"; \
else \
echo "false"; \
fi; \
else \
echo "false"; \
fi \
)
endef

WSL_MIRRORED := $(_check_wsl_mirroring)


ifeq ($(WSL_MIRRORED),true)
get_my_ip := 127.0.0.1
else
get_my_ip := $(shell (hostname --all-ip-addresses || hostname -i) 2>/dev/null | cut --delimiter=" " --fields=1)
endif

# NOTE: this is only for WSL2 as the WSL2 subsystem IP is changing on each reboot
ifeq ($(IS_WSL2),WSL2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path

from pydantic import BaseModel, ConfigDict, Field
from pydantic.config import JsonDict

from ..basic_types import PortInt
from ..projects import ProjectID
Expand Down Expand Up @@ -88,40 +89,45 @@ class RunningDynamicServiceDetails(ServiceDetails):
alias="service_message",
)

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [ # legacy
{
"service_key": "simcore/services/dynamic/raw-graphs",
"service_version": "2.10.6",
"user_id": 1,
"project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008",
"service_uuid": "0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_basepath": "/x/0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_host": "raw-graphs_0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_port": 4000,
"published_port": None,
"entry_point": "",
"service_state": "running",
"service_message": "",
},
# new style
{
"service_key": "simcore/services/dynamic/jupyter-math",
"service_version": "3.0.3",
"user_id": 1,
"project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008",
"service_uuid": "6e3cad3a-eb64-43de-b476-9ac3c413fd9c",
"boot_type": "V2",
"service_host": "dy-sidecar_6e3cad3a-eb64-43de-b476-9ac3c413fd9c",
"service_port": 8888,
"service_state": "running",
"service_message": "",
},
]
}
)

model_config = ConfigDict(
ignored_types=(cached_property,),
json_schema_extra={
"examples": [
# legacy
{
"service_key": "simcore/services/dynamic/raw-graphs",
"service_version": "2.10.6",
"user_id": 1,
"project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008",
"service_uuid": "0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_basepath": "/x/0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_host": "raw-graphs_0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb",
"service_port": 4000,
"published_port": None,
"entry_point": "",
"service_state": "running",
"service_message": "",
},
# new style
{
"service_key": "simcore/services/dynamic/jupyter-math",
"service_version": "3.0.3",
"user_id": 1,
"project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008",
"service_uuid": "6e3cad3a-eb64-43de-b476-9ac3c413fd9c",
"boot_type": "V2",
"service_host": "dy-sidecar_6e3cad3a-eb64-43de-b476-9ac3c413fd9c",
"service_port": 8888,
"service_state": "running",
"service_message": "",
},
]
},
json_schema_extra=_update_json_schema_extra,
)

@cached_property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Annotated, Any, Literal, TypeAlias

from pydantic import ConfigDict, Field
from pydantic.config import JsonDict

from ..access_rights import ExecutableAccessRights
from ..api_schemas_directorv2.dynamic_services import RetrieveDataOut
Expand Down Expand Up @@ -163,14 +164,20 @@ class NodeGetIdle(OutputSchema):
def from_node_id(cls, node_id: NodeID) -> "NodeGetIdle":
return cls(service_state="idle", service_uuid=node_id)

model_config = ConfigDict(
json_schema_extra={
"example": {
"service_uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"service_state": "idle",
@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"service_uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"service_state": "idle",
}
]
}
}
)
)

model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)


class NodeGetUnknown(OutputSchema):
Expand Down
80 changes: 75 additions & 5 deletions packages/models-library/src/models_library/projects_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Models Node as a central element in a project's pipeline
"""

from typing import Annotated, Any, TypeAlias, Union
from enum import auto
from typing import Annotated, Any, Self, TypeAlias, Union

from common_library.basic_types import DEFAULT_FACTORY
from pydantic import (
Expand All @@ -16,10 +17,12 @@
StrictInt,
StringConstraints,
field_validator,
model_validator,
)
from pydantic.config import JsonDict

from .basic_types import EnvVarKey, KeyIDStr
from .groups import GroupID
from .projects_access import AccessEnum
from .projects_nodes_io import (
DatCoreFileLink,
Expand All @@ -31,8 +34,9 @@
from .projects_nodes_layout import Position
from .projects_state import RunningState
from .services import ServiceKey, ServiceVersion
from .utils.enums import StrAutoEnum

InputTypes = Union[
InputTypes = Union[ # noqa: UP007
# NOTE: WARNING the order in Union[*] below matters!
StrictBool,
StrictInt,
Expand All @@ -44,7 +48,7 @@
DownloadLink,
list[Any] | dict[str, Any], # arrays | object
]
OutputTypes = Union[
OutputTypes = Union[ # noqa: UP007
# NOTE: WARNING the order in Union[*] below matters!
StrictBool,
StrictInt,
Expand All @@ -71,6 +75,69 @@
UnitStr: TypeAlias = Annotated[str, StringConstraints(strip_whitespace=True)]


class NodeShareStatus(StrAutoEnum):
OPENING = auto()
OPENED = auto()
CLOSING = auto()


class NodeShareState(BaseModel):
locked: Annotated[
bool,
Field(
description="True if the node is locked, False otherwise",
),
]

current_user_groupids: Annotated[
list[GroupID] | None,
Field(
description="Group(s) that currently have access to the node (or locked it)"
),
] = None

status: Annotated[
NodeShareStatus | None,
Field(
description="Reason why the node is locked, None if not locked",
),
] = None

@model_validator(mode="after")
def _validate_lock_state(self) -> Self:
if self.locked and (self.current_user_groupids is None or self.status is None):
msg = "If the node is locked, both 'current_user_groupids' and 'status' must be set"
raise ValueError(msg)

return self

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"locked": False,
},
{
"locked": True,
"current_user_groupids": [666],
"status": "OPENING",
},
{
"locked": False,
"current_user_groupids": [666, 4563],
"status": "OPENED",
},
]
}
)

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


class NodeState(BaseModel):
modified: Annotated[
bool,
Expand Down Expand Up @@ -104,6 +171,10 @@ class NodeState(BaseModel):
),
] = 0

lock_state: Annotated[
NodeShareState | None, Field(description="the node's lock state")
] = None

model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
Expand Down Expand Up @@ -192,7 +263,7 @@ class Node(BaseModel):
] = DEFAULT_FACTORY

inputs_required: Annotated[
list[InputID],
list[InputID] | None,
Field(
default_factory=list,
description="Defines inputs that are required in order to run the service",
Expand Down Expand Up @@ -286,7 +357,6 @@ def _convert_empty_str_to_none(cls, v):
@classmethod
def _convert_from_enum(cls, v):
if isinstance(v, str):

# the old version of state was a enum of RunningState
running_state_value = _convert_old_enum_name(v)
return NodeState(current_status=running_state_value)
Expand Down
4 changes: 3 additions & 1 deletion packages/models-library/src/models_library/utils/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
@unique
class StrAutoEnum(StrEnum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
def _generate_next_value_(
name: str, start: int, count: int, last_values: list[str] # noqa: ARG004
) -> str:
return name.upper()


Expand Down
3 changes: 2 additions & 1 deletion packages/models-library/tests/test_project_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def test_create_minimal_node(minimal_node_data_sample: dict[str, Any]):
assert node.state.current_status == RunningState.NOT_STARTED
assert node.state.modified is True
assert node.state.dependencies == set()
assert node.state.lock_state is None

assert node.parent is None
assert node.progress is None
Expand All @@ -37,7 +38,7 @@ def test_create_minimal_node(minimal_node_data_sample: dict[str, Any]):


def test_create_minimal_node_with_new_data_type(
minimal_node_data_sample: dict[str, Any]
minimal_node_data_sample: dict[str, Any],
):
old_node_data = minimal_node_data_sample
# found some old data with this aspect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def create_registered_user(
with contextlib.ExitStack() as stack:

def _(**user_kwargs) -> dict[str, Any]:

user_id = len(created_user_ids) + 1
user = stack.enter_context(
sync_insert_and_get_user_and_secrets_lifespan(
Expand Down Expand Up @@ -122,8 +121,11 @@ async def _(
await project_nodes_repo.add(
con,
nodes=[
ProjectNodeCreate(node_id=NodeID(node_id), **default_node_config)
for node_id in inserted_project.workbench
ProjectNodeCreate(
node_id=NodeID(node_id),
**(default_node_config | node_data.model_dump(mode="json")),
)
for node_id, node_data in inserted_project.workbench.items()
],
)
await con.execute(
Expand Down
Loading
Loading