Skip to content

Commit e35a13f

Browse files
♻️ VIP models introduce licensed versioning (🗃️) (#7215)
1 parent 5f92eca commit e35a13f

File tree

53 files changed

+1450
-650
lines changed

Some content is hidden

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

53 files changed

+1450
-650
lines changed

api/specs/web-server/_licensed_items.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
from typing import Annotated
1010

1111
from _common import as_query
12-
from fastapi import APIRouter, Depends, status
12+
from fastapi import APIRouter, Depends
1313
from models_library.api_schemas_webserver.licensed_items import LicensedItemRestGet
14-
from models_library.generics import Envelope
14+
from models_library.api_schemas_webserver.licensed_items_purchases import (
15+
LicensedItemPurchaseGet,
16+
)
1517
from models_library.rest_error import EnvelopedError
1618
from models_library.rest_pagination import Page
1719
from simcore_service_webserver._meta import API_VTAG
@@ -46,19 +48,9 @@ async def list_licensed_items(
4648
...
4749

4850

49-
@router.get(
50-
"/catalog/licensed-items/{licensed_item_id}",
51-
response_model=Envelope[LicensedItemRestGet],
52-
)
53-
async def get_licensed_item(
54-
_path: Annotated[LicensedItemsPathParams, Depends()],
55-
):
56-
...
57-
58-
5951
@router.post(
6052
"/catalog/licensed-items/{licensed_item_id}:purchase",
61-
status_code=status.HTTP_204_NO_CONTENT,
53+
response_model=LicensedItemPurchaseGet,
6254
)
6355
async def purchase_licensed_item(
6456
_path: Annotated[LicensedItemsPathParams, Depends()],

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

Lines changed: 5 additions & 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.licenses import LicensedItemID
4+
from models_library.licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
55
from models_library.products import ProductName
66
from models_library.resource_tracker_licensed_items_checkouts import (
77
LicensedItemCheckoutID,
@@ -15,6 +15,8 @@
1515
class LicensedItemCheckoutGet(BaseModel):
1616
licensed_item_checkout_id: LicensedItemCheckoutID
1717
licensed_item_id: LicensedItemID
18+
key: LicensedItemKey
19+
version: LicensedItemVersion
1820
wallet_id: WalletID
1921
user_id: UserID
2022
user_email: str
@@ -30,6 +32,8 @@ class LicensedItemCheckoutGet(BaseModel):
3032
{
3133
"licensed_item_checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
3234
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
35+
"key": "Duke",
36+
"version": "1.0.0",
3337
"wallet_id": 1,
3438
"user_id": 1,
3539
"user_email": "[email protected]",

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

Lines changed: 5 additions & 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.licenses import LicensedItemID
5+
from models_library.licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
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 (
@@ -17,6 +17,8 @@ class LicensedItemPurchaseGet(BaseModel):
1717
licensed_item_purchase_id: LicensedItemPurchaseID
1818
product_name: ProductName
1919
licensed_item_id: LicensedItemID
20+
key: LicensedItemKey
21+
version: LicensedItemVersion
2022
wallet_id: WalletID
2123
wallet_name: str
2224
pricing_unit_cost_id: PricingUnitCostId
@@ -36,6 +38,8 @@ class LicensedItemPurchaseGet(BaseModel):
3638
"licensed_item_purchase_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
3739
"product_name": "osparc",
3840
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
41+
"key": "Duke",
42+
"version": "1.0.0",
3943
"wallet_id": 1,
4044
"wallet_name": "My Wallet",
4145
"pricing_unit_cost_id": 1,

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

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
FeaturesDict,
1212
LicensedItem,
1313
LicensedItemID,
14+
LicensedItemKey,
15+
LicensedItemVersion,
1416
LicensedResourceType,
1517
)
1618
from ._base import OutputSchema
@@ -20,9 +22,11 @@
2022

2123
class LicensedItemRpcGet(BaseModel):
2224
licensed_item_id: LicensedItemID
25+
key: LicensedItemKey
26+
version: LicensedItemVersion
2327
display_name: str
2428
licensed_resource_type: LicensedResourceType
25-
licensed_resource_data: dict[str, Any]
29+
licensed_resources: list[dict[str, Any]]
2630
pricing_plan_id: PricingPlanId
2731
created_at: datetime
2832
modified_at: datetime
@@ -32,9 +36,11 @@ class LicensedItemRpcGet(BaseModel):
3236
"examples": [
3337
{
3438
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
39+
"key": "Duke",
40+
"version": "1.0.0",
3541
"display_name": "best-model",
3642
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
37-
"licensed_resource_data": cast(JsonDict, VIP_DETAILS_EXAMPLE),
43+
"licensed_resources": [cast(JsonDict, VIP_DETAILS_EXAMPLE)],
3844
"pricing_plan_id": "15",
3945
"created_at": "2024-12-12 09:59:26.422140",
4046
"modified_at": "2024-12-12 09:59:26.422140",
@@ -58,24 +64,28 @@ class _ItisVipRestData(OutputSchema):
5864
thumbnail: str
5965
features: FeaturesDict # NOTE: here there is a bit of coupling with domain model
6066
doi: str | None
67+
license_version: str
6168

6269

6370
class _ItisVipResourceRestData(OutputSchema):
64-
category_id: IDStr
65-
category_display: str
66-
category_icon: HttpUrl | None = None # NOTE: Placeholder until provide @odeimaiz
6771
source: _ItisVipRestData
68-
terms_of_use_url: HttpUrl | None = None # NOTE: Placeholder until provided @mguidon
6972

7073

7174
class LicensedItemRestGet(OutputSchema):
7275
licensed_item_id: LicensedItemID
76+
key: LicensedItemKey
77+
version: LicensedItemVersion
78+
7379
display_name: str
74-
# NOTE: to put here a discriminator we have to embed it one more layer
7580
licensed_resource_type: LicensedResourceType
76-
licensed_resource_data: _ItisVipResourceRestData
81+
licensed_resources: list[_ItisVipResourceRestData]
7782
pricing_plan_id: PricingPlanId
7883

84+
category_id: IDStr
85+
category_display: str
86+
category_icon: HttpUrl | None = None # NOTE: Placeholder until provide @odeimaiz
87+
terms_of_use_url: HttpUrl | None = None # NOTE: Placeholder until provided @mguidon
88+
7989
created_at: datetime
8090
modified_at: datetime
8191

@@ -86,17 +96,21 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
8696
"examples": [
8797
{
8898
"licensedItemId": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
99+
"key": "Duke",
100+
"version": "1.0.0",
89101
"displayName": "my best model",
90102
"licensedResourceType": f"{LicensedResourceType.VIP_MODEL}",
91-
"licensedResourceData": cast(
92-
JsonDict,
93-
{
94-
"categoryId": "HumanWholeBody",
95-
"categoryDisplay": "Humans",
96-
"source": {**VIP_DETAILS_EXAMPLE, "doi": doi},
97-
},
98-
),
103+
"licensedResources": [
104+
cast(
105+
JsonDict,
106+
{
107+
"source": {**VIP_DETAILS_EXAMPLE, "doi": doi},
108+
},
109+
)
110+
],
99111
"pricingPlanId": "15",
112+
"categoryId": "HumanWholeBody",
113+
"categoryDisplay": "Humans",
100114
"createdAt": "2024-12-12 09:59:26.422140",
101115
"modifiedAt": "2024-12-12 09:59:26.422140",
102116
}
@@ -114,6 +128,8 @@ def from_domain_model(cls, item: LicensedItem) -> Self:
114128
**item.model_dump(
115129
include={
116130
"licensed_item_id",
131+
"key",
132+
"version",
117133
"display_name",
118134
"licensed_resource_type",
119135
"pricing_plan_id",
@@ -122,9 +138,18 @@ def from_domain_model(cls, item: LicensedItem) -> Self:
122138
},
123139
exclude_unset=True,
124140
),
125-
"licensed_resource_data": {
126-
**item.licensed_resource_data,
127-
},
141+
"licensed_resources": [
142+
_ItisVipResourceRestData(**x)
143+
for x in sorted(
144+
item.licensed_resources,
145+
key=lambda x: datetime.strptime(
146+
x["source"]["features"]["date"], "%Y-%m-%d"
147+
),
148+
reverse=True,
149+
)
150+
],
151+
"category_id": item.licensed_resources[0]["category_id"],
152+
"category_display": item.licensed_resources[0]["category_display"],
128153
}
129154
)
130155

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

Lines changed: 7 additions & 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 ..licenses import LicensedItemID
7+
from ..licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
88
from ..products import ProductName
99
from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID
1010
from ..users import UserID
@@ -17,6 +17,8 @@
1717
class LicensedItemCheckoutRpcGet(BaseModel):
1818
licensed_item_checkout_id: LicensedItemCheckoutID
1919
licensed_item_id: LicensedItemID
20+
key: LicensedItemKey
21+
version: LicensedItemVersion
2022
wallet_id: WalletID
2123
user_id: UserID
2224
product_name: ProductName
@@ -29,6 +31,8 @@ class LicensedItemCheckoutRpcGet(BaseModel):
2931
{
3032
"licensed_item_checkout_id": "633ef980-6f3e-4b1a-989a-bd77bf9a5d6b",
3133
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
34+
"key": "Duke",
35+
"version": "1.0.0",
3236
"wallet_id": 6,
3337
"user_id": 27845,
3438
"product_name": "osparc",
@@ -52,6 +56,8 @@ class LicensedItemCheckoutRpcGetPage(NamedTuple):
5256
class LicensedItemCheckoutRestGet(OutputSchema):
5357
licensed_item_checkout_id: LicensedItemCheckoutID
5458
licensed_item_id: LicensedItemID
59+
key: str
60+
version: str
5561
wallet_id: WalletID
5662
user_id: UserID
5763
user_email: LowerCaseEmailStr

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

Lines changed: 3 additions & 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 ..licenses import LicensedItemID
8+
from ..licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
99
from ..products import ProductName
1010
from ..resource_tracker import PricingUnitCostId
1111
from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID
@@ -18,6 +18,8 @@ class LicensedItemPurchaseGet(OutputSchema):
1818
licensed_item_purchase_id: LicensedItemPurchaseID
1919
product_name: ProductName
2020
licensed_item_id: LicensedItemID
21+
key: LicensedItemKey
22+
version: LicensedItemVersion
2123
wallet_id: WalletID
2224
pricing_unit_cost_id: PricingUnitCostId
2325
pricing_unit_cost: Decimal

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from datetime import datetime
22
from enum import auto
3-
from typing import Any, NamedTuple, NotRequired, TypeAlias, cast
3+
from typing import Annotated, Any, NamedTuple, NewType, NotRequired, TypeAlias, cast
44
from uuid import UUID
55

66
from models_library.resource_tracker import PricingPlanId
7-
from pydantic import BaseModel, ConfigDict, PositiveInt
7+
from pydantic import BaseModel, ConfigDict, PositiveInt, StringConstraints
88
from pydantic.config import JsonDict
99
from typing_extensions import TypedDict
1010

@@ -15,6 +15,12 @@
1515
LicensedItemID: TypeAlias = UUID
1616
LicensedResourceID: TypeAlias = UUID
1717

18+
LICENSED_ITEM_VERSION_RE = r"^\d+\.\d+\.\d+$"
19+
LicensedItemKey = NewType("LicensedItemKey", str)
20+
LicensedItemVersion = Annotated[
21+
str, StringConstraints(pattern=LICENSED_ITEM_VERSION_RE)
22+
]
23+
1824

1925
class LicensedResourceType(StrAutoEnum):
2026
VIP_MODEL = auto()
@@ -69,26 +75,23 @@ class LicensedItemDB(BaseModel):
6975
licensed_item_id: LicensedItemID
7076
display_name: str
7177

72-
licensed_resource_name: str
78+
key: LicensedItemKey
79+
version: LicensedItemVersion
7380
licensed_resource_type: LicensedResourceType
74-
licensed_resource_data: dict[str, Any] | None
7581

76-
pricing_plan_id: PricingPlanId | None
77-
product_name: ProductName | None
82+
pricing_plan_id: PricingPlanId
83+
product_name: ProductName
7884

7985
# states
8086
created: datetime
8187
modified: datetime
82-
trashed: datetime | None
8388

8489
model_config = ConfigDict(from_attributes=True)
8590

8691

87-
class LicensedItemUpdateDB(BaseModel):
92+
class LicensedItemPatchDB(BaseModel):
8893
display_name: str | None = None
89-
licensed_resource_name: str | None = None
9094
pricing_plan_id: PricingPlanId | None = None
91-
trash: bool | None = None
9295

9396

9497
class LicensedResourceDB(BaseModel):
@@ -115,10 +118,11 @@ class LicensedResourcePatchDB(BaseModel):
115118

116119
class LicensedItem(BaseModel):
117120
licensed_item_id: LicensedItemID
121+
key: LicensedItemKey
122+
version: LicensedItemVersion
118123
display_name: str
119-
licensed_resource_name: str
120124
licensed_resource_type: LicensedResourceType
121-
licensed_resource_data: dict[str, Any]
125+
licensed_resources: list[dict[str, Any]]
122126
pricing_plan_id: PricingPlanId
123127
created_at: datetime
124128
modified_at: datetime
@@ -130,17 +134,20 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
130134
"examples": [
131135
{
132136
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
137+
"key": "Duke",
138+
"version": "1.0.0",
133139
"display_name": "my best model",
134-
"licensed_resource_name": "best-model",
135140
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
136-
"licensed_resource_data": cast(
137-
JsonDict,
138-
{
139-
"category_id": "HumanWholeBody",
140-
"category_display": "Humans",
141-
"source": VIP_DETAILS_EXAMPLE,
142-
},
143-
),
141+
"licensed_resources": [
142+
cast(
143+
JsonDict,
144+
{
145+
"category_id": "HumanWholeBody",
146+
"category_display": "Humans",
147+
"source": VIP_DETAILS_EXAMPLE,
148+
},
149+
)
150+
],
144151
"pricing_plan_id": "15",
145152
"created_at": "2024-12-12 09:59:26.422140",
146153
"modified_at": "2024-12-12 09:59:26.422140",

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

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

66
from pydantic import BaseModel, ConfigDict
77

8-
from .licenses import LicensedItemID
8+
from .licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
99
from .products import ProductName
1010
from .resource_tracker import PricingPlanId, PricingUnitCostId, PricingUnitId
1111
from .users import UserID
@@ -17,6 +17,8 @@
1717
class LicensedItemsPurchasesCreate(BaseModel):
1818
product_name: ProductName
1919
licensed_item_id: LicensedItemID
20+
key: LicensedItemKey
21+
version: LicensedItemVersion
2022
wallet_id: WalletID
2123
wallet_name: str
2224
pricing_plan_id: PricingPlanId

0 commit comments

Comments
 (0)