Skip to content

Commit

Permalink
♻️ VIP models introduce licensed versioning (🗃️) (#7215)
Browse files Browse the repository at this point in the history
  • Loading branch information
matusdrobuliak66 authored Feb 13, 2025
1 parent 5f92eca commit e35a13f
Show file tree
Hide file tree
Showing 53 changed files with 1,450 additions and 650 deletions.
18 changes: 5 additions & 13 deletions api/specs/web-server/_licensed_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
from typing import Annotated

from _common import as_query
from fastapi import APIRouter, Depends, status
from fastapi import APIRouter, Depends
from models_library.api_schemas_webserver.licensed_items import LicensedItemRestGet
from models_library.generics import Envelope
from models_library.api_schemas_webserver.licensed_items_purchases import (
LicensedItemPurchaseGet,
)
from models_library.rest_error import EnvelopedError
from models_library.rest_pagination import Page
from simcore_service_webserver._meta import API_VTAG
Expand Down Expand Up @@ -46,19 +48,9 @@ async def list_licensed_items(
...


@router.get(
"/catalog/licensed-items/{licensed_item_id}",
response_model=Envelope[LicensedItemRestGet],
)
async def get_licensed_item(
_path: Annotated[LicensedItemsPathParams, Depends()],
):
...


@router.post(
"/catalog/licensed-items/{licensed_item_id}:purchase",
status_code=status.HTTP_204_NO_CONTENT,
response_model=LicensedItemPurchaseGet,
)
async def purchase_licensed_item(
_path: Annotated[LicensedItemsPathParams, Depends()],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import NamedTuple

from models_library.licenses import LicensedItemID
from models_library.licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
from models_library.products import ProductName
from models_library.resource_tracker_licensed_items_checkouts import (
LicensedItemCheckoutID,
Expand All @@ -15,6 +15,8 @@
class LicensedItemCheckoutGet(BaseModel):
licensed_item_checkout_id: LicensedItemCheckoutID
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
wallet_id: WalletID
user_id: UserID
user_email: str
Expand All @@ -30,6 +32,8 @@ class LicensedItemCheckoutGet(BaseModel):
{
"licensed_item_checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
"key": "Duke",
"version": "1.0.0",
"wallet_id": 1,
"user_id": 1,
"user_email": "[email protected]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from decimal import Decimal
from typing import NamedTuple

from models_library.licenses import LicensedItemID
from models_library.licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
from models_library.products import ProductName
from models_library.resource_tracker import PricingUnitCostId
from models_library.resource_tracker_licensed_items_purchases import (
Expand All @@ -17,6 +17,8 @@ class LicensedItemPurchaseGet(BaseModel):
licensed_item_purchase_id: LicensedItemPurchaseID
product_name: ProductName
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
wallet_id: WalletID
wallet_name: str
pricing_unit_cost_id: PricingUnitCostId
Expand All @@ -36,6 +38,8 @@ class LicensedItemPurchaseGet(BaseModel):
"licensed_item_purchase_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
"product_name": "osparc",
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
"key": "Duke",
"version": "1.0.0",
"wallet_id": 1,
"wallet_name": "My Wallet",
"pricing_unit_cost_id": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
FeaturesDict,
LicensedItem,
LicensedItemID,
LicensedItemKey,
LicensedItemVersion,
LicensedResourceType,
)
from ._base import OutputSchema
Expand All @@ -20,9 +22,11 @@

class LicensedItemRpcGet(BaseModel):
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
display_name: str
licensed_resource_type: LicensedResourceType
licensed_resource_data: dict[str, Any]
licensed_resources: list[dict[str, Any]]
pricing_plan_id: PricingPlanId
created_at: datetime
modified_at: datetime
Expand All @@ -32,9 +36,11 @@ class LicensedItemRpcGet(BaseModel):
"examples": [
{
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
"key": "Duke",
"version": "1.0.0",
"display_name": "best-model",
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
"licensed_resource_data": cast(JsonDict, VIP_DETAILS_EXAMPLE),
"licensed_resources": [cast(JsonDict, VIP_DETAILS_EXAMPLE)],
"pricing_plan_id": "15",
"created_at": "2024-12-12 09:59:26.422140",
"modified_at": "2024-12-12 09:59:26.422140",
Expand All @@ -58,24 +64,28 @@ class _ItisVipRestData(OutputSchema):
thumbnail: str
features: FeaturesDict # NOTE: here there is a bit of coupling with domain model
doi: str | None
license_version: str


class _ItisVipResourceRestData(OutputSchema):
category_id: IDStr
category_display: str
category_icon: HttpUrl | None = None # NOTE: Placeholder until provide @odeimaiz
source: _ItisVipRestData
terms_of_use_url: HttpUrl | None = None # NOTE: Placeholder until provided @mguidon


class LicensedItemRestGet(OutputSchema):
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion

display_name: str
# NOTE: to put here a discriminator we have to embed it one more layer
licensed_resource_type: LicensedResourceType
licensed_resource_data: _ItisVipResourceRestData
licensed_resources: list[_ItisVipResourceRestData]
pricing_plan_id: PricingPlanId

category_id: IDStr
category_display: str
category_icon: HttpUrl | None = None # NOTE: Placeholder until provide @odeimaiz
terms_of_use_url: HttpUrl | None = None # NOTE: Placeholder until provided @mguidon

created_at: datetime
modified_at: datetime

Expand All @@ -86,17 +96,21 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
"examples": [
{
"licensedItemId": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
"key": "Duke",
"version": "1.0.0",
"displayName": "my best model",
"licensedResourceType": f"{LicensedResourceType.VIP_MODEL}",
"licensedResourceData": cast(
JsonDict,
{
"categoryId": "HumanWholeBody",
"categoryDisplay": "Humans",
"source": {**VIP_DETAILS_EXAMPLE, "doi": doi},
},
),
"licensedResources": [
cast(
JsonDict,
{
"source": {**VIP_DETAILS_EXAMPLE, "doi": doi},
},
)
],
"pricingPlanId": "15",
"categoryId": "HumanWholeBody",
"categoryDisplay": "Humans",
"createdAt": "2024-12-12 09:59:26.422140",
"modifiedAt": "2024-12-12 09:59:26.422140",
}
Expand All @@ -114,6 +128,8 @@ def from_domain_model(cls, item: LicensedItem) -> Self:
**item.model_dump(
include={
"licensed_item_id",
"key",
"version",
"display_name",
"licensed_resource_type",
"pricing_plan_id",
Expand All @@ -122,9 +138,18 @@ def from_domain_model(cls, item: LicensedItem) -> Self:
},
exclude_unset=True,
),
"licensed_resource_data": {
**item.licensed_resource_data,
},
"licensed_resources": [
_ItisVipResourceRestData(**x)
for x in sorted(
item.licensed_resources,
key=lambda x: datetime.strptime(
x["source"]["features"]["date"], "%Y-%m-%d"
),
reverse=True,
)
],
"category_id": item.licensed_resources[0]["category_id"],
"category_display": item.licensed_resources[0]["category_display"],
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from models_library.emails import LowerCaseEmailStr
from pydantic import BaseModel, ConfigDict, PositiveInt

from ..licenses import LicensedItemID
from ..licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
from ..products import ProductName
from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID
from ..users import UserID
Expand All @@ -17,6 +17,8 @@
class LicensedItemCheckoutRpcGet(BaseModel):
licensed_item_checkout_id: LicensedItemCheckoutID
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
wallet_id: WalletID
user_id: UserID
product_name: ProductName
Expand All @@ -29,6 +31,8 @@ class LicensedItemCheckoutRpcGet(BaseModel):
{
"licensed_item_checkout_id": "633ef980-6f3e-4b1a-989a-bd77bf9a5d6b",
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
"key": "Duke",
"version": "1.0.0",
"wallet_id": 6,
"user_id": 27845,
"product_name": "osparc",
Expand All @@ -52,6 +56,8 @@ class LicensedItemCheckoutRpcGetPage(NamedTuple):
class LicensedItemCheckoutRestGet(OutputSchema):
licensed_item_checkout_id: LicensedItemCheckoutID
licensed_item_id: LicensedItemID
key: str
version: str
wallet_id: WalletID
user_id: UserID
user_email: LowerCaseEmailStr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from models_library.emails import LowerCaseEmailStr
from pydantic import PositiveInt

from ..licenses import LicensedItemID
from ..licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
from ..products import ProductName
from ..resource_tracker import PricingUnitCostId
from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID
Expand All @@ -18,6 +18,8 @@ class LicensedItemPurchaseGet(OutputSchema):
licensed_item_purchase_id: LicensedItemPurchaseID
product_name: ProductName
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
wallet_id: WalletID
pricing_unit_cost_id: PricingUnitCostId
pricing_unit_cost: Decimal
Expand Down
49 changes: 28 additions & 21 deletions packages/models-library/src/models_library/licenses.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from datetime import datetime
from enum import auto
from typing import Any, NamedTuple, NotRequired, TypeAlias, cast
from typing import Annotated, Any, NamedTuple, NewType, NotRequired, TypeAlias, cast
from uuid import UUID

from models_library.resource_tracker import PricingPlanId
from pydantic import BaseModel, ConfigDict, PositiveInt
from pydantic import BaseModel, ConfigDict, PositiveInt, StringConstraints
from pydantic.config import JsonDict
from typing_extensions import TypedDict

Expand All @@ -15,6 +15,12 @@
LicensedItemID: TypeAlias = UUID
LicensedResourceID: TypeAlias = UUID

LICENSED_ITEM_VERSION_RE = r"^\d+\.\d+\.\d+$"
LicensedItemKey = NewType("LicensedItemKey", str)
LicensedItemVersion = Annotated[
str, StringConstraints(pattern=LICENSED_ITEM_VERSION_RE)
]


class LicensedResourceType(StrAutoEnum):
VIP_MODEL = auto()
Expand Down Expand Up @@ -69,26 +75,23 @@ class LicensedItemDB(BaseModel):
licensed_item_id: LicensedItemID
display_name: str

licensed_resource_name: str
key: LicensedItemKey
version: LicensedItemVersion
licensed_resource_type: LicensedResourceType
licensed_resource_data: dict[str, Any] | None

pricing_plan_id: PricingPlanId | None
product_name: ProductName | None
pricing_plan_id: PricingPlanId
product_name: ProductName

# states
created: datetime
modified: datetime
trashed: datetime | None

model_config = ConfigDict(from_attributes=True)


class LicensedItemUpdateDB(BaseModel):
class LicensedItemPatchDB(BaseModel):
display_name: str | None = None
licensed_resource_name: str | None = None
pricing_plan_id: PricingPlanId | None = None
trash: bool | None = None


class LicensedResourceDB(BaseModel):
Expand All @@ -115,10 +118,11 @@ class LicensedResourcePatchDB(BaseModel):

class LicensedItem(BaseModel):
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
display_name: str
licensed_resource_name: str
licensed_resource_type: LicensedResourceType
licensed_resource_data: dict[str, Any]
licensed_resources: list[dict[str, Any]]
pricing_plan_id: PricingPlanId
created_at: datetime
modified_at: datetime
Expand All @@ -130,17 +134,20 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
"examples": [
{
"licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1",
"key": "Duke",
"version": "1.0.0",
"display_name": "my best model",
"licensed_resource_name": "best-model",
"licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}",
"licensed_resource_data": cast(
JsonDict,
{
"category_id": "HumanWholeBody",
"category_display": "Humans",
"source": VIP_DETAILS_EXAMPLE,
},
),
"licensed_resources": [
cast(
JsonDict,
{
"category_id": "HumanWholeBody",
"category_display": "Humans",
"source": VIP_DETAILS_EXAMPLE,
},
)
],
"pricing_plan_id": "15",
"created_at": "2024-12-12 09:59:26.422140",
"modified_at": "2024-12-12 09:59:26.422140",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from pydantic import BaseModel, ConfigDict

from .licenses import LicensedItemID
from .licenses import LicensedItemID, LicensedItemKey, LicensedItemVersion
from .products import ProductName
from .resource_tracker import PricingPlanId, PricingUnitCostId, PricingUnitId
from .users import UserID
Expand All @@ -17,6 +17,8 @@
class LicensedItemsPurchasesCreate(BaseModel):
product_name: ProductName
licensed_item_id: LicensedItemID
key: LicensedItemKey
version: LicensedItemVersion
wallet_id: WalletID
wallet_name: str
pricing_plan_id: PricingPlanId
Expand Down
Loading

0 comments on commit e35a13f

Please sign in to comment.