Skip to content

Commit 03cb7bd

Browse files
committed
moved project_lock in servicelib
1 parent 9740b2f commit 03cb7bd

File tree

10 files changed

+154
-238
lines changed

10 files changed

+154
-238
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,29 @@
77
LockLostError,
88
)
99
from ._models import RedisManagerDBConfig
10+
from ._project_lock import (
11+
PROJECT_REDIS_LOCK_KEY,
12+
ProjectLockError,
13+
get_project_locked_state,
14+
is_project_locked,
15+
with_project_locked,
16+
)
1017
from ._utils import handle_redis_returns_union_types
1118

1219
__all__: tuple[str, ...] = (
1320
"CouldNotAcquireLockError",
1421
"CouldNotConnectToRedisError",
1522
"exclusive",
23+
"get_project_locked_state",
1624
"handle_redis_returns_union_types",
25+
"is_project_locked",
1726
"LockLostError",
27+
"PROJECT_REDIS_LOCK_KEY",
28+
"ProjectLockError",
1829
"RedisClientSDK",
1930
"RedisClientsManager",
2031
"RedisManagerDBConfig",
32+
"with_project_locked",
2133
)
2234

2335
# nopycln: file

packages/service-library/src/servicelib/project_lock.py renamed to packages/service-library/src/servicelib/redis/_project_lock.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import datetime
21
import functools
32
from collections.abc import Callable, Coroutine
43
from typing import Any, Final, ParamSpec, TypeAlias, TypeVar
@@ -9,10 +8,9 @@
98
from models_library.projects_access import Owner
109
from models_library.projects_state import ProjectLocked, ProjectStatus
1110

12-
from .redis import CouldNotAcquireLockError, RedisClientSDK, exclusive
11+
from . import CouldNotAcquireLockError, RedisClientSDK, exclusive
1312

14-
PROJECT_REDIS_LOCK_KEY: str = "project_lock:{}"
15-
PROJECT_LOCK_TIMEOUT: Final[datetime.timedelta] = datetime.timedelta(seconds=10)
13+
PROJECT_REDIS_LOCK_KEY: Final[str] = "project_lock:{}"
1614

1715
ProjectLockError: TypeAlias = redis.exceptions.LockError
1816

packages/service-library/tests/redis/test_decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ async def _():
7575
pass
7676

7777

