Skip to content

Commit 1b17b3d

Browse files
authored
✨ Sync task that auto-registers items from VIP as licensed items in the system 🗃️ 🚨 (#7140)
1 parent 4fa6ee2 commit 1b17b3d

File tree

60 files changed

+1386
-270
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1386
-270
lines changed

.env-devel

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ DYNAMIC_SCHEDULER_UI_STORAGE_SECRET=adminadmin
133133

134134
FUNCTION_SERVICES_AUTHORS='{"UN": {"name": "Unknown", "email": "[email protected]", "affiliation": "unknown"}}'
135135

136+
WEBSERVER_LICENSES={}
137+
LICENSES_ITIS_VIP_SYNCER_ENABLED=false
138+
LICENSES_ITIS_VIP_API_URL=https://some-api/{category}
139+
LICENSES_ITIS_VIP_CATEGORIES='{"HumanWholeBody": "Humans", "HumanBodyRegion": "Humans (Region)", "AnimalWholeBody": "Animal"}'
140+
136141
# Can use 'docker run -it itisfoundation/invitations:latest simcore-service-invitations generate-dotenv --auto-password'
137142
INVITATIONS_DEFAULT_PRODUCT=osparc
138143
INVITATIONS_HOST=invitations
@@ -371,7 +376,6 @@ WEBSERVER_GARBAGE_COLLECTOR=null
371376
WEBSERVER_GROUPS=1
372377
WEBSERVER_GUNICORN_CMD_ARGS=--timeout=180
373378
WEBSERVER_HOST=webserver
374-
WEBSERVER_LICENSES=true
375379
WEBSERVER_LOGIN={}
376380
WEBSERVER_LOGLEVEL=INFO
377381
WEBSERVER_META_MODELING=1

packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from typing import NamedTuple
33

4-
from models_library.licensed_items import LicensedItemID
4+
from models_library.licenses import LicensedItemID
55
from models_library.products import ProductName
66
from models_library.resource_tracker_licensed_items_checkouts import (
77
LicensedItemCheckoutID,

packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from decimal import Decimal
33
from typing import NamedTuple
44

5-
from models_library.licensed_items import LicensedItemID
5+
from models_library.licenses import LicensedItemID
66
from models_library.products import ProductName
77
from models_library.resource_tracker import PricingUnitCostId
88
from models_library.resource_tracker_licensed_items_purchases import (

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

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
from datetime import datetime
2-
from typing import Any, NamedTuple, cast
2+
from typing import Any, NamedTuple, Self, cast
33

4-
from models_library.licensed_items import (
4+
from models_library.basic_types import IDStr
5+
from models_library.resource_tracker import PricingPlanId
6+
from pydantic import BaseModel, ConfigDict, HttpUrl, PositiveInt
7+
from pydantic.config import JsonDict
8+
9+
from ..licenses import (
510
VIP_DETAILS_EXAMPLE,
11+
FeaturesDict,
12+
LicensedItem,
613
LicensedItemID,
714
LicensedResourceType,
815
)
9-
from models_library.resource_tracker import PricingPlanId
10-
from models_library.utils.common_validators import to_camel_recursive
11-
from pydantic import AfterValidator, BaseModel, ConfigDict, PositiveInt
12-
from pydantic.config import JsonDict
13-
from typing_extensions import Annotated
14-
1516
from ._base import OutputSchema
1617

1718
# RPC
@@ -25,6 +26,7 @@ class LicensedItemRpcGet(BaseModel):
2526
pricing_plan_id: PricingPlanId
2627
created_at: datetime
2728
modified_at: datetime
29+
2830
model_config = ConfigDict(
2931
json_schema_extra={
3032
"examples": [
@@ -50,33 +52,75 @@ class LicensedItemRpcGetPage(NamedTuple):
5052
# Rest
5153

5254

55+
class _ItisVipRestData(OutputSchema):
56+
description: str
57+
thumbnail: str
58+
features: FeaturesDict # NOTE: here there is a bit of coupling with domain model
59+
doi: str
60+
61+
62+
class _ItisVipResourceRestData(OutputSchema):
63+
category_id: IDStr
64+
category_display: str
65+
source: _ItisVipRestData
66+
terms_of_use_url: HttpUrl | None = None
67+
68+
5369
class LicensedItemRestGet(OutputSchema):
5470
licensed_item_id: LicensedItemID
5571
display_name: str
72+
# NOTE: to put here a discriminator we have to embed it one more layer
5673
licensed_resource_type: LicensedResourceType
57-
licensed_resource_data: Annotated[
58-
dict[str, Any], AfterValidator(to_camel_recursive)
59-
]
74+
licensed_resource_data: _ItisVipResourceRestData
6075
pricing_plan_id: PricingPlanId
6176

6277
created_at: datetime
6378
modified_at: datetime
6479

65-
model_config = ConfigDict(
66-
json_schema_extra={
67-
"examples": [
68-
{
69-
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
70-
"display_name": "best-model",
71-
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
72-
"licensed_resource_data": cast(JsonDict, VIP_DETAILS_EXAMPLE),
73-
"pricing_plan_id": "15",
74-
"created_at": "2024-12-12 09:59:26.422140",
75-
"modified_at": "2024-12-12 09:59:26.422140",
76-
}
77-
]
78-
}
79-
)
80+
@staticmethod
81+
def _update_json_schema_extra(schema: JsonDict) -> None:
82+
schema.update(
83+
{
84+
"examples": [
85+
{
86+
"licensedItemId": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
87+
"displayName": "my best model",
88+
"licensedResourceName": "best-model",
89+
"licensedResourceType": f"{LicensedResourceType.VIP_MODEL}",
90+
"licensedResourceData": cast(
91+
JsonDict,
92+
{
93+
"categoryId": "HumanWholeBody",
94+
"categoryDisplay": "Humans",
95+
"source": VIP_DETAILS_EXAMPLE,
96+
},
97+
),
98+
"pricingPlanId": "15",
99+
"createdAt": "2024-12-12 09:59:26.422140",
100+
"modifiedAt": "2024-12-12 09:59:26.422140",
101+
}
102+
]
103+
}
104+
)
105+
106+
model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)
107+
108+
@classmethod
109+
def from_domain_model(cls, item: LicensedItem) -> Self:
110+
111+
return cls.model_validate(
112+
{
113+
"licensed_item_id": item.licensed_item_id,
114+
"display_name": item.display_name,
115+
"licensed_resource_type": item.licensed_resource_type,
116+
"licensed_resource_data": {
117+
**item.licensed_resource_data,
118+
},
119+
"pricing_plan_id": item.pricing_plan_id,
120+
"created_at": item.created_at,
121+
"modified_at": item.modified_at,
122+
}
123+
)
80124

81125

82126
class LicensedItemRestGetPage(NamedTuple):

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from models_library.emails import LowerCaseEmailStr
55
from pydantic import BaseModel, ConfigDict, PositiveInt
66

7-
from ..licensed_items import LicensedItemID
7+
from ..licenses import LicensedItemID
88
from ..products import ProductName
99
from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID
1010
from ..users import UserID

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from models_library.emails import LowerCaseEmailStr
66
from pydantic import PositiveInt
77

8-
from ..licensed_items import LicensedItemID
8+
from ..licenses import LicensedItemID
99
from ..products import ProductName
1010
from ..resource_tracker import PricingUnitCostId
1111
from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID

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

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from datetime import datetime
2+
from enum import auto
3+
from typing import Any, NamedTuple, NotRequired, TypeAlias, cast
4+
from uuid import UUID
5+
6+
from models_library.resource_tracker import PricingPlanId
7+
from pydantic import BaseModel, ConfigDict, PositiveInt
8+
from pydantic.config import JsonDict
9+
from typing_extensions import TypedDict
10+
11+
from .products import ProductName
12+
from .resource_tracker import PricingPlanId
13+
from .utils.enums import StrAutoEnum
14+
15+
LicensedItemID: TypeAlias = UUID
16+
17+
18+
class LicensedResourceType(StrAutoEnum):
19+
VIP_MODEL = auto()
20+
21+
22+
VIP_FEATURES_EXAMPLE = {
23+
"name": "Duke",
24+
"version": "V2.0",
25+
"sex": "Male",
26+
"age": "34 years",
27+
"weight": "70.2 Kg",
28+
"height": "1.77 m",
29+
"date": "2015-03-01",
30+
"ethnicity": "Caucasian",
31+
"functionality": "Static",
32+
"additional_field": "allowed",
33+
}
34+
35+
36+
class FeaturesDict(TypedDict):
37+
name: NotRequired[str]
38+
version: NotRequired[str]
39+
sex: NotRequired[str]
40+
age: NotRequired[str]
41+
weight: NotRequired[str]
42+
height: NotRequired[str]
43+
date: str
44+
ethnicity: NotRequired[str]
45+
functionality: NotRequired[str]
46+
47+
48+
VIP_DETAILS_EXAMPLE = {
49+
"id": 1,
50+
"description": "A detailed description of the VIP model",
51+
"thumbnail": "https://example.com/thumbnail.jpg",
52+
"features": VIP_FEATURES_EXAMPLE,
53+
"doi": "10.1000/xyz123",
54+
"license_key": "ABC123XYZ",
55+
"license_version": "1.0",
56+
"protection": "Encrypted",
57+
"available_from_url": "https://example.com/download",
58+
"additional_field": "trimmed if rest",
59+
}
60+
61+
62+
#
63+
# DB
64+
#
65+
66+
67+
class LicensedItemDB(BaseModel):
68+
licensed_item_id: LicensedItemID
69+
display_name: str
70+
71+
licensed_resource_name: str
72+
licensed_resource_type: LicensedResourceType
73+
licensed_resource_data: dict[str, Any] | None
74+
75+
pricing_plan_id: PricingPlanId | None
76+
product_name: ProductName | None
77+
78+
# states
79+
created: datetime
80+
modified: datetime
81+
trashed: datetime | None
82+
83+
model_config = ConfigDict(from_attributes=True)
84+
85+
86+
class LicensedItemUpdateDB(BaseModel):
87+
display_name: str | None = None
88+
licensed_resource_name: str | None = None
89+
pricing_plan_id: PricingPlanId | None = None
90+
trash: bool | None = None
91+
92+
93+
class LicensedItem(BaseModel):
94+
licensed_item_id: LicensedItemID
95+
display_name: str
96+
licensed_resource_name: str
97+
licensed_resource_type: LicensedResourceType
98+
licensed_resource_data: dict[str, Any]
99+
pricing_plan_id: PricingPlanId
100+
created_at: datetime
101+
modified_at: datetime
102+
103+
@staticmethod
104+
def _update_json_schema_extra(schema: JsonDict) -> None:
105+
schema.update(
106+
{
107+
"examples": [
108+
{
109+
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
110+
"display_name": "my best model",
111+
"licensed_resource_name": "best-model",
112+
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
113+
"licensed_resource_data": cast(JsonDict, VIP_DETAILS_EXAMPLE),
114+
"pricing_plan_id": "15",
115+
"created_at": "2024-12-12 09:59:26.422140",
116+
"modified_at": "2024-12-12 09:59:26.422140",
117+
}
118+
]
119+
}
120+
)
121+
122+
model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)
123+
124+
125+
class LicensedItemPage(NamedTuple):
126+
total: PositiveInt
127+
items: list[LicensedItem]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from pydantic import BaseModel, ConfigDict
77

8-
from .licensed_items import LicensedItemID
8+
from .licenses import LicensedItemID
99
from .products import ProductName
1010
from .resource_tracker import PricingPlanId, PricingUnitCostId, PricingUnitId
1111
from .users import UserID

0 commit comments

Comments
 (0)