@@ -526,10 +526,7 @@ async def get_or_upsert(
526526 When using match_fields and actual model values differ from ``kwargs``, the
527527 model value will be updated.
528528 """
529- match_fields = match_fields or self .match_fields
530- if isinstance (match_fields , str ):
531- match_fields = [match_fields ]
532- if match_fields :
529+ if match_fields := self ._get_match_fields (match_fields = match_fields ):
533530 match_filter = {
534531 field_name : kwargs .get (field_name , None )
535532 for field_name in match_fields
@@ -601,10 +598,7 @@ async def get_and_update(
601598 Raises:
602599 NotFoundError: If no instance found identified by `item_id`.
603600 """
604- match_fields = match_fields or self .match_fields
605- if isinstance (match_fields , str ):
606- match_fields = [match_fields ]
607- if match_fields :
601+ if match_fields := self ._get_match_fields (match_fields = match_fields ):
608602 match_filter = {
609603 field_name : kwargs .get (field_name , None )
610604 for field_name in match_fields
@@ -932,10 +926,7 @@ async def upsert(
932926 Raises:
933927 NotFoundError: If no instance found with same identifier as `data`.
934928 """
935- match_fields = match_fields or self .match_fields
936- if isinstance (match_fields , str ):
937- match_fields = [match_fields ]
938- if match_fields :
929+ if match_fields := self ._get_match_fields (match_fields = match_fields ):
939930 match_filter = {
940931 field_name : getattr (data , field_name , None )
941932 for field_name in match_fields
@@ -1013,29 +1004,37 @@ async def upsert_many(
10131004 instances : list [ModelT ] = []
10141005 data_to_update : list [ModelT ] = []
10151006 data_to_insert : list [ModelT ] = []
1016- match_fields = match_fields or self .match_fields
1017- if isinstance (match_fields , str ):
1018- match_fields = [match_fields ]
1019- match_filter : list [FilterTypes | ColumnElement [bool ]] = [
1020- CollectionFilter (
1021- field_name = self .id_attribute ,
1022- values = [getattr (datum , self .id_attribute ) for datum in data if datum is not None ] if data else None ,
1023- ),
1024- ]
1007+ match_fields = self ._get_match_fields (match_fields = match_fields )
1008+ if match_fields is None :
1009+ match_fields = [self .id_attribute ]
1010+ match_filter : list [FilterTypes | ColumnElement [bool ]] = []
10251011 if match_fields :
10261012 for field_name in match_fields :
10271013 field = get_instrumented_attr (self .model_type , field_name )
1028- matched_values = [getattr (datum , field_name ) for datum in data if datum is not None ]
1014+ matched_values = [
1015+ field_data for datum in data if (field_data := getattr (datum , field_name )) is not None
1016+ ]
10291017 if self ._prefer_any :
10301018 match_filter .append (any_ (matched_values ) == field ) # type: ignore[arg-type]
10311019 else :
10321020 match_filter .append (field .in_ (matched_values ))
10331021
10341022 with wrap_sqlalchemy_exception ():
1035- existing_objs = await self .list (* match_filter , auto_expunge = False )
1036- existing_ids = [getattr (datum , self .id_attribute ) for datum in existing_objs if datum is not None ]
1023+ existing_objs = await self .list (
1024+ * match_filter ,
1025+ auto_expunge = False ,
1026+ )
1027+ for field_name in match_fields :
1028+ field = get_instrumented_attr (self .model_type , field_name )
1029+ matched_values = [getattr (datum , field_name ) for datum in existing_objs if datum is not None ]
1030+ if self ._prefer_any :
1031+ match_filter .append (any_ (matched_values ) == field ) # type: ignore[arg-type]
1032+ else :
1033+ match_filter .append (field .in_ (matched_values ))
1034+ existing_ids = self ._get_object_ids (existing_objs = existing_objs )
1035+ data = self ._merge_on_match_fields (data , existing_objs , match_fields )
10371036 for datum in data :
1038- if getattr (datum , self .id_attribute ) is not None in existing_ids :
1037+ if getattr (datum , self .id_attribute , None ) in existing_ids :
10391038 data_to_update .append (datum )
10401039 else :
10411040 data_to_insert .append (datum )
@@ -1052,6 +1051,38 @@ async def upsert_many(
10521051 self ._expunge (instance , auto_expunge = auto_expunge )
10531052 return instances
10541053
1054+ def _get_object_ids (self , existing_objs : list [ModelT ]) -> list [Any ]:
1055+ return [obj_id for datum in existing_objs if (obj_id := getattr (datum , self .id_attribute )) is not None ]
1056+
1057+ def _get_match_fields (
1058+ self ,
1059+ match_fields : list [str ] | str | None = None ,
1060+ id_attribute : str | None = None ,
1061+ ) -> list [str ] | None :
1062+ id_attribute = id_attribute or self .id_attribute
1063+ match_fields = match_fields or self .match_fields
1064+ if isinstance (match_fields , str ):
1065+ match_fields = [match_fields ]
1066+ return match_fields
1067+
1068+ def _merge_on_match_fields (
1069+ self ,
1070+ data : list [ModelT ],
1071+ existing_data : list [ModelT ],
1072+ match_fields : list [str ] | str | None = None ,
1073+ ) -> list [ModelT ]:
1074+ match_fields = self ._get_match_fields (match_fields = match_fields )
1075+ if match_fields is None :
1076+ match_fields = [self .id_attribute ]
1077+ for existing_datum in existing_data :
1078+ for row_id , datum in enumerate (data ):
1079+ match = all (
1080+ getattr (datum , field_name ) == getattr (existing_datum , field_name ) for field_name in match_fields
1081+ )
1082+ if match and getattr (existing_datum , self .id_attribute ) is not None :
1083+ setattr (data [row_id ], self .id_attribute , getattr (existing_datum , self .id_attribute ))
1084+ return data
1085+
10551086 async def list (
10561087 self ,
10571088 * filters : FilterTypes | ColumnElement [bool ],
0 commit comments