Skip to content

Commit a76f9bc

Browse files
committed
adapts repository
1 parent cd85444 commit a76f9bc

File tree

3 files changed

+118
-29
lines changed

3 files changed

+118
-29
lines changed

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,19 @@ class LicensedResourceType(StrAutoEnum):
2323

2424
class LicensedItemDB(BaseModel):
2525
licensed_item_id: LicensedItemID
26-
name: str
27-
2826
license_key: str | None
27+
28+
licensed_resource_name: str
2929
licensed_resource_type: LicensedResourceType
3030
licensed_resource_data: dict[str, Any] | None
3131

32-
pricing_plan_id: PricingPlanId
33-
product_name: ProductName
32+
pricing_plan_id: PricingPlanId | None
33+
product_name: ProductName | None
3434

35-
created: datetime # Timestamp upon creation
36-
modified: datetime # Timestamp on last modification
37-
trashed: datetime | None # Marked as trashed
35+
# states
36+
created: datetime
37+
modified: datetime
38+
trashed: datetime | None
3839

3940
model_config = ConfigDict(from_attributes=True)
4041

services/web/server/src/simcore_service_webserver/licenses/_licensed_items_repository.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
import logging
8-
from typing import cast
8+
from typing import Literal, cast
99

1010
from aiohttp import web
1111
from models_library.licensed_items import (
@@ -20,6 +20,7 @@
2020
from pydantic import NonNegativeInt
2121
from simcore_postgres_database.models.licensed_items import licensed_items
2222
from simcore_postgres_database.utils_repos import (
23+
get_columns_from_db_model,
2324
pass_or_acquire_connection,
2425
transaction_context,
2526
)
@@ -33,18 +34,7 @@
3334
_logger = logging.getLogger(__name__)
3435

3536

36-
_SELECTION_ARGS = (
37-
licensed_items.c.licensed_item_id,
38-
licensed_items.c.name,
39-
licensed_items.c.license_key,
40-
licensed_items.c.licensed_resource_type,
41-
licensed_items.c.pricing_plan_id,
42-
licensed_items.c.product_name,
43-
licensed_items.c.created,
44-
licensed_items.c.modified,
45-
)
46-
47-
assert set(LicensedItemDB.model_fields) == {c.name for c in _SELECTION_ARGS} # nosec
37+
_SELECTION_ARGS = get_columns_from_db_model(licensed_items, LicensedItemDB)
4838

4939

5040
async def create(
@@ -57,7 +47,7 @@ async def create(
5747
pricing_plan_id: PricingPlanId,
5848
) -> LicensedItemDB:
5949
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
60-
result = await conn.stream(
50+
result = await conn.execute(
6151
licensed_items.insert()
6252
.values(
6353
name=name,
@@ -69,7 +59,7 @@ async def create(
6959
)
7060
.returning(*_SELECTION_ARGS)
7161
)
72-
row = await result.first()
62+
row = result.one()
7363
return LicensedItemDB.model_validate(row)
7464

7565

@@ -81,13 +71,20 @@ async def list_(
8171
offset: NonNegativeInt,
8272
limit: NonNegativeInt,
8373
order_by: OrderBy,
74+
filter_trashed: Literal["exclude", "only", "include"] = "exclude",
8475
) -> tuple[int, list[LicensedItemDB]]:
8576
base_query = (
8677
select(*_SELECTION_ARGS)
8778
.select_from(licensed_items)
8879
.where(licensed_items.c.product_name == product_name)
8980
)
9081

82+
# Apply trashed filter
83+
if filter_trashed == "exclude":
84+
base_query = base_query.where(licensed_items.c.trashed.is_(None))
85+
elif filter_trashed == "only":
86+
base_query = base_query.where(licensed_items.c.trashed.is_not(None))
87+
9188
# Select total count from base_query
9289
subquery = base_query.subquery()
9390
count_query = select(func.count()).select_from(subquery)
@@ -147,11 +144,16 @@ async def update(
147144
# NOTE: at least 'touch' if updated_values is empty
148145
_updates = {
149146
**updates.model_dump(exclude_unset=True),
150-
"modified": func.now(),
147+
licensed_items.c.modified.name: func.now(),
151148
}
152149

150+
# trashing
151+
assert "trash" in dict(LicensedItemUpdateDB.model_fields) # nosec
152+
if trash := _updates.pop("trash", None):
153+
_updates[licensed_items.c.trashed.name] = func.now() if trash else None
154+
153155
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
154-
result = await conn.stream(
156+
result = await conn.execute(
155157
licensed_items.update()
156158
.values(**_updates)
157159
.where(
@@ -160,7 +162,7 @@ async def update(
160162
)
161163
.returning(*_SELECTION_ARGS)
162164
)
163-
row = await result.first()
165+
row = result.one_or_none()
164166
if row is None:
165167
raise LicensedItemNotFoundError(licensed_item_id=licensed_item_id)
166168
return LicensedItemDB.model_validate(row)

services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_repository.py

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# pylint: disable=unused-variable
44
# pylint: disable=too-many-arguments
55
# pylint: disable=too-many-statements
6-
from http import HTTPStatus
76

7+
import arrow
88
import pytest
99
from aiohttp.test_utils import TestClient
1010
from models_library.licensed_items import (
@@ -14,20 +14,22 @@
1414
)
1515
from models_library.rest_ordering import OrderBy
1616
from pytest_simcore.helpers.webserver_login import UserInfoDict
17-
from servicelib.aiohttp import status
1817
from simcore_service_webserver.db.models import UserRole
1918
from simcore_service_webserver.licenses import _licensed_items_repository
2019
from simcore_service_webserver.licenses.errors import LicensedItemNotFoundError
2120
from simcore_service_webserver.projects.models import ProjectDict
2221

2322

24-
@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)])
23+
@pytest.fixture
24+
def user_role() -> UserRole:
25+
return UserRole.USER
26+
27+
2528
async def test_licensed_items_db_crud(
2629
client: TestClient,
2730
logged_user: UserInfoDict,
2831
user_project: ProjectDict,
2932
osparc_product_name: str,
30-
expected: HTTPStatus,
3133
pricing_plan_id: int,
3234
):
3335
assert client.app
@@ -92,3 +94,87 @@ async def test_licensed_items_db_crud(
9294
licensed_item_id=_licensed_item_id,
9395
product_name=osparc_product_name,
9496
)
97+
98+
99+
async def test_licensed_items_db_trash(
100+
client: TestClient,
101+
logged_user: UserInfoDict,
102+
user_project: ProjectDict,
103+
osparc_product_name: str,
104+
pricing_plan_id: int,
105+
):
106+
assert client.app
107+
108+
# Create two licensed items
109+
licensed_item_ids = []
110+
for name in ["Model A", "Model B"]:
111+
licensed_item_db = await _licensed_items_repository.create(
112+
client.app,
113+
product_name=osparc_product_name,
114+
name=name,
115+
licensed_resource_type=LicensedResourceType.VIP_MODEL,
116+
pricing_plan_id=pricing_plan_id,
117+
)
118+
licensed_item_ids.append(licensed_item_db.licensed_item_id)
119+
120+
licensed_item_id1, licensed_item_id2 = licensed_item_ids
121+
122+
# Trash one licensed item
123+
trashing_at = arrow.now().datetime
124+
trashed_item = await _licensed_items_repository.update(
125+
client.app,
126+
licensed_item_id=licensed_item_id1,
127+
product_name=osparc_product_name,
128+
updates=LicensedItemUpdateDB(trash=True),
129+
)
130+
131+
assert trashed_item.licensed_item_id == licensed_item_id1
132+
assert trashed_item.trashed
133+
assert trashing_at < trashed_item.trashed
134+
assert trashed_item.trashed < arrow.now().datetime
135+
136+
# List with filter_trashed include
137+
total_count, items = await _licensed_items_repository.list_(
138+
client.app,
139+
product_name=osparc_product_name,
140+
offset=0,
141+
limit=10,
142+
order_by=OrderBy(field="modified"),
143+
filter_trashed="include",
144+
)
145+
assert total_count == 2
146+
assert {i.licensed_item_id for i in items} == set(licensed_item_ids)
147+
148+
# List with filter_trashed exclude
149+
total_count, items = await _licensed_items_repository.list_(
150+
client.app,
151+
product_name=osparc_product_name,
152+
offset=0,
153+
limit=10,
154+
order_by=OrderBy(field="modified"),
155+
filter_trashed="exclude",
156+
)
157+
assert total_count == 1
158+
assert items[0].licensed_item_id == licensed_item_id2
159+
assert items[0].trashed is None
160+
161+
# List with filter_trashed all
162+
total_count, items = await _licensed_items_repository.list_(
163+
client.app,
164+
product_name=osparc_product_name,
165+
offset=0,
166+
limit=10,
167+
order_by=OrderBy(field="modified"),
168+
filter_trashed="only",
169+
)
170+
assert total_count == 1
171+
assert items[0].licensed_item_id == trashed_item.licensed_item_id
172+
assert items[0].trashed
173+
174+
# Get the trashed licensed item
175+
got = await _licensed_items_repository.get(
176+
client.app,
177+
licensed_item_id=trashed_item.licensed_item_id,
178+
product_name=osparc_product_name,
179+
)
180+
assert got == trashed_item

0 commit comments

Comments
 (0)