3
3
from typing import Generic , List , Optional , Type , TypeVar , get_args
4
4
5
5
from sqlalchemy .orm import Session
6
+ from sqlalchemy .sql .elements import ColumnClause
6
7
from sqlmodel import col
7
8
from structlog import WriteLogger
8
9
15
16
16
17
class BaseRepository (Generic [GenericEntity ], ABC ):
17
18
"""Abstract base class for all repositories"""
19
+
18
20
_default_excluded_keys = ["_sa_instance_state" ]
19
21
20
22
def __init__ (self , logger : Optional [WriteLogger ] = None , sensitive_attribute_keys : Optional [list [str ]] = None ):
@@ -45,20 +47,31 @@ def find(self, **kwargs) -> List[GenericEntity]:
45
47
46
48
Returns:
47
49
List[GenericEntity]: The entities that were found in the repository for the given filters
48
-
50
+
49
51
Notes:
50
52
- Success log is covered by get_batch
51
53
"""
52
54
filters = []
53
55
self ._emit_operation_begin_log ("Finding" , ** kwargs )
54
-
55
- for key , value in kwargs .items ():
56
- try :
57
- filters .append (col (getattr (self .entity , key )) == value )
58
- except AttributeError as attribute_error :
59
- raise EntityDoesNotPossessAttributeException (f"Entity { self .entity } does not have the attribute { key } " ) from attribute_error
56
+ filters = self ._create_filters (** kwargs )
60
57
return self .get_batch (filters = filters )
61
58
59
+ def find_one (self , ** kwargs ) -> GenericEntity :
60
+ """Get a single entity with one query by filters
61
+
62
+ Args:
63
+ **kwargs: The filters to apply
64
+
65
+ Returns:
66
+ GenericEntity: The entity that was found in the repository for the given filters
67
+ """
68
+ session = self .get_session ()
69
+ self ._emit_operation_begin_log ("Finding one" , ** kwargs )
70
+ filters = self ._create_filters (** kwargs )
71
+ result = session .query (self .entity ).filter (* filters ).one ()
72
+ self ._emit_operation_success_log ("Finding one" , entities = [result ])
73
+ return result
74
+
62
75
def update (self , entity : GenericEntity , ** kwargs ) -> GenericEntity :
63
76
"""Updates an entity with the given attributes (keyword arguments) if they are not None
64
77
@@ -89,7 +102,7 @@ def update(self, entity: GenericEntity, **kwargs) -> GenericEntity:
89
102
90
103
session .commit ()
91
104
session .refresh (entity )
92
-
105
+
93
106
self ._emit_operation_success_log ("Updating" , entities = [entity ])
94
107
return entity
95
108
@@ -141,7 +154,7 @@ def get(self, entity_id: int) -> GenericEntity:
141
154
result = session .query (self .entity ).filter (self .entity .id == entity_id ).one_or_none ()
142
155
if result is None :
143
156
raise EntityNotFoundException (f"Entity { GenericEntity .__name__ } with ID { entity_id } not found" )
144
-
157
+
145
158
self ._emit_operation_success_log ("Getting" , entities = [result ])
146
159
return result
147
160
@@ -162,7 +175,7 @@ def get_batch(self, filters: Optional[list] = None) -> list[GenericEntity]:
162
175
self ._emit_operation_begin_log ("Batch get" )
163
176
164
177
result = session .query (self .entity ).filter (* filters ).all ()
165
-
178
+
166
179
self ._emit_operation_success_log ("Batch get" , entities = result )
167
180
return result
168
181
@@ -185,8 +198,8 @@ def create(self, entity: GenericEntity) -> GenericEntity:
185
198
session .add (entity )
186
199
session .commit ()
187
200
session .refresh (entity )
188
-
189
- self ._emit_operation_success_log ("Creating" , entities = [entity ])
201
+
202
+ self ._emit_operation_success_log ("Creating" , entities = [entity ])
190
203
return entity
191
204
except Exception as exception :
192
205
session .rollback ()
@@ -215,7 +228,7 @@ def create_batch(self, entities: list[GenericEntity]) -> list[GenericEntity]:
215
228
216
229
for entity in entities :
217
230
session .refresh (entity )
218
-
231
+
219
232
self ._emit_operation_success_log ("Batch creating" , entities = entities )
220
233
return entities
221
234
@@ -237,7 +250,7 @@ def delete(self, entity: GenericEntity) -> GenericEntity:
237
250
try :
238
251
session .delete (entity )
239
252
session .commit ()
240
-
253
+
241
254
self ._emit_operation_success_log ("Deleting" , entities = [entity ])
242
255
return entity
243
256
except Exception as exception :
@@ -266,6 +279,23 @@ def delete_batch(self, entities: list[GenericEntity]) -> None:
266
279
session .rollback ()
267
280
raise CouldNotDeleteEntityException from exception
268
281
282
+ def _create_filters (self , ** kwargs ) -> list [ColumnClause ]:
283
+ """Creates a list of filters for a query
284
+
285
+ Args:
286
+ **kwargs: The filters to build
287
+
288
+ Returns:
289
+ list: The filters to apply to a query
290
+ """
291
+ filters = []
292
+ for key , value in kwargs .items ():
293
+ try :
294
+ filters .append (col (getattr (self .entity , key )) == value )
295
+ except AttributeError as attribute_error :
296
+ raise EntityDoesNotPossessAttributeException (f"Entity { self .entity } does not have the attribute { key } " ) from attribute_error
297
+ return filters
298
+
269
299
def _safe_kwargs (self , prefix : str = "" , ** kwargs ) -> dict [str , str ]:
270
300
"""Filters out sensitive attributes from the log kwargs
271
301
@@ -290,7 +320,7 @@ def _emit_operation_success_log(self, operation: str, entities: Optional[list[Ge
290
320
entity_ids = [entity .id for entity in entities ]
291
321
entity_log : dict = {"entity_ids" : entity_ids }
292
322
self .logger .debug (f"{ operation } { self .entity .__name__ } succeeded" , ** entity_log )
293
- except Exception as exception : # pylint: disable=broad-except:
323
+ except Exception as exception : # pylint: disable=broad-except:
294
324
# We want to catch all exceptions here. Logs must be written by all means. It's no silent passing and thereby acceptable.
295
325
self .logger .exception (f"Could not emit log for concluding { operation } { self .entity .__name__ } " , exception = exception ) # type: ignore TODO: fix this
296
326
0 commit comments