1+ from typing import Any
2+
13from aiohttp import web
24from models_library .api_schemas_webserver .functions import (
35 Function ,
46 FunctionToRegister ,
57 RegisteredFunction ,
68 RegisteredFunctionGet ,
79 RegisteredFunctionUpdate ,
8- RegisteredProjectFunctionGet ,
910)
1011from models_library .api_schemas_webserver .users import MyFunctionPermissionsGet
11- from models_library .functions import FunctionClass , RegisteredProjectFunction
12+ from models_library .functions import (
13+ FunctionClass ,
14+ FunctionID ,
15+ RegisteredProjectFunction ,
16+ RegisteredSolverFunction ,
17+ )
1218from models_library .products import ProductName
19+ from models_library .projects import ProjectID
1320from models_library .rest_pagination import Page
1421from models_library .rest_pagination_utils import paginate_data
22+ from models_library .services_types import ServiceKey , ServiceVersion
1523from models_library .users import UserID
1624from pydantic import TypeAdapter
1725from servicelib .aiohttp import status
2937from ...security .decorators import permission_required
3038from ...utils_aiohttp import create_json_response_from_page , envelope_json_response
3139from .. import _functions_service
40+ from .._services_metadata import service as _services_metadata_service
41+ from .._services_metadata .service import ServiceMetadata
3242from ._functions_rest_exceptions import handle_rest_requests_exceptions
3343from ._functions_rest_schemas import (
3444 FunctionGetQueryParams ,
3949routes = web .RouteTableDef ()
4050
4151
42- async def _add_extras_to_project_function (
43- function : RegisteredProjectFunction ,
52+ async def _build_function_access_rights_dict (
4453 app : web .Application ,
4554 user_id : UserID ,
4655 product_name : ProductName ,
47- ) -> dict :
48- assert isinstance (function , RegisteredProjectFunction ) # nosec
56+ function_id : FunctionID ,
57+ ) -> dict [str , Any ]:
58+ access_rights = await _functions_service .get_function_user_permissions (
59+ app = app ,
60+ user_id = user_id ,
61+ product_name = product_name ,
62+ function_id = function_id ,
63+ )
64+
65+ return {
66+ "access_rights" : access_rights .model_dump (),
67+ }
4968
69+
70+ async def _build_project_function_extras_dict (
71+ app : web .Application ,
72+ * ,
73+ user_id : UserID ,
74+ function : RegisteredProjectFunction ,
75+ ) -> dict [str , Any ]:
5076 project_dict = await _projects_service .get_project_for_user (
5177 app = app ,
5278 project_uuid = f"{ function .project_id } " ,
5379 user_id = user_id ,
5480 )
5581
56- function_with_extras = function .model_dump (mode = "json" ) | {
57- "access_rights" : await _functions_service .get_function_user_permissions (
58- app ,
82+ return {
83+ "thumbnail" : project_dict .get ("thumbnail" , None ),
84+ }
85+
86+
87+ async def _build_function_extras (
88+ app : web .Application , user_id : UserID , * , function : RegisteredFunction
89+ ) -> dict [str , Any ]:
90+ extras : dict [str , Any ] = {}
91+ if function .function_class == FunctionClass .PROJECT :
92+ assert isinstance (function , RegisteredProjectFunction )
93+ extras |= await _build_project_function_extras_dict (
94+ function = function ,
95+ app = app ,
5996 user_id = user_id ,
60- product_name = product_name ,
61- function_id = function .uid ,
97+ )
98+ elif function .function_class == FunctionClass .SOLVER :
99+ assert isinstance (function , RegisteredSolverFunction )
100+ extras |= await _build_solver_function_extras_dict (
101+ app ,
102+ function = function ,
103+ )
104+ return extras
105+
106+
107+ async def _build_solver_function_extras_dict (
108+ app : web .Application ,
109+ * ,
110+ function : RegisteredSolverFunction ,
111+ ) -> dict [str , Any ]:
112+ services_metadata = await _services_metadata_service .get_service_metadata (
113+ app ,
114+ key = function .solver_key ,
115+ version = function .solver_version ,
116+ )
117+ return {
118+ "thumbnail" : (
119+ f"{ services_metadata .thumbnail } " if services_metadata .thumbnail else None
62120 ),
63- "thumbnail" : project_dict .get ("thumbnail" , None ),
64121 }
65- return function_with_extras
66122
67123
68124@routes .post (f"/{ VTAG } /functions" , name = "register_function" )
@@ -117,55 +173,76 @@ async def list_functions(request: web.Request) -> web.Response:
117173 )
118174
119175 chunk : list [RegisteredFunctionGet ] = []
120- projects_map : dict [str , ProjectDBGet | None ] = (
176+
177+ projects_cache : dict [ProjectID , ProjectDBGet ] = {}
178+ service_metadata_cache : dict [tuple [ServiceKey , ServiceVersion ], ServiceMetadata ] = (
121179 {}
122- ) # ProjectDBGet has to be renamed at some point!
180+ )
123181
124182 if query_params .include_extras :
125- project_ids = []
126- for function in functions :
127- if function . function_class == FunctionClass . PROJECT :
128- assert isinstance ( function , RegisteredProjectFunction )
129- project_ids . append ( function .project_id )
130-
131- projects_map = {
132- f" { p . uuid } " : p
133- for p in await _projects_service .batch_get_projects (
183+ if any (
184+ function . function_class == FunctionClass . PROJECT for function in functions
185+ ) :
186+ project_uuids = [
187+ function .project_id
188+ for function in functions
189+ if function . function_class == FunctionClass . PROJECT
190+ ]
191+ projects_cache = await _projects_service .batch_get_projects (
134192 request .app ,
135- project_uuids = project_ids ,
193+ project_uuids = project_uuids ,
136194 )
137- }
138-
139- for function in functions :
140- if (
141- query_params .include_extras
142- and function .function_class == FunctionClass .PROJECT
195+ if any (
196+ function .function_class == FunctionClass .SOLVER for function in functions
143197 ):
144- assert isinstance (function , RegisteredProjectFunction ) # nosec
145- if project := projects_map .get (f"{ function .project_id } " ):
146- chunk .append (
147- TypeAdapter (RegisteredProjectFunctionGet ).validate_python (
148- function .model_dump (mode = "json" )
149- | {
150- "access_rights" : await _functions_service .get_function_user_permissions (
151- request .app ,
152- user_id = req_ctx .user_id ,
153- product_name = req_ctx .product_name ,
154- function_id = function .uid ,
155- ),
156- "thumbnail" : (
157- f"{ project .thumbnail } " if project .thumbnail else None
158- ),
159- }
160- )
161- )
162- else :
163- chunk .append (
164- TypeAdapter (RegisteredFunctionGet ).validate_python (
165- function .model_dump (mode = "json" )
198+ service_keys_and_versions = [
199+ (function .solver_key , function .solver_version )
200+ for function in functions
201+ if function .function_class == FunctionClass .SOLVER
202+ ]
203+ service_metadata_cache = (
204+ await _services_metadata_service .batch_get_service_metadata (
205+ app = request .app , keys_and_versions = service_keys_and_versions
166206 )
167207 )
168208
209+ for function in functions :
210+ access_rights = await _build_function_access_rights_dict (
211+ request .app ,
212+ user_id = req_ctx .user_id ,
213+ product_name = req_ctx .product_name ,
214+ function_id = function .uid ,
215+ )
216+
217+ extras : dict [str , Any ] = {}
218+ if query_params .include_extras :
219+ if function .function_class == FunctionClass .PROJECT :
220+ assert isinstance (function , RegisteredProjectFunction ) # nosec
221+ if project := projects_cache .get (function .project_id ):
222+ extras = {
223+ "thumbnail" : (
224+ f"{ project .thumbnail } " if project .thumbnail else None
225+ ),
226+ }
227+ elif function .function_class == FunctionClass .SOLVER :
228+ assert isinstance (function , RegisteredSolverFunction )
229+ if service_metadata := service_metadata_cache .get (
230+ (function .solver_key , function .solver_version )
231+ ):
232+ extras = {
233+ "thumbnail" : (
234+ f"{ service_metadata .thumbnail } "
235+ if service_metadata .thumbnail
236+ else None
237+ ),
238+ }
239+
240+ chunk .append (
241+ TypeAdapter (RegisteredFunctionGet ).validate_python (
242+ function .model_dump () | access_rights | extras
243+ )
244+ )
245+
169246 page = Page [RegisteredFunctionGet ].model_validate (
170247 paginate_data (
171248 chunk = chunk ,
@@ -194,33 +271,29 @@ async def get_function(request: web.Request) -> web.Response:
194271 )
195272
196273 req_ctx = AuthenticatedRequestContext .model_validate (request )
197- registered_function : RegisteredFunction = await _functions_service .get_function (
274+ function = await _functions_service .get_function (
198275 app = request .app ,
199276 function_id = function_id ,
200277 user_id = req_ctx .user_id ,
201278 product_name = req_ctx .product_name ,
202279 )
203280
204- if (
205- query_params .include_extras
206- and registered_function .function_class == FunctionClass .PROJECT
207- ):
208- function_with_extras = await _add_extras_to_project_function (
209- function = registered_function ,
210- app = request .app ,
211- user_id = req_ctx .user_id ,
212- product_name = req_ctx .product_name ,
213- )
281+ access_rights = await _build_function_access_rights_dict (
282+ request .app ,
283+ user_id = req_ctx .user_id ,
284+ product_name = req_ctx .product_name ,
285+ function_id = function_id ,
286+ )
214287
215- return envelope_json_response (
216- TypeAdapter ( RegisteredProjectFunctionGet ). validate_python (
217- function_with_extras
218- )
219- )
288+ extras = (
289+ await _build_function_extras ( request . app , req_ctx . user_id , function = function )
290+ if query_params . include_extras
291+ else {}
292+ )
220293
221294 return envelope_json_response (
222295 TypeAdapter (RegisteredFunctionGet ).validate_python (
223- registered_function .model_dump (mode = "json" )
296+ function .model_dump () | access_rights | extras
224297 )
225298 )
226299
@@ -245,34 +318,30 @@ async def update_function(request: web.Request) -> web.Response:
245318 )
246319 req_ctx = AuthenticatedRequestContext .model_validate (request )
247320
248- updated_function = await _functions_service .update_function (
321+ function = await _functions_service .update_function (
249322 request .app ,
250323 user_id = req_ctx .user_id ,
251324 product_name = req_ctx .product_name ,
252325 function_id = function_id ,
253326 function = function_update ,
254327 )
255328
256- if (
257- query_params .include_extras
258- and updated_function .function_class == FunctionClass .PROJECT
259- ):
260- function_with_extras = await _add_extras_to_project_function (
261- function = updated_function ,
262- app = request .app ,
263- user_id = req_ctx .user_id ,
264- product_name = req_ctx .product_name ,
265- )
329+ access_rights = await _build_function_access_rights_dict (
330+ request .app ,
331+ user_id = req_ctx .user_id ,
332+ product_name = req_ctx .product_name ,
333+ function_id = function_id ,
334+ )
266335
267- return envelope_json_response (
268- TypeAdapter ( RegisteredProjectFunctionGet ). validate_python (
269- function_with_extras
270- )
271- )
336+ extras = (
337+ await _build_function_extras ( request . app , req_ctx . user_id , function = function )
338+ if query_params . include_extras
339+ else {}
340+ )
272341
273342 return envelope_json_response (
274343 TypeAdapter (RegisteredFunctionGet ).validate_python (
275- updated_function .model_dump (mode = "json" )
344+ function .model_dump () | access_rights | extras
276345 )
277346 )
278347
0 commit comments