Skip to content

Commit 3cdbdbc

Browse files
committed
tools and fix
1 parent 90bde6f commit 3cdbdbc

File tree

2 files changed

+64
-12
lines changed

2 files changed

+64
-12
lines changed

packages/postgres-database/src/simcore_postgres_database/utils_projects.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sqlalchemy.ext.asyncio import AsyncConnection
88

99
from .models.projects import projects
10-
from .utils_repos import transaction_context
10+
from .utils_repos import pass_or_acquire_connection, transaction_context
1111

1212

1313
class DBBaseProjectError(OsparcErrorMixin, Exception):
@@ -22,6 +22,23 @@ class ProjectsRepo:
2222
def __init__(self, engine):
2323
self.engine = engine
2424

25+
async def exists(
26+
self,
27+
project_uuid: uuid.UUID,
28+
*,
29+
connection: AsyncConnection | None = None,
30+
) -> bool:
31+
async with pass_or_acquire_connection(self.engine, connection) as conn:
32+
return (
33+
await conn.scalar(
34+
sa.select(1)
35+
.select_from(projects)
36+
.where(projects.c.uuid == f"{project_uuid}")
37+
.limit(1)
38+
)
39+
is not None
40+
)
41+
2542
async def get_project_last_change_date(
2643
self,
2744
project_uuid: uuid.UUID,

packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@
44
from typing import Annotated, Any
55

66
import asyncpg.exceptions # type: ignore[import-untyped]
7-
import sqlalchemy
87
import sqlalchemy.exc
98
from common_library.async_tools import maybe_await
109
from common_library.basic_types import DEFAULT_FACTORY
1110
from common_library.errors_classes import OsparcErrorMixin
1211
from pydantic import BaseModel, ConfigDict, Field
13-
from simcore_postgres_database.utils_aiosqlalchemy import map_db_exception
1412
from sqlalchemy.dialects.postgresql import insert as pg_insert
1513

1614
from ._protocols import DBConnection
1715
from .aiopg_errors import ForeignKeyViolation, UniqueViolation
1816
from .models.projects_node_to_pricing_unit import projects_node_to_pricing_unit
1917
from .models.projects_nodes import projects_nodes
18+
from .utils_aiosqlalchemy import map_db_exception
2019

2120

2221
#
@@ -71,6 +70,22 @@ class ProjectNodeCreate(BaseModel):
7170
def get_field_names(cls, *, exclude: set[str]) -> set[str]:
7271
return cls.model_fields.keys() - exclude
7372

73+
def model_dump_as_node(self) -> dict[str, Any]:
74+
"""Converts a ProjectNode from the database to a Node model for the API.
75+
76+
Handles field mapping and excludes database-specific fields that are not
77+
part of the Node model.
78+
"""
79+
# Get all ProjectNode fields except those that don't belong in Node
80+
exclude_fields = {"node_id", "required_resources"}
81+
return self.model_dump(
82+
# NOTE: this setup ensures using the defaults provided in Node model when the db does not
83+
# provide them, e.g. `state`
84+
exclude=exclude_fields,
85+
exclude_none=True,
86+
exclude_unset=True,
87+
)
88+
7489
model_config = ConfigDict(frozen=True)
7590

7691

@@ -80,6 +95,22 @@ class ProjectNode(ProjectNodeCreate):
8095

8196
model_config = ConfigDict(from_attributes=True)
8297

98+
def model_dump_as_node(self) -> dict[str, Any]:
99+
"""Converts a ProjectNode from the database to a Node model for the API.
100+
101+
Handles field mapping and excludes database-specific fields that are not
102+
part of the Node model.
103+
"""
104+
# Get all ProjectNode fields except those that don't belong in Node
105+
exclude_fields = {"node_id", "required_resources", "created", "modified"}
106+
return self.model_dump(
107+
# NOTE: this setup ensures using the defaults provided in Node model when the db does not
108+
# provide them, e.g. `state`
109+
exclude=exclude_fields,
110+
exclude_none=True,
111+
exclude_unset=True,
112+
)
113+
83114

84115
@dataclass(frozen=True, kw_only=True)
85116
class ProjectNodesRepo:
@@ -103,17 +134,18 @@ async def add(
103134
"""
104135
if not nodes:
105136
return []
137+
138+
values = [
139+
{
140+
"project_uuid": f"{self.project_uuid}",
141+
**node.model_dump(mode="json"),
142+
}
143+
for node in nodes
144+
]
145+
106146
insert_stmt = (
107147
projects_nodes.insert()
108-
.values(
109-
[
110-
{
111-
"project_uuid": f"{self.project_uuid}",
112-
**node.model_dump(exclude_unset=True, mode="json"),
113-
}
114-
for node in nodes
115-
]
116-
)
148+
.values(values)
117149
.returning(
118150
*[
119151
c
@@ -129,14 +161,17 @@ async def add(
129161
rows = await maybe_await(result.fetchall())
130162
assert isinstance(rows, list) # nosec
131163
return [ProjectNode.model_validate(r) for r in rows]
164+
132165
except ForeignKeyViolation as exc:
133166
# this happens when the project does not exist, as we first check the node exists
134167
raise ProjectNodesProjectNotFoundError(
135168
project_uuid=self.project_uuid
136169
) from exc
170+
137171
except UniqueViolation as exc:
138172
# this happens if the node already exists on creation
139173
raise ProjectNodesDuplicateNodeError from exc
174+
140175
except sqlalchemy.exc.IntegrityError as exc:
141176
raise map_db_exception(
142177
exc,

0 commit comments

Comments
 (0)