Skip to content

Commit c9e3d21

Browse files
✨ Is1647/collaboration feature - 1. iteration (OPS ⚠️) (#8140)
Co-authored-by: Sylvain <[email protected]>
1 parent 233031b commit c9e3d21

Some content is hidden

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

41 files changed

+876
-1026
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,25 @@ def to_domain_model(self) -> dict[str, Any]:
283283
return self.model_dump(exclude_unset=True, by_alias=False)
284284

285285

286+
class ProjectDocument(OutputSchema):
287+
uuid: ProjectID
288+
workspace_id: WorkspaceID | None
289+
name: str
290+
description: str
291+
thumbnail: HttpUrl | None
292+
last_change_date: datetime
293+
classifiers: list[ClassifierID]
294+
dev: dict | None
295+
quality: dict[str, Any]
296+
workbench: NodesDict
297+
ui: StudyUI | None
298+
type: ProjectType
299+
template_type: ProjectTemplateType | None
300+
301+
# config
302+
model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)
303+
304+
286305
__all__: tuple[str, ...] = (
287306
"EmptyModel",
288307
"ProjectCopyOverride",

packages/models-library/src/models_library/api_schemas_webserver/projects_ui.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,14 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
9191

9292
class StudyUI(OutputSchema):
9393
# Model fully controlled by the UI and stored under `projects.ui`
94-
icon: HttpUrl | None = None
94+
icon: HttpUrl | None = None # <-- Deprecated
9595

9696
workbench: dict[NodeIDStr, WorkbenchUI] | None = None
9797
slideshow: dict[NodeIDStr, SlideshowUI] | None = None
9898
current_node_id: NodeID | None = None
9999
annotations: dict[NodeIDStr, AnnotationUI] | None = None
100-
template_type: Literal["hypertool"] | None = None
100+
template_type: Literal["hypertool"] | None = None # <-- Deprecated
101+
mode: Literal["workbench", "app", "guided", "standalone", "pipeline"] | None = None
101102

102103
_empty_is_none = field_validator("*", mode="before")(
103104
empty_str_to_none_pre_validator

packages/models-library/src/models_library/api_schemas_webserver/socketio.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ..basic_types import IDStr
22
from ..groups import GroupID
3+
from ..projects import ProjectID
34
from ..users import UserID
45

56

@@ -15,3 +16,7 @@ def from_group_id(cls, group_id: GroupID) -> "SocketIORoomStr":
1516
@classmethod
1617
def from_user_id(cls, user_id: UserID) -> "SocketIORoomStr":
1718
return cls(f"user:{user_id}")
19+
20+
@classmethod
21+
def from_project_id(cls, project_id: ProjectID) -> "SocketIORoomStr":
22+
return cls(f"project:{project_id}")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class Node(BaseModel):
164164
ge=0,
165165
le=100,
166166
description="the node progress value (deprecated in DB, still used for API only)",
167-
deprecated=True,
167+
deprecated=True, # <-- Think this is not true, it is still used by the File Picker (frontend nodes)
168168
),
169169
] = None
170170

packages/pytest-simcore/pyproject.toml

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/pytest-simcore/uv.lock

Lines changed: 0 additions & 728 deletions
This file was deleted.

packages/service-library/src/servicelib/long_running_tasks/task.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from common_library.async_tools import cancel_wait_task
1313
from models_library.api_schemas_long_running_tasks.base import TaskProgress
1414
from pydantic import PositiveFloat
15-
from servicelib.background_task import create_periodic_task
16-
from servicelib.logging_utils import log_catch
1715

