@@ -94,6 +94,13 @@ class Meta:
9494
9595 oauth_name = UnicodeAttribute (hash_key = True )
9696
97+ class UserIdIndex (GlobalSecondaryIndex ):
98+ class Meta :
99+ index_name = "user_id-index"
100+ projection = AllProjection ()
101+
102+ user_id = GUID (hash_key = True )
103+
97104 if TYPE_CHECKING : # pragma: no cover
98105 id : ID
99106 oauth_name : str
@@ -113,6 +120,7 @@ class Meta:
113120 # Global Secondary Index
114121 account_id_index = AccountIdIndex ()
115122 oauth_name_index = OAuthNameIndex ()
123+ user_id_index = UserIdIndex ()
116124
117125
118126class DynamoDBBaseOAuthAccountTableUUID (DynamoDBBaseOAuthAccountTable [UUID_ID ]):
@@ -140,12 +148,37 @@ def __init__(
140148 self .user_table = user_table
141149 self .oauth_account_table = oauth_account_table
142150
151+ async def _hydrate_oauth_accounts (
152+ self ,
153+ user : UP ,
154+ instant_update : bool = False ,
155+ ) -> UP :
156+ """
157+ Populate the `oauth_accounts` list of a user by querying the OAuth table.
158+ This mimics SQLAlchemy's lazy relationship loading.
159+ """
160+ if self .oauth_account_table is None :
161+ return user
162+ await ensure_tables_exist (self .oauth_account_table )
163+
164+ user .oauth_accounts = [] # type: ignore
165+
166+ async for oauth_acc in self .oauth_account_table .user_id_index .query ( # type: ignore
167+ user .id ,
168+ consistent_read = instant_update ,
169+ ):
170+ user .oauth_accounts .append (oauth_acc ) # type: ignore
171+
172+ return user
173+
143174 async def get (self , id : ID , instant_update : bool = False ) -> UP | None :
144175 """Get a user by id and hydrate oauth_accounts if available."""
145176 await ensure_tables_exist (self .user_table ) # type: ignore
146177
147178 try :
148- return await self .user_table .get (id , consistent_read = instant_update ) # type: ignore
179+ user = await self .user_table .get (id , consistent_read = instant_update ) # type: ignore
180+ user = await self ._hydrate_oauth_accounts (user , instant_update )
181+ return user
149182 except self .user_table .DoesNotExist : # type: ignore
150183 return None
151184
@@ -157,7 +190,9 @@ async def get_by_email(self, email: str, instant_update: bool = False) -> UP | N
157190 async for user in self .user_table .email_index .query ( # type: ignore
158191 email_lower ,
159192 consistent_read = instant_update ,
193+ limit = 1 ,
160194 ):
195+ user = await self ._hydrate_oauth_accounts (user , instant_update )
161196 return user
162197 return None
163198
@@ -172,18 +207,21 @@ async def get_by_oauth_account(
172207 raise NotImplementedError ()
173208 await ensure_tables_exist (self .user_table , self .oauth_account_table ) # type: ignore
174209
175- async for oauth_acc in self .oauth_account_table .oauth_name_index .query (
176- oauth ,
210+ async for oauth_acc in self .oauth_account_table .account_id_index .query (
211+ account_id ,
177212 consistent_read = instant_update ,
213+ filter_condition = self .oauth_account_table .oauth_name == oauth , # type: ignore
214+ limit = 1 ,
178215 ):
179- if oauth_acc .account_id == account_id :
180- try :
181- return await self .user_table .get ( # type: ignore
182- oauth_acc .user_id ,
183- consistent_read = instant_update ,
184- )
185- except self .user_table .DoesNotExist : # type: ignore # pragma: no cover
186- return None
216+ try :
217+ user = await self .user_table .get ( # type: ignore
218+ oauth_acc .user_id ,
219+ consistent_read = instant_update ,
220+ )
221+ user = await self ._hydrate_oauth_accounts (user , instant_update )
222+ return user
223+ except self .user_table .DoesNotExist : # type: ignore # pragma: no cover
224+ return None
187225 return None
188226
189227 async def create (self , create_dict : dict [str , Any ] | UP ) -> UP :
0 commit comments