Skip to content

Commit 966e89a

Browse files
authored
Merge branch 'master' into is1771/share-tags
2 parents 4c2ce22 + 116a6c0 commit 966e89a

File tree

26 files changed

+675
-84
lines changed

26 files changed

+675
-84
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
""" Helper script to generate OAS automatically
2+
"""
3+
4+
# pylint: disable=redefined-outer-name
5+
# pylint: disable=unused-argument
6+
# pylint: disable=unused-variable
7+
# pylint: disable=too-many-arguments
8+
9+
from typing import Annotated
10+
11+
from _common import as_query
12+
from fastapi import APIRouter, Depends
13+
from models_library.api_schemas_webserver.licensed_items_purchases import (
14+
LicensedItemPurchaseGet,
15+
)
16+
from models_library.generics import Envelope
17+
from models_library.rest_error import EnvelopedError
18+
from models_library.rest_pagination import Page
19+
from simcore_service_webserver._meta import API_VTAG
20+
from simcore_service_webserver.licenses._exceptions_handlers import _TO_HTTP_ERROR_MAP
21+
from simcore_service_webserver.licenses._licensed_items_checkouts_models import (
22+
LicensedItemCheckoutPathParams,
23+
LicensedItemsCheckoutsListQueryParams,
24+
)
25+
from simcore_service_webserver.wallets._handlers import WalletsPathParams
26+
27+
router = APIRouter(
28+
prefix=f"/{API_VTAG}",
29+
tags=[
30+
"licenses",
31+
],
32+
responses={
33+
i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values()
34+
},
35+
)
36+
37+
38+
@router.get(
39+
"/wallets/{wallet_id}/licensed-items-checkouts",
40+
response_model=Page[LicensedItemPurchaseGet],
41+
tags=["wallets"],
42+
)
43+
async def list_licensed_item_checkouts_for_wallet(
44+
_path: Annotated[WalletsPathParams, Depends()],
45+
_query: Annotated[as_query(LicensedItemsCheckoutsListQueryParams), Depends()],
46+
):
47+
...
48+
49+
50+
@router.get(
51+
"/licensed-items-checkouts/{licensed_item_checkout_id}",
52+
response_model=Envelope[LicensedItemPurchaseGet],
53+
)
54+
async def get_licensed_item_checkout(
55+
_path: Annotated[LicensedItemCheckoutPathParams, Depends()],
56+
):
57+
...

api/specs/web-server/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"_long_running_tasks",
3939
"_licensed_items",
4040
"_licensed_items_purchases",
41+
"_licensed_items_checkouts",
4142
"_metamodeling",
4243
"_nih_sparc",
4344
"_nih_sparc_redirections",

packages/common-library/src/common_library/unset.py renamed to packages/common-library/src/common_library/exclude.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ class UnSet:
1010

1111
def as_dict_exclude_unset(**params) -> dict[str, Any]:
1212
return {k: v for k, v in params.items() if not isinstance(v, UnSet)}
13+
14+
15+
def as_dict_exclude_none(**params) -> dict[str, Any]:
16+
return {k: v for k, v in params.items() if v is not None}

packages/common-library/tests/test_unset.py renamed to packages/common-library/tests/test_exclude.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any
22

3-
from common_library.unset import UnSet, as_dict_exclude_unset
3+
from common_library.exclude import UnSet, as_dict_exclude_none, as_dict_exclude_unset
44

55

