Skip to content

Commit 09003df

Browse files
committed
merge master into 6948-expose-licensing-endpoints-in-api-server
2 parents 6c3682c + 4659473 commit 09003df

File tree

67 files changed

+1998
-545
lines changed

Some content is hidden

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

67 files changed

+1998
-545
lines changed

api/specs/web-server/_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
response_model=Envelope[Union[EmailTestFailed, EmailTestPassed]],
2929
)
3030
async def test_email(
31-
_test: TestEmail, x_simcore_products_name: str | None = Header(default=None)
31+
_body: TestEmail, x_simcore_products_name: str | None = Header(default=None)
3232
):
3333
# X-Simcore-Products-Name
3434
...

api/specs/web-server/_groups.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# pylint: disable=too-many-arguments
55

66

7+
from enum import Enum
78
from typing import Annotated, Any
89

910
from fastapi import APIRouter, Depends, status
@@ -87,19 +88,24 @@ async def delete_group(_path: Annotated[GroupsPathParams, Depends()]):
8788
"""
8889

8990

91+
_extra_tags: list[str | Enum] = ["users"]
92+
93+
9094
@router.get(
9195
"/groups/{gid}/users",
9296
response_model=Envelope[list[GroupUserGet]],
97+
tags=_extra_tags,
9398
)
9499
async def get_all_group_users(_path: Annotated[GroupsPathParams, Depends()]):
95100
"""
96-
Gets users in organization groups
101+
Gets users in organization or primary groups
97102
"""
98103

99104

100105
@router.post(
101106
"/groups/{gid}/users",
102107
status_code=status.HTTP_204_NO_CONTENT,
108+
tags=_extra_tags,
103109
)
104110
async def add_group_user(
105111
_path: Annotated[GroupsPathParams, Depends()],
@@ -113,6 +119,7 @@ async def add_group_user(
113119
@router.get(
114120
"/groups/{gid}/users/{uid}",
115121
response_model=Envelope[GroupUserGet],
122+
tags=_extra_tags,
116123
)
117124
async def get_group_user(
118125
_path: Annotated[GroupsUsersPathParams, Depends()],
@@ -125,6 +132,7 @@ async def get_group_user(
125132
@router.patch(
126133
"/groups/{gid}/users/{uid}",
127134
response_model=Envelope[GroupUserGet],
135+
tags=_extra_tags,
128136
)
129137
async def update_group_user(
130138
_path: Annotated[GroupsUsersPathParams, Depends()],
@@ -138,6 +146,7 @@ async def update_group_user(
138146
@router.delete(
139147
"/groups/{gid}/users/{uid}",
140148
status_code=status.HTTP_204_NO_CONTENT,
149+
tags=_extra_tags,
141150
)
142151
async def delete_group_user(
143152
_path: Annotated[GroupsUsersPathParams, Depends()],
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/_users.py

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# pylint: disable=too-many-arguments
55

66

7+
from enum import Enum
78
from typing import Annotated
89

910
from fastapi import APIRouter, Depends, status
@@ -13,8 +14,10 @@
1314
MyProfilePatch,
1415
MyTokenCreate,
1516
MyTokenGet,
17+
UserForAdminGet,
1618
UserGet,
17-
UsersSearchQueryParams,
19+
UsersForAdminSearchQueryParams,
20+
UsersSearch,
1821
)
1922
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
2023
from models_library.generics import Envelope
@@ -29,7 +32,7 @@
2932
from simcore_service_webserver.users._notifications_rest import _NotificationPathParams
3033
from simcore_service_webserver.users._tokens_rest import _TokenPathParams
3134

32-
router = APIRouter(prefix=f"/{API_VTAG}", tags=["user"])
35+
router = APIRouter(prefix=f"/{API_VTAG}", tags=["users"])
3336

3437

3538
@router.get(
@@ -44,7 +47,7 @@ async def get_my_profile():
4447
"/me",
4548
status_code=status.HTTP_204_NO_CONTENT,
4649
)
47-
async def update_my_profile(_profile: MyProfilePatch):
50+
async def update_my_profile(_body: MyProfilePatch):
4851
...
4952

5053

@@ -54,7 +57,7 @@ async def update_my_profile(_profile: MyProfilePatch):
5457
deprecated=True,
5558
description="Use PATCH instead",
5659
)
57-
async def replace_my_profile(_profile: MyProfilePatch):
60+
async def replace_my_profile(_body: MyProfilePatch):
5861
...
5962

6063

@@ -64,7 +67,7 @@ async def replace_my_profile(_profile: MyProfilePatch):
6467
)
6568
async def set_frontend_preference(
6669
preference_id: PreferenceIdentifier,
67-
body_item: PatchRequestBody,
70+
_body: PatchRequestBody,
6871
):
6972
...
7073

@@ -82,23 +85,25 @@ async def list_tokens():
8285
response_model=Envelope[MyTokenGet],
8386
status_code=status.HTTP_201_CREATED,
8487
)
85-
async def create_token(_token: MyTokenCreate):
88+
async def create_token(_body: MyTokenCreate):
8689
...
8790

8891

8992
@router.get(
9093
"/me/tokens/{service}",
9194
response_model=Envelope[MyTokenGet],
9295
)
93-
async def get_token(_params: Annotated[_TokenPathParams, Depends()]):
96+
async def get_token(
97+
_path: Annotated[_TokenPathParams, Depends()],
98+
):
9499
...
95100

96101

97102
@router.delete(
98103
"/me/tokens/{service}",
99104
status_code=status.HTTP_204_NO_CONTENT,
100105
)
101-
async def delete_token(_params: Annotated[_TokenPathParams, Depends()]):
106+
async def delete_token(_path: Annotated[_TokenPathParams, Depends()]):
102107
...
103108

104109

@@ -114,7 +119,9 @@ async def list_user_notifications():
114119
"/me/notifications",
115120
status_code=status.HTTP_204_NO_CONTENT,
116121
)
117-
async def create_user_notification(_notification: UserNotificationCreate):
122+
async def create_user_notification(
123+
_body: UserNotificationCreate,
124+
):
118125
...
119126

120127

@@ -123,8 +130,8 @@ async def create_user_notification(_notification: UserNotificationCreate):
123130
status_code=status.HTTP_204_NO_CONTENT,
124131
)
125132
async def mark_notification_as_read(
126-
_params: Annotated[_NotificationPathParams, Depends()],
127-
_notification: UserNotificationPatch,
133+
_path: Annotated[_NotificationPathParams, Depends()],
134+
_body: UserNotificationPatch,
128135
):
129136
...
130137

@@ -137,24 +144,43 @@ async def list_user_permissions():
137144
...
138145

139146

140-
@router.get(
147+
#
148+
# USERS public
149+
#
150+
151+
152+
@router.post(
141153
"/users:search",
142154
response_model=Envelope[list[UserGet]],
143-
tags=[
144-
"po",
145-
],
155+
description="Search among users who are publicly visible to the caller (i.e., me) based on their privacy settings.",
146156
)
147-
async def search_users(_params: Annotated[UsersSearchQueryParams, Depends()]):
157+
async def search_users(_body: UsersSearch):
158+
...
159+
160+
161+
#
162+
# USERS admin
163+
#
164+
165+
_extra_tags: list[str | Enum] = ["admin"]
166+
167+
168+
@router.get(
169+
"/admin/users:search",
170+
response_model=Envelope[list[UserForAdminGet]],
171+
tags=_extra_tags,
172+
)
173+
async def search_users_for_admin(
174+
_query: Annotated[UsersForAdminSearchQueryParams, Depends()]
175+
):
148176
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
149177
...
150178

151179

152180
@router.post(
153-
"/users:pre-register",
154-
response_model=Envelope[UserGet],
155-
tags=[
156-
"po",
157-
],
181+
"/admin/users:pre-register",
182+
response_model=Envelope[UserForAdminGet],
183+
tags=_extra_tags,
158184
)
159-
async def pre_register_user(_body: PreRegisteredUserGet):
185+
async def pre_register_user_for_admin(_body: PreRegisteredUserGet):
160186
...

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) == {}

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from ..users import UserID, UserNameID
3131
from ..utils.common_validators import create__check_only_one_is_set__root_validator
32-
from ._base import InputSchema, OutputSchema
32+
from ._base import InputSchema, OutputSchema, OutputSchemaWithoutCamelCase
3333

3434
S = TypeVar("S", bound=BaseModel)
3535

@@ -248,8 +248,7 @@ def from_model(
248248
)
249249

250250

251-
class GroupUserGet(BaseModel):
252-
# OutputSchema
251+
class GroupUserGet(OutputSchemaWithoutCamelCase):
253252

254253
# Identifiers
255254
id: Annotated[UserID | None, Field(description="the user's id")] = None
@@ -275,7 +274,14 @@ class GroupUserGet(BaseModel):
275274
] = None
276275

277276
# Access Rights
278-
access_rights: GroupAccessRights = Field(..., alias="accessRights")
277+
access_rights: Annotated[
278+
GroupAccessRights | None,
279+
Field(
280+
alias="accessRights",
281+
description="If group is standard, these are these are the access rights of the user to it."
282+
"None if primary group.",
283+
),
284+
] = None
279285

280286
model_config = ConfigDict(
281287
populate_by_name=True,
@@ -293,7 +299,23 @@ class GroupUserGet(BaseModel):
293299
"write": False,
294300
"delete": False,
295301
},
296-
}
302+
},
303+
"examples": [
304+
# unique member on a primary group with two different primacy settings
305+
{
306+
"id": "16",
307+
"userName": "mrprivate",
308+
"gid": "55",
309+
},
310+
{
311+
"id": "56",
312+
"userName": "mrpublic",
313+
"login": "[email protected]",
314+
"first_name": "Mr",
315+
"last_name": "Public",
316+
"gid": "42",
317+
},
318+
],
297319
},
298320
)
299321

0 commit comments

Comments
 (0)