66
77import sqlalchemy
88from aiohttp import web
9+ from models_library .basic_types import IDStr
910from models_library .functions import (
1011 FunctionAccessRightsDB ,
1112 FunctionClass ,
5455)
5556from models_library .groups import GroupID
5657from models_library .products import ProductName
58+ from models_library .rest_ordering import OrderBy , OrderDirection
5759from models_library .rest_pagination import PageMetaInfoLimitOffset
5860from models_library .users import UserID
5961from pydantic import TypeAdapter
8486 pass_or_acquire_connection ,
8587 transaction_context ,
8688)
87- from sqlalchemy import Text , cast
89+ from sqlalchemy import String , Text , cast
8890from sqlalchemy .engine .row import Row
8991from sqlalchemy .ext .asyncio import AsyncConnection
90- from sqlalchemy .sql import func
92+ from sqlalchemy .sql import ColumnElement , func
9193
9294from ..db .plugin import get_asyncpg_engine
9395from ..groups .api import list_all_user_groups_ids
110112 function_job_collections_access_rights_table , FunctionJobCollectionAccessRightsDB
111113)
112114
115+ DEFAULT_ORDER_BY = OrderBy (field = IDStr ("modified" ), direction = OrderDirection .DESC )
116+
113117
114118async def create_function ( # noqa: PLR0913
115119 app : web .Application ,
@@ -347,6 +351,38 @@ async def get_function(
347351 return RegisteredFunctionDB .model_validate (row )
348352
349353
354+ def _create_list_functions_attributes_filters (
355+ * ,
356+ filter_by_function_class : FunctionClass | None ,
357+ search_by_multi_columns : str | None ,
358+ search_by_function_title : str | None ,
359+ ) -> list [ColumnElement ]:
360+ attributes_filters : list [ColumnElement ] = []
361+
362+ if filter_by_function_class is not None :
363+ attributes_filters .append (
364+ functions_table .c .function_class == filter_by_function_class .value
365+ )
366+
367+ if search_by_multi_columns is not None :
368+ attributes_filters .append (
369+ (functions_table .c .title .ilike (f"%{ search_by_multi_columns } %" ))
370+ | (functions_table .c .description .ilike (f"%{ search_by_multi_columns } %" ))
371+ | (
372+ cast (functions_table .c .uuid , String ).ilike (
373+ f"%{ search_by_multi_columns } %"
374+ )
375+ )
376+ )
377+
378+ if search_by_function_title is not None :
379+ attributes_filters .append (
380+ functions_table .c .title .ilike (f"%{ search_by_function_title } %" )
381+ )
382+
383+ return attributes_filters
384+
385+
350386async def list_functions (
351387 app : web .Application ,
352388 connection : AsyncConnection | None = None ,
@@ -355,7 +391,12 @@ async def list_functions(
355391 product_name : ProductName ,
356392 pagination_limit : int ,
357393 pagination_offset : int ,
394+ order_by : OrderBy | None = None ,
395+ filter_by_function_class : FunctionClass | None = None ,
396+ search_by_multi_columns : str | None = None ,
397+ search_by_function_title : str | None = None ,
358398) -> tuple [list [RegisteredFunctionDB ], PageMetaInfoLimitOffset ]:
399+
359400 async with pass_or_acquire_connection (get_asyncpg_engine (app ), connection ) as conn :
360401 await check_user_api_access_rights (
361402 app ,
@@ -365,39 +406,59 @@ async def list_functions(
365406 api_access_rights = [FunctionsApiAccessRights .READ_FUNCTIONS ],
366407 )
367408 user_groups = await list_all_user_groups_ids (app , user_id = user_id )
409+ attributes_filters = _create_list_functions_attributes_filters (
410+ filter_by_function_class = filter_by_function_class ,
411+ search_by_multi_columns = search_by_multi_columns ,
412+ search_by_function_title = search_by_function_title ,
413+ )
368414
369- subquery = (
370- functions_access_rights_table .select ()
371- .with_only_columns (functions_access_rights_table .c .function_uuid )
415+ # Build the base query with join to access rights table
416+ base_query = (
417+ functions_table .select ()
418+ .join (
419+ functions_access_rights_table ,
420+ functions_table .c .uuid == functions_access_rights_table .c .function_uuid ,
421+ )
372422 .where (
373423 functions_access_rights_table .c .group_id .in_ (user_groups ),
374424 functions_access_rights_table .c .product_name == product_name ,
375425 functions_access_rights_table .c .read ,
426+ * attributes_filters ,
376427 )
377428 )
378429
379- total_count_result = await conn .scalar (
380- func .count ()
381- .select ()
382- .select_from (functions_table )
383- .where (functions_table .c .uuid .in_ (subquery ))
430+ # Get total count
431+ total_count = await conn .scalar (
432+ func .count ().select ().select_from (base_query .subquery ())
384433 )
385- if total_count_result == 0 :
434+ if total_count == 0 :
386435 return [], PageMetaInfoLimitOffset (
387436 total = 0 , offset = pagination_offset , limit = pagination_limit , count = 0
388437 )
438+
439+ if order_by is None :
440+ order_by = DEFAULT_ORDER_BY
441+ # Apply ordering and pagination
442+ if order_by .direction == OrderDirection .ASC :
443+ base_query = base_query .order_by (
444+ sqlalchemy .asc (getattr (functions_table .c , order_by .field )),
445+ functions_table .c .uuid ,
446+ )
447+ else :
448+ base_query = base_query .order_by (
449+ sqlalchemy .desc (getattr (functions_table .c , order_by .field )),
450+ functions_table .c .uuid ,
451+ )
452+
389453 function_rows = [
390454 RegisteredFunctionDB .model_validate (row )
391455 async for row in await conn .stream (
392- functions_table .select ()
393- .where (functions_table .c .uuid .in_ (subquery ))
394- .offset (pagination_offset )
395- .limit (pagination_limit )
456+ base_query .offset (pagination_offset ).limit (pagination_limit )
396457 )
397458 ]
398459
399460 return function_rows , PageMetaInfoLimitOffset (
400- total = total_count_result ,
461+ total = total_count ,
401462 offset = pagination_offset ,
402463 limit = pagination_limit ,
403464 count = len (function_rows ),
0 commit comments