66
def test_as_dict_exclude_unset():
@@ -13,3 +13,10 @@ def f(
1313
assert f(par1="hi") == {"par1": "hi"}
1414
assert f(par2=4) == {"par2": 4}
1515
assert f(par1="hi", par2=4) == {"par1": "hi", "par2": 4}
16+
17+
# still expected behavior
18+
assert as_dict_exclude_unset(par1=None) == {"par1": None}
19+
20+
21+
def test_as_dict_exclude_none():
22+
assert as_dict_exclude_none(par1=None) == {}
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from typing import NamedTuple
33

4-
from pydantic import PositiveInt
4+
from pydantic import BaseModel, PositiveInt
55

66
from ..licensed_items import LicensedItemID
77
from ..products import ProductName
@@ -10,8 +10,29 @@
1010
from ..wallets import WalletID
1111
from ._base import OutputSchema
1212

13+
# RPC
1314

14-
class LicensedItemCheckoutGet(OutputSchema):
15+
16+
class LicensedItemCheckoutRpcGet(BaseModel):
17+
licensed_item_checkout_id: LicensedItemCheckoutID
18+
licensed_item_id: LicensedItemID
19+
wallet_id: WalletID
20+
user_id: UserID
21+
product_name: ProductName
22+
started_at: datetime
23+
stopped_at: datetime | None
24+
num_of_seats: int
25+
26+
27+
class LicensedItemCheckoutRpcGetPage(NamedTuple):
28+
items: list[LicensedItemCheckoutRpcGet]
29+
total: PositiveInt
30+
31+
32+
# Rest
33+
34+
35+
class LicensedItemCheckoutRestGet(OutputSchema):
1536
licensed_item_checkout_id: LicensedItemCheckoutID
1637
licensed_item_id: LicensedItemID
1738
wallet_id: WalletID
@@ -22,6 +43,6 @@ class LicensedItemCheckoutGet(OutputSchema):
2243
num_of_seats: int
2344

2445

25-
class LicensedItemUsageGetPage(NamedTuple):
26-
items: list[LicensedItemCheckoutGet]
46+
class LicensedItemCheckoutRestGetPage(NamedTuple):
47+
items: list[LicensedItemCheckoutRestGet]
2748
total: PositiveInt

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
LicensedItemGetPage,
77
)
88
from models_library.api_schemas_webserver.licensed_items_checkouts import (
9-
LicensedItemCheckoutGet,
9+
LicensedItemCheckoutRpcGet,
1010
)
1111
from models_library.licensed_items import LicensedItemID
1212
from models_library.products import ProductName
@@ -78,7 +78,7 @@ async def checkout_licensed_item_for_wallet(
7878
licensed_item_id: LicensedItemID,
7979
num_of_seats: int,
8080
service_run_id: ServiceRunID,
81-
) -> LicensedItemCheckoutGet:
81+
) -> LicensedItemCheckoutRpcGet:
8282
result = await rabbitmq_rpc_client.request(
8383
WEBSERVER_RPC_NAMESPACE,
8484
TypeAdapter(RPCMethodName).validate_python("checkout_licensed_item_for_wallet"),
@@ -89,7 +89,7 @@ async def checkout_licensed_item_for_wallet(
8989
num_of_seats=num_of_seats,
9090
service_run_id=service_run_id,
9191
)
92-
assert isinstance(result, LicensedItemCheckoutGet) # nosec
92+
assert isinstance(result, LicensedItemCheckoutRpcGet) # nosec
9393
return result
9494

9595

@@ -100,13 +100,13 @@ async def release_licensed_item_for_wallet(
100100
product_name: ProductName,
101101
user_id: UserID,
102102
licensed_item_checkout_id: LicensedItemCheckoutID,
103-
) -> LicensedItemCheckoutGet:
103+
) -> LicensedItemCheckoutRpcGet:
104104
result = await rabbitmq_rpc_client.request(
105105
WEBSERVER_RPC_NAMESPACE,
106106
TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"),
107107
product_name=product_name,
108108
user_id=user_id,
109109
licensed_item_checkout_id=licensed_item_checkout_id,
110110
)
111-
assert isinstance(result, LicensedItemCheckoutGet) # nosec
111+
assert isinstance(result, LicensedItemCheckoutRpcGet) # nosec
112112
return result

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import datetime
22
from typing import cast
33

