Skip to content

Commit 81ec8eb

Browse files
modification of itis vip service
1 parent 778fc53 commit 81ec8eb

File tree

6 files changed

+156
-148
lines changed

6 files changed

+156
-148
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111
from servicelib.logging_utils import log_catch, log_context
1212
from simcore_service_webserver.licenses import (
1313
_itis_vip_service,
14-
_licensed_items_service,
14+
_licensed_resources_service,
1515
)
1616

1717
from ..redis import get_redis_lock_manager_client_sdk, setup_redis
1818
from ._itis_vip_models import CategoryTuple, ItisVipData, ItisVipResourceData
19-
from ._licensed_items_service import RegistrationState
19+
from ._licensed_resources_service import RegistrationState
2020

2121
_logger = logging.getLogger(__name__)
2222

2323

24-
async def sync_resources_with_licensed_items(
24+
async def sync_licensed_resources(
2525
app: web.Application, categories: list[CategoryTuple]
2626
):
2727
async with AsyncClient() as http_client:
@@ -46,7 +46,7 @@ async def sync_resources_with_licensed_items(
4646
with log_context(
4747
_logger, logging.INFO, "Registering %s", licensed_resource_name
4848
), log_catch(_logger, reraise=False):
49-
result = await _licensed_items_service.register_licensed_resource(
49+
result = await _licensed_resources_service.register_licensed_resource(
5050
app,
5151
licensed_item_display_name=f"{vip_data.features.get('name', 'UNNAMED!!')} "
5252
f"{vip_data.features.get('version', 'UNVERSIONED!!')}",
@@ -75,9 +75,9 @@ async def sync_resources_with_licensed_items(
7575
) # nosec
7676
# NOTE: inform since needs curation
7777
_logger.info(
78-
"%s . New licensed_item_id=%s pending for activation.",
78+
"%s . New licensed_resource_id=%s pending for activation.",
7979
result.message,
80-
result.registered.licensed_item_id,
80+
result.registered.licensed_resource_id,
8181
)
8282

8383

@@ -107,7 +107,7 @@ async def _lifespan(app_: web.Application):
107107
retry_after=timedelta(minutes=2),
108108
)
109109
async def _periodic_sync() -> None:
110-
await sync_resources_with_licensed_items(app_, categories=categories)
110+
await sync_licensed_resources(app_, categories=categories)
111111

112112
background_task = asyncio.create_task(
113113
_periodic_sync(), name=_BACKGROUND_TASK_NAME

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

Lines changed: 34 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,16 @@
22

33
import logging
44
from datetime import UTC, datetime, timedelta
5-
from enum import Enum, auto
6-
from pprint import pformat
7-
from typing import NamedTuple
85

96
from aiohttp import web
10-
from deepdiff import DeepDiff # type: ignore[attr-defined]
11-
from models_library.licenses import (
12-
LicensedItem,
13-
LicensedItemID,
14-
LicensedItemPage,
15-
LicensedItemPatchDB,
16-
LicensedResourceDB,
17-
LicensedResourceType,
18-
)
7+
from models_library.licenses import LicensedItem, LicensedItemID, LicensedItemPage
198
from models_library.products import ProductName
209
from models_library.resource_tracker_licensed_items_purchases import (
2110
LicensedItemsPurchasesCreate,
2211
)
2312
from models_library.rest_ordering import OrderBy
2413
from models_library.users import UserID
25-
from pydantic import BaseModel, NonNegativeInt
14+
from pydantic import NonNegativeInt
2615
from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import (
2716
licensed_items_purchases,
2817
)
@@ -32,92 +21,13 @@
3221
from ..users.api import get_user
3322
from ..wallets.api import get_wallet_with_available_credits_by_user_and_wallet
3423
from ..wallets.errors import WalletNotEnoughCreditsError
35-
from . import _licensed_items_repository, _licensed_resources_repository
24+
from . import _licensed_items_repository
3625
from ._common.models import LicensedItemsBodyParams
37-
from .errors import LicensedItemNotFoundError, LicensedItemPricingPlanMatchError
26+
from .errors import LicensedItemPricingPlanMatchError
3827

3928
_logger = logging.getLogger(__name__)
4029

4130

42-
class RegistrationState(Enum):
43-
ALREADY_REGISTERED = auto()
44-
DIFFERENT_RESOURCE = auto()
45-
NEWLY_REGISTERED = auto()
46-
47-
48-
class RegistrationResult(NamedTuple):
49-
registered: LicensedResourceDB
50-
state: RegistrationState
51-
message: str | None
52-
53-
54-
async def register_licensed_resource(
55-
app: web.Application,
56-
*,
57-
licensed_resource_name: str,
58-
licensed_resource_type: LicensedResourceType,
59-
licensed_resource_data: BaseModel,
60-
licensed_item_display_name: str,
61-
) -> RegistrationResult:
62-
# NOTE about the implementation choice:
63-
# Using `create_if_not_exists` (INSERT with IGNORE_ON_CONFLICT) would have been an option,
64-
# but it generates excessive error logs due to conflicts.
65-
#
66-
# To avoid this, we first attempt to retrieve the resource using `get_by_resource_identifier` (GET).
67-
# If the resource does not exist, we proceed with `create_if_not_exists` (INSERT with IGNORE_ON_CONFLICT).
68-
#
69-
# This approach not only reduces unnecessary error logs but also helps prevent race conditions
70-
# when multiple concurrent calls attempt to register the same resource.
71-
72-
resource_key = f"{licensed_resource_type}, {licensed_resource_name}"
73-
new_licensed_resource_data = licensed_resource_data.model_dump(
74-
mode="json",
75-
exclude_unset=True,
76-
)
77-
78-
try:
79-
licensed_resource = (
80-
await _licensed_resources_repository.get_by_resource_identifier(
81-
app,
82-
licensed_resource_name=licensed_resource_name,
83-
licensed_resource_type=licensed_resource_type,
84-
)
85-
)
86-
87-
if licensed_resource.licensed_resource_data != new_licensed_resource_data:
88-
ddiff = DeepDiff(
89-
licensed_resource.licensed_resource_data, new_licensed_resource_data
90-
)
91-
msg = (
92-
f"DIFFERENT_RESOURCE: {resource_key=} found in licensed_resource_id={licensed_resource.licensed_resource_id} with different data. "
93-
f"Diff:\n\t{pformat(ddiff, indent=2, width=200)}"
94-
)
95-
return RegistrationResult(
96-
licensed_resource, RegistrationState.DIFFERENT_RESOURCE, msg
97-
)
98-
99-
return RegistrationResult(
100-
licensed_resource,
101-
RegistrationState.ALREADY_REGISTERED,
102-
f"ALREADY_REGISTERED: {resource_key=} found in licensed_resource_id={licensed_resource.licensed_resource_id}",
103-
)
104-
105-
except LicensedItemNotFoundError:
106-
licensed_resource = await _licensed_resources_repository.create_if_not_exists(
107-
app,
108-
display_name=licensed_item_display_name,
109-
licensed_resource_name=licensed_resource_name,
110-
licensed_resource_type=licensed_resource_type,
111-
licensed_resource_data=new_licensed_resource_data,
112-
)
113-
114-
return RegistrationResult(
115-
licensed_resource,
116-
RegistrationState.NEWLY_REGISTERED,
117-
f"NEWLY_REGISTERED: {resource_key=} registered with licensed_resource_id={licensed_resource.licensed_resource_id}",
118-
)
119-
120-
12131
async def get_licensed_item(
12232
app: web.Application,
12333
*,
@@ -176,32 +86,32 @@ async def list_licensed_items(
17686
)
17787

17888

179-
async def trash_licensed_item(
180-
app: web.Application,
181-
*,
182-
product_name: ProductName,
183-
licensed_item_id: LicensedItemID,
184-
):
185-
await _licensed_items_repository.update(
186-
app,
187-
product_name=product_name,
188-
licensed_item_id=licensed_item_id,
189-
updates=LicensedItemPatchDB(trash=True),
190-
)
191-
192-
193-
async def untrash_licensed_item(
194-
app: web.Application,
195-
*,
196-
product_name: ProductName,
197-
licensed_item_id: LicensedItemID,
198-
):
199-
await _licensed_items_repository.update(
200-
app,
201-
product_name=product_name,
202-
licensed_item_id=licensed_item_id,
203-
updates=LicensedItemPatchDB(trash=True),
204-
)
89+
# async def trash_licensed_item(
90+
# app: web.Application,
91+
# *,
92+
# product_name: ProductName,
93+
# licensed_item_id: LicensedItemID,
94+
# ):
95+
# await _licensed_items_repository.update(
96+
# app,
97+
# product_name=product_name,
98+
# licensed_item_id=licensed_item_id,
99+
# updates=LicensedItemPatchDB(trash=True),
100+
# )
101+
102+
103+
# async def untrash_licensed_item(
104+
# app: web.Application,
105+
# *,
106+
# product_name: ProductName,
107+
# licensed_item_id: LicensedItemID,
108+
# ):
109+
# await _licensed_items_repository.update(
110+
# app,
111+
# product_name=product_name,
112+
# licensed_item_id=licensed_item_id,
113+
# updates=LicensedItemPatchDB(trash=True),
114+
# )
205115

206116

207117
async def purchase_licensed_item(
@@ -210,6 +120,8 @@ async def purchase_licensed_item(
210120
product_name: ProductName,
211121
user_id: UserID,
212122
licensed_item_id: LicensedItemID,
123+
key: str,
124+
version: str,
213125
body_params: LicensedItemsBodyParams,
214126
) -> None:
215127
# Check user wallet permissions
@@ -245,6 +157,8 @@ async def purchase_licensed_item(
245157
_data = LicensedItemsPurchasesCreate(
246158
product_name=product_name,
247159
licensed_item_id=licensed_item_id,
160+
key=key,
161+
version=version,
248162
wallet_id=wallet.wallet_id,
249163
wallet_name=wallet.name,
250164
pricing_plan_id=body_params.pricing_plan_id,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def get_by_resource_identifier(
9797
row = result.one_or_none()
9898
if row is None:
9999
raise LicensedResourceNotFoundError(
100-
licensed_item_id="Unkown", # <-- NOTE: will be changed for licensed_resource_id
100+
licensed_resource_id="Unknown",
101101
licensed_resource_name=licensed_resource_name,
102102
licensed_resource_type=licensed_resource_type,
103103
)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# pylint: disable=unused-argument
2+
3+
import logging
4+
from enum import Enum, auto
5+
from pprint import pformat
6+
from typing import NamedTuple
7+
8+
from aiohttp import web
9+
from deepdiff import DeepDiff # type: ignore[attr-defined]
10+
from models_library.licenses import LicensedResourceDB, LicensedResourceType
11+
from pydantic import BaseModel
12+
13+
from . import _licensed_resources_repository
14+
from .errors import LicensedResourceNotFoundError
15+
16+
_logger = logging.getLogger(__name__)
17+
18+
19+
class RegistrationState(Enum):
20+
ALREADY_REGISTERED = auto()
21+
DIFFERENT_RESOURCE = auto()
22+
NEWLY_REGISTERED = auto()
23+
24+
25+
class RegistrationResult(NamedTuple):
26+
registered: LicensedResourceDB
27+
state: RegistrationState
28+
message: str | None
29+
30+
31+
async def register_licensed_resource(
32+
app: web.Application,
33+
*,
34+
licensed_resource_name: str,
35+
licensed_resource_type: LicensedResourceType,
36+
licensed_resource_data: BaseModel,
37+
licensed_item_display_name: str,
38+
) -> RegistrationResult:
39+
# NOTE about the implementation choice:
40+
# Using `create_if_not_exists` (INSERT with IGNORE_ON_CONFLICT) would have been an option,
41+
# but it generates excessive error logs due to conflicts.
42+
#
43+
# To avoid this, we first attempt to retrieve the resource using `get_by_resource_identifier` (GET).
44+
# If the resource does not exist, we proceed with `create_if_not_exists` (INSERT with IGNORE_ON_CONFLICT).
45+
#
46+
# This approach not only reduces unnecessary error logs but also helps prevent race conditions
47+
# when multiple concurrent calls attempt to register the same resource.
48+
49+
resource_key = f"{licensed_resource_type}, {licensed_resource_name}"
50+
new_licensed_resource_data = licensed_resource_data.model_dump(
51+
mode="json",
52+
exclude_unset=True,
53+
)
54+
55+
try:
56+
licensed_resource = (
57+
await _licensed_resources_repository.get_by_resource_identifier(
58+
app,
59+
licensed_resource_name=licensed_resource_name,
60+
licensed_resource_type=licensed_resource_type,
61+
)
62+
)
63+
64+
if licensed_resource.licensed_resource_data != new_licensed_resource_data:
65+
ddiff = DeepDiff(
66+
licensed_resource.licensed_resource_data, new_licensed_resource_data
67+
)
68+
msg = (
69+
f"DIFFERENT_RESOURCE: {resource_key=} found in licensed_resource_id={licensed_resource.licensed_resource_id} with different data. "
70+
f"Diff:\n\t{pformat(ddiff, indent=2, width=200)}"
71+
)
72+
return RegistrationResult(
73+
licensed_resource, RegistrationState.DIFFERENT_RESOURCE, msg
74+
)
75+
76+
return RegistrationResult(
77+
licensed_resource,
78+
RegistrationState.ALREADY_REGISTERED,
79+
f"ALREADY_REGISTERED: {resource_key=} found in licensed_resource_id={licensed_resource.licensed_resource_id}",
80+
)
81+
82+
except LicensedResourceNotFoundError:
83+
licensed_resource = await _licensed_resources_repository.create_if_not_exists(
84+
app,
85+
display_name=licensed_item_display_name,
86+
licensed_resource_name=licensed_resource_name,
87+
licensed_resource_type=licensed_resource_type,
88+
licensed_resource_data=new_licensed_resource_data,
89+
)
90+
91+
return RegistrationResult(
92+
licensed_resource,
93+
RegistrationState.NEWLY_REGISTERED,
94+
f"NEWLY_REGISTERED: {resource_key=} registered with licensed_resource_id={licensed_resource.licensed_resource_id}",
95+
)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77
from aiohttp.test_utils import TestClient
88
from simcore_postgres_database.models.licensed_items import licensed_items
9+
from simcore_postgres_database.models.licensed_resources import licensed_resources
910
from simcore_postgres_database.models.resource_tracker_pricing_plans import (
1011
resource_tracker_pricing_plans,
1112
)
@@ -45,11 +46,11 @@ async def pricing_plan_id(
4546

4647

4748
@pytest.fixture
48-
async def ensure_empty_licensed_items(client: TestClient):
49+
async def ensure_empty_licensed_resources(client: TestClient):
4950
async def _cleanup():
5051
assert client.app
5152
async with transaction_context(get_asyncpg_engine(client.app)) as conn:
52-
await conn.execute(licensed_items.delete())
53+
await conn.execute(licensed_resources.delete())
5354

5455
await _cleanup()
5556

0 commit comments

Comments
 (0)