Skip to content

Commit 15e31b4

Browse files
committed
models for organizations
1 parent 2545bf4 commit 15e31b4

File tree

8 files changed

+97
-30
lines changed

8 files changed

+97
-30
lines changed

packages/models-library/src/models_library/api_schemas_webserver/groups.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from ast import TypeVar
12
from contextlib import suppress
2-
from typing import Annotated, Self
3+
from typing import Annotated, Any, Self
34

45
from common_library.basic_types import DEFAULT_FACTORY
56
from pydantic import (
@@ -16,7 +17,7 @@
1617

1718
from ..basic_types import IDStr
1819
from ..emails import LowerCaseEmailStr
19-
from ..groups import AccessRightsDict, Group
20+
from ..groups import AccessRightsDict, Group, OrganizationCreate, OrganizationUpdate
2021
from ..users import UserID
2122
from ..utils.common_validators import create__check_only_one_is_set__root_validator
2223
from ._base import InputSchema, OutputSchema
@@ -131,17 +132,47 @@ def _sanitize_legacy_data(cls, v):
131132
return None
132133

133134

135+
S = TypeVar("S", bound=BaseModel)
136+
137+
138+
def _model_dump_with_mapping(
139+
schema: S, field_mapping: dict[str, str]
140+
) -> dict[str, Any]:
141+
return {
142+
field_mapping.get(k, k): v
143+
for k, v in schema.model_dump(mode="json", exclude_unset=True).items()
144+
}
145+
146+
134147
class GroupCreate(InputSchema):
135148
label: str
136149
description: str
137150
thumbnail: AnyUrl | None = None
138151

152+
def to_model(self) -> OrganizationCreate:
153+
data = _model_dump_with_mapping(
154+
self,
155+
{
156+
"label": "name",
157+
},
158+
)
159+
return OrganizationCreate(**data)
160+
139161

140162
class GroupUpdate(InputSchema):
141163
label: str | None = None
142164
description: str | None = None
143165
thumbnail: AnyUrl | None = None
144166

167+
def to_model(self) -> OrganizationUpdate:
168+
data = _model_dump_with_mapping(
169+
self,
170+
{
171+
"label": "name",
172+
},
173+
)
174+
return OrganizationUpdate(**data)
175+
145176

146177
class MyGroupsGet(OutputSchema):
147178
me: GroupGet

packages/models-library/src/models_library/groups.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ class GroupMember(BaseModel):
7878
model_config = ConfigDict(from_attributes=True)
7979

8080

81+
class OrganizationCreate(BaseModel):
82+
name: str
83+
description: str | None
84+
thumbnail: str | None
85+
86+
87+
class OrganizationUpdate(BaseModel):
88+
name: str | None
89+
description: str | None
90+
thumbnail: str | None
91+
92+
8193
class GroupAtDB(Group):
8294
model_config = ConfigDict(
8395
from_attributes=True,

packages/pytest-simcore/src/pytest_simcore/simcore_webserver_groups_fixtures.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from aiohttp import web
1717
from aiohttp.test_utils import TestClient
1818
from models_library.api_schemas_webserver.groups import GroupGet
19-
from models_library.groups import GroupsByTypeTuple
19+
from models_library.groups import GroupsByTypeTuple, OrganizationCreate
2020
from models_library.users import UserID
2121
from pytest_simcore.helpers.webserver_login import NewUser, UserInfoDict
2222
from simcore_service_webserver.groups._groups_api import (
@@ -35,7 +35,9 @@ async def _create_organization(
3535
app: web.Application, user_id: UserID, new_group: dict
3636
) -> dict[str, Any]:
3737
group, access_rights = await create_organization(
38-
app, user_id=user_id, new_group_values=new_group
38+
app,
39+
user_id=user_id,
40+
new_group_values=OrganizationCreate.model_validate(new_group),
3941
)
4042
return _groupget_model_dump(group=group, access_rights=access_rights)
4143

services/web/server/src/simcore_service_webserver/groups/_groups_api.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
Group,
66
GroupMember,
77
GroupsByTypeTuple,
8+
OrganizationCreate,
9+
OrganizationUpdate,
810
)
911
from models_library.users import GroupID, UserID
1012

@@ -87,14 +89,19 @@ async def get_product_group_for_user(
8789

8890

8991
async def create_organization(
90-
app: web.Application, *, user_id: UserID, new_group_values: dict
92+
app: web.Application,
93+
*,
94+
user_id: UserID,
95+
new_group_values: OrganizationCreate,
9196
) -> tuple[Group, AccessRightsDict]:
9297
"""
9398
raises GroupNotFoundError
9499
raises UserInsufficientRightsError
95100
"""
96101
return await _groups_db.create_user_group(
97-
app, user_id=user_id, new_group=new_group_values
102+
app,
103+
user_id=user_id,
104+
new_group_values=new_group_values,
98105
)
99106

100107

@@ -118,15 +125,19 @@ async def update_organization(
118125
*,
119126
user_id: UserID,
120127
group_id: GroupID,
121-
new_group_values: dict[str, str],
128+
new_group_values: OrganizationUpdate,
122129
) -> tuple[Group, AccessRightsDict]:
123130
"""
124131
125132
raises GroupNotFoundError
126133
raises UserInsufficientRightsError
127134
"""
135+
128136
return await _groups_db.update_user_group(
129-
app, user_id=user_id, gid=group_id, new_group_values=new_group_values
137+
app,
138+
user_id=user_id,
139+
gid=group_id,
140+
updated_group_values=new_group_values,
130141
)
131142

132143

services/web/server/src/simcore_service_webserver/groups/_groups_db.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
GroupInfoTuple,
1010
GroupMember,
1111
GroupsByTypeTuple,
12+
OrganizationCreate,
13+
OrganizationUpdate,
1214
)
1315
from models_library.users import GroupID, UserID
1416
from simcore_postgres_database.errors import UniqueViolation
@@ -25,7 +27,6 @@
2527
from ..db.models import GroupType, groups, user_to_groups, users
2628
from ..db.plugin import get_asyncpg_engine
2729
from ..users.exceptions import UserNotFoundError
28-
from ._utils import convert_groups_schema_to_db
2930
from .exceptions import (
3031
GroupNotFoundError,
3132
UserAlreadyInGroupError,
@@ -251,25 +252,30 @@ async def get_product_group_for_user(
251252
return group, access_rights
252253

253254

255+
assert set(OrganizationCreate.model_fields).issubset({c.name for c in groups.columns})
256+
257+
254258
async def create_user_group(
255259
app: web.Application,
256260
connection: AsyncConnection | None = None,
257261
*,
258262
user_id: UserID,
259-
new_group: dict,
263+
new_group_values: OrganizationCreate,
260264
) -> tuple[Group, AccessRightsDict]:
265+
266+
values = new_group_values.model_dump(mode="json", exclude_unset=True)
267+
261268
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
262269
result = await conn.stream(
263270
sa.select(users.c.primary_gid).where(users.c.id == user_id)
264271
)
265-
user = await result.fetchone()
266-
if not user:
272+
if not await result.scalar_one_or_none():
267273
raise UserNotFoundError(uid=user_id)
268274

269275
result = await conn.stream(
270276
# pylint: disable=no-value-for-parameter
271277
groups.insert()
272-
.values(**convert_groups_schema_to_db(new_group))
278+
.values(**values)
273279
.returning(*_GROUP_COLUMNS)
274280
)
275281
row = await result.fetchone()
@@ -288,20 +294,21 @@ async def create_user_group(
288294
return group, deepcopy(_DEFAULT_GROUP_OWNER_ACCESS_RIGHTS)
289295

290296

297+
assert set(OrganizationUpdate.model_fields).issubset({c.name for c in groups.columns})
298+
299+
291300
async def update_user_group(
292301
app: web.Application,
293302
connection: AsyncConnection | None = None,
294303
*,
295304
user_id: UserID,
296305
gid: GroupID,
297-
new_group_values: dict[str, str],
306+
updated_group_values: OrganizationUpdate,
298307
) -> tuple[Group, AccessRightsDict]:
299308

300-
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
301-
new_values = {
302-
k: v for k, v in convert_groups_schema_to_db(new_group_values).items() if v
303-
}
309+
values = updated_group_values.model_dump(mode="json", exclude_unset=True)
304310

311+
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
305312
row = await _get_group_and_access_rights_or_raise(
306313
conn, user_id=user_id, gid=gid
307314
)
@@ -312,7 +319,7 @@ async def update_user_group(
312319
result = await conn.stream(
313320
# pylint: disable=no-value-for-parameter
314321
groups.update()
315-
.values(**new_values)
322+
.values(**values)
316323
.where(groups.c.gid == row.gid)
317324
.returning(*_GROUP_COLUMNS)
318325
)

services/web/server/src/simcore_service_webserver/groups/_groups_handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def create_group(request: web.Request):
112112
group, access_rights = await _groups_api.create_organization(
113113
request.app,
114114
user_id=req_ctx.user_id,
115-
new_group_values=create.model_dump(mode="json", exclude_unset=True),
115+
new_group_values=create.to_model(),
116116
)
117117

118118
created_group = GroupGet.from_model(group, access_rights)
@@ -133,7 +133,7 @@ async def update_group(request: web.Request):
133133
request.app,
134134
user_id=req_ctx.user_id,
135135
group_id=path_params.gid,
136-
new_group_values=update.model_dump(exclude_unset=True),
136+
new_group_values=update.to_model(),
137137
)
138138

139139
updated_group = GroupGet.from_model(group, access_rights)

services/web/server/tests/integration/01/test_garbage_collection.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from aiohttp import web
2222
from aiohttp.test_utils import TestClient
2323
from aioresponses import aioresponses
24-
from models_library.groups import EVERYONE_GROUP_ID
24+
from models_library.groups import EVERYONE_GROUP_ID, OrganizationCreate
2525
from models_library.projects_state import RunningState
2626
from pytest_mock import MockerFixture
2727
from pytest_simcore.helpers.webserver_login import UserInfoDict, log_client_in
@@ -285,7 +285,9 @@ async def get_group(client: TestClient, user: dict):
285285
return await create_organization(
286286
app=client.app,
287287
user_id=user["id"],
288-
new_group_values={"label": uuid4(), "description": uuid4(), "thumbnail": None},
288+
new_group_values=OrganizationCreate.model_validate(
289+
{"label": uuid4(), "description": uuid4(), "thumbnail": None}
290+
),
289291
)
290292

291293

services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_users.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from aiohttp.test_utils import TestClient
1212
from faker import Faker
1313
from models_library.api_schemas_webserver.groups import GroupGet, GroupUserGet
14-
from models_library.groups import AccessRightsDict, Group
14+
from models_library.groups import AccessRightsDict, Group, OrganizationCreate
1515
from pytest_simcore.helpers.assert_checks import assert_status
1616
from pytest_simcore.helpers.webserver_login import LoggedUser, NewUser, UserInfoDict
1717
from pytest_simcore.helpers.webserver_parametrizations import (
@@ -424,12 +424,14 @@ async def group_where_logged_user_is_the_owner(
424424
group, _ = await create_organization(
425425
app=client.app,
426426
user_id=logged_user["id"],
427-
new_group_values={
428-
"gid": "6543",
429-
"label": f"this is user {logged_user['id']} group",
430-
"description": f"user {logged_user['email']} is the owner of that one",
431-
"thumbnail": None,
432-
},
427+
new_group_values=OrganizationCreate.model_validate(
428+
{
429+
"gid": "6543",
430+
"label": f"this is user {logged_user['id']} group",
431+
"description": f"user {logged_user['email']} is the owner of that one",
432+
"thumbnail": None,
433+
}
434+
),
433435
)
434436

435437
yield group

0 commit comments

Comments
 (0)