Skip to content

Commit 9903871

Browse files
committed
Merge branch 'add-functions-locust-test' of github.com:bisgaard-itis/osparc-simcore into add-functions-locust-test
2 parents fb14d90 + 431b34e commit 9903871

File tree

65 files changed

+3997
-744
lines changed

Some content is hidden

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

65 files changed

+3997
-744
lines changed

api/specs/web-server/_auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ async def email_confirmation(code: str):
260260

261261
@router.get(
262262
"/auth/captcha",
263-
operation_id="request_captcha",
263+
operation_id="create_captcha",
264264
status_code=status.HTTP_200_OK,
265265
responses={status.HTTP_200_OK: {"content": {"image/png": {}}}},
266266
)
267-
async def request_captcha(): ...
267+
async def create_captcha(): ...

api/specs/web-server/_users.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,25 @@
77
from enum import Enum
88
from typing import Annotated
99

10+
from _common import as_query
1011
from fastapi import APIRouter, Depends, status
1112
from models_library.api_schemas_webserver.users import (
1213
MyPermissionGet,
1314
MyProfileGet,
1415
MyProfilePatch,
1516
MyTokenCreate,
1617
MyTokenGet,
17-
UserForAdminGet,
18+
UserAccountApprove,
19+
UserAccountGet,
20+
UserAccountReject,
21+
UserAccountSearchQueryParams,
1822
UserGet,
19-
UsersForAdminSearchQueryParams,
23+
UsersAccountListQueryParams,
2024
UsersSearch,
2125
)
2226
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
2327
from models_library.generics import Envelope
28+
from models_library.rest_pagination import Page
2429
from models_library.user_preferences import PreferenceIdentifier
2530
from simcore_service_webserver._meta import API_VTAG
2631
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
@@ -144,20 +149,46 @@ async def search_users(_body: UsersSearch): ...
144149

145150

146151
@router.get(
147-
"/admin/users:search",
148-
response_model=Envelope[list[UserForAdminGet]],
152+
"/admin/user-accounts",
153+
response_model=Page[UserAccountGet],
149154
tags=_extra_tags,
150155
)
151-
async def search_users_for_admin(
152-
_query: Annotated[UsersForAdminSearchQueryParams, Depends()],
156+
async def list_users_accounts(
157+
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
158+
): ...
159+
160+
161+
@router.post(
162+
"/admin/user-accounts:approve",
163+
status_code=status.HTTP_204_NO_CONTENT,
164+
tags=_extra_tags,
165+
)
166+
async def approve_user_account(_body: UserAccountApprove): ...
167+
168+
169+
@router.post(
170+
"/admin/user-accounts:reject",
171+
status_code=status.HTTP_204_NO_CONTENT,
172+
tags=_extra_tags,
173+
)
174+
async def reject_user_account(_body: UserAccountReject): ...
175+
176+
177+
@router.get(
178+
"/admin/user-accounts:search",
179+
response_model=Envelope[list[UserAccountGet]],
180+
tags=_extra_tags,
181+
)
182+
async def search_user_accounts(
183+
_query: Annotated[UserAccountSearchQueryParams, Depends()],
153184
):
154185
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
155186
...
156187

157188

158189
@router.post(
159-
"/admin/users:pre-register",
160-
response_model=Envelope[UserForAdminGet],
190+
"/admin/user-accounts:pre-register",
191+
response_model=Envelope[UserAccountGet],
161192
tags=_extra_tags,
162193
)
163-
async def pre_register_user_for_admin(_body: PreRegisteredUserGet): ...
194+
async def pre_register_user_account(_body: PreRegisteredUserGet): ...

packages/models-library/src/models_library/api_schemas_catalog/services.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,23 +172,57 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
172172
)
173173

174174

175-
class _BaseServiceGetV2(CatalogOutputSchema):
176-
# Model used in catalog's rpc and rest interfaces
175+
class ServiceSummary(CatalogOutputSchema):
177176
key: ServiceKey
178177
version: ServiceVersion
179-
180178
name: str
181-
thumbnail: HttpUrl | None = None
182-
icon: HttpUrl | None = None
183179
description: str
184180

185-
description_ui: bool = False
186-
187181
version_display: str | None = None
188182

