11import logging
2- from typing import Annotated , Any , Literal , TypeAlias , cast
2+ from datetime import datetime
3+ from typing import Annotated , Any , Literal , TypeAlias , TypedDict , cast
34
45import sqlalchemy as sa
56from annotated_types import doc
67from common_library .exclude import Unset , is_unset
78from common_library .users_enums import AccountRequestStatus
9+ from models_library .list_operations import OrderDirection
810from models_library .products import ProductName
911from models_library .users import (
1012 UserID ,
@@ -433,7 +435,43 @@ async def search_merged_pre_and_registered_users(
433435
434436
435437OrderKeys : TypeAlias = Literal ["email" , "current_status_created" ]
436- OrderDirs : TypeAlias = Literal ["asc" , "desc" ]
438+
439+
440+ class MergedUserData (TypedDict , total = False ):
441+ """Type definition for merged user data returned by list_merged_pre_and_registered_users."""
442+
443+ # Pre-registration specific fields
444+ id : int | None # pre-registration ID
445+ pre_reg_user_id : int | None # user_id from pre-registration table
446+ institution : str | None
447+ address : str | None
448+ city : str | None
449+ state : str | None
450+ postal_code : str | None
451+ country : str | None
452+ extras : dict [str , Any ] | None
453+ account_request_status : AccountRequestStatus | None
454+ account_request_reviewed_by : int | None
455+ account_request_reviewed_at : datetime | None
456+ created_by : int | None
457+ account_request_reviewed_by_username : str | None
458+
459+ # Common fields (from either pre-registration or users table)
460+ email : str
461+ first_name : str | None
462+ last_name : str | None
463+ phone : str | None
464+ created : datetime | None
465+ current_status_created : datetime
466+
467+ # User table specific fields
468+ user_id : int | None # actual user ID from users table
469+ user_name : str | None
470+ user_primary_group_id : int | None
471+ status : str | None # UserStatus
472+
473+ # Computed fields
474+ is_pre_registered : bool
437475
438476
439477@validate_call (config = {"arbitrary_types_allowed" : True })
@@ -452,12 +490,13 @@ async def list_merged_pre_and_registered_users(
452490 pagination_limit : int = 50 ,
453491 pagination_offset : int = 0 ,
454492 order_by : Annotated [
455- list [tuple [OrderKeys , OrderDirs ]] | None ,
493+ list [tuple [OrderKeys , OrderDirection ]] | None ,
456494 doc (
457- 'Valid fields: "email", "current_status_created". Default: [("email", "asc"), ("is_pre_registered", "desc"), ("current_status_created", "desc")]'
495+ 'Valid fields: "email", "current_status_created". '
496+ 'Default: [("email", OrderDirection.ASC), ("is_pre_registered", OrderDirection.DESC), ("current_status_created", OrderDirection.DESC)]'
458497 ),
459498 ] = None ,
460- ) -> tuple [list [dict [ str , Any ] ], int ]:
499+ ) -> tuple [list [MergedUserData ], int ]:
461500 """Retrieves and merges users from both users and pre-registration tables.
462501
463502 This returns:
@@ -633,34 +672,34 @@ async def list_merged_pre_and_registered_users(
633672 result = await conn .execute (final_query )
634673 records = result .mappings ().all ()
635674
636- return cast (list [dict [ str , Any ] ], records ), total_count
675+ return cast (list [MergedUserData ], records ), total_count
637676
638677
639678def _build_ordering_clauses_for_filtered_query (
640679 query : sa .sql .Select ,
641- order_by : list [tuple [OrderKeys , OrderDirs ]] | None = None ,
680+ order_by : list [tuple [OrderKeys , OrderDirection ]] | None = None ,
642681) -> list [sa .sql .ColumnElement ]:
643682 """Build ORDER BY clauses for filtered query (no DISTINCT ON constraints)."""
644- _ordering_criteria : list [tuple [str , OrderDirs ]] = []
683+ _ordering_criteria : list [tuple [str , OrderDirection ]] = []
645684
646685 if order_by is None :
647686 # Default ordering
648687 _ordering_criteria = [
649- ("email" , "asc" ),
650- ("is_pre_registered" , "desc" ),
651- ("current_status_created" , "desc" ),
688+ ("email" , OrderDirection . ASC ),
689+ ("is_pre_registered" , OrderDirection . DESC ),
690+ ("current_status_created" , OrderDirection . DESC ),
652691 ]
653692 else :
654693 _ordering_criteria = list (order_by )
655694 # Always append is_pre_registered prioritization for custom ordering
656695 if not any (field == "is_pre_registered" for field , _ in order_by ):
657- _ordering_criteria .append (("is_pre_registered" , "desc" ))
696+ _ordering_criteria .append (("is_pre_registered" , OrderDirection . DESC ))
658697
659698 order_by_clauses = []
660699 for field , direction in _ordering_criteria :
661700 # Get column from the query's selected columns
662701 column = next (col for col in query .selected_columns if col .name == field )
663- if direction == "asc" :
702+ if direction == OrderDirection . ASC :
664703 order_by_clauses .append (column .asc ())
665704 else :
666705 order_by_clauses .append (column .desc ())
0 commit comments