16+
from ..background_task import create_periodic_task
17+
from ..logging_utils import log_catch
1818
from .errors import (
1919
TaskAlreadyRunningError,
2020
TaskCancelledError,
@@ -33,7 +33,7 @@
3333
_DEFAULT_NAMESPACE: Final[str] = "lrt"
3434

3535
_CANCEL_TASK_TIMEOUT: Final[PositiveFloat] = datetime.timedelta(
36-
seconds=1
36+
seconds=10 # NOTE: 1 second is too short to cleanup a task
3737
).total_seconds()
3838

3939
RegisteredTaskName: TypeAlias = str
@@ -196,7 +196,6 @@ def _add_task(
196196
*,
197197
fire_and_forget: bool,
198198
) -> TrackedTask:
199-
200199
tracked_task = TrackedTask(
201200
task_id=task_id,
202201
task=task,

packages/service-library/src/servicelib/redis/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
ProjectLockError,
99
)
1010
from ._models import RedisManagerDBConfig
11+
from ._project_document_version import (
12+
PROJECT_DB_UPDATE_REDIS_LOCK_KEY,
13+
PROJECT_DOCUMENT_VERSION_KEY,
14+
increment_and_return_project_document_version,
15+
)
1116
from ._project_lock import (
1217
get_project_locked_state,
1318
is_project_locked,
@@ -19,10 +24,13 @@
1924
"CouldNotAcquireLockError",
2025
"CouldNotConnectToRedisError",
2126
"exclusive",
27+
"increment_and_return_project_document_version",
2228
"get_project_locked_state",
2329
"handle_redis_returns_union_types",
2430
"is_project_locked",
2531
"LockLostError",
32+
"PROJECT_DB_UPDATE_REDIS_LOCK_KEY",
33+
"PROJECT_DOCUMENT_VERSION_KEY",
2634
"ProjectLockError",
2735
"RedisClientSDK",
2836
"RedisClientsManager",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Project document versioning utilities.
2+
3+
This module provides utilities for managing project document versions using Redis.
4+
The versioning system ensures that all users working on a project are synchronized
5+
with the latest changes through atomic version incrementing.
6+
"""
7+
8+
from typing import Final
9+
10+
from models_library.projects import ProjectID
11+
12+
from ._client import RedisClientSDK
13+
14+
# Redis key patterns
15+
PROJECT_DOCUMENT_VERSION_KEY: Final[str] = "projects:{}:version"
16+
PROJECT_DB_UPDATE_REDIS_LOCK_KEY: Final[str] = "project_db_update:{}"
17+
18+
19+
async def increment_and_return_project_document_version(
20+
redis_client: RedisClientSDK, project_uuid: ProjectID
21+
) -> int:
22+
"""
23+
Atomically increments and returns the project document version using Redis.
24+
Returns the incremented version number.
25+
26+
This function ensures thread-safe version incrementing by using Redis INCR command
27+
which is atomic. The version starts at 1 for the first call.
28+
29+
Args:
30+
redis_client: The Redis client SDK instance
31+
project_uuid: The project UUID to get/increment version for
32+
33+
Returns:
34+
The new (incremented) version number
35+
"""
36+
version_key = PROJECT_DOCUMENT_VERSION_KEY.format(project_uuid)
37+
# If key doesn't exist, it's created with value 0 and then incremented to 1
38+
output = await redis_client.redis.incr(version_key)
39+
return int(output)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
4+
from typing import cast
5+
from uuid import UUID
6+
7+
import pytest
8+
from faker import Faker
9+
from models_library.projects import ProjectID
10+
from servicelib.redis import RedisClientSDK
11+
from servicelib.redis._project_document_version import (
12+
increment_and_return_project_document_version,
13+
)
14+
15+
pytest_simcore_core_services_selection = [
16+
"redis",
17+
]
18+
pytest_simcore_ops_services_selection = [
19+
"redis-commander",
20+
]
21+
22+
23+
@pytest.fixture()
24+
def project_uuid(faker: Faker) -> ProjectID:
25+
return cast(UUID, faker.uuid4(cast_to=None))
26+
27+
28+
async def test_project_document_version_workflow(
29+
redis_client_sdk: RedisClientSDK, project_uuid: ProjectID
30+
):
31+
"""Test the complete workflow of getting and incrementing project document versions."""
32+
33+
# First increment should return 1
34+
new_version = await increment_and_return_project_document_version(
35+
redis_client_sdk, project_uuid
36+
)
37+
assert new_version == 1
38+
39+
# Second increment should return 2
40+
new_version = await increment_and_return_project_document_version(
41+
redis_client_sdk, project_uuid
42+
)
43+
assert new_version == 2
44+
45+
# Multiple increments should work correctly
46+
for expected_version in range(3, 6):
47+
new_version = await increment_and_return_project_document_version(
48+
redis_client_sdk, project_uuid
49+
)
50+
assert new_version == expected_version

0 commit comments

Comments
 (0)