183+
contact: LowerCaseEmailStr | None
184+
185+
@staticmethod
186+
def _update_json_schema_extra(schema: JsonDict) -> None:
187+
schema.update(
188+
{
189+
"examples": [
190+
{
191+
"key": _EXAMPLE_SLEEPER["key"],
192+
"version": _EXAMPLE_SLEEPER["version"],
193+
"name": _EXAMPLE_SLEEPER["name"],
194+
"description": _EXAMPLE_SLEEPER["description"],
195+
"version_display": _EXAMPLE_SLEEPER["version_display"],
196+
"contact": _EXAMPLE_SLEEPER["contact"],
197+
},
198+
{
199+
"key": _EXAMPLE_FILEPICKER["key"],
200+
"version": _EXAMPLE_FILEPICKER["version"],
201+
"name": _EXAMPLE_FILEPICKER["name"],
202+
"description": _EXAMPLE_FILEPICKER["description"],
203+
"version_display": None,
204+
"contact": _EXAMPLE_FILEPICKER["contact"],
205+
},
206+
]
207+
}
208+
)
209+
210+
model_config = ConfigDict(
211+
extra="ignore",
212+
populate_by_name=True,
213+
alias_generator=snake_to_camel,
214+
json_schema_extra=_update_json_schema_extra,
215+
)
216+
217+
218+
class _BaseServiceGetV2(ServiceSummary):
189219
service_type: Annotated[ServiceType, Field(alias="type")]
190220

191-
contact: LowerCaseEmailStr | None
221+
thumbnail: HttpUrl | None = None
222+
icon: HttpUrl | None = None
223+
224+
description_ui: bool = False
225+
192226
authors: Annotated[list[Author], Field(min_length=1)]
193227
owner: Annotated[
194228
LowerCaseEmailStr | None,
@@ -217,6 +251,7 @@ class _BaseServiceGetV2(CatalogOutputSchema):
217251
extra="forbid",
218252
populate_by_name=True,
219253
alias_generator=snake_to_camel,
254+
json_schema_extra={"example": _EXAMPLE_SLEEPER},
220255
)
221256

222257

@@ -249,7 +284,6 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
249284

250285

251286
class ServiceGetV2(_BaseServiceGetV2):
252-
# Model used in catalog's rpc and rest interfaces
253287
history: Annotated[
254288
list[ServiceRelease],
255289
Field(
@@ -338,6 +372,9 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
338372
ServiceRelease
339373
]
340374

375+
# Create PageRpc types
376+
PageRpcServiceSummary = PageRpc[ServiceSummary]
377+
341378
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
342379

343380

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import re
2-
from datetime import date
2+
from datetime import date, datetime
33
from enum import Enum
44
from typing import Annotated, Any, Literal, Self
55

66
import annotated_types
77
from common_library.basic_types import DEFAULT_FACTORY
88
from common_library.dict_tools import remap_keys
9-
from common_library.users_enums import UserStatus
9+
from common_library.users_enums import AccountRequestStatus, UserStatus
1010
from models_library.groups import AccessRightsDict
11+
from models_library.rest_filters import Filters
12+
from models_library.rest_pagination import PageQueryParameters
1113
from pydantic import (
1214
ConfigDict,
1315
EmailStr,
@@ -39,6 +41,7 @@
3941
OutputSchemaWithoutCamelCase,
4042
)
4143
from .groups import MyGroupsGet
44+
from .products import InvitationGenerate
4245
from .users_preferences import AggregatedPreferences
4346

4447
#
@@ -238,7 +241,30 @@ def from_domain_model(cls, data):
238241
return cls.model_validate(data, from_attributes=True)
239242

240243

241-
class UsersForAdminSearchQueryParams(RequestParameters):
244+
class UsersForAdminListFilter(Filters):
245+
# 1. account_request_status: PENDING, REJECTED, APPROVED
246+
# 2. If APPROVED AND user uses the invitation link, then when user is registered,
247+
# it can be in any of these statuses:
248+
# CONFIRMATION_PENDING, ACTIVE, EXPIRED, BANNED, DELETED
249+
#
250+
review_status: Literal["PENDING", "REVIEWED"] | None = None
251+
252+
model_config = ConfigDict(extra="forbid")
253+
254+
255+
class UsersAccountListQueryParams(UsersForAdminListFilter, PageQueryParameters): ...
256+
257+
258+
class UserAccountApprove(InputSchema):
259+
email: EmailStr
260+
invitation: InvitationGenerate | None = None
261+
262+
263+
class UserAccountReject(InputSchema):
264+
email: EmailStr
265+
266+
267+
class UserAccountSearchQueryParams(RequestParameters):
242268
email: Annotated[
243269
str,
244270
Field(
@@ -249,7 +275,7 @@ class UsersForAdminSearchQueryParams(RequestParameters):
249275
]
250276

251277

252-
class UserForAdminGet(OutputSchema):
278+
class UserAccountGet(OutputSchema):
253279
# ONLY for admins
254280
first_name: str | None
255281
last_name: str | None
@@ -269,8 +295,12 @@ class UserForAdminGet(OutputSchema):
269295
),
270296
] = DEFAULT_FACTORY
271297

