11import logging
2+ from datetime import datetime
3+ from typing import Callable , cast
24
3- import sqlalchemy as sa
45from aiohttp import web
6+ from common_library .exclude import UnSet , is_set
7+ from models_library .basic_types import IDStr
58from models_library .groups import GroupID
69from models_library .projects import ProjectID
10+ from models_library .rest_ordering import OrderBy , OrderDirection
11+ from models_library .rest_pagination import MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE
12+ from pydantic import NonNegativeInt , PositiveInt
713from simcore_postgres_database .models .projects import projects
814from simcore_postgres_database .models .users import users
915from simcore_postgres_database .utils_repos import (
2935 ProjectDBGet ,
3036)
3137
38+ _OLDEST_TRASHED_FIRST = OrderBy (field = IDStr ("trashed" ), direction = OrderDirection .ASC )
39+
40+
41+ def _to_expression (order_by : OrderBy ):
42+ direction_func : Callable = {
43+ OrderDirection .ASC : sql .asc ,
44+ OrderDirection .DESC : sql .desc ,
45+ }[order_by .direction ]
46+ return direction_func (projects .columns [order_by .field ])
47+
48+
49+ async def list_trashed_projects (
50+ app : web .Application ,
51+ connection : AsyncConnection | None = None ,
52+ * ,
53+ # filter
54+ trashed_explicitly : bool | UnSet = UnSet .VALUE ,
55+ trashed_before : datetime | UnSet = UnSet .VALUE ,
56+ # pagination
57+ offset : NonNegativeInt = 0 ,
58+ limit : PositiveInt = MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE ,
59+ # order
60+ order_by : OrderBy = _OLDEST_TRASHED_FIRST ,
61+ ) -> tuple [int , list [ProjectDBGet ]]:
62+
63+ base_query = sql .select (PROJECT_DB_COLS ).where (projects .c .trashed .is_not (None ))
64+
65+ if is_set (trashed_explicitly ):
66+ assert isinstance (trashed_explicitly , bool ) # nosec
67+ base_query = base_query .where (
68+ projects .c .trashed_explicitly .is_ (trashed_explicitly )
69+ )
70+
71+ if is_set (trashed_before ):
72+ assert isinstance (trashed_before , datetime ) # nosec
73+ base_query = base_query .where (projects .c .trashed < trashed_before )
74+
75+ # Select total count from base_query
76+ count_query = sql .select (sql .func .count ()).select_from (base_query .subquery ())
77+
78+ # Ordering and pagination
79+ list_query = (
80+ base_query .order_by (_to_expression (order_by )).offset (offset ).limit (limit )
81+ )
82+
83+ async with pass_or_acquire_connection (get_asyncpg_engine (app ), connection ) as conn :
84+ total_count = await conn .scalar (count_query )
85+
86+ result = await conn .stream (list_query )
87+ folders : list [ProjectDBGet ] = [
88+ ProjectDBGet .model_validate (row ) async for row in result
89+ ]
90+ return cast (int , total_count ), folders
91+
3292
3393async def patch_project (
3494 app : web .Application ,
@@ -41,7 +101,7 @@ async def patch_project(
41101 async with transaction_context (get_asyncpg_engine (app ), connection ) as conn :
42102 result = await conn .stream (
43103 projects .update ()
44- .values (last_change_date = sa .func .now (), ** new_partial_project_data )
104+ .values (last_change_date = sql .func .now (), ** new_partial_project_data )
45105 .where (projects .c .uuid == f"{ project_uuid } " )
46106 .returning (* PROJECT_DB_COLS )
47107 )
@@ -52,7 +112,7 @@ async def patch_project(
52112
53113
54114def _select_trashed_by_primary_gid_query () -> sql .Select :
55- return sa .select (
115+ return sql .select (
56116 projects .c .uuid ,
57117 users .c .primary_gid .label ("trashed_by_primary_gid" ),
58118 ).select_from (projects .outerjoin (users , projects .c .trashed_by == users .c .id ))
@@ -97,7 +157,7 @@ async def batch_get_trashed_by_primary_gid(
97157 ).order_by (
98158 # Preserves the order of folders_ids
99159 # SEE https://docs.sqlalchemy.org/en/20/core/sqlelement.html#sqlalchemy.sql.expression.case
100- sa .case (
160+ sql .case (
101161 {
102162 project_uuid : index
103163 for index , project_uuid in enumerate (projects_uuids_str )
0 commit comments