78-
async def test_exclusive_decorator(
78+
async def test_exclusive_decorator_runs_original_method(
7979
redis_client_sdk: RedisClientSDK,
8080
lock_name: str,
8181
sleep_duration: float,
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# pylint: disable=no-value-for-parameter
2+
# pylint: disable=protected-access
3+
# pylint: disable=redefined-outer-name
4+
# pylint: disable=unused-argument
5+
# pylint: disable=unused-variable
6+
7+
import asyncio
8+
from typing import cast
9+
from uuid import UUID
10+
11+
import pytest
12+
from faker import Faker
13+
from models_library.projects import ProjectID
14+
from models_library.projects_access import Owner
15+
from models_library.projects_state import ProjectLocked, ProjectStatus
16+
from servicelib.async_utils import cancel_wait_task
17+
from servicelib.redis import (
18+
PROJECT_REDIS_LOCK_KEY,
19+
ProjectLockError,
20+
RedisClientSDK,
21+
get_project_locked_state,
22+
is_project_locked,
23+
with_project_locked,
24+
)
25+
26+
pytest_simcore_core_services_selection = [
27+
"redis",
28+
]
29+
pytest_simcore_ops_services_selection = [
30+
"redis-commander",
31+
]
32+
33+
34+
@pytest.fixture()
35+
def project_uuid(faker: Faker) -> ProjectID:
36+
return cast(UUID, faker.uuid4(cast_to=None))
37+
38+
39+
assert "json_schema_extra" in Owner.model_config
40+
assert isinstance(Owner.model_config["json_schema_extra"], dict)
41+
assert isinstance(Owner.model_config["json_schema_extra"]["examples"], list)
42+
43+
44+
@pytest.fixture(params=Owner.model_config["json_schema_extra"]["examples"])
45+
def owner(request: pytest.FixtureRequest) -> Owner:
46+
return Owner(**request.param)
47+
48+
49+
@pytest.mark.parametrize(
50+
"project_status",
51+
[
52+
ProjectStatus.CLOSING,
53+
ProjectStatus.CLONING,
54+
ProjectStatus.EXPORTING,
55+
ProjectStatus.OPENING,
56+
ProjectStatus.MAINTAINING,
57+
],
58+
)
59+
async def test_with_project_locked(
60+
redis_client_sdk: RedisClientSDK,
61+
project_uuid: ProjectID,
62+
owner: Owner,
63+
project_status: ProjectStatus,
64+
):
65+
@with_project_locked(
66+
redis_client_sdk,
67+
project_uuid=project_uuid,
68+
status=project_status,
69+
owner=owner,
70+
)
71+
async def _locked_fct() -> None:
72+
assert await is_project_locked(redis_client_sdk, project_uuid) is True
73+
locked_state = await get_project_locked_state(redis_client_sdk, project_uuid)
74+
assert locked_state is not None
75+
assert locked_state == ProjectLocked(
76+
value=True,
77+
owner=owner,
78+
status=project_status,
79+
)
80+
# check lock name formatting is correct
81+
redis_lock = await redis_client_sdk.redis.get(
82+
PROJECT_REDIS_LOCK_KEY.format(project_uuid)
83+
)
84+
assert redis_lock
85+
assert ProjectLocked.model_validate_json(redis_lock) == ProjectLocked(
86+
value=True,
87+
owner=owner,
88+
status=project_status,
89+
)
90+
91+
assert await get_project_locked_state(redis_client_sdk, project_uuid) is None
92+
assert await is_project_locked(redis_client_sdk, project_uuid) is False
93+
await _locked_fct()
94+
assert await is_project_locked(redis_client_sdk, project_uuid) is False
95+
assert await get_project_locked_state(redis_client_sdk, project_uuid) is None
96+
97+
98+
@pytest.mark.parametrize(
99+
"project_status",
100+
[
101+
ProjectStatus.CLOSING,
102+
ProjectStatus.CLONING,
103+
ProjectStatus.EXPORTING,
104+
ProjectStatus.OPENING,
105+
ProjectStatus.MAINTAINING,
106+
],
107+
)
108+
async def test_lock_already_locked_project_raises(
109+
redis_client_sdk: RedisClientSDK,
110+
project_uuid: ProjectID,
111+
owner: Owner,
112+
project_status: ProjectStatus,
113+
):
114+
started_event = asyncio.Event()
115+
116+
@with_project_locked(
117+
redis_client_sdk,
118+
project_uuid=project_uuid,
119+
status=project_status,
120+
owner=owner,
121+
)
122+
async def _locked_fct() -> None:
123+
started_event.set()
124+
await asyncio.sleep(10)
125+
126+
task1 = asyncio.create_task(_locked_fct(), name="pytest_task_1")
127+
await started_event.wait()
128+
with pytest.raises(ProjectLockError):
129+
await _locked_fct()
130+
131+
await cancel_wait_task(task1)

packages/service-library/tests/test_project_lock.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from models_library.projects import ProjectID
66
from models_library.projects_state import ProjectStatus
77
from servicelib.logging_utils import log_context
8-
from servicelib.project_lock import with_project_locked
8+
from servicelib.redis import with_project_locked
99
from simcore_postgres_database.utils_projects import (
1010
DBProjectNotFoundError,
1111
ProjectsRepo,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
X_SIMCORE_USER_AGENT,
3737
)
3838
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
39-
from servicelib.project_lock import get_project_locked_state
39+
from servicelib.redis import get_project_locked_state
4040
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
4141

4242
from .._meta import API_VTAG as VTAG

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from models_library.projects import ProjectID
77
from models_library.users import UserID
8-
from servicelib.project_lock import ProjectLockError
8+
from servicelib.redis import ProjectLockError
99

1010
from ..errors import WebServerBaseError
1111

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@
7272
X_SIMCORE_USER_AGENT,
7373
)
7474
from servicelib.logging_utils import get_log_record_extra, log_context
75-
from servicelib.project_lock import (
76-
get_project_locked_state,
77-
is_project_locked,
78-
with_project_locked,
79-
)
8075
from servicelib.rabbitmq import RemoteMethodNotRegisteredError, RPCServerError
8176
from servicelib.rabbitmq.rpc_interfaces.catalog import services as catalog_rpc
8277
from servicelib.rabbitmq.rpc_interfaces.clusters_keeper.ec2_instances import (
@@ -86,6 +81,11 @@
8681
ServiceWaitingForManualInterventionError,
8782
ServiceWasNotFoundError,
8883
)
84+
from servicelib.redis import (
85+
get_project_locked_state,
86+
is_project_locked,
87+
with_project_locked,
88+
)
8989
from servicelib.redis._decorators import exclusive
9090
from servicelib.utils import fire_and_forget_task, logged_gather
9191
from simcore_postgres_database.models.users import UserRole

0 commit comments

Comments
 (0)