272-
# authorization
298+
# pre-registration
299+
pre_registration_id: int | None
273300
invited_by: str | None = None
301+
account_request_status: AccountRequestStatus | None
302+
account_request_reviewed_by: UserID | None = None
303+
account_request_reviewed_at: datetime | None = None
274304

275305
# user status
276306
registered: bool

packages/pytest-simcore/src/pytest_simcore/helpers/catalog_rpc_server.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
LatestServiceGet,
1414
ServiceGetV2,
1515
ServiceListFilters,
16+
ServiceSummary,
1617
ServiceUpdateV2,
1718
)
1819
from models_library.api_schemas_catalog.services_ports import ServicePortGet
@@ -200,6 +201,66 @@ async def get_service_ports(
200201
ServicePortGet.model_json_schema()["examples"],
201202
)
202203

204+
@validate_call(config={"arbitrary_types_allowed": True})
205+
async def list_all_services_summaries_paginated(
206+
self,
207+
rpc_client: RabbitMQRPCClient | MockType,
208+
*,
209+
product_name: ProductName,
210+
user_id: UserID,
211+
limit: PageLimitInt,
212+
offset: NonNegativeInt,
213+
filters: ServiceListFilters | None = None,
214+
):
215+
assert rpc_client
216+
assert product_name
217+
assert user_id
218+
219+
service_summaries = TypeAdapter(list[ServiceSummary]).validate_python(
220+
ServiceSummary.model_json_schema()["examples"],
221+
)
222+
if filters:
223+
filtered_summaries = []
224+
for summary in service_summaries:
225+
# Match service type if specified
226+
if (
227+
filters.service_type
228+
and {
229+
ServiceType.COMPUTATIONAL: "/comp/",
230+
ServiceType.DYNAMIC: "/dynamic/",
231+
}[filters.service_type]
232+
not in summary.key
233+
):
234+
continue
235+
236+
# Match service key pattern if specified
237+
if filters.service_key_pattern and not fnmatch.fnmatch(
238+
summary.key, filters.service_key_pattern
239+
):
240+
continue
241+
242+
# Match version display pattern if specified
243+
if filters.version_display_pattern and (
244+
summary.version_display is None
245+
or not fnmatch.fnmatch(
246+
summary.version_display, filters.version_display_pattern
247+
)
248+
):
249+
continue
250+
251+
filtered_summaries.append(summary)
252+
253+
service_summaries = filtered_summaries
254+
255+
total_count = len(service_summaries)
256+
257+
return PageRpc[ServiceSummary].create(
258+
service_summaries[offset : offset + limit],
259+
total=total_count,
260+
limit=limit,
261+
offset=offset,
262+
)
263+
203264

204265
@dataclass
205266
class ZeroListingCatalogRpcSideEffects:
@@ -216,3 +277,11 @@ async def list_my_service_history_latest_first(self, *args, **kwargs):
216277
limit=10,
217278
offset=0,
218279
)
280+
281+
async def list_all_services_summaries_paginated(self, *args, **kwargs):
282+
return PageRpc[ServiceSummary].create(
283+
[],
284+
total=0,
285+
limit=10,
286+
offset=0,
287+
)

packages/service-library/src/servicelib/aiohttp/rest_responses.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
from common_library.error_codes import ErrorCodeStr
99
from common_library.json_serialization import json_dumps
1010
from models_library.rest_error import ErrorGet, ErrorItemType
11-
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
1211

1312
from ..aiohttp.status import HTTP_200_OK
1413
from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
14+
from ..rest_constants import RESPONSE_MODEL_POLICY
1515
from ..rest_responses import is_enveloped
1616
from ..status_codes_utils import get_code_description
1717

@@ -54,7 +54,7 @@ def create_http_error(
5454
http_error_cls: type[HTTPError] = web.HTTPInternalServerError,
5555
*,
5656
skip_internal_error_details: bool = False,
57-
error_code: ErrorCodeStr | None = None
57+
error_code: ErrorCodeStr | None = None,
5858
) -> HTTPError:
5959
"""
6060
- Response body conforms OAS schema model
@@ -92,7 +92,8 @@ def create_http_error(
9292
)
9393

9494
return http_error_cls(
95-
reason=reason,
95+
# Multiline not allowed in HTTP reason
96+
reason=reason.replace("\n", " ") if reason else None,
9697
text=json_dumps(
9798
payload,
9899
),

0 commit comments

Comments
 (0)