From 9c815b83339d386792273a4a439211ff690b3901 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 11:31:54 +0100 Subject: [PATCH 01/29] renaming wallets to credit account for frontend --- .../licenses/_exceptions_handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py index d12b95fafa0..26cf9478b5f 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py @@ -22,11 +22,11 @@ ), WalletAccessForbiddenError: HttpErrorInfo( status.HTTP_403_FORBIDDEN, - "Wallet {wallet_id} forbidden.", + "Credit account {wallet_id} forbidden.", ), WalletNotEnoughCreditsError: HttpErrorInfo( status.HTTP_402_PAYMENT_REQUIRED, - "Not enough credits in the wallet.", + "Not enough credits in the credit account.", ), LicensedItemPricingPlanMatchError: HttpErrorInfo( status.HTTP_400_BAD_REQUEST, From 6abf9bd9f7eb7c53c9fec989290df4eb3ea29121 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 11:32:23 +0100 Subject: [PATCH 02/29] fix rut export CSV usage --- .../services/modules/db/service_runs_db.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py index a4ea563803d..9d987a32cfc 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py @@ -427,10 +427,7 @@ async def export_service_runs_table_to_s3( resource_tracker_service_runs.c.stopped_at, resource_tracker_credit_transactions.c.osparc_credits, resource_tracker_credit_transactions.c.transaction_status, - sa.func.coalesce( - _project_tags_subquery.c.project_tags, - sa.cast(sa.text("'{}'"), sa.ARRAY(sa.String)), - ).label("project_tags"), + _project_tags_subquery.c.project_tags.label("project_tags"), ) .select_from( resource_tracker_service_runs.join( From e060492fe427f8e2d7f0a1460392164fa19a6b67 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 12:57:52 +0100 Subject: [PATCH 03/29] implement rut part of checkout/release license --- .../licensed_items_usages.py | 52 +++++ .../resource_tracker_licensed_items_usages.py | 17 ++ .../resource_tracker_licensed_items_usage.py | 2 +- .../exceptions/errors.py | 5 + .../models/licensed_items_usages.py | 43 ++++ .../services/licensed_items_usages.py | 183 ++++++++++++++++ .../modules/db/licensed_items_purchases_db.py | 36 +++ .../modules/db/licensed_items_usages_db.py | 205 ++++++++++++++++++ .../test_api_licensed_items_usages.py | 47 ++++ 9 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py create mode 100644 packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py create mode 100644 services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py new file mode 100644 index 00000000000..0636d567e97 --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py @@ -0,0 +1,52 @@ +from datetime import datetime +from typing import NamedTuple, TypeAlias +from uuid import UUID + +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import BaseModel, ConfigDict, PositiveInt + +LicenseCheckoutID: TypeAlias = UUID + + +class LicensedItemUsageGet(BaseModel): + licensed_item_usage_id: LicensedItemUsageID + licensed_item_id: LicensedItemID + wallet_id: WalletID + user_id: UserID + product_name: ProductName + service_run_id: ServiceRunId + start_at: datetime + stopped_at: datetime | None + num_of_seats: int + + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + { + "licensed_item_usage_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", + "licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953", + "wallet_id": 1, + "user_id": 1, + "product_name": "osparc", + "service_run_id": "run_1", + "start_at": "2023-01-11 13:11:47.293595", + "stopped_at": "2023-01-11 13:11:47.293595", + "num_of_seats": 1, + } + ] + } + ) + + +class LicensedItemsUsagesPage(NamedTuple): + items: list[LicensedItemUsageGet] + total: PositiveInt + + +class LicenseItemCheckoutGet(BaseModel): + checkout_id: LicenseCheckoutID # This is a licensed_item_usage_id generated in the `resource_tracker_licensed_items_usages` table diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py new file mode 100644 index 00000000000..8ab32f5c5a7 --- /dev/null +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py @@ -0,0 +1,17 @@ +from typing import TypeAlias +from uuid import UUID + +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutID, +) +from models_library.products import ProductName +from pydantic import BaseModel, ConfigDict + +LicensedItemUsageID: TypeAlias = UUID + + +class LicenseCheckoutCreate(BaseModel): + checkout_id: LicenseCheckoutID + product_name: ProductName + + model_config = ConfigDict(from_attributes=True) diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py index 27d6afe8250..95cbfee358a 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py @@ -19,7 +19,7 @@ ), sa.Column( "licensed_item_id", - sa.String, + UUID(as_uuid=True), nullable=True, ), sa.Column( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py index 55fde04b0f6..37af6fa72d6 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py @@ -2,6 +2,7 @@ from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, ) +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID class ResourceUsageTrackerBaseError(OsparcErrorMixin, Exception): @@ -75,3 +76,7 @@ class PricingPlanNotFoundForServiceError(RutNotFoundError): class LicensedItemPurchaseNotFoundError(RutNotFoundError): licensed_item_purchase_id: LicensedItemPurchaseID + + +class LicensedItemUsageNotFoundError(RutNotFoundError): + licensed_item_usage_id: LicensedItemUsageID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py new file mode 100644 index 00000000000..d673aa6190f --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py @@ -0,0 +1,43 @@ +from datetime import datetime + +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import BaseModel, ConfigDict + + +class LicensedItemUsageDB(BaseModel): + licensed_item_usage_id: LicensedItemUsageID + licensed_item_id: LicensedItemID + wallet_id: WalletID + user_id: UserID + user_email: str + product_name: ProductName + service_run_id: ServiceRunId + start_at: datetime + stopped_at: datetime | None + num_of_seats: int + + model_config = ConfigDict(from_attributes=True) + + +class CreateLicensedItemUsageDB(BaseModel): + licensed_item_id: LicensedItemID + wallet_id: WalletID + user_id: UserID + user_email: str + product_name: ProductName + service_run_id: ServiceRunId + start_at: datetime + num_of_seats: int + + model_config = ConfigDict(from_attributes=True) + + +class UpdateLicensedItemUsageDB(BaseModel): + stopped_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py new file mode 100644 index 00000000000..98fa2eab304 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -0,0 +1,183 @@ +from datetime import UTC, datetime +from typing import Annotated + +from fastapi import Depends +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicensedItemsUsagesPage, + LicensedItemUsageGet, + LicenseItemCheckoutGet, +) +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId, ServiceRunStatus +from models_library.resource_tracker_licensed_items_usages import ( + LicenseCheckoutCreate, + LicensedItemUsageID, +) +from models_library.rest_ordering import OrderBy +from models_library.users import UserID +from models_library.wallets import WalletID +from sqlalchemy.ext.asyncio import AsyncEngine + +from ..api.rest.dependencies import get_resource_tracker_db_engine +from ..models.licensed_items_usages import ( + CreateLicensedItemUsageDB, + LicensedItemUsageDB, +) +from .modules.db import ( + licensed_items_purchases_db, + licensed_items_usages_db, + service_runs_db, +) + + +async def list_licensed_items_purchases( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + product_name: ProductName, + filter_wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy, +) -> LicensedItemsUsagesPage: + total, licensed_items_usages_list_db = await licensed_items_usages_db.list_( + db_engine, + product_name=product_name, + filter_wallet_id=filter_wallet_id, + offset=offset, + limit=limit, + order_by=order_by, + ) + return LicensedItemsUsagesPage( + total=total, + items=[ + LicensedItemUsageGet( + licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + licensed_item_id=licensed_item_usage_db.licensed_item_id, + wallet_id=licensed_item_usage_db.wallet_id, + user_id=licensed_item_usage_db.user_id, + product_name=licensed_item_usage_db.product_name, + service_run_id=licensed_item_usage_db.service_run_id, + start_at=licensed_item_usage_db.start_at, + stopped_at=licensed_item_usage_db.stopped_at, + num_of_seats=licensed_item_usage_db.num_of_seats, + ) + for licensed_item_usage_db in licensed_items_usages_list_db + ], + ) + + +async def get_licensed_item_usage( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + product_name: ProductName, + licensed_item_usage_id: LicensedItemUsageID, +) -> LicensedItemUsageGet: + licensed_item_usage_db: LicensedItemUsageDB = await licensed_items_usages_db.get( + db_engine, + product_name=product_name, + licensed_item_usage_id=licensed_item_usage_id, + ) + + return LicensedItemUsageGet( + licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + licensed_item_id=licensed_item_usage_db.licensed_item_id, + wallet_id=licensed_item_usage_db.wallet_id, + user_id=licensed_item_usage_db.user_id, + product_name=licensed_item_usage_db.product_name, + service_run_id=licensed_item_usage_db.service_run_id, + start_at=licensed_item_usage_db.start_at, + stopped_at=licensed_item_usage_db.stopped_at, + num_of_seats=licensed_item_usage_db.num_of_seats, + ) + + +async def checkout_licensed_item( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, + num_of_seats: int, + service_run_id: ServiceRunId, + user_id: UserID, + user_email: str, +) -> LicenseItemCheckoutGet: + + _active_purchased_seats: int = await licensed_items_purchases_db.get_active_purchased_seats_for_item_and_wallet( + db_engine, + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + ) + + _currently_used_seats = ( + await licensed_items_usages_db.get_currently_used_seats_for_item_and_wallet( + db_engine, + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + ) + ) + + available_seats = _active_purchased_seats - _currently_used_seats + if available_seats <= 0: + raise ValueError("Not enough available seats") + + if available_seats - num_of_seats < 0: + raise ValueError("Can not checkout num of seats, not enough available") + + # Check if the service run ID is currently running + service_run = await service_runs_db.get_service_run_by_id( + db_engine, service_run_id=service_run_id + ) + if ( + service_run is None + or service_run.service_run_status != ServiceRunStatus.RUNNING + ): + raise ValueError("This should not happen") + + _create_item_usage = CreateLicensedItemUsageDB( + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + user_id=user_id, + user_email=user_email, + product_name=product_name, + service_run_id=service_run_id, + start_at=datetime.now(tz=UTC), + num_of_seats=num_of_seats, + ) + license_item_usage_db = await licensed_items_usages_db.create( + db_engine, data=_create_item_usage + ) + + # Return checkout ID + return LicenseItemCheckoutGet( + checkout_id=license_item_usage_db.licensed_item_usage_id + ) + + +async def release_licensed_item( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + data: LicenseCheckoutCreate, +) -> LicensedItemUsageGet: + + licensed_item_usage_db: LicensedItemUsageDB = await licensed_items_usages_db.update( + db_engine, + licensed_item_usage_id=data.checkout_id, + product_name=data.product_name, + stopped_at=datetime.now(tz=UTC), + ) + + return LicensedItemUsageGet( + licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + licensed_item_id=licensed_item_usage_db.licensed_item_id, + wallet_id=licensed_item_usage_db.wallet_id, + user_id=licensed_item_usage_db.user_id, + product_name=licensed_item_usage_db.product_name, + service_run_id=licensed_item_usage_db.service_run_id, + start_at=licensed_item_usage_db.start_at, + stopped_at=licensed_item_usage_db.stopped_at, + num_of_seats=licensed_item_usage_db.num_of_seats, + ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index e9951042ddc..7137b5b6a2c 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -1,6 +1,8 @@ +from datetime import UTC, datetime from typing import cast import sqlalchemy as sa +from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, @@ -150,3 +152,37 @@ async def get( licensed_item_purchase_id=licensed_item_purchase_id ) return LicensedItemsPurchasesDB.model_validate(row) + + +async def get_active_purchased_seats_for_item_and_wallet( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, +) -> int: + """ + Exclude expired seats + """ + _current_time = datetime.now(tz=UTC) + + sum_stmt = sa.select( + sa.func.sum(resource_tracker_licensed_items_purchases.c.num_of_seats) + ).where( + (resource_tracker_licensed_items_purchases.c.wallet_id == wallet_id) + & ( + resource_tracker_licensed_items_purchases.c.licensed_item_id + == licensed_item_id + ) + & (resource_tracker_licensed_items_purchases.c.product_name == product_name) + & (resource_tracker_licensed_items_purchases.c.start_at <= _current_time) + & (resource_tracker_licensed_items_purchases.c.expire_at >= _current_time) + ) + + async with pass_or_acquire_connection(engine, connection) as conn: + result = await conn.execute(sum_stmt) + row = result.first() + if row is None or row[0] is None: + return 0 + return row[0] diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py new file mode 100644 index 00000000000..fbd65761af1 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py @@ -0,0 +1,205 @@ +from datetime import datetime +from typing import cast + +import sqlalchemy as sa +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) +from models_library.rest_ordering import OrderBy, OrderDirection +from models_library.wallets import WalletID +from pydantic import NonNegativeInt +from simcore_postgres_database.models.resource_tracker_licensed_items_usage import ( + resource_tracker_licensed_items_usage, +) +from simcore_postgres_database.utils_repos import ( + pass_or_acquire_connection, + transaction_context, +) +from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine + +from ....exceptions.errors import LicensedItemUsageNotFoundError +from ....models.licensed_items_usages import ( + CreateLicensedItemUsageDB, + LicensedItemUsageDB, +) + +_SELECTION_ARGS = ( + resource_tracker_licensed_items_usage.c.licensed_item_usage_id, + resource_tracker_licensed_items_usage.c.licensed_item_id, + resource_tracker_licensed_items_usage.c.wallet_id, + resource_tracker_licensed_items_usage.c.user_id, + resource_tracker_licensed_items_usage.c.user_email, + resource_tracker_licensed_items_usage.c.product_name, + resource_tracker_licensed_items_usage.c.service_run_id, + resource_tracker_licensed_items_usage.c.start_at, + resource_tracker_licensed_items_usage.c.stopped_at, + resource_tracker_licensed_items_usage.c.num_of_seats, + resource_tracker_licensed_items_usage.c.modified, +) + +assert set(LicensedItemUsageDB.model_fields) == { + c.name for c in _SELECTION_ARGS +} # nosec + + +async def create( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + data: CreateLicensedItemUsageDB, +) -> LicensedItemUsageDB: + async with transaction_context(engine, connection) as conn: + result = await conn.execute( + resource_tracker_licensed_items_usage.insert() + .values( + licensed_item_id=data.licensed_item_id, + wallet_id=data.wallet_id, + user_id=data.user_id, + user_email=data.user_email, + product_name=data.product_name, + service_run_id=data.service_run_id, + start_at=data.start_at, + stopped_at=None, + num_of_seats=data.num_of_seats, + modified=sa.func.now(), + ) + .returning(*_SELECTION_ARGS) + ) + row = result.first() + return LicensedItemUsageDB.model_validate(row) + + +async def list_( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + product_name: ProductName, + filter_wallet_id: WalletID, + offset: NonNegativeInt, + limit: NonNegativeInt, + order_by: OrderBy, +) -> tuple[int, list[LicensedItemUsageDB]]: + base_query = ( + sa.select(*_SELECTION_ARGS) + .select_from(resource_tracker_licensed_items_usage) + .where( + (resource_tracker_licensed_items_usage.c.product_name == product_name) + & (resource_tracker_licensed_items_usage.c.wallet_id == filter_wallet_id) + ) + ) + + # Select total count from base_query + subquery = base_query.subquery() + count_query = sa.select(sa.func.count()).select_from(subquery) + + # Ordering and pagination + if order_by.direction == OrderDirection.ASC: + list_query = base_query.order_by( + sa.asc(getattr(resource_tracker_licensed_items_usage.c, order_by.field)) + ) + else: + list_query = base_query.order_by( + sa.desc(getattr(resource_tracker_licensed_items_usage.c, order_by.field)) + ) + list_query = list_query.offset(offset).limit(limit) + + async with pass_or_acquire_connection(engine, connection) as conn: + total_count = await conn.scalar(count_query) + + result = await conn.stream(list_query) + items: list[LicensedItemUsageDB] = [ + LicensedItemUsageDB.model_validate(row) async for row in result + ] + + return cast(int, total_count), items + + +async def get( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + licensed_item_usage_id: LicensedItemPurchaseID, + product_name: ProductName, +) -> LicensedItemUsageDB: + base_query = ( + sa.select(*_SELECTION_ARGS) + .select_from(resource_tracker_licensed_items_usage) + .where( + ( + resource_tracker_licensed_items_usage.c.licensed_item_purchase_id + == licensed_item_usage_id + ) + & (resource_tracker_licensed_items_usage.c.product_name == product_name) + ) + ) + + async with pass_or_acquire_connection(engine, connection) as conn: + result = await conn.stream(base_query) + row = await result.first() + if row is None: + raise LicensedItemUsageNotFoundError( + licensed_item_usage_id=licensed_item_usage_id + ) + return LicensedItemUsageDB.model_validate(row) + + +async def update( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + licensed_item_usage_id: LicensedItemPurchaseID, + product_name: ProductName, + stopped_at: datetime, +) -> LicensedItemUsageDB: + update_stmt = ( + resource_tracker_licensed_items_usage.update() + .values( + modified=sa.func.now(), + stopped_at=stopped_at, + ) + .where( + ( + resource_tracker_licensed_items_usage.c.licensed_item_usage_id + == licensed_item_usage_id + ) + & (resource_tracker_licensed_items_usage.c.product_name == product_name) + & (resource_tracker_licensed_items_usage.c.stopped_at.is_(None)) + ) + .returning(sa.literal_column("*")) + ) + + async with transaction_context(engine, connection) as conn: + result = await conn.execute(update_stmt) + row = result.first() + if row is None: + raise LicensedItemUsageNotFoundError( + licensed_item_usage_id=licensed_item_usage_id + ) + return LicensedItemUsageDB.model_validate(row) + + +async def get_currently_used_seats_for_item_and_wallet( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, +) -> int: + sum_stmt = sa.select( + sa.func.sum(resource_tracker_licensed_items_usage.c.num_of_seats) + ).where( + (resource_tracker_licensed_items_usage.c.wallet_id == wallet_id) + & (resource_tracker_licensed_items_usage.c.licensed_item_id == licensed_item_id) + & (resource_tracker_licensed_items_usage.c.product_name == product_name) + & (resource_tracker_licensed_items_usage.c.stopped_at.is_(None)) + ) + + async with pass_or_acquire_connection(engine, connection) as conn: + result = await conn.execute(sum_stmt) + row = result.first() + if row is None or row[0] is None: + return 0 + return row[0] diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py new file mode 100644 index 00000000000..b4495bc9948 --- /dev/null +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py @@ -0,0 +1,47 @@ +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name +# pylint:disable=too-many-arguments + + +import sqlalchemy as sa +from servicelib.rabbitmq import RabbitMQRPCClient + +pytest_simcore_core_services_selection = [ + "postgres", + "rabbit", +] +pytest_simcore_ops_services_selection = [ + "adminer", +] + + +async def test_service_licensed_items_usages( + mocked_redis_server: None, + postgres_db: sa.engine.Engine, + rpc_client: RabbitMQRPCClient, +): + ... + + # List licensed items usages + + # Get licensed items usages + + +async def test_rpc_licensed_items_usages_workflow( + mocked_redis_server: None, + postgres_db: sa.engine.Engine, + rpc_client: RabbitMQRPCClient, +): + ... + + # Can I use the license? + + # Checkout with num of seats + + # Can I use the license? + + # Release num of seats + + +# Add test for heartbeat check! From 200b1db8c2461043b5ce3e01a49fe4c4e7528722 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 13:13:13 +0100 Subject: [PATCH 04/29] implement rut part of checkout/release license --- .../resource_tracker_licensed_items_usages.py | 13 ----- .../api/rpc/_licensed_items_usages.py | 49 +++++++++++++++++++ .../services/licensed_items_usages.py | 13 +++-- 3 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py index 8ab32f5c5a7..87a737aa5ca 100644 --- a/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py @@ -1,17 +1,4 @@ from typing import TypeAlias from uuid import UUID -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( - LicenseCheckoutID, -) -from models_library.products import ProductName -from pydantic import BaseModel, ConfigDict - LicensedItemUsageID: TypeAlias = UUID - - -class LicenseCheckoutCreate(BaseModel): - checkout_id: LicenseCheckoutID - product_name: ProductName - - model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py new file mode 100644 index 00000000000..d261f6082d1 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicensedItemUsageGet, + LicenseItemCheckoutGet, +) +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_usages import LicenseCheckoutID +from models_library.users import UserID +from models_library.wallets import WalletID +from servicelib.rabbitmq import RPCRouter + +from ...services import licensed_items_usages + +router = RPCRouter() + + +@router.expose(reraise_if_error_type=()) +async def checkout_licensed_item( + app: FastAPI, + *, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, + num_of_seats: int, + service_run_id: ServiceRunId, + user_id: UserID, + user_email: str, +) -> LicenseItemCheckoutGet: + return await licensed_items_usages.checkout_licensed_item( + db_engine=app.state.engine, + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + user_id=user_id, + user_email=user_email, + ) + + +@router.expose(reraise_if_error_type=()) +async def release_licensed_item( + app: FastAPI, *, checkout_id: LicenseCheckoutID, product_name: ProductName +) -> LicensedItemUsageGet: + return await licensed_items_usages.release_licensed_item( + db_engine=app.state.engine, checkout_id=checkout_id, product_name=product_name + ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py index 98fa2eab304..8abca701be4 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -3,6 +3,7 @@ from fastapi import Depends from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutID, LicensedItemsUsagesPage, LicensedItemUsageGet, LicenseItemCheckoutGet, @@ -10,10 +11,7 @@ from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId, ServiceRunStatus -from models_library.resource_tracker_licensed_items_usages import ( - LicenseCheckoutCreate, - LicensedItemUsageID, -) +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID @@ -160,13 +158,14 @@ async def checkout_licensed_item( async def release_licensed_item( db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, - data: LicenseCheckoutCreate, + checkout_id: LicenseCheckoutID, + product_name: ProductName, ) -> LicensedItemUsageGet: licensed_item_usage_db: LicensedItemUsageDB = await licensed_items_usages_db.update( db_engine, - licensed_item_usage_id=data.checkout_id, - product_name=data.product_name, + licensed_item_usage_id=checkout_id, + product_name=product_name, stopped_at=datetime.now(tz=UTC), ) From 7406371266706003eac8b1f778e90d3478ed4e0e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 13:45:03 +0100 Subject: [PATCH 05/29] adding tests to rut --- .../licensed_items_purchases.py | 4 +- .../licensed_items_usages.py | 116 ++++++++++++++++++ .../api/rpc/_licensed_items_usages.py | 2 +- .../api/rpc/routes.py | 3 +- .../models/licensed_items_usages.py | 5 +- .../services/licensed_items_usages.py | 4 +- .../modules/db/licensed_items_usages_db.py | 4 +- .../test_api_licensed_items_usages.py | 49 +++++++- .../licenses/_rpc.py | 4 +- 9 files changed, 176 insertions(+), 15 deletions(-) create mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py index a9463271d75..f74ec93760e 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -17,7 +17,7 @@ ) from models_library.rest_ordering import OrderBy from models_library.wallets import WalletID -from pydantic import AnyUrl, NonNegativeInt, TypeAdapter +from pydantic import NonNegativeInt, TypeAdapter from ....logging_utils import log_decorator from ....rabbitmq import RabbitMQRPCClient @@ -76,7 +76,7 @@ async def get_licensed_item_purchase( async def create_licensed_item_purchase( rabbitmq_rpc_client: RabbitMQRPCClient, *, data: LicensedItemsPurchasesCreate ) -> LicensedItemPurchaseGet: - result: AnyUrl = await rabbitmq_rpc_client.request( + result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("create_licensed_item_purchase"), data=data, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py new file mode 100644 index 00000000000..b2619c1f0a9 --- /dev/null +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py @@ -0,0 +1,116 @@ +import logging +from typing import Final + +from models_library.api_schemas_resource_usage_tracker import ( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, +) +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutID, + LicensedItemUsageGet, + LicenseItemCheckoutGet, +) +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.resource_tracker import ServiceRunId +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import NonNegativeInt, TypeAdapter + +from ....logging_utils import log_decorator +from ....rabbitmq import RabbitMQRPCClient + +_logger = logging.getLogger(__name__) + + +_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 30 + +_RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName) + + +# @log_decorator(_logger, level=logging.DEBUG) +# async def get_licensed_items_usages_page( +# rabbitmq_rpc_client: RabbitMQRPCClient, +# *, +# product_name: ProductName, +# wallet_id: WalletID, +# offset: int = 0, +# limit: int = 20, +# order_by: OrderBy = OrderBy(field=IDStr("purchased_at")), +# ) -> LicensedItemsPurchasesPage: +# result = await rabbitmq_rpc_client.request( +# RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, +# _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_purchases_page"), +# product_name=product_name, +# wallet_id=wallet_id, +# limit=limit, +# offset=offset, +# order_by=order_by, +# timeout_s=_DEFAULT_TIMEOUT_S, +# ) +# assert isinstance(result, LicensedItemsPurchasesPage) # nosec +# return result + + +# @log_decorator(_logger, level=logging.DEBUG) +# async def get_licensed_item_usage( +# rabbitmq_rpc_client: RabbitMQRPCClient, +# *, +# product_name: ProductName, +# licensed_item_purchase_id: LicensedItemPurchaseID, +# ) -> LicensedItemPurchaseGet: +# result = await rabbitmq_rpc_client.request( +# RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, +# _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_purchase"), +# product_name=product_name, +# licensed_item_purchase_id=licensed_item_purchase_id, +# timeout_s=_DEFAULT_TIMEOUT_S, +# ) +# assert isinstance(result, LicensedItemPurchaseGet) # nosec +# return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def checkout_licensed_item( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, + num_of_seats: int, + service_run_id: ServiceRunId, + user_id: UserID, + user_email: str, +) -> LicenseItemCheckoutGet: + result: LicenseItemCheckoutGet = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("checkout_licensed_item"), + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + user_id=user_id, + user_email=user_email, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicenseItemCheckoutGet) # nosec + return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def release_licensed_item( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + checkout_id: LicenseCheckoutID, + product_name: ProductName, +) -> LicensedItemUsageGet: + result: LicensedItemUsageGet = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("release_licensed_item"), + checkout_id=checkout_id, + product_name=product_name, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicensedItemUsageGet) # nosec + return result diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py index d261f6082d1..9c11d6e7e8f 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py @@ -1,12 +1,12 @@ from fastapi import FastAPI from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutID, LicensedItemUsageGet, LicenseItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId -from models_library.resource_tracker_licensed_items_usages import LicenseCheckoutID from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq import RPCRouter diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py index f1fd1276161..cb93fb4d9bf 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py @@ -8,7 +8,7 @@ from servicelib.rabbitmq import RPCRouter from ...services.modules.rabbitmq import get_rabbitmq_rpc_server -from . import _licensed_items_purchases, _resource_tracker +from . import _licensed_items_purchases, _licensed_items_usages, _resource_tracker _logger = logging.getLogger(__name__) @@ -16,6 +16,7 @@ ROUTERS: list[RPCRouter] = [ _resource_tracker.router, _licensed_items_purchases.router, + _licensed_items_usages.router, ] diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py index d673aa6190f..d415a5aa8e7 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py @@ -17,9 +17,10 @@ class LicensedItemUsageDB(BaseModel): user_email: str product_name: ProductName service_run_id: ServiceRunId - start_at: datetime + started_at: datetime stopped_at: datetime | None num_of_seats: int + modified: datetime model_config = ConfigDict(from_attributes=True) @@ -31,7 +32,7 @@ class CreateLicensedItemUsageDB(BaseModel): user_email: str product_name: ProductName service_run_id: ServiceRunId - start_at: datetime + started_at: datetime num_of_seats: int model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py index 8abca701be4..afd1e8c2b59 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -142,7 +142,7 @@ async def checkout_licensed_item( user_email=user_email, product_name=product_name, service_run_id=service_run_id, - start_at=datetime.now(tz=UTC), + started_at=datetime.now(tz=UTC), num_of_seats=num_of_seats, ) license_item_usage_db = await licensed_items_usages_db.create( @@ -176,7 +176,7 @@ async def release_licensed_item( user_id=licensed_item_usage_db.user_id, product_name=licensed_item_usage_db.product_name, service_run_id=licensed_item_usage_db.service_run_id, - start_at=licensed_item_usage_db.start_at, + start_at=licensed_item_usage_db.started_at, stopped_at=licensed_item_usage_db.stopped_at, num_of_seats=licensed_item_usage_db.num_of_seats, ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py index fbd65761af1..9095e2b28b9 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py @@ -33,7 +33,7 @@ resource_tracker_licensed_items_usage.c.user_email, resource_tracker_licensed_items_usage.c.product_name, resource_tracker_licensed_items_usage.c.service_run_id, - resource_tracker_licensed_items_usage.c.start_at, + resource_tracker_licensed_items_usage.c.started_at, resource_tracker_licensed_items_usage.c.stopped_at, resource_tracker_licensed_items_usage.c.num_of_seats, resource_tracker_licensed_items_usage.c.modified, @@ -60,7 +60,7 @@ async def create( user_email=data.user_email, product_name=data.product_name, service_run_id=data.service_run_id, - start_at=data.start_at, + started_at=data.started_at, stopped_at=None, num_of_seats=data.num_of_seats, modified=sa.func.now(), diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py index b4495bc9948..69af9fd5176 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py @@ -4,8 +4,18 @@ # pylint:disable=too-many-arguments +from datetime import UTC, datetime, timedelta +from decimal import Decimal + import sqlalchemy as sa +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemsPurchasesCreate, +) from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_purchases, + licensed_items_usages, +) pytest_simcore_core_services_selection = [ "postgres", @@ -33,15 +43,46 @@ async def test_rpc_licensed_items_usages_workflow( postgres_db: sa.engine.Engine, rpc_client: RabbitMQRPCClient, ): - ... + # Purchase license item + _create_data = LicensedItemsPurchasesCreate( + product_name="osparc", + licensed_item_id="beb16d18-d57d-44aa-a638-9727fa4a72ef", + wallet_id=1, + wallet_name="My Wallet", + pricing_plan_id=1, + pricing_unit_id=1, + pricing_unit_cost_id=1, + pricing_unit_cost=Decimal(10), + start_at=datetime.now(tz=UTC), + expire_at=datetime.now(tz=UTC) + timedelta(days=1), + num_of_seats=5, + purchased_by_user=1, + user_email="test@test.com", + purchased_at=datetime.now(tz=UTC), + ) - # Can I use the license? + created_item = await licensed_items_purchases.create_licensed_item_purchase( + rpc_client, data=_create_data + ) # Checkout with num of seats - - # Can I use the license? + checkout = await licensed_items_usages.checkout_licensed_item( + rpc_client, + licensed_item_id=created_item.licensed_item_id, + wallet_id=1, + product_name="osparc", + num_of_seats=3, + service_run_id="run_1", + user_id=1, + user_email="test@test.com", + ) # Release num of seats + license_item_usage = await licensed_items_usages.release_licensed_item( + rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" + ) + assert license_item_usage +# TODO: MD # Add test for heartbeat check! diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index fede0759b0d..5744109dff7 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -43,7 +43,9 @@ async def get_licensed_items_for_wallet( user_id: UserID, product_name: ProductName, wallet_id: WalletID, -) -> None: + offset: int, + limit: int, +) -> LicensedItemGetPage: raise NotImplementedError From c4e56df59788c6aaa8c86f17ccceeeea06888993 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Fri, 13 Dec 2024 16:14:03 +0100 Subject: [PATCH 06/29] checkout release functionality --- .../licensed_items_usages.py | 4 +- .../3720518f82a7_modify_licenses_db.py | 28 +++++++++++ .../services/licensed_items_usages.py | 6 +-- .../test_api_licensed_items_usages.py | 48 +++++++++++++++---- .../licenses/_licensed_items_purchases_api.py | 1 + 5 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py index 0636d567e97..b943f8be87f 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py @@ -20,7 +20,7 @@ class LicensedItemUsageGet(BaseModel): user_id: UserID product_name: ProductName service_run_id: ServiceRunId - start_at: datetime + started_at: datetime stopped_at: datetime | None num_of_seats: int @@ -34,7 +34,7 @@ class LicensedItemUsageGet(BaseModel): "user_id": 1, "product_name": "osparc", "service_run_id": "run_1", - "start_at": "2023-01-11 13:11:47.293595", + "started_at": "2023-01-11 13:11:47.293595", "stopped_at": "2023-01-11 13:11:47.293595", "num_of_seats": 1, } diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py new file mode 100644 index 00000000000..5b4ca0b1436 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py @@ -0,0 +1,28 @@ +"""modify licenses DB + +Revision ID: 3720518f82a7 +Revises: 77ac824a77ff +Create Date: 2024-12-13 12:46:38.302027+00:00 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "3720518f82a7" +down_revision = "77ac824a77ff" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column("resource_tracker_licensed_items_usage", "licensed_item_id") + op.add_column( + "resource_tracker_licensed_items_usage", + sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=False), + ) + + +def downgrade(): + ... diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py index afd1e8c2b59..4f083e79b1a 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -56,7 +56,7 @@ async def list_licensed_items_purchases( user_id=licensed_item_usage_db.user_id, product_name=licensed_item_usage_db.product_name, service_run_id=licensed_item_usage_db.service_run_id, - start_at=licensed_item_usage_db.start_at, + started_at=licensed_item_usage_db.started_at, stopped_at=licensed_item_usage_db.stopped_at, num_of_seats=licensed_item_usage_db.num_of_seats, ) @@ -84,7 +84,7 @@ async def get_licensed_item_usage( user_id=licensed_item_usage_db.user_id, product_name=licensed_item_usage_db.product_name, service_run_id=licensed_item_usage_db.service_run_id, - start_at=licensed_item_usage_db.start_at, + started_at=licensed_item_usage_db.started_at, stopped_at=licensed_item_usage_db.stopped_at, num_of_seats=licensed_item_usage_db.num_of_seats, ) @@ -176,7 +176,7 @@ async def release_licensed_item( user_id=licensed_item_usage_db.user_id, product_name=licensed_item_usage_db.product_name, service_run_id=licensed_item_usage_db.service_run_id, - start_at=licensed_item_usage_db.started_at, + started_at=licensed_item_usage_db.started_at, stopped_at=licensed_item_usage_db.stopped_at, num_of_seats=licensed_item_usage_db.num_of_seats, ) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py index 69af9fd5176..22fad44b72c 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py @@ -6,7 +6,9 @@ from datetime import UTC, datetime, timedelta from decimal import Decimal +from typing import Generator +import pytest import sqlalchemy as sa from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemsPurchasesCreate, @@ -16,6 +18,9 @@ licensed_items_purchases, licensed_items_usages, ) +from simcore_postgres_database.models.resource_tracker_service_runs import ( + resource_tracker_service_runs, +) pytest_simcore_core_services_selection = [ "postgres", @@ -38,16 +43,43 @@ async def test_service_licensed_items_usages( # Get licensed items usages +_USER_ID_1 = 1 +_WALLET_ID = 6 + + +@pytest.fixture() +def resource_tracker_service_run_id( + postgres_db: sa.engine.Engine, random_resource_tracker_service_run +) -> Generator[str, None, None]: + with postgres_db.connect() as con: + result = con.execute( + resource_tracker_service_runs.insert() + .values( + **random_resource_tracker_service_run( + user_id=_USER_ID_1, wallet_id=_WALLET_ID + ) + ) + .returning(resource_tracker_service_runs.c.service_run_id) + ) + row = result.first() + assert row + + yield row[0] + + con.execute(resource_tracker_service_runs.delete()) + + async def test_rpc_licensed_items_usages_workflow( mocked_redis_server: None, postgres_db: sa.engine.Engine, + resource_tracker_service_run_id: str, rpc_client: RabbitMQRPCClient, ): # Purchase license item _create_data = LicensedItemsPurchasesCreate( product_name="osparc", licensed_item_id="beb16d18-d57d-44aa-a638-9727fa4a72ef", - wallet_id=1, + wallet_id=_WALLET_ID, wallet_name="My Wallet", pricing_plan_id=1, pricing_unit_id=1, @@ -56,11 +88,10 @@ async def test_rpc_licensed_items_usages_workflow( start_at=datetime.now(tz=UTC), expire_at=datetime.now(tz=UTC) + timedelta(days=1), num_of_seats=5, - purchased_by_user=1, + purchased_by_user=_USER_ID_1, user_email="test@test.com", purchased_at=datetime.now(tz=UTC), ) - created_item = await licensed_items_purchases.create_licensed_item_purchase( rpc_client, data=_create_data ) @@ -69,11 +100,11 @@ async def test_rpc_licensed_items_usages_workflow( checkout = await licensed_items_usages.checkout_licensed_item( rpc_client, licensed_item_id=created_item.licensed_item_id, - wallet_id=1, + wallet_id=_WALLET_ID, product_name="osparc", num_of_seats=3, - service_run_id="run_1", - user_id=1, + service_run_id=resource_tracker_service_run_id, + user_id=_USER_ID_1, user_email="test@test.com", ) @@ -82,7 +113,4 @@ async def test_rpc_licensed_items_usages_workflow( rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" ) assert license_item_usage - - -# TODO: MD -# Add test for heartbeat check! + assert isinstance(license_item_usage.stopped_at, datetime) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py index 4aae82ae768..b42d593bed1 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py @@ -26,6 +26,7 @@ async def list_licensed_items_purchases( app: web.Application, + *, product_name: ProductName, user_id: UserID, wallet_id: WalletID, From 504d2c819637dc52f7c5c5b09ab83b9d30ea112d Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Mon, 16 Dec 2024 09:56:51 +0100 Subject: [PATCH 07/29] webserver functionality --- .../licensed_items_usages.py | 2 +- .../licensed_items_purchases.py | 14 ++- .../licensed_items_usages.py | 32 +++++++ .../licensed_items_usages.py | 8 +- .../api/rpc/_licensed_items_usages.py | 4 +- .../services/licensed_items_usages.py | 8 +- .../licenses/_licensed_items_usages_api.py | 94 +++++++++++++++++++ 7 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py create mode 100644 services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py index b943f8be87f..653baf8d17c 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py @@ -48,5 +48,5 @@ class LicensedItemsUsagesPage(NamedTuple): total: PositiveInt -class LicenseItemCheckoutGet(BaseModel): +class LicenseCheckoutGet(BaseModel): checkout_id: LicenseCheckoutID # This is a licensed_item_usage_id generated in the `resource_tracker_licensed_items_usages` table diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py index 0264e713256..2f413f3d10f 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py @@ -2,16 +2,14 @@ from decimal import Decimal from typing import NamedTuple -from models_library.licensed_items import LicensedItemID -from models_library.products import ProductName -from models_library.resource_tracker import PricingUnitCostId -from models_library.resource_tracker_licensed_items_purchases import ( - LicensedItemPurchaseID, -) -from models_library.users import UserID -from models_library.wallets import WalletID from pydantic import PositiveInt +from ..licensed_items import LicensedItemID +from ..products import ProductName +from ..resource_tracker import PricingUnitCostId +from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID +from ..users import UserID +from ..wallets import WalletID from ._base import OutputSchema diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py new file mode 100644 index 00000000000..e5799108ca8 --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py @@ -0,0 +1,32 @@ +from datetime import datetime +from typing import NamedTuple + +from pydantic import BaseModel, PositiveInt + +from ..api_schemas_resource_usage_tracker.licensed_items_usages import LicenseCheckoutID +from ..licensed_items import LicensedItemID +from ..products import ProductName +from ..resource_tracker_licensed_items_usages import LicensedItemUsageID +from ..users import UserID +from ..wallets import WalletID +from ._base import OutputSchema + + +class LicensedItemUsageGet(OutputSchema): + licensed_item_usage_id: LicensedItemUsageID + licensed_item_id: LicensedItemID + wallet_id: WalletID + user_id: UserID + product_name: ProductName + started_at: datetime + stopped_at: datetime | None + num_of_seats: int + + +class LicensedItemUsageGetPage(NamedTuple): + items: list[LicensedItemUsageGet] + total: PositiveInt + + +class LicenseCheckoutGet(BaseModel): + checkout_id: LicenseCheckoutID diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py index b2619c1f0a9..b40c3619b6e 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py @@ -5,9 +5,9 @@ RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, ) from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutGet, LicenseCheckoutID, LicensedItemUsageGet, - LicenseItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -81,8 +81,8 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseItemCheckoutGet: - result: LicenseItemCheckoutGet = await rabbitmq_rpc_client.request( +) -> LicenseCheckoutGet: + result: LicenseCheckoutGet = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("checkout_licensed_item"), licensed_item_id=licensed_item_id, @@ -94,7 +94,7 @@ async def checkout_licensed_item( user_email=user_email, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, LicenseItemCheckoutGet) # nosec + assert isinstance(result, LicenseCheckoutGet) # nosec return result diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py index 9c11d6e7e8f..cea97188fd3 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py @@ -1,8 +1,8 @@ from fastapi import FastAPI from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutGet, LicenseCheckoutID, LicensedItemUsageGet, - LicenseItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -27,7 +27,7 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseItemCheckoutGet: +) -> LicenseCheckoutGet: return await licensed_items_usages.checkout_licensed_item( db_engine=app.state.engine, licensed_item_id=licensed_item_id, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py index 4f083e79b1a..fa09ffe8a9c 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -3,10 +3,10 @@ from fastapi import Depends from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutGet, LicenseCheckoutID, LicensedItemsUsagesPage, LicensedItemUsageGet, - LicenseItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -100,7 +100,7 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseItemCheckoutGet: +) -> LicenseCheckoutGet: _active_purchased_seats: int = await licensed_items_purchases_db.get_active_purchased_seats_for_item_and_wallet( db_engine, @@ -150,9 +150,7 @@ async def checkout_licensed_item( ) # Return checkout ID - return LicenseItemCheckoutGet( - checkout_id=license_item_usage_db.licensed_item_usage_id - ) + return LicenseCheckoutGet(checkout_id=license_item_usage_db.licensed_item_usage_id) async def release_licensed_item( diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py new file mode 100644 index 00000000000..3c233998f85 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py @@ -0,0 +1,94 @@ +from aiohttp import web +from models_library.api_schemas_resource_usage_tracker import ( + licensed_items_usages as rut_licensed_items_usages, +) +from models_library.api_schemas_webserver import ( + licensed_items_usages as webserver_licensed_items_usages, +) +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId +from models_library.users import UserID +from models_library.wallets import WalletID +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_usages, +) + +from ..rabbitmq import get_rabbitmq_rpc_client +from ..wallets.api import get_wallet_by_user + + +async def checkout_licensed_item_for_wallet( + app: web.Application, + licensed_item_id: LicensedItemID, + wallet_id: WalletID, + product_name: ProductName, + num_of_seats: int, + service_run_id: ServiceRunId, + user_id: UserID, + user_email: str, +) -> webserver_licensed_items_usages.LicenseCheckoutGet: + # Check whether user has access to the wallet + await get_wallet_by_user( + app, + user_id=user_id, + wallet_id=wallet_id, + product_name=product_name, + ) + + rpc_client = get_rabbitmq_rpc_client(app) + license_checkout_get: rut_licensed_items_usages.LicenseCheckoutGet = ( + await licensed_items_usages.checkout_licensed_item( + rpc_client, + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + user_id=user_id, + user_email=user_email, + ) + ) + + return webserver_licensed_items_usages.LicenseCheckoutGet( + checkout_id=license_checkout_get.checkout_id + ) + + +async def release_licensed_item_for_wallet( + app: web.Application, + product_name: ProductName, + user_id: UserID, + checkout_id: rut_licensed_items_usages.LicenseCheckoutID, +) -> webserver_licensed_items_usages.LicensedItemUsageGet: + rpc_client = get_rabbitmq_rpc_client(app) + licensed_item_get: rut_licensed_items_usages.LicensedItemUsageGet = ( + await licensed_items_usages.release_licensed_item( + rpc_client, + product_name=product_name, + checkout_id=checkout_id, + ) + ) + + # Check whether user has access to the wallet + await get_wallet_by_user( + app, + user_id=user_id, + wallet_id=licensed_item_get.wallet_id, + product_name=product_name, + ) + + return webserver_licensed_items_usages.LicensedItemUsageGet( + licensed_item_purchase_id=licensed_item_get.licensed_item_purchase_id, + product_name=licensed_item_get.product_name, + licensed_item_id=licensed_item_get.licensed_item_id, + wallet_id=licensed_item_get.wallet_id, + pricing_unit_cost_id=licensed_item_get.pricing_unit_cost_id, + pricing_unit_cost=licensed_item_get.pricing_unit_cost, + start_at=licensed_item_get.start_at, + expire_at=licensed_item_get.expire_at, + num_of_seats=licensed_item_get.num_of_seats, + purchased_by_user=licensed_item_get.purchased_by_user, + purchased_at=licensed_item_get.purchased_at, + modified_at=licensed_item_get.modified, + ) From 68068fb4def165d64e8136be5533ff4435f61db4 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Mon, 16 Dec 2024 14:43:30 +0100 Subject: [PATCH 08/29] improve playwright e2e test --- .../tests/platform_CI_tests/test_platform.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index edcac0fca64..791ee2edb30 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -6,8 +6,10 @@ # pylint: disable=no-name-in-module from pathlib import Path +from typing import Iterable import pytest +from playwright.sync_api import BrowserContext @pytest.fixture(scope="session") @@ -15,13 +17,29 @@ def store_browser_context() -> bool: return True +@pytest.fixture +def results_path(request): + """ + Fixture to retrieve the path to the test's results directory. + """ + # Check if `results_dir` is available in the current test's user properties + results_dir = dict(request.node.user_properties).get("results_dir") + if not results_dir: + results_dir = "test-results" # Default results directory + test_name = request.node.name + test_dir = Path(results_dir) / test_name + test_dir.mkdir(parents=True, exist_ok=True) # Ensure the test directory exists + return test_dir + + @pytest.fixture def logged_in_context( playwright, store_browser_context: bool, request: pytest.FixtureRequest, pytestconfig, -): + results_path: Path, +) -> Iterable[BrowserContext]: is_headed = "--headed" in pytestconfig.invocation_params.args file_path = Path("state.json") @@ -30,7 +48,14 @@ def logged_in_context( browser = playwright.chromium.launch(headless=not is_headed) context = browser.new_context(storage_state="state.json") + test_name = request.node.name + context.tracing.start( + title=f"Trace for Browser 2 in test {test_name}", + snapshots=True, + screenshots=True, + ) yield context + context.tracing.stop(path=f"{results_path}/second_browser_trace.zip") context.close() browser.close() @@ -80,7 +105,10 @@ def test_simple_workspace_workflow( and response.request.method == "POST" ) as response_info: page.get_by_test_id("newWorkspaceButton").click() + page.wait_for_timeout(500) page.get_by_test_id("workspaceEditorSave").click() + page.wait_for_timeout(500) + _workspace_id = response_info.value.json()["data"]["workspaceId"] page.get_by_test_id(f"workspaceItem_{_workspace_id}").click() page.get_by_test_id("workspacesAndFoldersTreeItem_null_null").click() From 3f30f05d6a2cd0a7f588b26d4ab309c0c42195e9 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 11:11:35 +0100 Subject: [PATCH 09/29] add get and list license usages --- .../licensed_items_purchases.py | 8 +- .../licensed_items_usages.py | 86 +++++++++++-------- .../api/rpc/_licensed_items_usages.py | 37 ++++++++ .../services/licensed_items_usages.py | 6 +- .../modules/db/licensed_items_usages_db.py | 2 +- .../test_api_licensed_items_usages.py | 47 +++++++--- .../licenses/_licensed_items_usages_api.py | 33 +++---- 7 files changed, 147 insertions(+), 72 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py index f74ec93760e..125dbe655a0 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -38,8 +38,14 @@ async def get_licensed_items_purchases_page( wallet_id: WalletID, offset: int = 0, limit: int = 20, - order_by: OrderBy = OrderBy(field=IDStr("purchased_at")), + order_by: OrderBy | None = None, ) -> LicensedItemsPurchasesPage: + """ + Default order_by field is "purchased_at" + """ + if order_by is None: + order_by = OrderBy(field=IDStr("purchased_at")) + result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_purchases_page"), diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py index b40c3619b6e..cb0e28902c4 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py @@ -7,12 +7,16 @@ from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( LicenseCheckoutGet, LicenseCheckoutID, + LicensedItemsUsagesPage, LicensedItemUsageGet, ) +from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID from pydantic import NonNegativeInt, TypeAdapter @@ -28,46 +32,52 @@ _RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName) -# @log_decorator(_logger, level=logging.DEBUG) -# async def get_licensed_items_usages_page( -# rabbitmq_rpc_client: RabbitMQRPCClient, -# *, -# product_name: ProductName, -# wallet_id: WalletID, -# offset: int = 0, -# limit: int = 20, -# order_by: OrderBy = OrderBy(field=IDStr("purchased_at")), -# ) -> LicensedItemsPurchasesPage: -# result = await rabbitmq_rpc_client.request( -# RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, -# _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_purchases_page"), -# product_name=product_name, -# wallet_id=wallet_id, -# limit=limit, -# offset=offset, -# order_by=order_by, -# timeout_s=_DEFAULT_TIMEOUT_S, -# ) -# assert isinstance(result, LicensedItemsPurchasesPage) # nosec -# return result +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_item_usage( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + product_name: ProductName, + licensed_item_usage_id: LicensedItemUsageID, +) -> LicensedItemUsageGet: + result = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_usage"), + product_name=product_name, + licensed_item_usage_id=licensed_item_usage_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicensedItemUsageGet) # nosec + return result -# @log_decorator(_logger, level=logging.DEBUG) -# async def get_licensed_item_usage( -# rabbitmq_rpc_client: RabbitMQRPCClient, -# *, -# product_name: ProductName, -# licensed_item_purchase_id: LicensedItemPurchaseID, -# ) -> LicensedItemPurchaseGet: -# result = await rabbitmq_rpc_client.request( -# RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, -# _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_purchase"), -# product_name=product_name, -# licensed_item_purchase_id=licensed_item_purchase_id, -# timeout_s=_DEFAULT_TIMEOUT_S, -# ) -# assert isinstance(result, LicensedItemPurchaseGet) # nosec -# return result +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_items_usages_page( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + product_name: ProductName, + filter_wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy | None = None, +) -> LicensedItemsUsagesPage: + """ + Default order_by field is "started_at" + """ + if order_by is None: + order_by = OrderBy(field=IDStr("started_at")) + + result = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_usages_page"), + product_name=product_name, + filter_wallet_id=filter_wallet_id, + limit=limit, + offset=offset, + order_by=order_by, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicensedItemsUsagesPage) # nosec + return result @log_decorator(_logger, level=logging.DEBUG) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py index cea97188fd3..a8a1909435a 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py @@ -2,11 +2,14 @@ from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( LicenseCheckoutGet, LicenseCheckoutID, + LicensedItemsUsagesPage, LicensedItemUsageGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq import RPCRouter @@ -16,6 +19,40 @@ router = RPCRouter() +@router.expose(reraise_if_error_type=()) +async def get_licensed_item_usage( + app: FastAPI, + *, + product_name: ProductName, + licensed_item_usage_id: LicensedItemUsageID, +) -> LicensedItemUsageGet: + return await licensed_items_usages.get_licensed_item_usage( + db_engine=app.state.engine, + product_name=product_name, + licensed_item_usage_id=licensed_item_usage_id, + ) + + +@router.expose(reraise_if_error_type=()) +async def get_licensed_items_usages_page( + app: FastAPI, + *, + product_name: ProductName, + filter_wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy, +) -> LicensedItemsUsagesPage: + return await licensed_items_usages.list_licensed_items_usages( + db_engine=app.state.engine, + product_name=product_name, + filter_wallet_id=filter_wallet_id, + offset=offset, + limit=limit, + order_by=order_by, + ) + + @router.expose(reraise_if_error_type=()) async def checkout_licensed_item( app: FastAPI, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py index fa09ffe8a9c..86ceee9a5d7 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py @@ -29,13 +29,13 @@ ) -async def list_licensed_items_purchases( +async def list_licensed_items_usages( db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, product_name: ProductName, filter_wallet_id: WalletID, - offset: int = 0, - limit: int = 20, + offset: int, + limit: int, order_by: OrderBy, ) -> LicensedItemsUsagesPage: total, licensed_items_usages_list_db = await licensed_items_usages_db.list_( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py index 9095e2b28b9..978ba1ee984 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py @@ -128,7 +128,7 @@ async def get( .select_from(resource_tracker_licensed_items_usage) .where( ( - resource_tracker_licensed_items_usage.c.licensed_item_purchase_id + resource_tracker_licensed_items_usage.c.licensed_item_usage_id == licensed_item_usage_id ) & (resource_tracker_licensed_items_usage.c.product_name == product_name) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py index 22fad44b72c..12e90fd4931 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py @@ -10,6 +10,10 @@ import pytest import sqlalchemy as sa +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicensedItemsUsagesPage, + LicensedItemUsageGet, +) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemsPurchasesCreate, ) @@ -18,6 +22,9 @@ licensed_items_purchases, licensed_items_usages, ) +from simcore_postgres_database.models.resource_tracker_licensed_items_usage import ( + resource_tracker_licensed_items_usage, +) from simcore_postgres_database.models.resource_tracker_service_runs import ( resource_tracker_service_runs, ) @@ -31,18 +38,6 @@ ] -async def test_service_licensed_items_usages( - mocked_redis_server: None, - postgres_db: sa.engine.Engine, - rpc_client: RabbitMQRPCClient, -): - ... - - # List licensed items usages - - # Get licensed items usages - - _USER_ID_1 = 1 _WALLET_ID = 6 @@ -66,15 +61,24 @@ def resource_tracker_service_run_id( yield row[0] + con.execute(resource_tracker_licensed_items_usage.delete()) con.execute(resource_tracker_service_runs.delete()) async def test_rpc_licensed_items_usages_workflow( mocked_redis_server: None, - postgres_db: sa.engine.Engine, resource_tracker_service_run_id: str, rpc_client: RabbitMQRPCClient, ): + # List licensed items usages + output = await licensed_items_usages.get_licensed_items_usages_page( + rpc_client, + product_name="osparc", + filter_wallet_id=_WALLET_ID, + ) + assert output.total == 0 + assert output.items == [] + # Purchase license item _create_data = LicensedItemsPurchasesCreate( product_name="osparc", @@ -108,6 +112,23 @@ async def test_rpc_licensed_items_usages_workflow( user_email="test@test.com", ) + # List licensed items usages + output = await licensed_items_usages.get_licensed_items_usages_page( + rpc_client, + product_name="osparc", + filter_wallet_id=_WALLET_ID, + ) + assert output.total == 1 + assert isinstance(output, LicensedItemsUsagesPage) + + # Get licensed items usages + output = await licensed_items_usages.get_licensed_item_usage( + rpc_client, + product_name="osparc", + licensed_item_usage_id=output.items[0].licensed_item_usage_id, + ) + assert isinstance(output, LicensedItemUsageGet) + # Release num of seats license_item_usage = await licensed_items_usages.release_licensed_item( rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py index 3c233998f85..209fcef97ed 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py @@ -62,33 +62,34 @@ async def release_licensed_item_for_wallet( checkout_id: rut_licensed_items_usages.LicenseCheckoutID, ) -> webserver_licensed_items_usages.LicensedItemUsageGet: rpc_client = get_rabbitmq_rpc_client(app) - licensed_item_get: rut_licensed_items_usages.LicensedItemUsageGet = ( - await licensed_items_usages.release_licensed_item( - rpc_client, - product_name=product_name, - checkout_id=checkout_id, - ) + # Get + checkout_item = await licensed_items_usages.get_licensed_item_usage( + rpc_client, product_name=product_name, licensed_item_usage_id=checkout_id ) # Check whether user has access to the wallet await get_wallet_by_user( app, user_id=user_id, - wallet_id=licensed_item_get.wallet_id, + wallet_id=checkout_item.wallet_id, product_name=product_name, ) + licensed_item_get: rut_licensed_items_usages.LicensedItemUsageGet = ( + await licensed_items_usages.release_licensed_item( + rpc_client, + product_name=product_name, + checkout_id=checkout_id, + ) + ) + return webserver_licensed_items_usages.LicensedItemUsageGet( - licensed_item_purchase_id=licensed_item_get.licensed_item_purchase_id, - product_name=licensed_item_get.product_name, + licensed_item_usage_id=licensed_item_get.licensed_item_usage_id, licensed_item_id=licensed_item_get.licensed_item_id, wallet_id=licensed_item_get.wallet_id, - pricing_unit_cost_id=licensed_item_get.pricing_unit_cost_id, - pricing_unit_cost=licensed_item_get.pricing_unit_cost, - start_at=licensed_item_get.start_at, - expire_at=licensed_item_get.expire_at, + user_id=licensed_item_get.user_id, + product_name=licensed_item_get.product_name, + started_at=licensed_item_get.started_at, + stopped_at=licensed_item_get.stopped_at, num_of_seats=licensed_item_get.num_of_seats, - purchased_by_user=licensed_item_get.purchased_by_user, - purchased_at=licensed_item_get.purchased_at, - modified_at=licensed_item_get.modified, ) From c0d5c8efe1ae7c3fcfbc8f24a86288788b5bca47 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 13:22:13 +0100 Subject: [PATCH 10/29] wire checkout and release in webserver --- ...ages_api.py => _licensed_checkouts_api.py} | 8 +++-- .../licenses/_rpc.py | 34 +++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) rename services/web/server/src/simcore_service_webserver/licenses/{_licensed_items_usages_api.py => _licensed_checkouts_api.py} (96%) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py similarity index 96% rename from services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py rename to services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index 209fcef97ed..6d7a574b2a8 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_usages_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -15,6 +15,7 @@ ) from ..rabbitmq import get_rabbitmq_rpc_client +from ..users.api import get_user from ..wallets.api import get_wallet_by_user @@ -26,7 +27,6 @@ async def checkout_licensed_item_for_wallet( num_of_seats: int, service_run_id: ServiceRunId, user_id: UserID, - user_email: str, ) -> webserver_licensed_items_usages.LicenseCheckoutGet: # Check whether user has access to the wallet await get_wallet_by_user( @@ -36,6 +36,8 @@ async def checkout_licensed_item_for_wallet( product_name=product_name, ) + user = await get_user(app, user_id=user_id) + rpc_client = get_rabbitmq_rpc_client(app) license_checkout_get: rut_licensed_items_usages.LicenseCheckoutGet = ( await licensed_items_usages.checkout_licensed_item( @@ -46,7 +48,7 @@ async def checkout_licensed_item_for_wallet( num_of_seats=num_of_seats, service_run_id=service_run_id, user_id=user_id, - user_email=user_email, + user_email=user["email"], ) ) @@ -62,7 +64,7 @@ async def release_licensed_item_for_wallet( checkout_id: rut_licensed_items_usages.LicenseCheckoutID, ) -> webserver_licensed_items_usages.LicensedItemUsageGet: rpc_client = get_rabbitmq_rpc_client(app) - # Get + checkout_item = await licensed_items_usages.get_licensed_item_usage( rpc_client, product_name=product_name, licensed_item_usage_id=checkout_id ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index 5744109dff7..c3ba86a3011 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -1,6 +1,11 @@ from aiohttp import web from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage +from models_library.api_schemas_webserver.licensed_items_usages import ( + LicenseCheckoutGet, + LicenseCheckoutID, + LicensedItemUsageGet, +) from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -11,7 +16,7 @@ from servicelib.rabbitmq import RPCRouter from ..rabbitmq import get_rabbitmq_rpc_server -from . import _licensed_items_api +from . import _licensed_checkouts_api, _licensed_items_api router = RPCRouter() @@ -59,22 +64,29 @@ async def checkout_licensed_item_for_wallet( licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, -) -> None: - raise NotImplementedError +) -> LicenseCheckoutGet: + return await _licensed_checkouts_api.checkout_licensed_item_for_wallet( + app, + licensed_item_id=licensed_item_id, + wallet_id=wallet_id, + product_name=product_name, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + user_id=user_id, + ) @router.expose(reraise_if_error_type=(NotImplementedError,)) async def release_licensed_item_for_wallet( app: web.Application, *, - user_id: str, - product_name: str, - wallet_id: WalletID, - licensed_item_id: LicensedItemID, - num_of_seats: int, - service_run_id: ServiceRunId, -) -> None: - raise NotImplementedError + user_id: UserID, + checkout_id: LicenseCheckoutID, + product_name: ProductName, +) -> LicensedItemUsageGet: + return await _licensed_checkouts_api.release_licensed_item_for_wallet( + app, product_name=product_name, user_id=user_id, checkout_id=checkout_id + ) async def register_rpc_routes_on_startup(app: web.Application): From 4c269c3a369f9825cf0558298720d2b68afd7df7 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 13:31:34 +0100 Subject: [PATCH 11/29] wire checkout and release in webserver --- .../webserver/licenses/licensed_items.py | 42 ++++++++++++------- .../licenses/_rpc.py | 8 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index e212854bae5..84c6d543629 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -5,6 +5,10 @@ LicensedItemGet, LicensedItemGetPage, ) +from models_library.api_schemas_webserver.licensed_items_usages import ( + LicenseCheckoutGet, + LicensedItemUsageGet, +) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName @@ -23,8 +27,8 @@ async def get_licensed_items( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: str, - offset: int, - limit: int, + offset: int = 0, + limit: int = 20, ) -> LicensedItemGetPage: result: LicensedItemGetPage = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, @@ -33,26 +37,32 @@ async def get_licensed_items( offset=offset, limit=limit, ) - assert isinstance(result, LicensedItemGetPage) + assert isinstance(result, LicensedItemGetPage) # nosec return result @log_decorator(_logger, level=logging.DEBUG) -async def get_licensed_items_for_wallet( +async def get_purchased_licensed_items_for_wallet( rabbitmq_rpc_client: RabbitMQRPCClient, *, - user_id: UserID, product_name: ProductName, wallet_id: WalletID, -) -> LicensedItemGet: + user_id: UserID, + offset: int = 0, + limit: int = 20, +) -> LicensedItemGetPage: result: LicensedItemGet = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, - TypeAdapter(RPCMethodName).validate_python("get_licensed_items_for_wallet"), - user_id=user_id, + TypeAdapter(RPCMethodName).validate_python( + "get_purchased_licensed_items_for_wallet" + ), product_name=product_name, + user_id=user_id, wallet_id=wallet_id, + offset=offset, + limit=limit, ) - assert isinstance(result, LicensedItemGet) # nosec + assert isinstance(result, LicensedItemGetPage) # nosec return result @@ -60,24 +70,25 @@ async def get_licensed_items_for_wallet( async def checkout_licensed_item_for_wallet( rabbitmq_rpc_client: RabbitMQRPCClient, *, - user_id: UserID, product_name: ProductName, + user_id: UserID, wallet_id: WalletID, licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, -) -> None: +) -> LicenseCheckoutGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("checkout_licensed_item_for_wallet"), - user_id=user_id, product_name=product_name, + user_id=user_id, wallet_id=wallet_id, licensed_item_id=licensed_item_id, num_of_seats=num_of_seats, service_run_id=service_run_id, ) - assert result is None # nosec + assert isinstance(result, LicenseCheckoutGet) # nosec + return result @log_decorator(_logger, level=logging.DEBUG) @@ -90,7 +101,7 @@ async def release_licensed_item_for_wallet( licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, -) -> None: +) -> LicensedItemUsageGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"), @@ -101,4 +112,5 @@ async def release_licensed_item_for_wallet( num_of_seats=num_of_seats, service_run_id=service_run_id, ) - assert result is None # nosec + assert isinstance(result, LicensedItemUsageGet) # nosec + return result diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index c3ba86a3011..b7538eca0fe 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -42,11 +42,11 @@ async def get_licensed_items( @router.expose(reraise_if_error_type=(NotImplementedError,)) -async def get_licensed_items_for_wallet( +async def get_purchased_licensed_items_for_wallet( app: web.Application, *, - user_id: UserID, product_name: ProductName, + user_id: UserID, wallet_id: WalletID, offset: int, limit: int, @@ -58,8 +58,8 @@ async def get_licensed_items_for_wallet( async def checkout_licensed_item_for_wallet( app: web.Application, *, - user_id: UserID, product_name: ProductName, + user_id: UserID, wallet_id: WalletID, licensed_item_id: LicensedItemID, num_of_seats: int, @@ -80,9 +80,9 @@ async def checkout_licensed_item_for_wallet( async def release_licensed_item_for_wallet( app: web.Application, *, + product_name: ProductName, user_id: UserID, checkout_id: LicenseCheckoutID, - product_name: ProductName, ) -> LicensedItemUsageGet: return await _licensed_checkouts_api.release_licensed_item_for_wallet( app, product_name=product_name, user_id=user_id, checkout_id=checkout_id From 4eaaf169204ac6a3d4e3c55a9dd817a802e5d5e0 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 13:34:44 +0100 Subject: [PATCH 12/29] wire checkout and release in webserver --- .../webserver/licenses/licensed_items.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index 84c6d543629..27377f0239d 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -7,6 +7,7 @@ ) from models_library.api_schemas_webserver.licensed_items_usages import ( LicenseCheckoutGet, + LicenseCheckoutID, LicensedItemUsageGet, ) from models_library.licensed_items import LicensedItemID @@ -95,22 +96,16 @@ async def checkout_licensed_item_for_wallet( async def release_licensed_item_for_wallet( rabbitmq_rpc_client: RabbitMQRPCClient, *, - user_id: UserID, product_name: ProductName, - wallet_id: WalletID, - licensed_item_id: LicensedItemID, - num_of_seats: int, - service_run_id: ServiceRunId, + user_id: UserID, + checkout_id: LicenseCheckoutID, ) -> LicensedItemUsageGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"), - user_id=user_id, product_name=product_name, - wallet_id=wallet_id, - licensed_item_id=licensed_item_id, - num_of_seats=num_of_seats, - service_run_id=service_run_id, + user_id=user_id, + checkout_id=checkout_id, ) assert isinstance(result, LicensedItemUsageGet) # nosec return result From 34d45772255b00260fcdbd4f88da9457441e5d02 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 14:02:26 +0100 Subject: [PATCH 13/29] adding unit tests in webserver --- .../with_dbs/04/licenses/test_licenses_rpc.py | 101 +++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index e3ab4f4cb3d..031cb1ea7c1 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -7,6 +7,10 @@ import pytest from aiohttp.test_utils import TestClient +from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( + LicenseCheckoutGet, + LicensedItemUsageGet, +) from models_library.licensed_items import LicensedResourceType from models_library.products import ProductName from pytest_mock import MockerFixture @@ -17,7 +21,7 @@ from servicelib.rabbitmq.rpc_interfaces.webserver.licenses.licensed_items import ( checkout_licensed_item_for_wallet, get_licensed_items, - get_licensed_items_for_wallet, + get_purchased_licensed_items_for_wallet, release_licensed_item_for_wallet, ) from settings_library.rabbit import RabbitSettings @@ -67,12 +71,66 @@ async def rpc_client( return await rabbitmq_rpc_client("client") -async def test_api_keys_workflow( +@pytest.fixture +def mock_get_wallet_by_user(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_checkouts_api.get_wallet_by_user", + spec=True, + ) + + +_LICENSE_CHECKOUT_GET = LicenseCheckoutGet.model_validate( + { + "checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", + } +) + + +@pytest.fixture +def mock_checkout_licensed_item(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.checkout_licensed_item", + spec=True, + return_value=_LICENSE_CHECKOUT_GET, + ) + + +_LICENSED_ITEM_USAGE_GET = LicensedItemUsageGet.model_validate( + LicensedItemUsageGet.model_config["json_schema_extra"]["examples"][0] +) + + +@pytest.fixture +def mock_get_licensed_item_usage(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.get_licensed_item_usage", + spec=True, + return_value=_LICENSED_ITEM_USAGE_GET, + ) + + +@pytest.fixture +def mock_release_licensed_item(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.release_licensed_item", + spec=True, + return_value=_LICENSED_ITEM_USAGE_GET, + ) + + +@pytest.mark.acceptance_test( + "Implements https://github.com/ITISFoundation/osparc-issues/issues/1800" +) +async def test_license_checkout_workflow( client: TestClient, rpc_client: RabbitMQRPCClient, osparc_product_name: ProductName, logged_user: UserInfoDict, pricing_plan_id: int, + mock_get_wallet_by_user: MockerFixture, + mock_checkout_licensed_item: MockerFixture, + mock_release_licensed_item: MockerFixture, + mock_get_licensed_item_usage: MockerFixture, ): assert client.app @@ -82,7 +140,7 @@ async def test_api_keys_workflow( assert len(result.items) == 0 assert result.total == 0 - await _licensed_items_db.create( + license_item_db = await _licensed_items_db.create( client.app, product_name=osparc_product_name, name="Model A", @@ -97,31 +155,26 @@ async def test_api_keys_workflow( assert result.total == 1 with pytest.raises(NotImplementedError): - await get_licensed_items_for_wallet( + await get_purchased_licensed_items_for_wallet( rpc_client, user_id=logged_user["id"], product_name=osparc_product_name, wallet_id=1, ) - with pytest.raises(NotImplementedError): - await checkout_licensed_item_for_wallet( - rpc_client, - user_id=logged_user["id"], - product_name=osparc_product_name, - wallet_id=1, - licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee", - num_of_seats=1, - service_run_id="run_1", - ) + checkout = await checkout_licensed_item_for_wallet( + rpc_client, + product_name=osparc_product_name, + user_id=logged_user["id"], + wallet_id=1, + licensed_item_id=license_item_db.licensed_item_id, + num_of_seats=1, + service_run_id="run_1", + ) - with pytest.raises(NotImplementedError): - await release_licensed_item_for_wallet( - rpc_client, - user_id=logged_user["id"], - product_name=osparc_product_name, - wallet_id=1, - licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee", - num_of_seats=1, - service_run_id="run_1", - ) + await release_licensed_item_for_wallet( + rpc_client, + product_name=osparc_product_name, + user_id=logged_user["id"], + checkout_id=checkout.checkout_id, + ) From 4826fa866af0b14a5e29e4283b93efd633feee54 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:02:33 +0100 Subject: [PATCH 14/29] renaming --- ..._usages.py => licensed_items_checkouts.py} | 16 ++- ..._usages.py => licensed_items_checkouts.py} | 12 +- ...ource_tracker_licensed_items_checkouts.py} | 2 +- ...aa6da21a0055_rename_usages_to_checkouts.py | 134 ++++++++++++++++++ ...ource_tracker_licensed_items_checkouts.py} | 6 +- ..._usages.py => licensed_items_checkouts.py} | 36 ++--- .../webserver/licenses/licensed_items.py | 8 +- ...usages.py => _licensed_items_checkouts.py} | 32 +++-- .../api/rpc/routes.py | 4 +- .../exceptions/errors.py | 6 +- ..._usages.py => licensed_items_checkouts.py} | 12 +- ..._usages.py => licensed_items_checkouts.py} | 80 ++++++----- ...s_db.py => licensed_items_checkouts_db.py} | 98 +++++++------ ...y => test_api_licensed_items_checkouts.py} | 38 ++--- .../licenses/_licensed_checkouts_api.py | 28 ++-- .../licenses/_rpc.py | 6 +- .../with_dbs/04/licenses/test_licenses_rpc.py | 14 +- 17 files changed, 347 insertions(+), 185 deletions(-) rename packages/models-library/src/models_library/api_schemas_resource_usage_tracker/{licensed_items_usages.py => licensed_items_checkouts.py} (71%) rename packages/models-library/src/models_library/api_schemas_webserver/{licensed_items_usages.py => licensed_items_checkouts.py} (65%) rename packages/models-library/src/models_library/{resource_tracker_licensed_items_usages.py => resource_tracker_licensed_items_checkouts.py} (55%) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py rename packages/postgres-database/src/simcore_postgres_database/models/{resource_tracker_licensed_items_usage.py => resource_tracker_licensed_items_checkouts.py} (92%) rename packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/{licensed_items_usages.py => licensed_items_checkouts.py} (82%) rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/{_licensed_items_usages.py => _licensed_items_checkouts.py} (71%) rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/{licensed_items_usages.py => licensed_items_checkouts.py} (77%) rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/{licensed_items_usages.py => licensed_items_checkouts.py} (70%) rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/{licensed_items_usages_db.py => licensed_items_checkouts_db.py} (60%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_licensed_items_usages.py => test_api_licensed_items_checkouts.py} (77%) diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py similarity index 71% rename from packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py rename to packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py index 653baf8d17c..fdbb6bebe19 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_usages.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py @@ -5,7 +5,9 @@ from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.users import UserID from models_library.wallets import WalletID from pydantic import BaseModel, ConfigDict, PositiveInt @@ -13,8 +15,8 @@ LicenseCheckoutID: TypeAlias = UUID -class LicensedItemUsageGet(BaseModel): - licensed_item_usage_id: LicensedItemUsageID +class LicensedItemCheckoutGet(BaseModel): + licensed_item_checkout_id: LicensedItemCheckoutID licensed_item_id: LicensedItemID wallet_id: WalletID user_id: UserID @@ -28,7 +30,7 @@ class LicensedItemUsageGet(BaseModel): json_schema_extra={ "examples": [ { - "licensed_item_usage_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", + "licensed_item_checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", "licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953", "wallet_id": 1, "user_id": 1, @@ -43,10 +45,10 @@ class LicensedItemUsageGet(BaseModel): ) -class LicensedItemsUsagesPage(NamedTuple): - items: list[LicensedItemUsageGet] +class LicensedItemsCheckoutsPage(NamedTuple): + items: list[LicensedItemCheckoutGet] total: PositiveInt class LicenseCheckoutGet(BaseModel): - checkout_id: LicenseCheckoutID # This is a licensed_item_usage_id generated in the `resource_tracker_licensed_items_usages` table + checkout_id: LicenseCheckoutID # This is a licensed_item_checkout_id generated in the `resource_tracker_licensed_items_checkouts` table diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py similarity index 65% rename from packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py rename to packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py index e5799108ca8..3b8a5ce9ad6 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_usages.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py @@ -3,17 +3,19 @@ from pydantic import BaseModel, PositiveInt -from ..api_schemas_resource_usage_tracker.licensed_items_usages import LicenseCheckoutID +from ..api_schemas_resource_usage_tracker.licensed_items_checkouts import ( + LicenseCheckoutID, +) from ..licensed_items import LicensedItemID from ..products import ProductName -from ..resource_tracker_licensed_items_usages import LicensedItemUsageID +from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID from ..users import UserID from ..wallets import WalletID from ._base import OutputSchema -class LicensedItemUsageGet(OutputSchema): - licensed_item_usage_id: LicensedItemUsageID +class LicensedItemCheckoutGet(OutputSchema): + licensed_item_checkout_id: LicensedItemCheckoutID licensed_item_id: LicensedItemID wallet_id: WalletID user_id: UserID @@ -24,7 +26,7 @@ class LicensedItemUsageGet(OutputSchema): class LicensedItemUsageGetPage(NamedTuple): - items: list[LicensedItemUsageGet] + items: list[LicensedItemCheckoutGet] total: PositiveInt diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_checkouts.py similarity index 55% rename from packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py rename to packages/models-library/src/models_library/resource_tracker_licensed_items_checkouts.py index 87a737aa5ca..cd09440b822 100644 --- a/packages/models-library/src/models_library/resource_tracker_licensed_items_usages.py +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_checkouts.py @@ -1,4 +1,4 @@ from typing import TypeAlias from uuid import UUID -LicensedItemUsageID: TypeAlias = UUID +LicensedItemCheckoutID: TypeAlias = UUID diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py new file mode 100644 index 00000000000..b050ee0eaa8 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py @@ -0,0 +1,134 @@ +"""rename usages to checkouts + +Revision ID: aa6da21a0055 +Revises: 3720518f82a7 +Create Date: 2024-12-17 13:47:09.304574+00:00 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "aa6da21a0055" +down_revision = "3720518f82a7" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "resource_tracker_licensed_items_checkouts", + sa.Column( + "licensed_item_checkout_id", + postgresql.UUID(as_uuid=True), + server_default=sa.text("gen_random_uuid()"), + nullable=False, + ), + sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=True), + sa.Column("wallet_id", sa.BigInteger(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.Column("user_email", sa.String(), nullable=True), + sa.Column("product_name", sa.String(), nullable=False), + sa.Column("service_run_id", sa.String(), nullable=True), + sa.Column("started_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("stopped_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("num_of_seats", sa.SmallInteger(), nullable=False), + sa.Column( + "modified", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["product_name", "service_run_id"], + [ + "resource_tracker_service_runs.product_name", + "resource_tracker_service_runs.service_run_id", + ], + name="resource_tracker_license_checkouts_service_run_id_fkey", + onupdate="CASCADE", + ondelete="RESTRICT", + ), + sa.PrimaryKeyConstraint("licensed_item_checkout_id"), + ) + op.create_index( + op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"), + "resource_tracker_licensed_items_checkouts", + ["wallet_id"], + unique=False, + ) + op.drop_index( + "ix_resource_tracker_licensed_items_usage_wallet_id", + table_name="resource_tracker_licensed_items_usage", + ) + op.drop_table("resource_tracker_licensed_items_usage") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "resource_tracker_licensed_items_usage", + sa.Column( + "licensed_item_usage_id", + postgresql.UUID(), + server_default=sa.text("gen_random_uuid()"), + autoincrement=False, + nullable=False, + ), + sa.Column("wallet_id", sa.BIGINT(), autoincrement=False, nullable=False), + sa.Column("user_id", sa.BIGINT(), autoincrement=False, nullable=False), + sa.Column("user_email", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column("product_name", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column("service_run_id", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column( + "started_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=False, + ), + sa.Column( + "stopped_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column("num_of_seats", sa.SMALLINT(), autoincrement=False, nullable=False), + sa.Column( + "modified", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "licensed_item_id", postgresql.UUID(), autoincrement=False, nullable=False + ), + sa.ForeignKeyConstraint( + ["product_name", "service_run_id"], + [ + "resource_tracker_service_runs.product_name", + "resource_tracker_service_runs.service_run_id", + ], + name="resource_tracker_license_checkouts_service_run_id_fkey", + onupdate="CASCADE", + ondelete="RESTRICT", + ), + sa.PrimaryKeyConstraint( + "licensed_item_usage_id", name="resource_tracker_licensed_items_usage_pkey" + ), + ) + op.create_index( + "ix_resource_tracker_licensed_items_usage_wallet_id", + "resource_tracker_licensed_items_usage", + ["wallet_id"], + unique=False, + ) + op.drop_index( + op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"), + table_name="resource_tracker_licensed_items_checkouts", + ) + op.drop_table("resource_tracker_licensed_items_checkouts") + # ### end Alembic commands ### diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_checkouts.py similarity index 92% rename from packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py rename to packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_checkouts.py index 95cbfee358a..e3cabb899f7 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_usage.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_checkouts.py @@ -7,11 +7,11 @@ from ._common import RefActions, column_modified_datetime from .base import metadata -resource_tracker_licensed_items_usage = sa.Table( - "resource_tracker_licensed_items_usage", +resource_tracker_licensed_items_checkouts = sa.Table( + "resource_tracker_licensed_items_checkouts", metadata, sa.Column( - "licensed_item_usage_id", + "licensed_item_checkout_id", UUID(as_uuid=True), nullable=False, primary_key=True, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py similarity index 82% rename from packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py rename to packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py index cb0e28902c4..9fca11ca280 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_usages.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py @@ -4,25 +4,27 @@ from models_library.api_schemas_resource_usage_tracker import ( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, ) -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( LicenseCheckoutGet, LicenseCheckoutID, - LicensedItemsUsagesPage, - LicensedItemUsageGet, + LicensedItemCheckoutGet, + LicensedItemsCheckoutsPage, ) from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName from models_library.resource_tracker import ServiceRunId -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID from pydantic import NonNegativeInt, TypeAdapter from ....logging_utils import log_decorator -from ....rabbitmq import RabbitMQRPCClient +from ... import RabbitMQRPCClient _logger = logging.getLogger(__name__) @@ -33,25 +35,25 @@ @log_decorator(_logger, level=logging.DEBUG) -async def get_licensed_item_usage( +async def get_licensed_item_checkout( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: ProductName, - licensed_item_usage_id: LicensedItemUsageID, -) -> LicensedItemUsageGet: + licensed_item_usage_id: LicensedItemCheckoutID, +) -> LicensedItemCheckoutGet: result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_usage"), + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_checkout"), product_name=product_name, licensed_item_usage_id=licensed_item_usage_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, LicensedItemUsageGet) # nosec + assert isinstance(result, LicensedItemCheckoutGet) # nosec return result @log_decorator(_logger, level=logging.DEBUG) -async def get_licensed_items_usages_page( +async def get_licensed_items_checkouts_page( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: ProductName, @@ -59,7 +61,7 @@ async def get_licensed_items_usages_page( offset: int = 0, limit: int = 20, order_by: OrderBy | None = None, -) -> LicensedItemsUsagesPage: +) -> LicensedItemsCheckoutsPage: """ Default order_by field is "started_at" """ @@ -68,7 +70,7 @@ async def get_licensed_items_usages_page( result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_usages_page"), + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_checkouts_page"), product_name=product_name, filter_wallet_id=filter_wallet_id, limit=limit, @@ -76,7 +78,7 @@ async def get_licensed_items_usages_page( order_by=order_by, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, LicensedItemsUsagesPage) # nosec + assert isinstance(result, LicensedItemsCheckoutsPage) # nosec return result @@ -114,13 +116,13 @@ async def release_licensed_item( *, checkout_id: LicenseCheckoutID, product_name: ProductName, -) -> LicensedItemUsageGet: - result: LicensedItemUsageGet = await rabbitmq_rpc_client.request( +) -> LicensedItemCheckoutGet: + result: LicensedItemCheckoutGet = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("release_licensed_item"), checkout_id=checkout_id, product_name=product_name, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, LicensedItemUsageGet) # nosec + assert isinstance(result, LicensedItemCheckoutGet) # nosec return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index 27377f0239d..8529e026587 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -5,10 +5,10 @@ LicensedItemGet, LicensedItemGetPage, ) -from models_library.api_schemas_webserver.licensed_items_usages import ( +from models_library.api_schemas_webserver.licensed_items_checkouts import ( LicenseCheckoutGet, LicenseCheckoutID, - LicensedItemUsageGet, + LicensedItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -99,7 +99,7 @@ async def release_licensed_item_for_wallet( product_name: ProductName, user_id: UserID, checkout_id: LicenseCheckoutID, -) -> LicensedItemUsageGet: +) -> LicensedItemCheckoutGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"), @@ -107,5 +107,5 @@ async def release_licensed_item_for_wallet( user_id=user_id, checkout_id=checkout_id, ) - assert isinstance(result, LicensedItemUsageGet) # nosec + assert isinstance(result, LicensedItemCheckoutGet) # nosec return result diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py similarity index 71% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py index a8a1909435a..fc010d2d710 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py @@ -1,20 +1,22 @@ from fastapi import FastAPI -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( LicenseCheckoutGet, LicenseCheckoutID, - LicensedItemsUsagesPage, - LicensedItemUsageGet, + LicensedItemCheckoutGet, + LicensedItemsCheckoutsPage, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq import RPCRouter -from ...services import licensed_items_usages +from ...services import licensed_items_checkouts router = RPCRouter() @@ -24,17 +26,17 @@ async def get_licensed_item_usage( app: FastAPI, *, product_name: ProductName, - licensed_item_usage_id: LicensedItemUsageID, -) -> LicensedItemUsageGet: - return await licensed_items_usages.get_licensed_item_usage( + licensed_item_usage_id: LicensedItemCheckoutID, +) -> LicensedItemCheckoutGet: + return await licensed_items_checkouts.get_licensed_item_checkout( db_engine=app.state.engine, product_name=product_name, - licensed_item_usage_id=licensed_item_usage_id, + licensed_item_checkout_id=licensed_item_usage_id, ) @router.expose(reraise_if_error_type=()) -async def get_licensed_items_usages_page( +async def get_licensed_items_checkouts_page( app: FastAPI, *, product_name: ProductName, @@ -42,8 +44,8 @@ async def get_licensed_items_usages_page( offset: int = 0, limit: int = 20, order_by: OrderBy, -) -> LicensedItemsUsagesPage: - return await licensed_items_usages.list_licensed_items_usages( +) -> LicensedItemsCheckoutsPage: + return await licensed_items_checkouts.list_licensed_items_checkouts( db_engine=app.state.engine, product_name=product_name, filter_wallet_id=filter_wallet_id, @@ -65,7 +67,7 @@ async def checkout_licensed_item( user_id: UserID, user_email: str, ) -> LicenseCheckoutGet: - return await licensed_items_usages.checkout_licensed_item( + return await licensed_items_checkouts.checkout_licensed_item( db_engine=app.state.engine, licensed_item_id=licensed_item_id, wallet_id=wallet_id, @@ -80,7 +82,7 @@ async def checkout_licensed_item( @router.expose(reraise_if_error_type=()) async def release_licensed_item( app: FastAPI, *, checkout_id: LicenseCheckoutID, product_name: ProductName -) -> LicensedItemUsageGet: - return await licensed_items_usages.release_licensed_item( +) -> LicensedItemCheckoutGet: + return await licensed_items_checkouts.release_licensed_item( db_engine=app.state.engine, checkout_id=checkout_id, product_name=product_name ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py index cb93fb4d9bf..e5da8f44411 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py @@ -8,7 +8,7 @@ from servicelib.rabbitmq import RPCRouter from ...services.modules.rabbitmq import get_rabbitmq_rpc_server -from . import _licensed_items_purchases, _licensed_items_usages, _resource_tracker +from . import _licensed_items_checkouts, _licensed_items_purchases, _resource_tracker _logger = logging.getLogger(__name__) @@ -16,7 +16,7 @@ ROUTERS: list[RPCRouter] = [ _resource_tracker.router, _licensed_items_purchases.router, - _licensed_items_usages.router, + _licensed_items_checkouts.router, ] diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py index 37af6fa72d6..678814b4fe5 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py @@ -1,8 +1,10 @@ from common_library.errors_classes import OsparcErrorMixin +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, ) -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID class ResourceUsageTrackerBaseError(OsparcErrorMixin, Exception): @@ -79,4 +81,4 @@ class LicensedItemPurchaseNotFoundError(RutNotFoundError): class LicensedItemUsageNotFoundError(RutNotFoundError): - licensed_item_usage_id: LicensedItemUsageID + licensed_item_usage_id: LicensedItemCheckoutID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_checkouts.py similarity index 77% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_checkouts.py index d415a5aa8e7..30ec170a9ec 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_checkouts.py @@ -3,14 +3,16 @@ from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.users import UserID from models_library.wallets import WalletID from pydantic import BaseModel, ConfigDict -class LicensedItemUsageDB(BaseModel): - licensed_item_usage_id: LicensedItemUsageID +class LicensedItemCheckoutDB(BaseModel): + licensed_item_checkout_id: LicensedItemCheckoutID licensed_item_id: LicensedItemID wallet_id: WalletID user_id: UserID @@ -25,7 +27,7 @@ class LicensedItemUsageDB(BaseModel): model_config = ConfigDict(from_attributes=True) -class CreateLicensedItemUsageDB(BaseModel): +class CreateLicensedItemCheckoutDB(BaseModel): licensed_item_id: LicensedItemID wallet_id: WalletID user_id: UserID @@ -38,7 +40,7 @@ class CreateLicensedItemUsageDB(BaseModel): model_config = ConfigDict(from_attributes=True) -class UpdateLicensedItemUsageDB(BaseModel): +class UpdateLicensedItemCheckoutDB(BaseModel): stopped_at: datetime model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py similarity index 70% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py index 86ceee9a5d7..60be8d542ed 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_usages.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py @@ -2,34 +2,36 @@ from typing import Annotated from fastapi import Depends -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( LicenseCheckoutGet, LicenseCheckoutID, - LicensedItemsUsagesPage, - LicensedItemUsageGet, + LicensedItemCheckoutGet, + LicensedItemsCheckoutsPage, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId, ServiceRunStatus -from models_library.resource_tracker_licensed_items_usages import LicensedItemUsageID +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID from sqlalchemy.ext.asyncio import AsyncEngine from ..api.rest.dependencies import get_resource_tracker_db_engine -from ..models.licensed_items_usages import ( - CreateLicensedItemUsageDB, - LicensedItemUsageDB, +from ..models.licensed_items_checkouts import ( + CreateLicensedItemCheckoutDB, + LicensedItemCheckoutDB, ) from .modules.db import ( + licensed_items_checkouts_db, licensed_items_purchases_db, - licensed_items_usages_db, service_runs_db, ) -async def list_licensed_items_usages( +async def list_licensed_items_checkouts( db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, product_name: ProductName, @@ -37,8 +39,8 @@ async def list_licensed_items_usages( offset: int, limit: int, order_by: OrderBy, -) -> LicensedItemsUsagesPage: - total, licensed_items_usages_list_db = await licensed_items_usages_db.list_( +) -> LicensedItemsCheckoutsPage: + total, licensed_items_checkouts_list_db = await licensed_items_checkouts_db.list_( db_engine, product_name=product_name, filter_wallet_id=filter_wallet_id, @@ -46,11 +48,11 @@ async def list_licensed_items_usages( limit=limit, order_by=order_by, ) - return LicensedItemsUsagesPage( + return LicensedItemsCheckoutsPage( total=total, items=[ - LicensedItemUsageGet( - licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, licensed_item_id=licensed_item_usage_db.licensed_item_id, wallet_id=licensed_item_usage_db.wallet_id, user_id=licensed_item_usage_db.user_id, @@ -60,25 +62,27 @@ async def list_licensed_items_usages( stopped_at=licensed_item_usage_db.stopped_at, num_of_seats=licensed_item_usage_db.num_of_seats, ) - for licensed_item_usage_db in licensed_items_usages_list_db + for licensed_item_usage_db in licensed_items_checkouts_list_db ], ) -async def get_licensed_item_usage( +async def get_licensed_item_checkout( db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, product_name: ProductName, - licensed_item_usage_id: LicensedItemUsageID, -) -> LicensedItemUsageGet: - licensed_item_usage_db: LicensedItemUsageDB = await licensed_items_usages_db.get( - db_engine, - product_name=product_name, - licensed_item_usage_id=licensed_item_usage_id, + licensed_item_checkout_id: LicensedItemCheckoutID, +) -> LicensedItemCheckoutGet: + licensed_item_usage_db: LicensedItemCheckoutDB = ( + await licensed_items_checkouts_db.get( + db_engine, + product_name=product_name, + licensed_item_usage_id=licensed_item_checkout_id, + ) ) - return LicensedItemUsageGet( - licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + return LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, licensed_item_id=licensed_item_usage_db.licensed_item_id, wallet_id=licensed_item_usage_db.wallet_id, user_id=licensed_item_usage_db.user_id, @@ -110,7 +114,7 @@ async def checkout_licensed_item( ) _currently_used_seats = ( - await licensed_items_usages_db.get_currently_used_seats_for_item_and_wallet( + await licensed_items_checkouts_db.get_currently_used_seats_for_item_and_wallet( db_engine, licensed_item_id=licensed_item_id, wallet_id=wallet_id, @@ -135,7 +139,7 @@ async def checkout_licensed_item( ): raise ValueError("This should not happen") - _create_item_usage = CreateLicensedItemUsageDB( + _create_item_usage = CreateLicensedItemCheckoutDB( licensed_item_id=licensed_item_id, wallet_id=wallet_id, user_id=user_id, @@ -145,12 +149,14 @@ async def checkout_licensed_item( started_at=datetime.now(tz=UTC), num_of_seats=num_of_seats, ) - license_item_usage_db = await licensed_items_usages_db.create( + license_item_checkout_db = await licensed_items_checkouts_db.create( db_engine, data=_create_item_usage ) # Return checkout ID - return LicenseCheckoutGet(checkout_id=license_item_usage_db.licensed_item_usage_id) + return LicenseCheckoutGet( + checkout_id=license_item_checkout_db.licensed_item_checkout_id + ) async def release_licensed_item( @@ -158,17 +164,19 @@ async def release_licensed_item( *, checkout_id: LicenseCheckoutID, product_name: ProductName, -) -> LicensedItemUsageGet: +) -> LicensedItemCheckoutGet: - licensed_item_usage_db: LicensedItemUsageDB = await licensed_items_usages_db.update( - db_engine, - licensed_item_usage_id=checkout_id, - product_name=product_name, - stopped_at=datetime.now(tz=UTC), + licensed_item_usage_db: LicensedItemCheckoutDB = ( + await licensed_items_checkouts_db.update( + db_engine, + licensed_item_usage_id=checkout_id, + product_name=product_name, + stopped_at=datetime.now(tz=UTC), + ) ) - return LicensedItemUsageGet( - licensed_item_usage_id=licensed_item_usage_db.licensed_item_usage_id, + return LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, licensed_item_id=licensed_item_usage_db.licensed_item_id, wallet_id=licensed_item_usage_db.wallet_id, user_id=licensed_item_usage_db.user_id, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py similarity index 60% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index 978ba1ee984..100fae218ce 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_usages_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -10,8 +10,8 @@ from models_library.rest_ordering import OrderBy, OrderDirection from models_library.wallets import WalletID from pydantic import NonNegativeInt -from simcore_postgres_database.models.resource_tracker_licensed_items_usage import ( - resource_tracker_licensed_items_usage, +from simcore_postgres_database.models.resource_tracker_licensed_items_checkouts import ( + resource_tracker_licensed_items_checkouts, ) from simcore_postgres_database.utils_repos import ( pass_or_acquire_connection, @@ -20,26 +20,26 @@ from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine from ....exceptions.errors import LicensedItemUsageNotFoundError -from ....models.licensed_items_usages import ( - CreateLicensedItemUsageDB, - LicensedItemUsageDB, +from ....models.licensed_items_checkouts import ( + CreateLicensedItemCheckoutDB, + LicensedItemCheckoutDB, ) _SELECTION_ARGS = ( - resource_tracker_licensed_items_usage.c.licensed_item_usage_id, - resource_tracker_licensed_items_usage.c.licensed_item_id, - resource_tracker_licensed_items_usage.c.wallet_id, - resource_tracker_licensed_items_usage.c.user_id, - resource_tracker_licensed_items_usage.c.user_email, - resource_tracker_licensed_items_usage.c.product_name, - resource_tracker_licensed_items_usage.c.service_run_id, - resource_tracker_licensed_items_usage.c.started_at, - resource_tracker_licensed_items_usage.c.stopped_at, - resource_tracker_licensed_items_usage.c.num_of_seats, - resource_tracker_licensed_items_usage.c.modified, + resource_tracker_licensed_items_checkouts.c.licensed_item_checkout_id, + resource_tracker_licensed_items_checkouts.c.licensed_item_id, + resource_tracker_licensed_items_checkouts.c.wallet_id, + resource_tracker_licensed_items_checkouts.c.user_id, + resource_tracker_licensed_items_checkouts.c.user_email, + resource_tracker_licensed_items_checkouts.c.product_name, + resource_tracker_licensed_items_checkouts.c.service_run_id, + resource_tracker_licensed_items_checkouts.c.started_at, + resource_tracker_licensed_items_checkouts.c.stopped_at, + resource_tracker_licensed_items_checkouts.c.num_of_seats, + resource_tracker_licensed_items_checkouts.c.modified, ) -assert set(LicensedItemUsageDB.model_fields) == { +assert set(LicensedItemCheckoutDB.model_fields) == { c.name for c in _SELECTION_ARGS } # nosec @@ -48,11 +48,11 @@ async def create( engine: AsyncEngine, connection: AsyncConnection | None = None, *, - data: CreateLicensedItemUsageDB, -) -> LicensedItemUsageDB: + data: CreateLicensedItemCheckoutDB, +) -> LicensedItemCheckoutDB: async with transaction_context(engine, connection) as conn: result = await conn.execute( - resource_tracker_licensed_items_usage.insert() + resource_tracker_licensed_items_checkouts.insert() .values( licensed_item_id=data.licensed_item_id, wallet_id=data.wallet_id, @@ -68,7 +68,7 @@ async def create( .returning(*_SELECTION_ARGS) ) row = result.first() - return LicensedItemUsageDB.model_validate(row) + return LicensedItemCheckoutDB.model_validate(row) async def list_( @@ -80,13 +80,16 @@ async def list_( offset: NonNegativeInt, limit: NonNegativeInt, order_by: OrderBy, -) -> tuple[int, list[LicensedItemUsageDB]]: +) -> tuple[int, list[LicensedItemCheckoutDB]]: base_query = ( sa.select(*_SELECTION_ARGS) - .select_from(resource_tracker_licensed_items_usage) + .select_from(resource_tracker_licensed_items_checkouts) .where( - (resource_tracker_licensed_items_usage.c.product_name == product_name) - & (resource_tracker_licensed_items_usage.c.wallet_id == filter_wallet_id) + (resource_tracker_licensed_items_checkouts.c.product_name == product_name) + & ( + resource_tracker_licensed_items_checkouts.c.wallet_id + == filter_wallet_id + ) ) ) @@ -97,11 +100,13 @@ async def list_( # Ordering and pagination if order_by.direction == OrderDirection.ASC: list_query = base_query.order_by( - sa.asc(getattr(resource_tracker_licensed_items_usage.c, order_by.field)) + sa.asc(getattr(resource_tracker_licensed_items_checkouts.c, order_by.field)) ) else: list_query = base_query.order_by( - sa.desc(getattr(resource_tracker_licensed_items_usage.c, order_by.field)) + sa.desc( + getattr(resource_tracker_licensed_items_checkouts.c, order_by.field) + ) ) list_query = list_query.offset(offset).limit(limit) @@ -109,8 +114,8 @@ async def list_( total_count = await conn.scalar(count_query) result = await conn.stream(list_query) - items: list[LicensedItemUsageDB] = [ - LicensedItemUsageDB.model_validate(row) async for row in result + items: list[LicensedItemCheckoutDB] = [ + LicensedItemCheckoutDB.model_validate(row) async for row in result ] return cast(int, total_count), items @@ -122,16 +127,16 @@ async def get( *, licensed_item_usage_id: LicensedItemPurchaseID, product_name: ProductName, -) -> LicensedItemUsageDB: +) -> LicensedItemCheckoutDB: base_query = ( sa.select(*_SELECTION_ARGS) - .select_from(resource_tracker_licensed_items_usage) + .select_from(resource_tracker_licensed_items_checkouts) .where( ( - resource_tracker_licensed_items_usage.c.licensed_item_usage_id + resource_tracker_licensed_items_checkouts.c.licensed_item_usage_id == licensed_item_usage_id ) - & (resource_tracker_licensed_items_usage.c.product_name == product_name) + & (resource_tracker_licensed_items_checkouts.c.product_name == product_name) ) ) @@ -142,7 +147,7 @@ async def get( raise LicensedItemUsageNotFoundError( licensed_item_usage_id=licensed_item_usage_id ) - return LicensedItemUsageDB.model_validate(row) + return LicensedItemCheckoutDB.model_validate(row) async def update( @@ -152,20 +157,20 @@ async def update( licensed_item_usage_id: LicensedItemPurchaseID, product_name: ProductName, stopped_at: datetime, -) -> LicensedItemUsageDB: +) -> LicensedItemCheckoutDB: update_stmt = ( - resource_tracker_licensed_items_usage.update() + resource_tracker_licensed_items_checkouts.update() .values( modified=sa.func.now(), stopped_at=stopped_at, ) .where( ( - resource_tracker_licensed_items_usage.c.licensed_item_usage_id + resource_tracker_licensed_items_checkouts.c.licensed_item_usage_id == licensed_item_usage_id ) - & (resource_tracker_licensed_items_usage.c.product_name == product_name) - & (resource_tracker_licensed_items_usage.c.stopped_at.is_(None)) + & (resource_tracker_licensed_items_checkouts.c.product_name == product_name) + & (resource_tracker_licensed_items_checkouts.c.stopped_at.is_(None)) ) .returning(sa.literal_column("*")) ) @@ -177,7 +182,7 @@ async def update( raise LicensedItemUsageNotFoundError( licensed_item_usage_id=licensed_item_usage_id ) - return LicensedItemUsageDB.model_validate(row) + return LicensedItemCheckoutDB.model_validate(row) async def get_currently_used_seats_for_item_and_wallet( @@ -189,12 +194,15 @@ async def get_currently_used_seats_for_item_and_wallet( product_name: ProductName, ) -> int: sum_stmt = sa.select( - sa.func.sum(resource_tracker_licensed_items_usage.c.num_of_seats) + sa.func.sum(resource_tracker_licensed_items_checkouts.c.num_of_seats) ).where( - (resource_tracker_licensed_items_usage.c.wallet_id == wallet_id) - & (resource_tracker_licensed_items_usage.c.licensed_item_id == licensed_item_id) - & (resource_tracker_licensed_items_usage.c.product_name == product_name) - & (resource_tracker_licensed_items_usage.c.stopped_at.is_(None)) + (resource_tracker_licensed_items_checkouts.c.wallet_id == wallet_id) + & ( + resource_tracker_licensed_items_checkouts.c.licensed_item_id + == licensed_item_id + ) + & (resource_tracker_licensed_items_checkouts.c.product_name == product_name) + & (resource_tracker_licensed_items_checkouts.c.stopped_at.is_(None)) ) async with pass_or_acquire_connection(engine, connection) as conn: diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py similarity index 77% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py index 12e90fd4931..ca4b84a3263 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_usages.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py @@ -10,20 +10,20 @@ import pytest import sqlalchemy as sa -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( - LicensedItemsUsagesPage, - LicensedItemUsageGet, +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( + LicensedItemCheckoutGet, + LicensedItemsCheckoutsPage, ) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemsPurchasesCreate, ) from servicelib.rabbitmq import RabbitMQRPCClient from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_checkouts, licensed_items_purchases, - licensed_items_usages, ) -from simcore_postgres_database.models.resource_tracker_licensed_items_usage import ( - resource_tracker_licensed_items_usage, +from simcore_postgres_database.models.resource_tracker_licensed_items_checkouts import ( + resource_tracker_licensed_items_checkouts, ) from simcore_postgres_database.models.resource_tracker_service_runs import ( resource_tracker_service_runs, @@ -61,17 +61,17 @@ def resource_tracker_service_run_id( yield row[0] - con.execute(resource_tracker_licensed_items_usage.delete()) + con.execute(resource_tracker_licensed_items_checkouts.delete()) con.execute(resource_tracker_service_runs.delete()) -async def test_rpc_licensed_items_usages_workflow( +async def test_rpc_licensed_items_checkouts_workflow( mocked_redis_server: None, resource_tracker_service_run_id: str, rpc_client: RabbitMQRPCClient, ): - # List licensed items usages - output = await licensed_items_usages.get_licensed_items_usages_page( + # List licensed items checkouts + output = await licensed_items_checkouts.get_licensed_items_checkouts_page( rpc_client, product_name="osparc", filter_wallet_id=_WALLET_ID, @@ -101,7 +101,7 @@ async def test_rpc_licensed_items_usages_workflow( ) # Checkout with num of seats - checkout = await licensed_items_usages.checkout_licensed_item( + checkout = await licensed_items_checkouts.checkout_licensed_item( rpc_client, licensed_item_id=created_item.licensed_item_id, wallet_id=_WALLET_ID, @@ -112,25 +112,25 @@ async def test_rpc_licensed_items_usages_workflow( user_email="test@test.com", ) - # List licensed items usages - output = await licensed_items_usages.get_licensed_items_usages_page( + # List licensed items checkouts + output = await licensed_items_checkouts.get_licensed_items_checkouts_page( rpc_client, product_name="osparc", filter_wallet_id=_WALLET_ID, ) assert output.total == 1 - assert isinstance(output, LicensedItemsUsagesPage) + assert isinstance(output, LicensedItemsCheckoutsPage) - # Get licensed items usages - output = await licensed_items_usages.get_licensed_item_usage( + # Get licensed items checkouts + output = await licensed_items_checkouts.get_licensed_item_checkout( rpc_client, product_name="osparc", - licensed_item_usage_id=output.items[0].licensed_item_usage_id, + licensed_item_usage_id=output.items[0].licensed_item_checkout_id, ) - assert isinstance(output, LicensedItemUsageGet) + assert isinstance(output, LicensedItemCheckoutGet) # Release num of seats - license_item_usage = await licensed_items_usages.release_licensed_item( + license_item_usage = await licensed_items_checkouts.release_licensed_item( rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" ) assert license_item_usage diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index 6d7a574b2a8..b1eb04f9929 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -1,9 +1,9 @@ from aiohttp import web from models_library.api_schemas_resource_usage_tracker import ( - licensed_items_usages as rut_licensed_items_usages, + licensed_items_checkouts as rut_licensed_items_checkouts, ) from models_library.api_schemas_webserver import ( - licensed_items_usages as webserver_licensed_items_usages, + licensed_items_checkouts as webserver_licensed_items_checkouts, ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -11,7 +11,7 @@ from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( - licensed_items_usages, + licensed_items_checkouts, ) from ..rabbitmq import get_rabbitmq_rpc_client @@ -27,7 +27,7 @@ async def checkout_licensed_item_for_wallet( num_of_seats: int, service_run_id: ServiceRunId, user_id: UserID, -) -> webserver_licensed_items_usages.LicenseCheckoutGet: +) -> webserver_licensed_items_checkouts.LicenseCheckoutGet: # Check whether user has access to the wallet await get_wallet_by_user( app, @@ -39,8 +39,8 @@ async def checkout_licensed_item_for_wallet( user = await get_user(app, user_id=user_id) rpc_client = get_rabbitmq_rpc_client(app) - license_checkout_get: rut_licensed_items_usages.LicenseCheckoutGet = ( - await licensed_items_usages.checkout_licensed_item( + license_checkout_get: rut_licensed_items_checkouts.LicenseCheckoutGet = ( + await licensed_items_checkouts.checkout_licensed_item( rpc_client, licensed_item_id=licensed_item_id, wallet_id=wallet_id, @@ -52,7 +52,7 @@ async def checkout_licensed_item_for_wallet( ) ) - return webserver_licensed_items_usages.LicenseCheckoutGet( + return webserver_licensed_items_checkouts.LicenseCheckoutGet( checkout_id=license_checkout_get.checkout_id ) @@ -61,11 +61,11 @@ async def release_licensed_item_for_wallet( app: web.Application, product_name: ProductName, user_id: UserID, - checkout_id: rut_licensed_items_usages.LicenseCheckoutID, -) -> webserver_licensed_items_usages.LicensedItemUsageGet: + checkout_id: rut_licensed_items_checkouts.LicenseCheckoutID, +) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: rpc_client = get_rabbitmq_rpc_client(app) - checkout_item = await licensed_items_usages.get_licensed_item_usage( + checkout_item = await licensed_items_checkouts.get_licensed_item_checkout( rpc_client, product_name=product_name, licensed_item_usage_id=checkout_id ) @@ -77,16 +77,16 @@ async def release_licensed_item_for_wallet( product_name=product_name, ) - licensed_item_get: rut_licensed_items_usages.LicensedItemUsageGet = ( - await licensed_items_usages.release_licensed_item( + licensed_item_get: rut_licensed_items_checkouts.LicensedItemCheckoutGet = ( + await licensed_items_checkouts.release_licensed_item( rpc_client, product_name=product_name, checkout_id=checkout_id, ) ) - return webserver_licensed_items_usages.LicensedItemUsageGet( - licensed_item_usage_id=licensed_item_get.licensed_item_usage_id, + return webserver_licensed_items_checkouts.LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_get.licensed_item_usage_id, licensed_item_id=licensed_item_get.licensed_item_id, wallet_id=licensed_item_get.wallet_id, user_id=licensed_item_get.user_id, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index b7538eca0fe..abb9ae8ac2b 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -1,10 +1,10 @@ from aiohttp import web from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage -from models_library.api_schemas_webserver.licensed_items_usages import ( +from models_library.api_schemas_webserver.licensed_items_checkouts import ( LicenseCheckoutGet, LicenseCheckoutID, - LicensedItemUsageGet, + LicensedItemCheckoutGet, ) from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID @@ -83,7 +83,7 @@ async def release_licensed_item_for_wallet( product_name: ProductName, user_id: UserID, checkout_id: LicenseCheckoutID, -) -> LicensedItemUsageGet: +) -> LicensedItemCheckoutGet: return await _licensed_checkouts_api.release_licensed_item_for_wallet( app, product_name=product_name, user_id=user_id, checkout_id=checkout_id ) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index 031cb1ea7c1..abfe28a0af9 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -7,9 +7,9 @@ import pytest from aiohttp.test_utils import TestClient -from models_library.api_schemas_resource_usage_tracker.licensed_items_usages import ( +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( LicenseCheckoutGet, - LicensedItemUsageGet, + LicensedItemCheckoutGet, ) from models_library.licensed_items import LicensedResourceType from models_library.products import ProductName @@ -89,21 +89,21 @@ def mock_get_wallet_by_user(mocker: MockerFixture) -> tuple: @pytest.fixture def mock_checkout_licensed_item(mocker: MockerFixture) -> tuple: return mocker.patch( - "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.checkout_licensed_item", + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.checkout_licensed_item", spec=True, return_value=_LICENSE_CHECKOUT_GET, ) -_LICENSED_ITEM_USAGE_GET = LicensedItemUsageGet.model_validate( - LicensedItemUsageGet.model_config["json_schema_extra"]["examples"][0] +_LICENSED_ITEM_USAGE_GET = LicensedItemCheckoutGet.model_validate( + LicensedItemCheckoutGet.model_config["json_schema_extra"]["examples"][0] ) @pytest.fixture def mock_get_licensed_item_usage(mocker: MockerFixture) -> tuple: return mocker.patch( - "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.get_licensed_item_usage", + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.get_licensed_item_usage", spec=True, return_value=_LICENSED_ITEM_USAGE_GET, ) @@ -112,7 +112,7 @@ def mock_get_licensed_item_usage(mocker: MockerFixture) -> tuple: @pytest.fixture def mock_release_licensed_item(mocker: MockerFixture) -> tuple: return mocker.patch( - "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_usages.release_licensed_item", + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.release_licensed_item", spec=True, return_value=_LICENSED_ITEM_USAGE_GET, ) From 1eaba293dc72f18c269c15a455ac1ca57b410b1e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:14:33 +0100 Subject: [PATCH 15/29] renaming --- .../licensed_items_checkouts.py | 4 +- .../api/rpc/_licensed_items_checkouts.py | 6 +- .../exceptions/errors.py | 4 +- .../services/licensed_items_checkouts.py | 68 +++++++++---------- .../modules/db/licensed_items_checkouts_db.py | 28 ++++---- .../test_api_licensed_items_checkouts.py | 2 +- .../licenses/_licensed_checkouts_api.py | 4 +- .../with_dbs/04/licenses/test_licenses_rpc.py | 6 +- 8 files changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py index 9fca11ca280..dae6ea46b54 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py @@ -39,13 +39,13 @@ async def get_licensed_item_checkout( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: ProductName, - licensed_item_usage_id: LicensedItemCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, ) -> LicensedItemCheckoutGet: result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_checkout"), product_name=product_name, - licensed_item_usage_id=licensed_item_usage_id, + licensed_item_checkout_id=licensed_item_checkout_id, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, LicensedItemCheckoutGet) # nosec diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py index fc010d2d710..9bad05e66fb 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py @@ -22,16 +22,16 @@ @router.expose(reraise_if_error_type=()) -async def get_licensed_item_usage( +async def get_licensed_item_checkout( app: FastAPI, *, product_name: ProductName, - licensed_item_usage_id: LicensedItemCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, ) -> LicensedItemCheckoutGet: return await licensed_items_checkouts.get_licensed_item_checkout( db_engine=app.state.engine, product_name=product_name, - licensed_item_checkout_id=licensed_item_usage_id, + licensed_item_checkout_id=licensed_item_checkout_id, ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py index 678814b4fe5..bac981dc352 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py @@ -80,5 +80,5 @@ class LicensedItemPurchaseNotFoundError(RutNotFoundError): licensed_item_purchase_id: LicensedItemPurchaseID -class LicensedItemUsageNotFoundError(RutNotFoundError): - licensed_item_usage_id: LicensedItemCheckoutID +class LicensedItemCheckoutNotFoundError(RutNotFoundError): + licensed_item_checkout_id: LicensedItemCheckoutID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py index 60be8d542ed..584a4555b13 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py @@ -52,17 +52,17 @@ async def list_licensed_items_checkouts( total=total, items=[ LicensedItemCheckoutGet( - licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, - licensed_item_id=licensed_item_usage_db.licensed_item_id, - wallet_id=licensed_item_usage_db.wallet_id, - user_id=licensed_item_usage_db.user_id, - product_name=licensed_item_usage_db.product_name, - service_run_id=licensed_item_usage_db.service_run_id, - started_at=licensed_item_usage_db.started_at, - stopped_at=licensed_item_usage_db.stopped_at, - num_of_seats=licensed_item_usage_db.num_of_seats, + licensed_item_checkout_id=licensed_item_checkout_db.licensed_item_checkout_id, + licensed_item_id=licensed_item_checkout_db.licensed_item_id, + wallet_id=licensed_item_checkout_db.wallet_id, + user_id=licensed_item_checkout_db.user_id, + product_name=licensed_item_checkout_db.product_name, + service_run_id=licensed_item_checkout_db.service_run_id, + started_at=licensed_item_checkout_db.started_at, + stopped_at=licensed_item_checkout_db.stopped_at, + num_of_seats=licensed_item_checkout_db.num_of_seats, ) - for licensed_item_usage_db in licensed_items_checkouts_list_db + for licensed_item_checkout_db in licensed_items_checkouts_list_db ], ) @@ -73,24 +73,24 @@ async def get_licensed_item_checkout( product_name: ProductName, licensed_item_checkout_id: LicensedItemCheckoutID, ) -> LicensedItemCheckoutGet: - licensed_item_usage_db: LicensedItemCheckoutDB = ( + licensed_item_checkout_db: LicensedItemCheckoutDB = ( await licensed_items_checkouts_db.get( db_engine, product_name=product_name, - licensed_item_usage_id=licensed_item_checkout_id, + licensed_item_checkout_id=licensed_item_checkout_id, ) ) return LicensedItemCheckoutGet( - licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, - licensed_item_id=licensed_item_usage_db.licensed_item_id, - wallet_id=licensed_item_usage_db.wallet_id, - user_id=licensed_item_usage_db.user_id, - product_name=licensed_item_usage_db.product_name, - service_run_id=licensed_item_usage_db.service_run_id, - started_at=licensed_item_usage_db.started_at, - stopped_at=licensed_item_usage_db.stopped_at, - num_of_seats=licensed_item_usage_db.num_of_seats, + licensed_item_checkout_id=licensed_item_checkout_db.licensed_item_checkout_id, + licensed_item_id=licensed_item_checkout_db.licensed_item_id, + wallet_id=licensed_item_checkout_db.wallet_id, + user_id=licensed_item_checkout_db.user_id, + product_name=licensed_item_checkout_db.product_name, + service_run_id=licensed_item_checkout_db.service_run_id, + started_at=licensed_item_checkout_db.started_at, + stopped_at=licensed_item_checkout_db.stopped_at, + num_of_seats=licensed_item_checkout_db.num_of_seats, ) @@ -139,7 +139,7 @@ async def checkout_licensed_item( ): raise ValueError("This should not happen") - _create_item_usage = CreateLicensedItemCheckoutDB( + _create_item_checkout = CreateLicensedItemCheckoutDB( licensed_item_id=licensed_item_id, wallet_id=wallet_id, user_id=user_id, @@ -150,7 +150,7 @@ async def checkout_licensed_item( num_of_seats=num_of_seats, ) license_item_checkout_db = await licensed_items_checkouts_db.create( - db_engine, data=_create_item_usage + db_engine, data=_create_item_checkout ) # Return checkout ID @@ -166,23 +166,23 @@ async def release_licensed_item( product_name: ProductName, ) -> LicensedItemCheckoutGet: - licensed_item_usage_db: LicensedItemCheckoutDB = ( + licensed_item_checkout_db: LicensedItemCheckoutDB = ( await licensed_items_checkouts_db.update( db_engine, - licensed_item_usage_id=checkout_id, + licensed_item_checkout_id=checkout_id, product_name=product_name, stopped_at=datetime.now(tz=UTC), ) ) return LicensedItemCheckoutGet( - licensed_item_checkout_id=licensed_item_usage_db.licensed_item_checkout_id, - licensed_item_id=licensed_item_usage_db.licensed_item_id, - wallet_id=licensed_item_usage_db.wallet_id, - user_id=licensed_item_usage_db.user_id, - product_name=licensed_item_usage_db.product_name, - service_run_id=licensed_item_usage_db.service_run_id, - started_at=licensed_item_usage_db.started_at, - stopped_at=licensed_item_usage_db.stopped_at, - num_of_seats=licensed_item_usage_db.num_of_seats, + licensed_item_checkout_id=licensed_item_checkout_db.licensed_item_checkout_id, + licensed_item_id=licensed_item_checkout_db.licensed_item_id, + wallet_id=licensed_item_checkout_db.wallet_id, + user_id=licensed_item_checkout_db.user_id, + product_name=licensed_item_checkout_db.product_name, + service_run_id=licensed_item_checkout_db.service_run_id, + started_at=licensed_item_checkout_db.started_at, + stopped_at=licensed_item_checkout_db.stopped_at, + num_of_seats=licensed_item_checkout_db.num_of_seats, ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index 100fae218ce..eff9b3e91d4 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -4,9 +4,6 @@ import sqlalchemy as sa from models_library.licensed_items import LicensedItemID from models_library.products import ProductName -from models_library.resource_tracker_licensed_items_purchases import ( - LicensedItemPurchaseID, -) from models_library.rest_ordering import OrderBy, OrderDirection from models_library.wallets import WalletID from pydantic import NonNegativeInt @@ -17,9 +14,12 @@ pass_or_acquire_connection, transaction_context, ) +from simcore_service_resource_usage_tracker.services.licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine -from ....exceptions.errors import LicensedItemUsageNotFoundError +from ....exceptions.errors import LicensedItemCheckoutNotFoundError from ....models.licensed_items_checkouts import ( CreateLicensedItemCheckoutDB, LicensedItemCheckoutDB, @@ -125,7 +125,7 @@ async def get( engine: AsyncEngine, connection: AsyncConnection | None = None, *, - licensed_item_usage_id: LicensedItemPurchaseID, + licensed_item_checkout_id: LicensedItemCheckoutID, product_name: ProductName, ) -> LicensedItemCheckoutDB: base_query = ( @@ -133,8 +133,8 @@ async def get( .select_from(resource_tracker_licensed_items_checkouts) .where( ( - resource_tracker_licensed_items_checkouts.c.licensed_item_usage_id - == licensed_item_usage_id + resource_tracker_licensed_items_checkouts.c.licensed_item_checkout_id + == licensed_item_checkout_id ) & (resource_tracker_licensed_items_checkouts.c.product_name == product_name) ) @@ -144,8 +144,8 @@ async def get( result = await conn.stream(base_query) row = await result.first() if row is None: - raise LicensedItemUsageNotFoundError( - licensed_item_usage_id=licensed_item_usage_id + raise LicensedItemCheckoutNotFoundError( + licensed_item_checkout_id=licensed_item_checkout_id ) return LicensedItemCheckoutDB.model_validate(row) @@ -154,7 +154,7 @@ async def update( engine: AsyncEngine, connection: AsyncConnection | None = None, *, - licensed_item_usage_id: LicensedItemPurchaseID, + licensed_item_checkout_id: LicensedItemCheckoutID, product_name: ProductName, stopped_at: datetime, ) -> LicensedItemCheckoutDB: @@ -166,8 +166,8 @@ async def update( ) .where( ( - resource_tracker_licensed_items_checkouts.c.licensed_item_usage_id - == licensed_item_usage_id + resource_tracker_licensed_items_checkouts.c.licensed_item_checkout_id + == licensed_item_checkout_id ) & (resource_tracker_licensed_items_checkouts.c.product_name == product_name) & (resource_tracker_licensed_items_checkouts.c.stopped_at.is_(None)) @@ -179,8 +179,8 @@ async def update( result = await conn.execute(update_stmt) row = result.first() if row is None: - raise LicensedItemUsageNotFoundError( - licensed_item_usage_id=licensed_item_usage_id + raise LicensedItemCheckoutNotFoundError( + licensed_item_checkout_id=licensed_item_checkout_id ) return LicensedItemCheckoutDB.model_validate(row) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py index ca4b84a3263..233fac5df16 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py @@ -125,7 +125,7 @@ async def test_rpc_licensed_items_checkouts_workflow( output = await licensed_items_checkouts.get_licensed_item_checkout( rpc_client, product_name="osparc", - licensed_item_usage_id=output.items[0].licensed_item_checkout_id, + licensed_item_checkout_id=output.items[0].licensed_item_checkout_id, ) assert isinstance(output, LicensedItemCheckoutGet) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index b1eb04f9929..85ccfdf1884 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -66,7 +66,7 @@ async def release_licensed_item_for_wallet( rpc_client = get_rabbitmq_rpc_client(app) checkout_item = await licensed_items_checkouts.get_licensed_item_checkout( - rpc_client, product_name=product_name, licensed_item_usage_id=checkout_id + rpc_client, product_name=product_name, licensed_item_checkout_id=checkout_id ) # Check whether user has access to the wallet @@ -86,7 +86,7 @@ async def release_licensed_item_for_wallet( ) return webserver_licensed_items_checkouts.LicensedItemCheckoutGet( - licensed_item_checkout_id=licensed_item_get.licensed_item_usage_id, + licensed_item_checkout_id=licensed_item_get.licensed_item_checkout_id, licensed_item_id=licensed_item_get.licensed_item_id, wallet_id=licensed_item_get.wallet_id, user_id=licensed_item_get.user_id, diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index abfe28a0af9..81d76648d1d 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -101,9 +101,9 @@ def mock_checkout_licensed_item(mocker: MockerFixture) -> tuple: @pytest.fixture -def mock_get_licensed_item_usage(mocker: MockerFixture) -> tuple: +def mock_get_licensed_item_checkout(mocker: MockerFixture) -> tuple: return mocker.patch( - "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.get_licensed_item_usage", + "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.get_licensed_item_checkout", spec=True, return_value=_LICENSED_ITEM_USAGE_GET, ) @@ -130,7 +130,7 @@ async def test_license_checkout_workflow( mock_get_wallet_by_user: MockerFixture, mock_checkout_licensed_item: MockerFixture, mock_release_licensed_item: MockerFixture, - mock_get_licensed_item_usage: MockerFixture, + mock_get_licensed_item_checkout: MockerFixture, ): assert client.app From a43626d2cf19b82ded7e410e0dfa53698a39eabf Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:15:27 +0100 Subject: [PATCH 16/29] renaming --- .../unit/with_dbs/test_api_licensed_items_checkouts.py | 6 +++--- .../tests/unit/with_dbs/04/licenses/test_licenses_rpc.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py index 233fac5df16..b85cbf77956 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py @@ -130,8 +130,8 @@ async def test_rpc_licensed_items_checkouts_workflow( assert isinstance(output, LicensedItemCheckoutGet) # Release num of seats - license_item_usage = await licensed_items_checkouts.release_licensed_item( + license_item_checkout = await licensed_items_checkouts.release_licensed_item( rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" ) - assert license_item_usage - assert isinstance(license_item_usage.stopped_at, datetime) + assert license_item_checkout + assert isinstance(license_item_checkout.stopped_at, datetime) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index 81d76648d1d..1931284d73f 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -95,7 +95,7 @@ def mock_checkout_licensed_item(mocker: MockerFixture) -> tuple: ) -_LICENSED_ITEM_USAGE_GET = LicensedItemCheckoutGet.model_validate( +_LICENSED_ITEM_CHECKOUT_GET = LicensedItemCheckoutGet.model_validate( LicensedItemCheckoutGet.model_config["json_schema_extra"]["examples"][0] ) @@ -105,7 +105,7 @@ def mock_get_licensed_item_checkout(mocker: MockerFixture) -> tuple: return mocker.patch( "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.get_licensed_item_checkout", spec=True, - return_value=_LICENSED_ITEM_USAGE_GET, + return_value=_LICENSED_ITEM_CHECKOUT_GET, ) @@ -114,7 +114,7 @@ def mock_release_licensed_item(mocker: MockerFixture) -> tuple: return mocker.patch( "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.release_licensed_item", spec=True, - return_value=_LICENSED_ITEM_USAGE_GET, + return_value=_LICENSED_ITEM_CHECKOUT_GET, ) From 7a1dcf7b0ff81151c2e82a88f86013663e2e81bd Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:24:09 +0100 Subject: [PATCH 17/29] renaming --- .../licensed_items_checkouts.py | 9 +------- .../licensed_items_checkouts.py | 9 +------- .../licensed_items_checkouts.py | 12 ++++------ .../webserver/licenses/licensed_items.py | 6 ++--- .../api/rpc/_licensed_items_checkouts.py | 13 +++++++---- .../services/licensed_items_checkouts.py | 22 +++++++++++------- .../test_api_licensed_items_checkouts.py | 4 +++- .../licenses/_licensed_checkouts_api.py | 23 +++++++++++++------ .../licenses/_rpc.py | 14 +++++++---- .../with_dbs/04/licenses/test_licenses_rpc.py | 14 +++-------- 10 files changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py index fdbb6bebe19..00e136d79ef 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_checkouts.py @@ -1,6 +1,5 @@ from datetime import datetime -from typing import NamedTuple, TypeAlias -from uuid import UUID +from typing import NamedTuple from models_library.licensed_items import LicensedItemID from models_library.products import ProductName @@ -12,8 +11,6 @@ from models_library.wallets import WalletID from pydantic import BaseModel, ConfigDict, PositiveInt -LicenseCheckoutID: TypeAlias = UUID - class LicensedItemCheckoutGet(BaseModel): licensed_item_checkout_id: LicensedItemCheckoutID @@ -48,7 +45,3 @@ class LicensedItemCheckoutGet(BaseModel): class LicensedItemsCheckoutsPage(NamedTuple): items: list[LicensedItemCheckoutGet] total: PositiveInt - - -class LicenseCheckoutGet(BaseModel): - checkout_id: LicenseCheckoutID # This is a licensed_item_checkout_id generated in the `resource_tracker_licensed_items_checkouts` table diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py index 3b8a5ce9ad6..a3ee122ddee 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_checkouts.py @@ -1,11 +1,8 @@ from datetime import datetime from typing import NamedTuple -from pydantic import BaseModel, PositiveInt +from pydantic import PositiveInt -from ..api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicenseCheckoutID, -) from ..licensed_items import LicensedItemID from ..products import ProductName from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID @@ -28,7 +25,3 @@ class LicensedItemCheckoutGet(OutputSchema): class LicensedItemUsageGetPage(NamedTuple): items: list[LicensedItemCheckoutGet] total: PositiveInt - - -class LicenseCheckoutGet(BaseModel): - checkout_id: LicenseCheckoutID diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py index dae6ea46b54..62032c63383 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_checkouts.py @@ -5,8 +5,6 @@ RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, ) from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicenseCheckoutGet, - LicenseCheckoutID, LicensedItemCheckoutGet, LicensedItemsCheckoutsPage, ) @@ -93,8 +91,8 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseCheckoutGet: - result: LicenseCheckoutGet = await rabbitmq_rpc_client.request( +) -> LicensedItemCheckoutGet: + result: LicensedItemCheckoutGet = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("checkout_licensed_item"), licensed_item_id=licensed_item_id, @@ -106,7 +104,7 @@ async def checkout_licensed_item( user_email=user_email, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, LicenseCheckoutGet) # nosec + assert isinstance(result, LicensedItemCheckoutGet) # nosec return result @@ -114,13 +112,13 @@ async def checkout_licensed_item( async def release_licensed_item( rabbitmq_rpc_client: RabbitMQRPCClient, *, - checkout_id: LicenseCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, product_name: ProductName, ) -> LicensedItemCheckoutGet: result: LicensedItemCheckoutGet = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("release_licensed_item"), - checkout_id=checkout_id, + licensed_item_checkout_id=licensed_item_checkout_id, product_name=product_name, timeout_s=_DEFAULT_TIMEOUT_S, ) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index 8529e026587..36141607103 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -7,7 +7,6 @@ ) from models_library.api_schemas_webserver.licensed_items_checkouts import ( LicenseCheckoutGet, - LicenseCheckoutID, LicensedItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID @@ -19,6 +18,7 @@ from pydantic import TypeAdapter from servicelib.logging_utils import log_decorator from servicelib.rabbitmq import RabbitMQRPCClient +from simcore_service_webserver.licenses._rpc import LicensedItemCheckoutID _logger = logging.getLogger(__name__) @@ -98,14 +98,14 @@ async def release_licensed_item_for_wallet( *, product_name: ProductName, user_id: UserID, - checkout_id: LicenseCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, ) -> LicensedItemCheckoutGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"), product_name=product_name, user_id=user_id, - checkout_id=checkout_id, + licensed_item_checkout_id=licensed_item_checkout_id, ) assert isinstance(result, LicensedItemCheckoutGet) # nosec return result diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py index 9bad05e66fb..f99680ab856 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py @@ -1,7 +1,5 @@ from fastapi import FastAPI from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicenseCheckoutGet, - LicenseCheckoutID, LicensedItemCheckoutGet, LicensedItemsCheckoutsPage, ) @@ -66,7 +64,7 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseCheckoutGet: +) -> LicensedItemCheckoutGet: return await licensed_items_checkouts.checkout_licensed_item( db_engine=app.state.engine, licensed_item_id=licensed_item_id, @@ -81,8 +79,13 @@ async def checkout_licensed_item( @router.expose(reraise_if_error_type=()) async def release_licensed_item( - app: FastAPI, *, checkout_id: LicenseCheckoutID, product_name: ProductName + app: FastAPI, + *, + licensed_item_checkout_id: LicensedItemCheckoutID, + product_name: ProductName, ) -> LicensedItemCheckoutGet: return await licensed_items_checkouts.release_licensed_item( - db_engine=app.state.engine, checkout_id=checkout_id, product_name=product_name + db_engine=app.state.engine, + licensed_item_checkout_id=licensed_item_checkout_id, + product_name=product_name, ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py index 584a4555b13..bc1b663cbb7 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py @@ -3,8 +3,6 @@ from fastapi import Depends from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicenseCheckoutGet, - LicenseCheckoutID, LicensedItemCheckoutGet, LicensedItemsCheckoutsPage, ) @@ -104,7 +102,7 @@ async def checkout_licensed_item( service_run_id: ServiceRunId, user_id: UserID, user_email: str, -) -> LicenseCheckoutGet: +) -> LicensedItemCheckoutGet: _active_purchased_seats: int = await licensed_items_purchases_db.get_active_purchased_seats_for_item_and_wallet( db_engine, @@ -149,27 +147,35 @@ async def checkout_licensed_item( started_at=datetime.now(tz=UTC), num_of_seats=num_of_seats, ) - license_item_checkout_db = await licensed_items_checkouts_db.create( + licensed_item_checkout_db = await licensed_items_checkouts_db.create( db_engine, data=_create_item_checkout ) # Return checkout ID - return LicenseCheckoutGet( - checkout_id=license_item_checkout_db.licensed_item_checkout_id + return LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_checkout_db.licensed_item_checkout_id, + licensed_item_id=licensed_item_checkout_db.licensed_item_id, + wallet_id=licensed_item_checkout_db.wallet_id, + user_id=licensed_item_checkout_db.user_id, + product_name=licensed_item_checkout_db.product_name, + service_run_id=licensed_item_checkout_db.service_run_id, + started_at=licensed_item_checkout_db.started_at, + stopped_at=licensed_item_checkout_db.stopped_at, + num_of_seats=licensed_item_checkout_db.num_of_seats, ) async def release_licensed_item( db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, - checkout_id: LicenseCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, product_name: ProductName, ) -> LicensedItemCheckoutGet: licensed_item_checkout_db: LicensedItemCheckoutDB = ( await licensed_items_checkouts_db.update( db_engine, - licensed_item_checkout_id=checkout_id, + licensed_item_checkout_id=licensed_item_checkout_id, product_name=product_name, stopped_at=datetime.now(tz=UTC), ) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py index b85cbf77956..b1036c49aef 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_checkouts.py @@ -131,7 +131,9 @@ async def test_rpc_licensed_items_checkouts_workflow( # Release num of seats license_item_checkout = await licensed_items_checkouts.release_licensed_item( - rpc_client, checkout_id=checkout.checkout_id, product_name="osparc" + rpc_client, + licensed_item_checkout_id=checkout.licensed_item_checkout_id, + product_name="osparc", ) assert license_item_checkout assert isinstance(license_item_checkout.stopped_at, datetime) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index 85ccfdf1884..93cbd49b95c 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -27,7 +27,7 @@ async def checkout_licensed_item_for_wallet( num_of_seats: int, service_run_id: ServiceRunId, user_id: UserID, -) -> webserver_licensed_items_checkouts.LicenseCheckoutGet: +) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: # Check whether user has access to the wallet await get_wallet_by_user( app, @@ -39,7 +39,7 @@ async def checkout_licensed_item_for_wallet( user = await get_user(app, user_id=user_id) rpc_client = get_rabbitmq_rpc_client(app) - license_checkout_get: rut_licensed_items_checkouts.LicenseCheckoutGet = ( + licensed_item_get: rut_licensed_items_checkouts.LicensedItemCheckoutGet = ( await licensed_items_checkouts.checkout_licensed_item( rpc_client, licensed_item_id=licensed_item_id, @@ -52,8 +52,15 @@ async def checkout_licensed_item_for_wallet( ) ) - return webserver_licensed_items_checkouts.LicenseCheckoutGet( - checkout_id=license_checkout_get.checkout_id + return webserver_licensed_items_checkouts.LicensedItemCheckoutGet( + licensed_item_checkout_id=licensed_item_get.licensed_item_checkout_id, + licensed_item_id=licensed_item_get.licensed_item_id, + wallet_id=licensed_item_get.wallet_id, + user_id=licensed_item_get.user_id, + product_name=licensed_item_get.product_name, + started_at=licensed_item_get.started_at, + stopped_at=licensed_item_get.stopped_at, + num_of_seats=licensed_item_get.num_of_seats, ) @@ -61,12 +68,14 @@ async def release_licensed_item_for_wallet( app: web.Application, product_name: ProductName, user_id: UserID, - checkout_id: rut_licensed_items_checkouts.LicenseCheckoutID, + licensed_item_checkout_id: rut_licensed_items_checkouts.LicensedItemCheckoutID, ) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: rpc_client = get_rabbitmq_rpc_client(app) checkout_item = await licensed_items_checkouts.get_licensed_item_checkout( - rpc_client, product_name=product_name, licensed_item_checkout_id=checkout_id + rpc_client, + product_name=product_name, + licensed_item_checkout_id=licensed_item_checkout_id, ) # Check whether user has access to the wallet @@ -81,7 +90,7 @@ async def release_licensed_item_for_wallet( await licensed_items_checkouts.release_licensed_item( rpc_client, product_name=product_name, - checkout_id=checkout_id, + licensed_item_checkout_id=licensed_item_checkout_id, ) ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index abb9ae8ac2b..a9188577cc2 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -1,9 +1,10 @@ from aiohttp import web +from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage from models_library.api_schemas_webserver.licensed_items_checkouts import ( - LicenseCheckoutGet, - LicenseCheckoutID, LicensedItemCheckoutGet, ) from models_library.basic_types import IDStr @@ -64,7 +65,7 @@ async def checkout_licensed_item_for_wallet( licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, -) -> LicenseCheckoutGet: +) -> LicensedItemCheckoutGet: return await _licensed_checkouts_api.checkout_licensed_item_for_wallet( app, licensed_item_id=licensed_item_id, @@ -82,10 +83,13 @@ async def release_licensed_item_for_wallet( *, product_name: ProductName, user_id: UserID, - checkout_id: LicenseCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, ) -> LicensedItemCheckoutGet: return await _licensed_checkouts_api.release_licensed_item_for_wallet( - app, product_name=product_name, user_id=user_id, checkout_id=checkout_id + app, + product_name=product_name, + user_id=user_id, + licensed_item_checkout_id=licensed_item_checkout_id, ) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index 1931284d73f..b58a7dd00be 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -8,7 +8,6 @@ import pytest from aiohttp.test_utils import TestClient from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicenseCheckoutGet, LicensedItemCheckoutGet, ) from models_library.licensed_items import LicensedResourceType @@ -79,10 +78,8 @@ def mock_get_wallet_by_user(mocker: MockerFixture) -> tuple: ) -_LICENSE_CHECKOUT_GET = LicenseCheckoutGet.model_validate( - { - "checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", - } +_LICENSED_ITEM_CHECKOUT_GET = LicensedItemCheckoutGet.model_validate( + LicensedItemCheckoutGet.model_config["json_schema_extra"]["examples"][0] ) @@ -91,15 +88,10 @@ def mock_checkout_licensed_item(mocker: MockerFixture) -> tuple: return mocker.patch( "simcore_service_webserver.licenses._licensed_checkouts_api.licensed_items_checkouts.checkout_licensed_item", spec=True, - return_value=_LICENSE_CHECKOUT_GET, + return_value=_LICENSED_ITEM_CHECKOUT_GET, ) -_LICENSED_ITEM_CHECKOUT_GET = LicensedItemCheckoutGet.model_validate( - LicensedItemCheckoutGet.model_config["json_schema_extra"]["examples"][0] -) - - @pytest.fixture def mock_get_licensed_item_checkout(mocker: MockerFixture) -> tuple: return mocker.patch( From 2a0b64f772713e36349fe0345b92851d8afcc0db Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:29:14 +0100 Subject: [PATCH 18/29] renaming --- .../rpc_interfaces/webserver/licenses/licensed_items.py | 5 ++--- .../tests/unit/with_dbs/04/licenses/test_licenses_rpc.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index 36141607103..76ab8b5c52c 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -6,7 +6,6 @@ LicensedItemGetPage, ) from models_library.api_schemas_webserver.licensed_items_checkouts import ( - LicenseCheckoutGet, LicensedItemCheckoutGet, ) from models_library.licensed_items import LicensedItemID @@ -77,7 +76,7 @@ async def checkout_licensed_item_for_wallet( licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, -) -> LicenseCheckoutGet: +) -> LicensedItemCheckoutGet: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python("checkout_licensed_item_for_wallet"), @@ -88,7 +87,7 @@ async def checkout_licensed_item_for_wallet( num_of_seats=num_of_seats, service_run_id=service_run_id, ) - assert isinstance(result, LicenseCheckoutGet) # nosec + assert isinstance(result, LicensedItemCheckoutGet) # nosec return result diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index b58a7dd00be..30eb906cd0e 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -168,5 +168,5 @@ async def test_license_checkout_workflow( rpc_client, product_name=osparc_product_name, user_id=logged_user["id"], - checkout_id=checkout.checkout_id, + licensed_item_checkout_id=checkout.licensed_item_checkout_id, ) From f8c6567f07da6da792a917cee112d77691f5a87f Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 15:54:58 +0100 Subject: [PATCH 19/29] imporve error handling --- .../resource_usage_tracker/errors.py | 29 +++++++++++++++++++ .../api/rpc/_licensed_items_checkouts.py | 11 ++++--- .../exceptions/errors.py | 7 ----- .../services/licensed_items_checkouts.py | 19 ++++++++++-- .../modules/db/licensed_items_checkouts_db.py | 4 ++- .../licenses/_rpc.py | 9 ++++-- 6 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/errors.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/errors.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/errors.py new file mode 100644 index 00000000000..42e578ee482 --- /dev/null +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/errors.py @@ -0,0 +1,29 @@ +from common_library.errors_classes import OsparcErrorMixin + + +class LicensesBaseError(OsparcErrorMixin, Exception): + ... + + +class NotEnoughAvailableSeatsError(LicensesBaseError): + msg_template = "Not enough available seats. Current available seats {available_num_of_seats} for license item {license_item_id}" + + +class CanNotCheckoutNotEnoughAvailableSeatsError(LicensesBaseError): + msg_template = "Can not checkout license item {licensed_item_id} with num of seats {num_of_seats}. Currently available seats {available_num_of_seats}" + + +class CanNotCheckoutServiceIsNotRunningError(LicensesBaseError): + msg_template = "Can not checkout license item {licensed_item_id} as dynamic service is not running. Current service {service_run}" + + +class LicensedItemCheckoutNotFoundError(LicensesBaseError): + msg_template = "Licensed item checkout {licensed_item_checkout_id} not found." + + +LICENSES_ERRORS = ( + NotEnoughAvailableSeatsError, + CanNotCheckoutNotEnoughAvailableSeatsError, + CanNotCheckoutServiceIsNotRunningError, + LicensedItemCheckoutNotFoundError, +) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py index f99680ab856..19ff9847374 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_checkouts.py @@ -13,13 +13,16 @@ from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq import RPCRouter +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker.errors import ( + LICENSES_ERRORS, +) from ...services import licensed_items_checkouts router = RPCRouter() -@router.expose(reraise_if_error_type=()) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def get_licensed_item_checkout( app: FastAPI, *, @@ -33,7 +36,7 @@ async def get_licensed_item_checkout( ) -@router.expose(reraise_if_error_type=()) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def get_licensed_items_checkouts_page( app: FastAPI, *, @@ -53,7 +56,7 @@ async def get_licensed_items_checkouts_page( ) -@router.expose(reraise_if_error_type=()) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def checkout_licensed_item( app: FastAPI, *, @@ -77,7 +80,7 @@ async def checkout_licensed_item( ) -@router.expose(reraise_if_error_type=()) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def release_licensed_item( app: FastAPI, *, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py index bac981dc352..55fde04b0f6 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py @@ -1,7 +1,4 @@ from common_library.errors_classes import OsparcErrorMixin -from models_library.resource_tracker_licensed_items_checkouts import ( - LicensedItemCheckoutID, -) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, ) @@ -78,7 +75,3 @@ class PricingPlanNotFoundForServiceError(RutNotFoundError): class LicensedItemPurchaseNotFoundError(RutNotFoundError): licensed_item_purchase_id: LicensedItemPurchaseID - - -class LicensedItemCheckoutNotFoundError(RutNotFoundError): - licensed_item_checkout_id: LicensedItemCheckoutID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py index bc1b663cbb7..4f2446dfb14 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_checkouts.py @@ -15,6 +15,11 @@ from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker.errors import ( + CanNotCheckoutNotEnoughAvailableSeatsError, + CanNotCheckoutServiceIsNotRunningError, + NotEnoughAvailableSeatsError, +) from sqlalchemy.ext.asyncio import AsyncEngine from ..api.rest.dependencies import get_resource_tracker_db_engine @@ -122,10 +127,16 @@ async def checkout_licensed_item( available_seats = _active_purchased_seats - _currently_used_seats if available_seats <= 0: - raise ValueError("Not enough available seats") + raise NotEnoughAvailableSeatsError( + license_item_id=licensed_item_id, available_num_of_seats=available_seats + ) if available_seats - num_of_seats < 0: - raise ValueError("Can not checkout num of seats, not enough available") + raise CanNotCheckoutNotEnoughAvailableSeatsError( + license_item_id=licensed_item_id, + available_num_of_seats=available_seats, + num_of_seats=num_of_seats, + ) # Check if the service run ID is currently running service_run = await service_runs_db.get_service_run_by_id( @@ -135,7 +146,9 @@ async def checkout_licensed_item( service_run is None or service_run.service_run_status != ServiceRunStatus.RUNNING ): - raise ValueError("This should not happen") + raise CanNotCheckoutServiceIsNotRunningError( + license_item_id=licensed_item_id, service_run=service_run + ) _create_item_checkout = CreateLicensedItemCheckoutDB( licensed_item_id=licensed_item_id, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index eff9b3e91d4..3ed23e2490d 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -7,6 +7,9 @@ from models_library.rest_ordering import OrderBy, OrderDirection from models_library.wallets import WalletID from pydantic import NonNegativeInt +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker.errors import ( + LicensedItemCheckoutNotFoundError, +) from simcore_postgres_database.models.resource_tracker_licensed_items_checkouts import ( resource_tracker_licensed_items_checkouts, ) @@ -19,7 +22,6 @@ ) from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine -from ....exceptions.errors import LicensedItemCheckoutNotFoundError from ....models.licensed_items_checkouts import ( CreateLicensedItemCheckoutDB, LicensedItemCheckoutDB, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index a9188577cc2..c5e4e320002 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -15,6 +15,9 @@ from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq import RPCRouter +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker.errors import ( + LICENSES_ERRORS, +) from ..rabbitmq import get_rabbitmq_rpc_server from . import _licensed_checkouts_api, _licensed_items_api @@ -22,7 +25,7 @@ router = RPCRouter() -@router.expose() +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def get_licensed_items( app: web.Application, *, @@ -55,7 +58,7 @@ async def get_purchased_licensed_items_for_wallet( raise NotImplementedError -@router.expose(reraise_if_error_type=(NotImplementedError,)) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def checkout_licensed_item_for_wallet( app: web.Application, *, @@ -77,7 +80,7 @@ async def checkout_licensed_item_for_wallet( ) -@router.expose(reraise_if_error_type=(NotImplementedError,)) +@router.expose(reraise_if_error_type=LICENSES_ERRORS) async def release_licensed_item_for_wallet( app: web.Application, *, From f38de47c78beb7e151ed00b3cac4489da8fe5aa1 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 16:15:30 +0100 Subject: [PATCH 20/29] fix api server --- .../rpc_interfaces/webserver/licenses/licensed_items.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index 76ab8b5c52c..a063983718c 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -12,12 +12,14 @@ from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.users import UserID from models_library.wallets import WalletID from pydantic import TypeAdapter from servicelib.logging_utils import log_decorator from servicelib.rabbitmq import RabbitMQRPCClient -from simcore_service_webserver.licenses._rpc import LicensedItemCheckoutID _logger = logging.getLogger(__name__) From a150857484b8621a9e8dccf18c4a8368c2750452 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 17:06:45 +0100 Subject: [PATCH 21/29] fix failing tests --- .../services/modules/db/licensed_items_checkouts_db.py | 6 +++--- .../server/src/simcore_service_webserver/licenses/_rpc.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index 3ed23e2490d..83879aa9dee 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -4,6 +4,9 @@ import sqlalchemy as sa from models_library.licensed_items import LicensedItemID from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.rest_ordering import OrderBy, OrderDirection from models_library.wallets import WalletID from pydantic import NonNegativeInt @@ -17,9 +20,6 @@ pass_or_acquire_connection, transaction_context, ) -from simcore_service_resource_usage_tracker.services.licensed_items_checkouts import ( - LicensedItemCheckoutID, -) from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine from ....models.licensed_items_checkouts import ( diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index c5e4e320002..b9a12e8a3c9 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -1,7 +1,4 @@ from aiohttp import web -from models_library.api_schemas_resource_usage_tracker.licensed_items_checkouts import ( - LicensedItemCheckoutID, -) from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage from models_library.api_schemas_webserver.licensed_items_checkouts import ( @@ -11,6 +8,9 @@ from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.rest_ordering import OrderBy from models_library.users import UserID from models_library.wallets import WalletID From 8b41ebfa2e9d780554b518332ef22e391ba6938d Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 17:11:14 +0100 Subject: [PATCH 22/29] fix available tests --- .../rpc_interfaces/webserver/licenses/licensed_items.py | 4 ++-- .../web/server/src/simcore_service_webserver/licenses/_rpc.py | 2 +- .../tests/unit/with_dbs/04/licenses/test_licenses_rpc.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py index a063983718c..cb20f00be0a 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -44,7 +44,7 @@ async def get_licensed_items( @log_decorator(_logger, level=logging.DEBUG) -async def get_purchased_licensed_items_for_wallet( +async def get_available_licensed_items_for_wallet( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: ProductName, @@ -56,7 +56,7 @@ async def get_purchased_licensed_items_for_wallet( result: LicensedItemGet = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, TypeAdapter(RPCMethodName).validate_python( - "get_purchased_licensed_items_for_wallet" + "get_available_licensed_items_for_wallet" ), product_name=product_name, user_id=user_id, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index b9a12e8a3c9..0b5d1b65fe9 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -46,7 +46,7 @@ async def get_licensed_items( @router.expose(reraise_if_error_type=(NotImplementedError,)) -async def get_purchased_licensed_items_for_wallet( +async def get_available_licensed_items_for_wallet( app: web.Application, *, product_name: ProductName, diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index 30eb906cd0e..6888711b2da 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -19,8 +19,8 @@ from servicelib.rabbitmq import RabbitMQRPCClient from servicelib.rabbitmq.rpc_interfaces.webserver.licenses.licensed_items import ( checkout_licensed_item_for_wallet, + get_available_licensed_items_for_wallet, get_licensed_items, - get_purchased_licensed_items_for_wallet, release_licensed_item_for_wallet, ) from settings_library.rabbit import RabbitSettings @@ -147,7 +147,7 @@ async def test_license_checkout_workflow( assert result.total == 1 with pytest.raises(NotImplementedError): - await get_purchased_licensed_items_for_wallet( + await get_available_licensed_items_for_wallet( rpc_client, user_id=logged_user["id"], product_name=osparc_product_name, From de312d33f66b4ed9482638e0f1f718d444ce1e5f Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 17 Dec 2024 17:47:28 +0100 Subject: [PATCH 23/29] fix mypy --- .../services/modules/db/licensed_items_checkouts_db.py | 2 +- .../services/modules/db/licensed_items_purchases_db.py | 2 +- .../licenses/_licensed_checkouts_api.py | 5 ++++- .../e2e-playwright/tests/platform_CI_tests/test_platform.py | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index 83879aa9dee..29da2775f1f 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -212,4 +212,4 @@ async def get_currently_used_seats_for_item_and_wallet( row = result.first() if row is None or row[0] is None: return 0 - return row[0] + return int(row[0]) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index 7137b5b6a2c..3f5c6e7db0c 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -185,4 +185,4 @@ async def get_active_purchased_seats_for_item_and_wallet( row = result.first() if row is None or row[0] is None: return 0 - return row[0] + return int(row[0]) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index 93cbd49b95c..f8336be4c25 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -8,6 +8,9 @@ from models_library.licensed_items import LicensedItemID from models_library.products import ProductName from models_library.resource_tracker import ServiceRunId +from models_library.resource_tracker_licensed_items_checkouts import ( + LicensedItemCheckoutID, +) from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( @@ -68,7 +71,7 @@ async def release_licensed_item_for_wallet( app: web.Application, product_name: ProductName, user_id: UserID, - licensed_item_checkout_id: rut_licensed_items_checkouts.LicensedItemCheckoutID, + licensed_item_checkout_id: LicensedItemCheckoutID, ) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: rpc_client = get_rabbitmq_rpc_client(app) diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index c6f492a6085..758f1a396a1 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -5,7 +5,6 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -from collections.abc import Iterable from pathlib import Path from typing import Iterable From 68ee78c7eb8dc88bf33330444e40565488560864 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 10:46:10 +0100 Subject: [PATCH 24/29] fix e2e tests --- .../tests/platform_CI_tests/test_platform.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index 758f1a396a1..e0680f8ddaa 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -9,7 +9,7 @@ from typing import Iterable import pytest -from playwright.sync_api._generated import BrowserContext, Playwright +from playwright.sync_api._generated import BrowserContext, Playwright, expect from pydantic import AnyUrl @@ -108,9 +108,11 @@ def test_simple_workspace_workflow( and response.request.method == "POST" ) as response_info: page.get_by_test_id("newWorkspaceButton").click() - page.wait_for_timeout(500) + + workspace_title_field = page.get_by_test_id("workspaceEditorTitle") + # wait until the title is automatically filled up + expect(workspace_title_field).not_to_have_value("") page.get_by_test_id("workspaceEditorSave").click() - page.wait_for_timeout(500) _workspace_id = response_info.value.json()["data"]["workspaceId"] page.get_by_test_id(f"workspaceItem_{_workspace_id}").click() From 1ee88864a35819339be9cfb3c08ba735ae84ec73 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 10:54:44 +0100 Subject: [PATCH 25/29] review @sanderegg --- .../3720518f82a7_modify_licenses_db.py | 28 ------------------- ...aa6da21a0055_rename_usages_to_checkouts.py | 4 +-- .../tests/platform_CI_tests/conftest.py | 18 ++++++++++++ .../tests/platform_CI_tests/test_platform.py | 15 ---------- 4 files changed, 20 insertions(+), 45 deletions(-) delete mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py deleted file mode 100644 index 5b4ca0b1436..00000000000 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/3720518f82a7_modify_licenses_db.py +++ /dev/null @@ -1,28 +0,0 @@ -"""modify licenses DB - -Revision ID: 3720518f82a7 -Revises: 77ac824a77ff -Create Date: 2024-12-13 12:46:38.302027+00:00 - -""" -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = "3720518f82a7" -down_revision = "77ac824a77ff" -branch_labels = None -depends_on = None - - -def upgrade(): - op.drop_column("resource_tracker_licensed_items_usage", "licensed_item_id") - op.add_column( - "resource_tracker_licensed_items_usage", - sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=False), - ) - - -def downgrade(): - ... diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py index b050ee0eaa8..f2bfe077fb5 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py @@ -1,7 +1,7 @@ """rename usages to checkouts Revision ID: aa6da21a0055 -Revises: 3720518f82a7 +Revises: 77ac824a77ff Create Date: 2024-12-17 13:47:09.304574+00:00 """ @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "aa6da21a0055" -down_revision = "3720518f82a7" +down_revision = "77ac824a77ff" branch_labels = None depends_on = None diff --git a/tests/e2e-playwright/tests/platform_CI_tests/conftest.py b/tests/e2e-playwright/tests/platform_CI_tests/conftest.py index e69de29bb2d..300e0d75972 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/conftest.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/conftest.py @@ -0,0 +1,18 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def results_path(request): + """ + Fixture to retrieve the path to the test's results directory. + """ + # Check if `results_dir` is available in the current test's user properties + results_dir = dict(request.node.user_properties).get("results_dir") + if not results_dir: + results_dir = "test-results" # Default results directory + test_name = request.node.name + test_dir = Path(results_dir) / test_name + test_dir.mkdir(parents=True, exist_ok=True) # Ensure the test directory exists + return test_dir diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index e0680f8ddaa..882ba92d86d 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -18,21 +18,6 @@ def store_browser_context() -> bool: return True -@pytest.fixture -def results_path(request): - """ - Fixture to retrieve the path to the test's results directory. - """ - # Check if `results_dir` is available in the current test's user properties - results_dir = dict(request.node.user_properties).get("results_dir") - if not results_dir: - results_dir = "test-results" # Default results directory - test_name = request.node.name - test_dir = Path(results_dir) / test_name - test_dir.mkdir(parents=True, exist_ok=True) # Ensure the test directory exists - return test_dir - - @pytest.fixture def logged_in_context( playwright: Playwright, From 84eb893e32eb928c587d8aec197f252b877d30ec Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 11:26:38 +0100 Subject: [PATCH 26/29] review @pcrespov --- .../aa6da21a0055_rename_usages_to_checkouts.py | 4 ++-- .../modules/db/licensed_items_checkouts_db.py | 9 +++++---- .../modules/db/licensed_items_purchases_db.py | 9 +++++---- .../services/modules/db/service_runs_db.py | 6 ++++-- .../licenses/_licensed_checkouts_api.py | 12 +++++++++--- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py index f2bfe077fb5..882be09dd2c 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/aa6da21a0055_rename_usages_to_checkouts.py @@ -1,7 +1,7 @@ """rename usages to checkouts Revision ID: aa6da21a0055 -Revises: 77ac824a77ff +Revises: 52a0e8148dd5 Create Date: 2024-12-17 13:47:09.304574+00:00 """ @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "aa6da21a0055" -down_revision = "77ac824a77ff" +down_revision = "52a0e8148dd5" branch_labels = None depends_on = None diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py index 29da2775f1f..2402a8c52be 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_checkouts_db.py @@ -114,6 +114,8 @@ async def list_( async with pass_or_acquire_connection(engine, connection) as conn: total_count = await conn.scalar(count_query) + if total_count is None: + total_count = 0 result = await conn.stream(list_query) items: list[LicensedItemCheckoutDB] = [ @@ -208,8 +210,7 @@ async def get_currently_used_seats_for_item_and_wallet( ) async with pass_or_acquire_connection(engine, connection) as conn: - result = await conn.execute(sum_stmt) - row = result.first() - if row is None or row[0] is None: + total_sum = await conn.scalar(sum_stmt) + if total_sum is None: return 0 - return int(row[0]) + return cast(int, total_sum) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index 3f5c6e7db0c..2fd8718784e 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -116,6 +116,8 @@ async def list_( async with pass_or_acquire_connection(engine, connection) as conn: total_count = await conn.scalar(count_query) + if total_count is None: + total_count = 0 result = await conn.stream(list_query) items: list[LicensedItemsPurchasesDB] = [ @@ -181,8 +183,7 @@ async def get_active_purchased_seats_for_item_and_wallet( ) async with pass_or_acquire_connection(engine, connection) as conn: - result = await conn.execute(sum_stmt) - row = result.first() - if row is None or row[0] is None: + total_sum = await conn.scalar(sum_stmt) + if total_sum is None: return 0 - return int(row[0]) + return cast(int, total_sum) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py index 9d987a32cfc..e7e1ace3ff8 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py @@ -376,7 +376,9 @@ async def get_osparc_credits_aggregated_by_service( subquery = base_query.subquery() count_query = sa.select(sa.func.count()).select_from(subquery) - count_result = await conn.execute(count_query) + count_result = await conn.scalar(count_query) + if count_result is None: + count_result = 0 # Default ordering and pagination list_query = ( @@ -387,7 +389,7 @@ async def get_osparc_credits_aggregated_by_service( list_result = await conn.execute(list_query) return ( - cast(int, count_result.scalar()), + cast(int, count_result), [ OsparcCreditsAggregatedByServiceKeyDB.model_validate(row) for row in list_result.fetchall() diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py index f8336be4c25..869953fccd3 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_checkouts_api.py @@ -24,12 +24,15 @@ async def checkout_licensed_item_for_wallet( app: web.Application, - licensed_item_id: LicensedItemID, - wallet_id: WalletID, + *, + # access context product_name: ProductName, + wallet_id: WalletID, + user_id: UserID, + # checkout args + licensed_item_id: LicensedItemID, num_of_seats: int, service_run_id: ServiceRunId, - user_id: UserID, ) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: # Check whether user has access to the wallet await get_wallet_by_user( @@ -69,8 +72,11 @@ async def checkout_licensed_item_for_wallet( async def release_licensed_item_for_wallet( app: web.Application, + *, + # access context product_name: ProductName, user_id: UserID, + # release args licensed_item_checkout_id: LicensedItemCheckoutID, ) -> webserver_licensed_items_checkouts.LicensedItemCheckoutGet: rpc_client = get_rabbitmq_rpc_client(app) From 58e2f0319cd456dd4275f57a73e6433da49a41a1 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 13:22:01 +0100 Subject: [PATCH 27/29] add license key --- .../api_schemas_webserver/licensed_items.py | 2 ++ .../src/models_library/licensed_items.py | 1 + .../versions/d31c23845017_add_license_key.py | 29 +++++++++++++++++++ .../models/licensed_items.py | 5 ++++ .../licenses/_licensed_items_api.py | 2 ++ .../licenses/_licensed_items_db.py | 1 + 6 files changed, 40 insertions(+) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/d31c23845017_add_license_key.py 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 3455e8a81ac..5dafd9d5804 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 @@ -11,6 +11,7 @@ class LicensedItemGet(OutputSchema): licensed_item_id: LicensedItemID name: str + license_key: str | None licensed_resource_type: LicensedResourceType pricing_plan_id: PricingPlanId created_at: datetime @@ -21,6 +22,7 @@ class LicensedItemGet(OutputSchema): { "licensed_item_id": "0362b88b-91f8-4b41-867c-35544ad1f7a1", "name": "best-model", + "license_key": "license-specific-key", "licensed_resource_type": f"{LicensedResourceType.VIP_MODEL}", "pricing_plan_id": "15", "created_at": "2024-12-12 09:59:26.422140", diff --git a/packages/models-library/src/models_library/licensed_items.py b/packages/models-library/src/models_library/licensed_items.py index 021cf214ce5..79cd4fa87e0 100644 --- a/packages/models-library/src/models_library/licensed_items.py +++ b/packages/models-library/src/models_library/licensed_items.py @@ -24,6 +24,7 @@ class LicensedResourceType(StrAutoEnum): class LicensedItemDB(BaseModel): licensed_item_id: LicensedItemID name: str + license_key: str | None licensed_resource_type: LicensedResourceType pricing_plan_id: PricingPlanId product_name: ProductName diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/d31c23845017_add_license_key.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d31c23845017_add_license_key.py new file mode 100644 index 00000000000..59856c49d52 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d31c23845017_add_license_key.py @@ -0,0 +1,29 @@ +"""add license key + +Revision ID: d31c23845017 +Revises: aa6da21a0055 +Create Date: 2024-12-18 11:11:52.644534+00:00 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "d31c23845017" +down_revision = "aa6da21a0055" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "licensed_items", sa.Column("license_key", sa.String(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("licensed_items", "license_key") + # ### end Alembic commands ### diff --git a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py index 63301eb9c1d..861f28537f1 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py @@ -58,6 +58,11 @@ class LicensedResourceType(str, enum.Enum): nullable=False, doc="Product name", ), + sa.Column( + "license_key", + sa.String, + nullable=True, + ), column_created_datetime(timezone=True), column_modified_datetime(timezone=True), ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py index 6feacf24b1d..1f839ae31fa 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py @@ -45,6 +45,7 @@ async def get_licensed_item( return LicensedItemGet( licensed_item_id=licensed_item_db.licensed_item_id, name=licensed_item_db.name, + license_key=licensed_item_db.license_key, licensed_resource_type=licensed_item_db.licensed_resource_type, pricing_plan_id=licensed_item_db.pricing_plan_id, created_at=licensed_item_db.created, @@ -68,6 +69,7 @@ async def list_licensed_items( LicensedItemGet( licensed_item_id=licensed_item_db.licensed_item_id, name=licensed_item_db.name, + license_key=licensed_item_db.license_key, licensed_resource_type=licensed_item_db.licensed_resource_type, pricing_plan_id=licensed_item_db.pricing_plan_id, created_at=licensed_item_db.created, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py index 415dec7149d..57861698161 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py @@ -36,6 +36,7 @@ _SELECTION_ARGS = ( licensed_items.c.licensed_item_id, licensed_items.c.name, + licensed_items.c.license_key, licensed_items.c.licensed_resource_type, licensed_items.c.pricing_plan_id, licensed_items.c.product_name, From 172d343945670dab62f1a1e85cba96346abb322b Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 13:30:58 +0100 Subject: [PATCH 28/29] openapi specs --- .../models/schemas/model_adapter.py | 1 + .../src/simcore_service_webserver/api/v0/openapi.yaml | 6 ++++++ 2 files changed, 7 insertions(+) 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 e5a04e198c5..7765ad95980 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 @@ -141,4 +141,5 @@ class LicensedItemGet(BaseModel): assert set(LicensedItemGet.model_fields.keys()) == set( _LicensedItemGet.model_fields.keys() + - {"license_key"} # TODO: @bisgaard-itis please expose ) 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 93cf60aa82c..27bdb777e6f 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 @@ -10342,6 +10342,11 @@ components: name: type: string title: Name + licenseKey: + anyOf: + - type: string + - type: 'null' + title: Licensekey licensedResourceType: $ref: '#/components/schemas/LicensedResourceType' pricingPlanId: @@ -10361,6 +10366,7 @@ components: required: - licensedItemId - name + - licenseKey - licensedResourceType - pricingPlanId - createdAt From 1ebc3cdbb08a77f53812a6f3bae5dad30689746d Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 18 Dec 2024 14:35:33 +0100 Subject: [PATCH 29/29] review @pcrespov --- .../src/simcore_postgres_database/models/licensed_items.py | 1 + .../models/schemas/model_adapter.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py index 861f28537f1..a0ea136f4bb 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py @@ -62,6 +62,7 @@ class LicensedResourceType(str, enum.Enum): "license_key", sa.String, nullable=True, + doc="Purpose: Acts as a mapping key to the internal license server. Usage: The Sim4Life base applications use this key to check out a seat from the internal license server.", ), column_created_datetime(timezone=True), column_modified_datetime(timezone=True), 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 7765ad95980..06da65580f8 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 @@ -141,5 +141,7 @@ class LicensedItemGet(BaseModel): assert set(LicensedItemGet.model_fields.keys()) == set( _LicensedItemGet.model_fields.keys() - - {"license_key"} # TODO: @bisgaard-itis please expose + - { + "license_key" + } # NOTE: @bisgaard-itis please expose https://github.com/ITISFoundation/osparc-simcore/issues/6875 )