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,39 @@ async def get_function(
347351 return RegisteredFunctionDB .model_validate (row )
348352
349353
354+ @staticmethod
355+ def _create_list_functions_attributes_filters (
356+ * ,
357+ filter_by_function_class : FunctionClass | None ,
358+ search_by_multi_columns : str | None ,
359+ search_by_function_title : str | None ,
360+ ) -> list [ColumnElement ]:
361+ attributes_filters : list [ColumnElement ] = []
362+
363+ if filter_by_function_class is not None :
364+ attributes_filters .append (
365+ functions_table .c .function_class == filter_by_function_class .value
366+ )
367+
368+ if search_by_multi_columns is not None :
369+ attributes_filters .append (
370+ (functions_table .c .title .ilike (f"%{ search_by_multi_columns } %" ))
371+ | (functions_table .c .description .ilike (f"%{ search_by_multi_columns } %" ))
372+ | (
373+ cast (functions_table .c .uuid , String ).ilike (
374+ f"%{ search_by_multi_columns } %"
375+ )
376+ )
377+ )
378+
379+ if search_by_function_title is not None :
380+ attributes_filters .append (
381+ functions_table .c .title .like (f"%{ search_by_function_title } %" )
382+ )
383+
384+ return attributes_filters
385+
386+
350387async def list_functions (
351388 app : web .Application ,
352389 connection : AsyncConnection | None = None ,
@@ -355,7 +392,12 @@ async def list_functions(
355392 product_name : ProductName ,
356393 pagination_limit : int ,
357394 pagination_offset : int ,
395+ order_by : OrderBy | None = None ,
396+ filter_by_function_class : FunctionClass | None = None ,
397+ search_by_multi_columns : str | None = None ,
398+ search_by_function_title : str | None = None ,
358399) -> tuple [list [RegisteredFunctionDB ], PageMetaInfoLimitOffset ]:
400+
359401 async with pass_or_acquire_connection (get_asyncpg_engine (app ), connection ) as conn :
360402 await check_user_api_access_rights (
361403 app ,
@@ -365,39 +407,59 @@ async def list_functions(
365407 api_access_rights = [FunctionsApiAccessRights .READ_FUNCTIONS ],
366408 )
367409 user_groups = await list_all_user_groups_ids (app , user_id = user_id )
410+ attributes_filters = _create_list_functions_attributes_filters (
411+ filter_by_function_class = filter_by_function_class ,
412+ search_by_multi_columns = search_by_multi_columns ,
413+ search_by_function_title = search_by_function_title ,
414+ )
368415
369- subquery = (
370- functions_access_rights_table .select ()
371- .with_only_columns (functions_access_rights_table .c .function_uuid )
416+ # Build the base query with join to access rights table
417+ base_query = (
418+ functions_table .select ()
419+ .join (
420+ functions_access_rights_table ,
421+ functions_table .c .uuid == functions_access_rights_table .c .function_uuid ,
422+ )
372423 .where (
373424 functions_access_rights_table .c .group_id .in_ (user_groups ),
374425 functions_access_rights_table .c .product_name == product_name ,
375426 functions_access_rights_table .c .read ,
427+ * attributes_filters ,
376428 )
377429 )
378430
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 ))
431+ # Get total count
432+ total_count = await conn .scalar (
433+ func .count ().select ().select_from (base_query .subquery ())
384434 )
385- if total_count_result == 0 :
435+ if total_count == 0 :
386436 return [], PageMetaInfoLimitOffset (
387437 total = 0 , offset = pagination_offset , limit = pagination_limit , count = 0
388438 )
439+
440+ if order_by is None :
441+ order_by = DEFAULT_ORDER_BY
442+ # Apply ordering and pagination
443+ if order_by .direction == OrderDirection .ASC :
444+ base_query = base_query .order_by (
445+ sqlalchemy .asc (getattr (functions_table .c , order_by .field )),
446+ functions_table .c .uuid ,
447+ )
448+ else :
449+ base_query = base_query .order_by (
450+ sqlalchemy .desc (getattr (functions_table .c , order_by .field )),
451+ functions_table .c .uuid ,
452+ )
453+
389454 function_rows = [
390455 RegisteredFunctionDB .model_validate (row )
391456 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 )
457+ base_query .offset (pagination_offset ).limit (pagination_limit )
396458 )
397459 ]
398460
399461 return function_rows , PageMetaInfoLimitOffset (
400- total = total_count_result ,
462+ total = total_count ,
401463 offset = pagination_offset ,
402464 limit = pagination_limit ,
403465 count = len (function_rows ),
0 commit comments