4+
from common_library.exclude import as_dict_exclude_none
45
from common_library.json_serialization import json_dumps
5-
from common_library.unset import UnSet, as_dict_exclude_unset
66
from fastapi import FastAPI, status
77
from httpx import Response, Timeout
88
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -133,14 +133,11 @@ async def dynamic_service_retrieve(
133133
@retry_on_errors()
134134
@expect_status(status.HTTP_200_OK)
135135
async def get_dynamic_services(
136-
self,
137-
*,
138-
user_id: UserID | None | UnSet = UnSet.VALUE,
139-
project_id: ProjectID | None | UnSet = UnSet.VALUE,
136+
self, *, user_id: UserID | None = None, project_id: ProjectID | None = None
140137
) -> Response:
141138
return await self.client.get(
142139
"/dynamic_services",
143-
params=as_dict_exclude_unset(user_id=user_id, project_id=project_id),
140+
params=as_dict_exclude_none(user_id=user_id, project_id=project_id),
144141
)
145142

146143
@retry_on_errors()

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,6 +3333,121 @@ paths:
33333333
schema:
33343334
$ref: '#/components/schemas/EnvelopedError'
33353335
description: Bad Request
3336+
/v0/wallets/{wallet_id}/licensed-items-checkouts:
3337+
get:
3338+
tags:
3339+
- licenses
3340+
- wallets
3341+
summary: List Licensed Item Checkouts For Wallet
3342+
operationId: list_licensed_item_checkouts_for_wallet
3343+
parameters:
3344+
- name: wallet_id
3345+
in: path
3346+
required: true
3347+
schema:
3348+
type: integer
3349+
exclusiveMinimum: true
3350+
title: Wallet Id
3351+
minimum: 0
3352+
- name: order_by
3353+
in: query
3354+
required: false
3355+
schema:
3356+
type: string
3357+
contentMediaType: application/json
3358+
contentSchema: {}
3359+
default: '{"field":"started_at","direction":"desc"}'
3360+
title: Order By
3361+
- name: limit
3362+
in: query
3363+
required: false
3364+
schema:
3365+
type: integer
3366+
default: 20
3367+
title: Limit
3368+
- name: offset
3369+
in: query
3370+
required: false
3371+
schema:
3372+
type: integer
3373+
default: 0
3374+
title: Offset
3375+
responses:
3376+
'200':
3377+
description: Successful Response
3378+
content:
3379+
application/json:
3380+
schema:
3381+
$ref: '#/components/schemas/Page_LicensedItemPurchaseGet_'
3382+
'404':
3383+
content:
3384+
application/json:
3385+
schema:
3386+
$ref: '#/components/schemas/EnvelopedError'
3387+
description: Not Found
3388+
'403':
3389+
content:
3390+
application/json:
3391+
schema:
3392+
$ref: '#/components/schemas/EnvelopedError'
3393+
description: Forbidden
3394+
'402':
3395+
content:
3396+
application/json:
3397+
schema:
3398+
$ref: '#/components/schemas/EnvelopedError'
3399+
description: Payment Required
3400+
'400':
3401+
content:
3402+
application/json:
3403+
schema:
3404+
$ref: '#/components/schemas/EnvelopedError'
3405+
description: Bad Request
3406+
/v0/licensed-items-checkouts/{licensed_item_checkout_id}:
3407+
get:
3408+
tags:
3409+
- licenses
3410+
summary: Get Licensed Item Checkout
3411+
operationId: get_licensed_item_checkout
3412+
parameters:
3413+
- name: licensed_item_checkout_id
3414+
in: path
3415+
required: true
3416+
schema:
3417+
type: string
3418+
format: uuid
3419+
title: Licensed Item Checkout Id
3420+
responses:
3421+
'200':
3422+
description: Successful Response
3423+
content:
3424+
application/json:
3425+
schema:
3426+
$ref: '#/components/schemas/Envelope_LicensedItemPurchaseGet_'
3427+
'404':
3428+
content:
3429+
application/json:
3430+
schema:
3431+
$ref: '#/components/schemas/EnvelopedError'
3432+
description: Not Found
3433+
'403':
3434+
content:
3435+
application/json:
3436+
schema:
3437+
$ref: '#/components/schemas/EnvelopedError'
3438+
description: Forbidden
3439+
'402':
3440+
content:
3441+
application/json:
3442+
schema:
3443+
$ref: '#/components/schemas/EnvelopedError'
3444+
description: Payment Required
3445+
'400':
3446+
content:
3447+
application/json:
3448+
schema:
3449+
$ref: '#/components/schemas/EnvelopedError'
3450+
description: Bad Request
33363451
/v0/projects/{project_uuid}/checkpoint/{ref_id}/iterations:
33373452
get:
33383453
tags:

services/web/server/src/simcore_service_webserver/folders/_folders_db.py

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

1111
import sqlalchemy as sa
1212
from aiohttp import web
13-
from common_library.unset import UnSet, as_dict_exclude_unset
13+
from common_library.exclude import UnSet, as_dict_exclude_unset
1414
from models_library.folders import (
1515
FolderDB,
1616
FolderID,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from datetime import datetime
2+
from typing import NamedTuple
3+
4+
from models_library.basic_types import IDStr
5+
from models_library.licensed_items import LicensedItemID
6+
from models_library.products import ProductName
7+
from models_library.resource_tracker_licensed_items_checkouts import (
8+
LicensedItemCheckoutID,
9+
)
10+
from models_library.rest_base import RequestParameters, StrictRequestParameters
11+
from models_library.rest_ordering import (
12+
OrderBy,
13+
OrderDirection,
14+
create_ordering_query_model_class,
15+
)
16+
from models_library.rest_pagination import PageQueryParameters
17+
from models_library.users import UserID
18+
from models_library.wallets import WalletID
19+
from pydantic import BaseModel, PositiveInt
20+
21+
22+
class LicensedItemCheckoutGet(BaseModel):
23+
licensed_item_checkout_id: LicensedItemCheckoutID
24+
licensed_item_id: LicensedItemID
25+
wallet_id: WalletID
26+
user_id: UserID
27+
product_name: ProductName
28+
started_at: datetime
29+
stopped_at: datetime | None
30+
num_of_seats: int
31+
32+
33+
class LicensedItemCheckoutGetPage(NamedTuple):
34+
items: list[LicensedItemCheckoutGet]
35+
total: PositiveInt
36+
37+
38+
class LicensedItemCheckoutPathParams(StrictRequestParameters):
39+
licensed_item_checkout_id: LicensedItemCheckoutID
40+
41+
42+
_LicensedItemsCheckoutsListOrderQueryParams: type[
43+
RequestParameters
44+
] = create_ordering_query_model_class(
45+
ordering_fields={
46+
"started_at",
47+
},
48+
default=OrderBy(field=IDStr("started_at"), direction=OrderDirection.DESC),
49+
)
50+
51+
52+
class LicensedItemsCheckoutsListQueryParams(
53+
PageQueryParameters,
54+
_LicensedItemsCheckoutsListOrderQueryParams, # type: ignore[misc, valid-type]
55+
):
56+
...

0 commit comments

Comments
 (0)