Skip to content

Commit 3d7c505

Browse files
refactor duplication
1 parent 6befcd9 commit 3d7c505

File tree

5 files changed

+83
-111
lines changed

5 files changed

+83
-111
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
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+
get_and_increment_project_document_version,
15+
get_project_document_version,
16+
)
1117
from ._project_lock import (
1218
get_project_locked_state,
1319
is_project_locked,
@@ -19,15 +25,17 @@
1925
"CouldNotAcquireLockError",
2026
"CouldNotConnectToRedisError",
2127
"exclusive",
28+
"get_and_increment_project_document_version",
29+
"get_project_document_version",
2230
"get_project_locked_state",
2331
"handle_redis_returns_union_types",
2432
"is_project_locked",
2533
"LockLostError",
34+
"PROJECT_DB_UPDATE_REDIS_LOCK_KEY",
35+
"PROJECT_DOCUMENT_VERSION_KEY",
2636
"ProjectLockError",
2737
"RedisClientSDK",
2838
"RedisClientsManager",
2939
"RedisManagerDBConfig",
3040
"with_project_locked",
3141
)
32-
33-
# nopycln: file
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 get_and_increment_project_document_version(
20+
redis_client: RedisClientSDK, project_uuid: ProjectID
21+
) -> int:
22+
"""
23+
Atomically gets and increments 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+
return await redis_client.redis.incr(version_key)
39+
40+
41+
async def get_project_document_version(
42+
redis_client: RedisClientSDK, project_uuid: ProjectID
43+
) -> int:
44+
"""
45+
Gets the current project document version without incrementing it.
46+
47+
Args:
48+
redis_client: The Redis client SDK instance
49+
project_uuid: The project UUID to get version for
50+
51+
Returns:
52+
The current version number (0 if no version exists yet)
53+
"""
54+
version_key = PROJECT_DOCUMENT_VERSION_KEY.format(project_uuid)
55+
version = await redis_client.redis.get(version_key)
56+
return int(version) if version is not None else 0

services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@
4444
from pydantic.types import PositiveInt
4545
from servicelib.aiohttp.application_keys import APP_AIOPG_ENGINE_KEY
4646
from servicelib.logging_utils import get_log_record_extra, log_context
47-
from servicelib.redis import exclusive
47+
from servicelib.redis import (
48+
PROJECT_DB_UPDATE_REDIS_LOCK_KEY,
49+
exclusive,
50+
get_and_increment_project_document_version,
51+
)
4852
from simcore_postgres_database.aiopg_errors import UniqueViolation
4953
from simcore_postgres_database.models.groups import user_to_groups
5054
from simcore_postgres_database.models.project_to_groups import project_to_groups
@@ -78,7 +82,10 @@
7882
from tenacity.asyncio import AsyncRetrying
7983
from tenacity.retry import retry_if_exception_type
8084

81-
from ..redis import get_redis_lock_manager_client_sdk
85+
from ..redis import (
86+
get_redis_document_manager_client_sdk,
87+
get_redis_lock_manager_client_sdk,
88+
)
8289
from ..utils import now_str
8390
from . import _projects_repository
8491
from ._comments_repository import (
@@ -122,32 +129,6 @@
122129
field=IDStr("last_change_date"), direction=OrderDirection.DESC
123130
)
124131

125-
# Project locking and versioning constants
126-
PROJECT_DOCUMENT_VERSION_KEY: str = "projects:{}:version"
127-
PROJECT_DB_UPDATE_REDIS_LOCK_KEY: str = "project_db_update:{}"
128-
129-
130-
async def _get_and_increment_project_document_version(
131-
app: web.Application, project_uuid: ProjectID
132-
) -> int:
133-
"""
134-
Atomically gets and increments the project document version using Redis.
135-
Returns the incremented version number.
136-
137-
Args:
138-
app: The web application instance
139-
project_uuid: The project UUID to get/increment version for
140-
141-
Returns:
142-
The new (incremented) version number
143-
"""
144-
from ..redis import get_redis_document_manager_client_sdk
145-
146-
redis_client_sdk = get_redis_document_manager_client_sdk(app)
147-
version_key = PROJECT_DOCUMENT_VERSION_KEY.format(project_uuid)
148-
# If key doesn't exist, it's created with value 0 and then incremented to 1
149-
return await redis_client_sdk.redis.incr(version_key)
150-
151132

152133
# pylint: disable=too-many-public-methods
153134
# NOTE: https://github.com/ITISFoundation/osparc-simcore/issues/3516
@@ -995,8 +976,9 @@ async def _update_workbench_and_notify() -> (
995976
)
996977

997978
# Increment document version and notify users
998-
document_version = await _get_and_increment_project_document_version(
999-
app=self._app, project_uuid=project_uuid
979+
redis_client_sdk = get_redis_document_manager_client_sdk(self._app)
980+
document_version = await get_and_increment_project_document_version(
981+
redis_client=redis_client_sdk, project_uuid=project_uuid
1000982
)
1001983
await notify_project_document_updated(
1002984
app=self._app,

services/web/server/src/simcore_service_webserver/projects/_projects_service.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@
8484
ServiceWasNotFoundError,
8585
)
8686
from servicelib.redis import (
87+
PROJECT_DB_UPDATE_REDIS_LOCK_KEY,
8788
exclusive,
89+
get_and_increment_project_document_version,
8890
get_project_locked_state,
8991
is_project_locked,
9092
with_project_locked,
@@ -168,33 +170,6 @@
168170

169171
log = logging.getLogger(__name__)
170172

171-
PROJECT_DOCUMENT_VERSION_KEY: str = "projects:{}:version"
172-
PROJECT_DB_UPDATE_REDIS_LOCK_KEY: str = "project_db_update:{}"
173-
174-
175-
async def _get_and_increment_project_document_version(
176-
app: web.Application, project_uuid: ProjectID
177-
) -> int:
178-
"""
179-
Atomically gets and increments the project document version using Redis.
180-
181-
This function ensures thread-safe version incrementing by using Redis INCR command
182-
which is atomic. The version starts at 1 for the first call.
183-
184-
Args:
185-
app: The web application instance
186-
project_uuid: The project UUID
187-
188-
Returns:
189-
The new incremented version number
190-
"""
191-
redis_client_sdk = get_redis_document_manager_client_sdk(app)
192-
version_key = PROJECT_DOCUMENT_VERSION_KEY.format(project_uuid)
193-
194-
# Redis INCR is atomic and returns the new value
195-
# If key doesn't exist, it's created with value 0 and then incremented to 1
196-
return await redis_client_sdk.redis.incr(version_key)
197-
198173

199174
async def patch_project_and_notify_users(
200175
app: web.Application,
@@ -256,8 +231,9 @@ async def _patch_and_notify() -> None:
256231
ProjectTemplateType, project_with_workbench.template_type
257232
),
258233
)
259-
document_version = await _get_and_increment_project_document_version(
260-
app=app, project_uuid=project_uuid
234+
redis_client_sdk = get_redis_document_manager_client_sdk(app)
235+
document_version = await get_and_increment_project_document_version(
236+
redis_client=redis_client_sdk, project_uuid=project_uuid
261237
)
262238
await notify_project_document_updated(
263239
app=app,

services/web/server/src/simcore_service_webserver/projects/_socketio.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
from ..socketio.messages import send_message_to_project_room
1111

12-
# SOCKET_IO_PROJECT_CREATED_EVENT: Final[str] = "projectDocument:created"
13-
# SOCKET_IO_PROJECT_DELETED_EVENT: Final[str] = "projectDocument:deleted"
1412
SOCKET_IO_PROJECT_DOCUMENT_UPDATED_EVENT: Final[str] = "projectDocument:updated"
1513

1614

@@ -31,33 +29,6 @@ class ProjectDocumentEvent(BaseEvent):
3129
document: ProjectDocument
3230

3331

34-
# async def notify_project_created(
35-
# app: web.Application,
36-
# *,
37-
# project_id: ProjectID,
38-
# product_name: ProductName,
39-
# user_group_id: GroupID,
40-
# project_name: str,
41-
# created: datetime.datetime,
42-
# modified: datetime.datetime,
43-
# ) -> None:
44-
# notification_message = SocketMessageDict(
45-
# event_type=SOCKET_IO_PROJECT_CREATED_EVENT,
46-
# data={
47-
# **ProjectCreatedOrUpdatedEvent(
48-
# product_name=product_name,
49-
# project_id=project_id,
50-
# user_group_id=user_group_id,
51-
# name=project_name,
52-
# created=created,
53-
# modified=modified,
54-
# ).model_dump(mode="json", by_alias=True),
55-
# },
56-
# )
57-
58-
# await send_message_to_project_room(app, project_id, notification_message)
59-
60-
6132
async def notify_project_document_updated(
6233
app: web.Application,
6334
*,
@@ -78,24 +49,3 @@ async def notify_project_document_updated(
7849
},
7950
)
8051
await send_message_to_project_room(app, project_id, notification_message)
81-
82-
83-
# async def notify_project_deleted(
84-
# app: web.Application,
85-
# *,
86-
# project_id: ProjectID,
87-
# product_name: ProductName,
88-
# user_group_id: GroupID,
89-
# ) -> None:
90-
# notification_message = SocketMessageDict(
91-
# event_type=SOCKET_IO_PROJECT_DELETED_EVENT,
92-
# data={
93-
# **ProjectDeletedEvent(
94-
# product_name=product_name,
95-
# project_id=project_id,
96-
# user_group_id=user_group_id,
97-
# ).model_dump(mode="json", by_alias=True),
98-
# },
99-
# )
100-
101-
# await send_message_to_project_room(app, project_id, notification_message)

0 commit comments

Comments
 (0)