Skip to content

Commit bcd0ff7

Browse files
feat: add function list
1 parent 2dc7497 commit bcd0ff7

File tree

5 files changed

+157
-20
lines changed

5 files changed

+157
-20
lines changed

api/specs/web-server/_functions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

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.functions import (
1213
FunctionToRegister,
@@ -16,7 +17,9 @@
1617
from models_library.generics import Envelope
1718
from simcore_service_webserver._meta import API_VTAG
1819
from simcore_service_webserver.functions._controller._functions_rest_schemas import (
20+
FunctionGetQueryParams,
1921
FunctionPathParams,
22+
FunctionsListQueryParams,
2023
)
2124

2225
router = APIRouter(
@@ -40,7 +43,9 @@ async def register_function(
4043
"/functions",
4144
response_model=Envelope[list[RegisteredFunctionGet]],
4245
)
43-
async def list_functions(): ...
46+
async def list_functions(
47+
_query: Annotated[as_query(FunctionsListQueryParams), Depends()],
48+
): ...
4449

4550

4651
@router.get(
@@ -49,6 +54,7 @@ async def list_functions(): ...
4954
)
5055
async def get_function(
5156
_path: Annotated[FunctionPathParams, Depends()],
57+
_query: Annotated[as_query(FunctionGetQueryParams), Depends()],
5258
): ...
5359

5460

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

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3348,44 +3348,66 @@ paths:
33483348
$ref: '#/components/schemas/EnvelopedError'
33493349
description: Service Unavailable
33503350
/v0/functions:
3351-
get:
3352-
tags:
3353-
- functions
3354-
summary: List Functions
3355-
operationId: list_functions
3356-
responses:
3357-
'200':
3358-
description: Successful Response
3359-
content:
3360-
application/json:
3361-
schema:
3362-
$ref: '#/components/schemas/Envelope_list_Annotated_Union_RegisteredProjectFunctionGet__RegisteredSolverFunctionGet___FieldInfo_annotation_NoneType__required_True__discriminator__function_class_____'
33633351
post:
33643352
tags:
33653353
- functions
33663354
summary: Register Function
33673355
operationId: register_function
33683356
requestBody:
3357+
required: true
33693358
content:
33703359
application/json:
33713360
schema:
33723361
oneOf:
33733362
- $ref: '#/components/schemas/ProjectFunctionToRegister'
33743363
- $ref: '#/components/schemas/SolverFunctionToRegister'
3375-
title: ' Body'
33763364
discriminator:
33773365
propertyName: functionClass
33783366
mapping:
33793367
PROJECT: '#/components/schemas/ProjectFunctionToRegister'
33803368
SOLVER: '#/components/schemas/SolverFunctionToRegister'
3381-
required: true
3369+
title: ' Body'
33823370
responses:
33833371
'200':
33843372
description: Successful Response
33853373
content:
33863374
application/json:
33873375
schema:
33883376
$ref: '#/components/schemas/Envelope_Annotated_Union_RegisteredProjectFunctionGet__RegisteredSolverFunctionGet___FieldInfo_annotation_NoneType__required_True__discriminator__function_class____'
3377+
get:
3378+
tags:
3379+
- functions
3380+
summary: List Functions
3381+
operationId: list_functions
3382+
parameters:
3383+
- name: include_extras
3384+
in: query
3385+
required: false
3386+
schema:
3387+
type: boolean
3388+
default: false
3389+
title: Include Extras
3390+
- name: limit
3391+
in: query
3392+
required: false
3393+
schema:
3394+
type: integer
3395+
default: 20
3396+
title: Limit
3397+
- name: offset
3398+
in: query
3399+
required: false
3400+
schema:
3401+
type: integer
3402+
default: 0
3403+
title: Offset
3404+
responses:
3405+
'200':
3406+
description: Successful Response
3407+
content:
3408+
application/json:
3409+
schema:
3410+
$ref: '#/components/schemas/Envelope_list_Annotated_Union_RegisteredProjectFunctionGet__RegisteredSolverFunctionGet___FieldInfo_annotation_NoneType__required_True__discriminator__function_class_____'
33893411
/v0/functions/{function_id}:
33903412
get:
33913413
tags:
@@ -3400,6 +3422,27 @@ paths:
34003422
type: string
34013423
format: uuid
34023424
title: Function Id
3425+
- name: include_extras
3426+
in: query
3427+
required: false
3428+
schema:
3429+
type: boolean
3430+
default: false
3431+
title: Include Extras
3432+
- name: limit
3433+
in: query
3434+
required: false
3435+
schema:
3436+
type: integer
3437+
default: 20
3438+
title: Limit
3439+
- name: offset
3440+
in: query
3441+
required: false
3442+
schema:
3443+
type: integer
3444+
default: 0
3445+
title: Offset
34033446
responses:
34043447
'200':
34053448
description: Successful Response

services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rest.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
RegisteredProjectFunctionGet,
99
)
1010
from models_library.api_schemas_webserver.users import MyFunctionPermissionsGet
11-
from models_library.functions import FunctionClass
11+
from models_library.functions import FunctionClass, RegisteredProjectFunction
12+
from models_library.rest_pagination import Page
13+
from models_library.rest_pagination_utils import paginate_data
1214
from pydantic import TypeAdapter
1315
from servicelib.aiohttp import status
1416
from servicelib.aiohttp.requests_validation import (
1517
handle_validation_as_http_error,
1618
parse_request_path_parameters_as,
19+
parse_request_query_parameters_as,
1720
)
1821

1922
from ..._meta import API_VTAG as VTAG
@@ -24,7 +27,11 @@
2427
from ...utils_aiohttp import envelope_json_response
2528
from .. import _functions_service
2629
from ._functions_rest_exceptions import handle_rest_requests_exceptions
27-
from ._functions_rest_schemas import FunctionPathParams
30+
from ._functions_rest_schemas import (
31+
FunctionGetQueryParams,
32+
FunctionPathParams,
33+
FunctionsListQueryParams,
34+
)
2835

2936
routes = web.RouteTableDef()
3037

@@ -68,7 +75,68 @@ async def register_function(request: web.Request) -> web.Response:
6875
@permission_required("function.read")
6976
@handle_rest_requests_exceptions
7077
async def list_functions(request: web.Request) -> web.Response:
71-
raise NotImplementedError
78+
query_params: FunctionsListQueryParams = parse_request_query_parameters_as(
79+
FunctionsListQueryParams, request
80+
)
81+
82+
req_ctx = AuthenticatedRequestContext.model_validate(request)
83+
functions, page_meta_info = await _functions_service.list_functions(
84+
request.app,
85+
user_id=req_ctx.user_id,
86+
product_name=req_ctx.product_name,
87+
pagination_limit=query_params.limit,
88+
pagination_offset=query_params.offset,
89+
)
90+
91+
chunk = []
92+
93+
if query_params.include_extras:
94+
project_ids = []
95+
for function in functions:
96+
if function.function_class == FunctionClass.PROJECT:
97+
assert isinstance(function, RegisteredProjectFunction)
98+
project_ids.append(function.project_id)
99+
100+
projects = await _projects_service.batch_get_projects(
101+
request.app,
102+
project_uuids=project_ids,
103+
)
104+
projects_map = {f"{p.uuid}": p for p in projects}
105+
106+
for function in functions:
107+
if (
108+
query_params.include_extras
109+
and function.function_class == FunctionClass.PROJECT
110+
):
111+
assert isinstance(function, RegisteredProjectFunction) # nosec
112+
project = projects_map.get(f"{function.project_id}")
113+
if project:
114+
chunk.append(
115+
TypeAdapter(RegisteredProjectFunctionGet).validate_python(
116+
function.model_dump(mode="json")
117+
| {
118+
"thumbnail": project.thumbnail,
119+
"template_id": project.id,
120+
}
121+
)
122+
)
123+
else:
124+
chunk.append(
125+
TypeAdapter(RegisteredFunctionGet).validate_python(
126+
function.model_dump(mode="json")
127+
)
128+
)
129+
130+
page = Page[RegisteredFunctionGet].model_validate(
131+
paginate_data(
132+
chunk=chunk,
133+
request_url=request.url,
134+
total=page_meta_info.total,
135+
limit=query_params.limit,
136+
offset=query_params.offset,
137+
)
138+
)
139+
return envelope_json_response(page)
72140

73141

74142
@routes.get(
@@ -82,6 +150,10 @@ async def get_function(request: web.Request) -> web.Response:
82150
path_params = parse_request_path_parameters_as(FunctionPathParams, request)
83151
function_id = path_params.function_id
84152

153+
query_params: FunctionGetQueryParams = parse_request_query_parameters_as(
154+
FunctionGetQueryParams, request
155+
)
156+
85157
req_ctx = AuthenticatedRequestContext.model_validate(request)
86158
registered_function: RegisteredFunction = await _functions_service.get_function(
87159
app=request.app,
@@ -90,7 +162,10 @@ async def get_function(request: web.Request) -> web.Response:
90162
product_name=req_ctx.product_name,
91163
)
92164

93-
if registered_function.function_class == FunctionClass.PROJECT:
165+
if (
166+
query_params.include_extras
167+
and registered_function.function_class == FunctionClass.PROJECT
168+
):
94169
assert isinstance(registered_function, RegisteredProjectFunctionGet) # nosec
95170

96171
project_dict = await _projects_service.get_project_for_user(

services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rest_schemas.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from models_library.functions import FunctionID
2+
from models_library.rest_pagination import PageQueryParameters
23
from pydantic import BaseModel, ConfigDict
34

45
from ...models import AuthenticatedRequestContext
@@ -11,4 +12,14 @@ class FunctionPathParams(BaseModel):
1112
model_config = ConfigDict(populate_by_name=True, extra="forbid")
1213

1314

15+
class _FunctionQueryParams(BaseModel):
16+
include_extras: bool = False
17+
18+
19+
class FunctionGetQueryParams(PageQueryParameters, _FunctionQueryParams): ...
20+
21+
22+
class FunctionsListQueryParams(PageQueryParameters, _FunctionQueryParams): ...
23+
24+
1425
__all__: tuple[str, ...] = ("AuthenticatedRequestContext",)

services/web/server/src/simcore_service_webserver/projects/_projects_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ async def batch_get_project_name(
276276

277277

278278
async def batch_get_projects(
279-
app: web.Application, project_uuids: list[ProjectID]
279+
app: web.Application,
280+
*,
281+
project_uuids: list[ProjectID],
280282
) -> list[ProjectDBGet]:
281283
return await _projects_repository.batch_get_projects(
282284
app=app,

0 commit comments

Comments
 (0)