Skip to content

Commit 6558b5a

Browse files
fix: webserver
1 parent b16810c commit 6558b5a

File tree

13 files changed

+130
-130
lines changed

13 files changed

+130
-130
lines changed

packages/pytest-simcore/src/pytest_simcore/helpers/webserver_projects.py

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import json
66
import uuid as uuidlib
7+
from collections.abc import AsyncIterator
8+
from contextlib import asynccontextmanager
79
from pathlib import Path
810
from typing import Any
911

@@ -73,7 +75,9 @@ async def create_project(
7375

7476
db: ProjectDBAPI = app[APP_PROJECT_DBAPI]
7577

76-
new_project = await db.insert_project(
78+
workbench: dict[str, Any] = project_data.pop("workbench", {})
79+
80+
project_created = await db.insert_project(
7781
project_data,
7882
user_id,
7983
product_name=product_name,
@@ -86,11 +90,9 @@ async def create_project(
8690
required_resources=ServiceResourcesDictHelpers.model_config[
8791
"json_schema_extra"
8892
]["examples"][0],
89-
key=node_info.get("key"),
90-
version=node_info.get("version"),
91-
label=node_info.get("label"),
93+
**node_info,
9294
)
93-
for node_id, node_info in project_data.get("workbench", {}).items()
95+
for node_id, node_info in workbench.items()
9496
},
9597
)
9698

@@ -103,7 +105,7 @@ async def create_project(
103105
for group_id, permissions in _access_rights.items():
104106
await update_or_insert_project_group(
105107
app,
106-
project_id=new_project["uuid"],
108+
project_id=project_created["uuid"],
107109
group_id=int(group_id),
108110
read=permissions["read"],
109111
write=permissions["write"],
@@ -112,20 +114,22 @@ async def create_project(
112114

113115
try:
114116
uuidlib.UUID(str(project_data["uuid"]))
115-
assert new_project["uuid"] == project_data["uuid"]
117+
assert project_created["uuid"] == project_data["uuid"]
116118
except (ValueError, AssertionError):
117119
# in that case the uuid gets replaced
118-
assert new_project["uuid"] != project_data["uuid"]
119-
project_data["uuid"] = new_project["uuid"]
120+
assert project_created["uuid"] != project_data["uuid"]
121+
project_data["uuid"] = project_created["uuid"]
120122

121123
for key in DB_EXCLUSIVE_COLUMNS:
122124
project_data.pop(key, None)
123125

124-
new_project: ProjectDict = remap_keys(
125-
new_project,
126+
project_created: ProjectDict = remap_keys(
127+
project_created,
126128
rename={"trashed": "trashedAt"},
127129
)
128-
return new_project
130+
131+
project_created["workbench"] = workbench # NOTE: restore workbench
132+
return project_created
129133

130134

131135
async def delete_all_projects(app: web.Application):
@@ -137,50 +141,35 @@ async def delete_all_projects(app: web.Application):
137141
await conn.execute(query)
138142

139143

140-
class NewProject:
141-
def __init__(
142-
self,
143-
params_override: dict | None = None,
144-
app: web.Application | None = None,
145-
*,
146-
user_id: int,
147-
product_name: str,
148-
tests_data_dir: Path,
149-
force_uuid: bool = False,
150-
as_template: bool = False,
151-
):
152-
assert app # nosec
153-
154-
self.params_override = params_override
155-
self.user_id = user_id
156-
self.product_name = product_name
157-
self.app = app
158-
self.prj = {}
159-
self.force_uuid = force_uuid
160-
self.tests_data_dir = tests_data_dir
161-
self.as_template = as_template
162-
163-
assert tests_data_dir.exists()
164-
assert tests_data_dir.is_dir()
165-
166-
async def __aenter__(self) -> ProjectDict:
167-
assert self.app # nosec
168-
169-
self.prj = await create_project(
170-
self.app,
171-
self.params_override,
172-
self.user_id,
173-
product_name=self.product_name,
174-
force_uuid=self.force_uuid,
175-
default_project_json=self.tests_data_dir / "fake-project.json",
176-
as_template=self.as_template,
177-
)
178-
179-
return self.prj
144+
@asynccontextmanager
145+
async def new_project(
146+
params_override: dict | None = None,
147+
app: web.Application | None = None,
148+
*,
149+
user_id: int,
150+
product_name: str,
151+
tests_data_dir: Path,
152+
force_uuid: bool = False,
153+
as_template: bool = False,
154+
) -> AsyncIterator[ProjectDict]:
155+
assert app # nosec
156+
assert tests_data_dir.exists()
157+
assert tests_data_dir.is_dir()
158+
159+
project = await create_project(
160+
app,
161+
params_override,
162+
user_id,
163+
product_name=product_name,
164+
force_uuid=force_uuid,
165+
default_project_json=tests_data_dir / "fake-project.json",
166+
as_template=as_template,
167+
)
180168

181-
async def __aexit__(self, *args):
182-
assert self.app # nosec
183-
await delete_all_projects(self.app)
169+
try:
170+
yield project
171+
finally:
172+
await delete_all_projects(app)
184173

185174

186175
async def assert_get_same_project(

services/web/server/src/simcore_service_webserver/director_v2/_computations_service.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Awaitable
12
from decimal import Decimal
23

34
from aiohttp import web
@@ -14,6 +15,8 @@
1415
)
1516
from models_library.products import ProductName
1617
from models_library.projects import ProjectID
18+
from models_library.projects_nodes import Node
19+
from models_library.projects_nodes_io import NodeID
1720
from models_library.rest_ordering import OrderBy
1821
from models_library.services_types import ServiceRunID
1922
from models_library.users import UserID
@@ -29,6 +32,7 @@
2932
CreditTransactionNotFoundError,
3033
)
3134
from servicelib.utils import limited_gather
35+
from simcore_service_webserver.projects._projects_nodes_repository import get_by_project
3236

3337
from ..products.products_service import is_product_billable
3438
from ..projects.api import (
@@ -248,15 +252,23 @@ async def list_computations_latest_iteration_tasks(
248252
unique_project_uuids = {task.project_uuid for task in _tasks_get.items}
249253
# Fetch projects metadata concurrently
250254
# NOTE: MD: can be improved with a single batch call
251-
project_dicts = await limited_gather(
255+
256+
async def _wrap_with_id(
257+
project_id: ProjectID, coro: Awaitable[list[tuple[NodeID, Node]]]
258+
) -> tuple[ProjectID, dict[NodeID, Node]]:
259+
nodes = await coro
260+
return project_id, dict(nodes)
261+
262+
results = await limited_gather(
252263
*[
253-
get_project_dict_legacy(app, project_uuid=project_uuid)
264+
_wrap_with_id(project_uuid, get_by_project(app, project_id=project_uuid))
254265
for project_uuid in unique_project_uuids
255266
],
256267
limit=20,
257268
)
269+
258270
# Build a dict: project_uuid -> workbench
259-
project_uuid_to_workbench = {prj["uuid"]: prj["workbench"] for prj in project_dicts}
271+
project_uuid_to_workbench: dict[ProjectID, dict[NodeID, Node]] = dict(results)
260272

261273
_service_run_ids = [item.service_run_id for item in _tasks_get.items]
262274
_is_product_billable = await is_product_billable(app, product_name=product_name)
@@ -286,9 +298,8 @@ async def list_computations_latest_iteration_tasks(
286298
started_at=item.started_at,
287299
ended_at=item.ended_at,
288300
log_download_link=item.log_download_link,
289-
node_name=project_uuid_to_workbench[f"{item.project_uuid}"][
290-
f"{item.node_id}"
291-
].get("label", ""),
301+
node_name=project_uuid_to_workbench[item.project_uuid][item.node_id].label
302+
or "",
292303
osparc_credits=credits_or_none,
293304
)
294305
for item, credits_or_none in zip(

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import logging
22

33
import sqlalchemy as sa
4-
54
from aiohttp import web
65
from models_library.projects import ProjectID
76
from models_library.projects_nodes import Node, PartialNode
87
from models_library.projects_nodes_io import NodeID
9-
from simcore_postgres_database.utils_repos import transaction_context
8+
from simcore_postgres_database.utils_repos import (
9+
pass_or_acquire_connection,
10+
transaction_context,
11+
)
1012
from simcore_postgres_database.webserver_models import projects_nodes
1113
from sqlalchemy.ext.asyncio import AsyncConnection
1214

13-
from .exceptions import NodeNotFoundError
1415
from ..db.plugin import get_asyncpg_engine
16+
from .exceptions import NodeNotFoundError
1517

1618
_logger = logging.getLogger(__name__)
1719

1820

1921
_SELECTION_PROJECTS_NODES_DB_ARGS = [
22+
projects_nodes.c.node_id,
2023
projects_nodes.c.key,
2124
projects_nodes.c.version,
2225
projects_nodes.c.label,
26+
projects_nodes.c.created,
27+
projects_nodes.c.modified,
2328
projects_nodes.c.progress,
2429
projects_nodes.c.thumbnail,
2530
projects_nodes.c.input_access,
@@ -43,27 +48,56 @@ async def get(
4348
project_id: ProjectID,
4449
node_id: NodeID,
4550
) -> Node:
46-
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
47-
get_stmt = sa.select(
48-
*_SELECTION_PROJECTS_NODES_DB_ARGS
49-
).where(
51+
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
52+
query = sa.select(*_SELECTION_PROJECTS_NODES_DB_ARGS).where(
5053
(projects_nodes.c.project_uuid == f"{project_id}")
5154
& (projects_nodes.c.node_id == f"{node_id}")
5255
)
5356

54-
result = await conn.stream(get_stmt)
57+
result = await conn.stream(query)
5558
assert result # nosec
5659

5760
row = await result.first()
5861
if row is None:
5962
raise NodeNotFoundError(
60-
project_uuid=f"{project_id}",
61-
node_uuid=f"{node_id}"
63+
project_uuid=f"{project_id}", node_uuid=f"{node_id}"
6264
)
6365
assert row # nosec
6466
return Node.model_validate(row, from_attributes=True)
6567

6668

69+
async def get_by_project(
70+
app: web.Application,
71+
connection: AsyncConnection | None = None,
72+
*,
73+
project_id: ProjectID,
74+
) -> list[tuple[NodeID, Node]]:
75+
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
76+
query = sa.select(*_SELECTION_PROJECTS_NODES_DB_ARGS).where(
77+
projects_nodes.c.project_uuid == f"{project_id}"
78+
)
79+
80+
result = await conn.stream(query)
81+
assert result # nosec
82+
83+
from simcore_postgres_database.utils_projects_nodes import ProjectNode
84+
85+
rows = await result.all()
86+
return [
87+
(
88+
NodeID(row.node_id),
89+
Node.model_validate(
90+
ProjectNode.model_validate(row, from_attributes=True).model_dump(
91+
exclude_none=True,
92+
exclude_unset=True,
93+
exclude={"node_id", "created", "modified"},
94+
)
95+
),
96+
)
97+
for row in rows
98+
]
99+
100+
67101
async def update(
68102
app: web.Application,
69103
connection: AsyncConnection | None = None,

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

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -234,41 +234,10 @@ def _reraise_if_not_unique_uuid_error(err: UniqueViolation):
234234
)
235235
selected_values["tags"] = project_tag_ids
236236

237-
# NOTE: this will at some point completely replace workbench in the DB
238-
if selected_values["workbench"]:
239-
project_nodes_repo = ProjectNodesRepo(
240-
project_uuid=project_uuid
237+
if project_nodes:
238+
await ProjectNodesRepo(project_uuid=project_uuid).add(
239+
conn, nodes=list(project_nodes.values())
241240
)
242-
if project_nodes is None:
243-
project_nodes = {
244-
NodeID(node_id): ProjectNodeCreate(
245-
node_id=NodeID(node_id),
246-
required_resources={},
247-
key=node_info.get("key"),
248-
version=node_info.get("version"),
249-
label=node_info.get("label"),
250-
)
251-
for node_id, node_info in selected_values[
252-
"workbench"
253-
].items()
254-
}
255-
256-
nodes = [
257-
project_nodes.get(
258-
NodeID(node_id),
259-
ProjectNodeCreate(
260-
node_id=NodeID(node_id),
261-
required_resources={},
262-
key=node_info.get("key"),
263-
version=node_info.get("version"),
264-
label=node_info.get("label"),
265-
),
266-
)
267-
for node_id, node_info in selected_values[
268-
"workbench"
269-
].items()
270-
]
271-
await project_nodes_repo.add(conn, nodes=nodes)
272241
return selected_values
273242

274243
async def insert_project(
@@ -333,7 +302,6 @@ async def insert_project(
333302
# ensure we have the minimal amount of data here
334303
# All non-default in projects table
335304
insert_values.setdefault("name", "New Study")
336-
insert_values.setdefault("workbench", {})
337305
insert_values.setdefault("workspace_id", None)
338306

339307
# must be valid uuid
@@ -738,7 +706,6 @@ async def get_project_db(self, project_uuid: ProjectID) -> ProjectDBGet:
738706
result = await conn.execute(
739707
sa.select(
740708
*PROJECT_DB_COLS,
741-
projects.c.workbench,
742709
).where(projects.c.uuid == f"{project_uuid}")
743710
)
744711
row = await result.fetchone()

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ async def _get_project(
224224
query = (
225225
sa.select(
226226
*PROJECT_DB_COLS,
227-
projects.c.workbench,
228227
users.c.primary_gid.label("trashed_by_primary_gid"),
229228
access_rights_subquery.c.access_rights,
230229
)

services/web/server/tests/integration/02/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import pytest
1111
from models_library.projects import ProjectID
12-
from pytest_simcore.helpers.webserver_projects import NewProject
12+
from pytest_simcore.helpers.webserver_projects import new_project
1313

1414

1515
@pytest.fixture(scope="session")
@@ -45,7 +45,7 @@ async def user_project(
4545
fake_project["prjOwner"] = logged_user["name"]
4646
fake_project["uuid"] = f"{project_id}"
4747

48-
async with NewProject(
48+
async with new_project(
4949
fake_project,
5050
client.app,
5151
user_id=logged_user["id"],

0 commit comments

Comments
 (0)