diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py index e52131de7a32..86c2cee56d60 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py @@ -1,10 +1,11 @@ -from datetime import datetime -from typing import Any, NamedTuple, Self, cast +from datetime import date, datetime +from typing import Literal, NamedTuple, NotRequired, Self, cast from models_library.basic_types import IDStr from models_library.resource_tracker import PricingPlanId from pydantic import BaseModel, ConfigDict, HttpUrl, PositiveInt from pydantic.config import JsonDict +from typing_extensions import TypedDict from ..licenses import ( VIP_DETAILS_EXAMPLE, @@ -20,13 +21,38 @@ # RPC +class LicensedResourceFeaturesDict(TypedDict): + age: NotRequired[str] + date: date + ethnicity: NotRequired[str] + functionality: NotRequired[str] + height: NotRequired[str] + name: NotRequired[str] + sex: NotRequired[str] + species: NotRequired[str] + version: NotRequired[str] + weight: NotRequired[str] + + +class LicensedResource(BaseModel): + id: int + description: str + thumbnail: str + features: LicensedResourceFeaturesDict + doi: str | None + license_key: str + license_version: str + protection: Literal["Code", "PayPal"] + available_from_url: HttpUrl | None + + class LicensedItemRpcGet(BaseModel): licensed_item_id: LicensedItemID key: LicensedItemKey version: LicensedItemVersion display_name: str licensed_resource_type: LicensedResourceType - licensed_resources: list[dict[str, Any]] + licensed_resources: list[LicensedResource] pricing_plan_id: PricingPlanId is_hidden_on_market: bool created_at: datetime diff --git a/services/api-server/openapi.json b/services/api-server/openapi.json index 424a80730f4d..52fd414ba564 100644 --- a/services/api-server/openapi.json +++ b/services/api-server/openapi.json @@ -6705,7 +6705,7 @@ }, "licensed_resources": { "items": { - "type": "object" + "$ref": "#/components/schemas/LicensedResource" }, "type": "array", "title": "Licensed Resources" @@ -6746,6 +6746,129 @@ ], "title": "LicensedItemGet" }, + "LicensedResource": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "description": { + "type": "string", + "title": "Description" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail" + }, + "features": { + "$ref": "#/components/schemas/LicensedResourceFeaturesDict" + }, + "doi": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Doi" + }, + "license_key": { + "type": "string", + "title": "License Key" + }, + "license_version": { + "type": "string", + "title": "License Version" + }, + "protection": { + "type": "string", + "enum": [ + "Code", + "PayPal" + ], + "title": "Protection" + }, + "available_from_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Available From Url" + } + }, + "type": "object", + "required": [ + "id", + "description", + "thumbnail", + "features", + "doi", + "license_key", + "license_version", + "protection", + "available_from_url" + ], + "title": "LicensedResource" + }, + "LicensedResourceFeaturesDict": { + "properties": { + "age": { + "type": "string", + "title": "Age" + }, + "date": { + "type": "string", + "format": "date", + "title": "Date" + }, + "ethnicity": { + "type": "string", + "title": "Ethnicity" + }, + "functionality": { + "type": "string", + "title": "Functionality" + }, + "height": { + "type": "string", + "title": "Height" + }, + "name": { + "type": "string", + "title": "Name" + }, + "sex": { + "type": "string", + "title": "Sex" + }, + "species": { + "type": "string", + "title": "Species" + }, + "version": { + "type": "string", + "title": "Version" + }, + "weight": { + "type": "string", + "title": "Weight" + } + }, + "type": "object", + "required": [ + "date" + ], + "title": "LicensedResourceFeaturesDict" + }, "LicensedResourceType": { "type": "string", "enum": [ diff --git a/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py b/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py index 6c41399f1d44..a653584f07bd 100644 --- a/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py +++ b/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py @@ -1,8 +1,8 @@ # Models added here "cover" models from within the deployment in order to restore backwards compatibility -from datetime import datetime +from datetime import date, datetime from decimal import Decimal -from typing import Annotated, Any +from typing import Annotated, Literal, NotRequired from models_library.api_schemas_api_server.pricing_plans import ( ServicePricingPlanGet as _ServicePricingPlanGet, @@ -10,6 +10,12 @@ from models_library.api_schemas_webserver.licensed_items import ( LicensedItemRpcGet as _LicensedItemGet, ) +from models_library.api_schemas_webserver.licensed_items import ( + LicensedResource as _LicensedResource, +) +from models_library.api_schemas_webserver.licensed_items import ( + LicensedResourceFeaturesDict as _LicensedResourceFeaturesDict, +) from models_library.api_schemas_webserver.licensed_items_checkouts import ( LicensedItemCheckoutRpcGet as _LicensedItemCheckoutRpcGet, ) @@ -46,10 +52,12 @@ BaseModel, ConfigDict, Field, + HttpUrl, NonNegativeFloat, NonNegativeInt, PlainSerializer, ) +from typing_extensions import TypedDict class GetCreditPriceLegacy(BaseModel): @@ -77,7 +85,7 @@ class GetCreditPriceLegacy(BaseModel): ) -assert set(GetCreditPriceLegacy.model_fields.keys()) == set( +assert set(GetCreditPriceLegacy.model_fields.keys()) == set( # nosec _GetCreditPrice.model_fields.keys() ) @@ -97,7 +105,7 @@ class PricingUnitGetLegacy(BaseModel): ) -assert set(PricingUnitGetLegacy.model_fields.keys()) == set( +assert set(PricingUnitGetLegacy.model_fields.keys()) == set( # nosec _PricingUnitGet.model_fields.keys() ) @@ -119,7 +127,7 @@ class WalletGetWithAvailableCreditsLegacy(BaseModel): ) -assert set(WalletGetWithAvailableCreditsLegacy.model_fields.keys()) == set( +assert set(WalletGetWithAvailableCreditsLegacy.model_fields.keys()) == set( # nosec _WalletGetWithAvailableCredits.model_fields.keys() ) @@ -137,18 +145,66 @@ class ServicePricingPlanGetLegacy(BaseModel): ) -assert set(ServicePricingPlanGetLegacy.model_fields.keys()) == set( +assert set(ServicePricingPlanGetLegacy.model_fields.keys()) == set( # nosec _ServicePricingPlanGet.model_fields.keys() ) +class LicensedResourceFeaturesDict(TypedDict): + age: NotRequired[str] + date: date + ethnicity: NotRequired[str] + functionality: NotRequired[str] + height: NotRequired[str] + name: NotRequired[str] + sex: NotRequired[str] + species: NotRequired[str] + version: NotRequired[str] + weight: NotRequired[str] + + +assert set(LicensedResourceFeaturesDict.__annotations__.keys()) == set( # nosec + _LicensedResourceFeaturesDict.__annotations__.keys() +), "LicensedResourceFeaturesDict keys do not match" + +for key in LicensedResourceFeaturesDict.__annotations__: + assert ( # nosec + LicensedResourceFeaturesDict.__annotations__[key] + == _LicensedResourceFeaturesDict.__annotations__[key] + ), f"Type of {key} in LicensedResourceFeaturesDict does not match" + + +class LicensedResource(BaseModel): + id: int + description: str + thumbnail: str + features: LicensedResourceFeaturesDict + doi: str | None + license_key: str + license_version: str + protection: Literal["Code", "PayPal"] + available_from_url: HttpUrl | None + + +assert set(LicensedResource.model_fields.keys()) == set( # nosec + _LicensedResource.model_fields.keys() +), "LicensedResource keys do not match" + +for key in LicensedResource.model_fields.keys(): + if key == "features": + continue + assert ( # nosec + LicensedResource.__annotations__[key] == _LicensedResource.__annotations__[key] + ), f"Type of {key} in LicensedResource does not match" + + class LicensedItemGet(BaseModel): licensed_item_id: LicensedItemID key: LicensedItemKey version: LicensedItemVersion display_name: str licensed_resource_type: LicensedResourceType - licensed_resources: list[dict[str, Any]] + licensed_resources: list[LicensedResource] pricing_plan_id: PricingPlanId is_hidden_on_market: bool created_at: datetime @@ -158,7 +214,7 @@ class LicensedItemGet(BaseModel): ) -assert set(LicensedItemGet.model_fields.keys()) == set( +assert set(LicensedItemGet.model_fields.keys()) == set( # nosec _LicensedItemGet.model_fields.keys() ) @@ -176,6 +232,6 @@ class LicensedItemCheckoutGet(BaseModel): num_of_seats: int -assert set(LicensedItemCheckoutGet.model_fields.keys()) == set( +assert set(LicensedItemCheckoutGet.model_fields.keys()) == set( # nosec _LicensedItemCheckoutRpcGet.model_fields.keys() ) diff --git a/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py b/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py index 1d74960ea766..0cee0a7aef18 100644 --- a/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py +++ b/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py @@ -46,7 +46,11 @@ ) from ..exceptions.service_errors_utils import service_exception_mapper from ..models.pagination import Page, PaginationParams -from ..models.schemas.model_adapter import LicensedItemCheckoutGet, LicensedItemGet +from ..models.schemas.model_adapter import ( + LicensedItemCheckoutGet, + LicensedItemGet, + LicensedResource, +) _exception_mapper = partial(service_exception_mapper, service_name="WebApiServer") @@ -62,7 +66,10 @@ def _create_licensed_items_get_page( version=elm.version, display_name=elm.display_name, licensed_resource_type=elm.licensed_resource_type, - licensed_resources=elm.licensed_resources, + licensed_resources=[ + LicensedResource.model_validate(res.model_dump()) + for res in elm.licensed_resources + ], pricing_plan_id=elm.pricing_plan_id, is_hidden_on_market=elm.is_hidden_on_market, created_at=elm.created_at, diff --git a/services/storage/openapi.json b/services/storage/openapi.json index 79c56f056fa1..f826e6b05261 100644 --- a/services/storage/openapi.json +++ b/services/storage/openapi.json @@ -2443,7 +2443,7 @@ "type": "string", "format": "path", "title": "Display Path", - "description": "the path to display with UUID replaced" + "description": "the path to display with UUID replaced (URL Encoded by parts as names may contain '/')" }, "file_meta_data": { "anyOf": [ diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 42573752b6f5..252a7267012e 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -12389,7 +12389,8 @@ components: type: string format: path title: Display Path - description: the path to display with UUID replaced + description: the path to display with UUID replaced (URL Encoded by parts + as names may contain '/') file_meta_data: anyOf: - $ref: '#/components/schemas/FileMetaDataGet'