Skip to content

Commit ff2b2bc

Browse files
authored
Merge branch 'master' into feature/notify-in-conversations
2 parents 7443e2d + d88921c commit ff2b2bc

File tree

5 files changed

+79
-19
lines changed

5 files changed

+79
-19
lines changed

services/api-server/src/simcore_service_api_server/api/routes/studies.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ async def clone_study(
111111
description=description,
112112
)
113113
await webserver_api.patch_project(
114-
project_id=study_id, patch_params=patch_params
114+
project_id=project.uuid, patch_params=patch_params
115115
)
116+
project = await webserver_api.get_project(project_id=project.uuid)
116117
return _create_study_from_project(project)
117118

118119

services/api-server/tests/unit/api_studies/test_api_routes_studies.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# pylint: disable=redefined-outer-name
22
# pylint: disable=unused-argument
33
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
# pylint: disable=too-many-statements
46

57

68
import json
@@ -13,6 +15,8 @@
1315
import pytest
1416
from faker import Faker
1517
from fastapi import status
18+
from models_library.api_schemas_webserver.projects import ProjectGet
19+
from models_library.generics import Envelope
1620
from pydantic import TypeAdapter
1721
from pytest_mock import MockType
1822
from pytest_simcore.helpers.httpx_calls_capture_models import HttpApiCallCaptureModel
@@ -224,12 +228,8 @@ async def test_clone_study_with_title(
224228
study_id: StudyID,
225229
mocked_webserver_rest_api_base: MockRouter,
226230
patch_webserver_long_running_project_tasks: Callable[[MockRouter], MockRouter],
227-
mock_webserver_patch_project: Callable[
228-
[
229-
MockRouter,
230-
],
231-
MockRouter,
232-
],
231+
mock_webserver_patch_project: Callable[[MockRouter], MockRouter],
232+
mock_webserver_get_project: Callable[[MockRouter], MockRouter],
233233
hidden: bool | None,
234234
title: str | None,
235235
description: str | None,
@@ -238,11 +238,14 @@ async def test_clone_study_with_title(
238238
# Mocks /projects
239239
patch_webserver_long_running_project_tasks(mocked_webserver_rest_api_base)
240240
mock_webserver_patch_project(mocked_webserver_rest_api_base)
241+
mock_webserver_get_project(mocked_webserver_rest_api_base)
241242

242243
create_callback = mocked_webserver_rest_api_base["create_projects"].side_effect
243244
assert create_callback is not None
244245
patch_callback = mocked_webserver_rest_api_base["project_patch"].side_effect
245246
assert patch_callback is not None
247+
get_callback = mocked_webserver_rest_api_base["project_get"].side_effect
248+
assert get_callback is not None
246249

247250
def clone_project_side_effect(request: httpx.Request):
248251
if hidden is not None:
@@ -260,12 +263,28 @@ def patch_project_side_effect(request: httpx.Request, *args, **kwargs):
260263
assert _description is not None and _description in description
261264
return patch_callback(request, *args, **kwargs)
262265

266+
def get_project_side_effect(request: httpx.Request, *args, **kwargs):
267+
# this is needed to return the patched project
268+
_project_id = kwargs.get("project_id")
269+
assert _project_id is not None
270+
result = Envelope[ProjectGet].model_validate(
271+
{"data": ProjectGet.model_json_schema()["examples"][0]}
272+
)
273+
assert result.data is not None
274+
if title is not None:
275+
result.data.name = title
276+
if description is not None:
277+
result.data.description = description
278+
result.data.uuid = UUID(_project_id)
279+
return httpx.Response(status.HTTP_200_OK, content=result.model_dump_json())
280+
263281
mocked_webserver_rest_api_base["create_projects"].side_effect = (
264282
clone_project_side_effect
265283
)
266284
mocked_webserver_rest_api_base["project_patch"].side_effect = (
267285
patch_project_side_effect
268286
)
287+
mocked_webserver_rest_api_base["project_get"].side_effect = get_project_side_effect
269288

270289
query = dict()
271290
if hidden is not None:
@@ -286,8 +305,14 @@ def patch_project_side_effect(request: httpx.Request, *args, **kwargs):
286305
assert mocked_webserver_rest_api_base["create_projects"].called
287306
if title or description:
288307
assert mocked_webserver_rest_api_base["project_patch"].called
308+
assert mocked_webserver_rest_api_base["project_get"].called
289309

290310
assert resp.status_code == expected_status_code
311+
study = Study.model_validate(resp.json())
312+
if title is not None:
313+
assert study.title == title
314+
if description is not None:
315+
assert study.description == description
291316

292317

293318
async def test_clone_study_not_found(

services/api-server/tests/unit/conftest.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ def _mock(webserver_mock_router: MockRouter) -> MockRouter:
717717

718718
@pytest.fixture
719719
def mock_webserver_patch_project(
720-
app: FastAPI, faker: Faker, services_mocks_enabled: bool
720+
app: FastAPI, services_mocks_enabled: bool
721721
) -> Callable[[MockRouter], MockRouter]:
722722
settings: ApplicationSettings = app.state.settings
723723
assert settings.API_SERVER_WEBSERVER is not None
@@ -736,6 +736,30 @@ def _patch_project(request: httpx.Request, *args, **kwargs):
736736
return _mock
737737

738738

739+
@pytest.fixture
740+
def mock_webserver_get_project(
741+
app: FastAPI, services_mocks_enabled: bool
742+
) -> Callable[[MockRouter], MockRouter]:
743+
settings: ApplicationSettings = app.state.settings
744+
assert settings.API_SERVER_WEBSERVER is not None
745+
746+
def _mock(webserver_mock_router: MockRouter) -> MockRouter:
747+
def _get_project(request: httpx.Request, *args, **kwargs):
748+
result = Envelope[ProjectGet].model_validate(
749+
{"data": ProjectGet.model_json_schema()["examples"][0]}
750+
)
751+
return httpx.Response(status.HTTP_200_OK, json=result.model_dump())
752+
753+
if services_mocks_enabled:
754+
webserver_mock_router.get(
755+
path__regex=r"/projects/(?P<project_id>[\w-]+)$",
756+
name="project_get",
757+
).mock(side_effect=_get_project)
758+
return webserver_mock_router
759+
760+
return _mock
761+
762+
739763
@pytest.fixture
740764
def openapi_dev_specs(project_slug_dir: Path) -> dict[str, Any]:
741765
openapi_file = (project_slug_dir / "openapi-dev.json").resolve()

services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/_scheduler_base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,7 @@ async def _process_executing_tasks(
590590
"""process executing tasks from the 3rd party backend"""
591591

592592
@abstractmethod
593-
async def _release_resources(
594-
self, user_id: UserID, project_id: ProjectID, comp_run: CompRunsAtDB
595-
) -> None:
593+
async def _release_resources(self, comp_run: CompRunsAtDB) -> None:
596594
"""release resources used by the scheduler for a given user and project"""
597595

598596
async def apply(
@@ -660,7 +658,7 @@ async def apply(
660658

661659
# 7. Are we done scheduling that pipeline?
662660
if not dag.nodes() or pipeline_result in COMPLETED_STATES:
663-
await self._release_resources(user_id, project_id, comp_run)
661+
await self._release_resources(comp_run)
664662
# there is nothing left, the run is completed, we're done here
665663
_logger.info(
666664
"pipeline %s scheduling completed with result %s",

services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/_scheduler_dask.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
_logger = logging.getLogger(__name__)
5858

59-
_DASK_CLIENT_RUN_REF: Final[str] = "{user_id}:{run_id}"
59+
_DASK_CLIENT_RUN_REF: Final[str] = "{user_id}:{project_id}:{run_id}"
6060

6161

6262
@asynccontextmanager
@@ -65,6 +65,7 @@ async def _cluster_dask_client(
6565
scheduler: "DaskScheduler",
6666
*,
6767
use_on_demand_clusters: bool,
68+
project_id: ProjectID,
6869
run_id: PositiveInt,
6970
run_metadata: RunMetadataDict,
7071
) -> AsyncIterator[DaskClient]:
@@ -76,7 +77,10 @@ async def _cluster_dask_client(
7677
wallet_id=run_metadata.get("wallet_id"),
7778
)
7879
async with scheduler.dask_clients_pool.acquire(
79-
cluster, ref=_DASK_CLIENT_RUN_REF.format(user_id=user_id, run_id=run_id)
80+
cluster,
81+
ref=_DASK_CLIENT_RUN_REF.format(
82+
user_id=user_id, project_id=project_id, run_id=run_id
83+
),
8084
) as client:
8185
yield client
8286

@@ -106,6 +110,7 @@ async def _start_tasks(
106110
user_id,
107111
self,
108112
use_on_demand_clusters=comp_run.use_on_demand_clusters,
113+
project_id=comp_run.project_uuid,
109114
run_id=comp_run.run_id,
110115
run_metadata=comp_run.metadata,
111116
) as client:
@@ -157,6 +162,7 @@ async def _get_tasks_status(
157162
user_id,
158163
self,
159164
use_on_demand_clusters=comp_run.use_on_demand_clusters,
165+
project_id=comp_run.project_uuid,
160166
run_id=comp_run.run_id,
161167
run_metadata=comp_run.metadata,
162168
) as client:
@@ -178,6 +184,7 @@ async def _process_executing_tasks(
178184
user_id,
179185
self,
180186
use_on_demand_clusters=comp_run.use_on_demand_clusters,
187+
project_id=comp_run.project_uuid,
181188
run_id=comp_run.run_id,
182189
run_metadata=comp_run.metadata,
183190
) as client:
@@ -225,20 +232,22 @@ async def _process_executing_tasks(
225232
)
226233
)
227234

228-
async def _release_resources(
229-
self, user_id: UserID, project_id: ProjectID, comp_run: CompRunsAtDB
230-
) -> None:
235+
async def _release_resources(self, comp_run: CompRunsAtDB) -> None:
231236
"""release resources used by the scheduler for a given user and project"""
232237
with (
233238
log_catch(_logger, reraise=False),
234239
log_context(
235240
_logger,
236241
logging.INFO,
237-
msg=f"releasing resources for {user_id=}, {project_id=}, {comp_run.run_id=}",
242+
msg=f"releasing resources for {comp_run.user_id=}, {comp_run.project_uuid=}, {comp_run.run_id=}",
238243
),
239244
):
240245
await self.dask_clients_pool.release_client_ref(
241-
ref=_DASK_CLIENT_RUN_REF.format(user_id=user_id, run_id=comp_run.run_id)
246+
ref=_DASK_CLIENT_RUN_REF.format(
247+
user_id=comp_run.user_id,
248+
project_id=comp_run.project_uuid,
249+
run_id=comp_run.run_id,
250+
)
242251
)
243252

244253
async def _stop_tasks(
@@ -250,6 +259,7 @@ async def _stop_tasks(
250259
user_id,
251260
self,
252261
use_on_demand_clusters=comp_run.use_on_demand_clusters,
262+
project_id=comp_run.project_uuid,
253263
run_id=comp_run.run_id,
254264
run_metadata=comp_run.metadata,
255265
) as client:
@@ -284,6 +294,7 @@ async def _process_completed_tasks(
284294
user_id,
285295
self,
286296
use_on_demand_clusters=comp_run.use_on_demand_clusters,
297+
project_id=comp_run.project_uuid,
287298
run_id=comp_run.run_id,
288299
run_metadata=comp_run.metadata,
289300
) as client:
@@ -304,6 +315,7 @@ async def _process_completed_tasks(
304315
user_id,
305316
self,
306317
use_on_demand_clusters=comp_run.use_on_demand_clusters,
318+
project_id=comp_run.project_uuid,
307319
run_id=comp_run.run_id,
308320
run_metadata=comp_run.metadata,
309321
) as client:

0 commit comments

Comments
 (0)