@@ -46,6 +46,7 @@ class ColumnInfo:
4646 serialize: Function to transform Python values before database storage
4747 deserialize: Function to transform database values back to Python objects
4848 insert_only: Whether this field should only be set on INSERT, not UPDATE in upsert operations
49+ replace_ignore: Whether this field should be ignored for `replace_multiple`
4950
5051 Example:
5152 >>> from dataclasses import dataclass
@@ -72,6 +73,7 @@ class ColumnInfo:
7273 serialize : Optional [Callable [[Any ], Any ]] = None
7374 deserialize : Optional [Callable [[Any ], Any ]] = None
7475 insert_only : Optional [bool ] = None
76+ replace_ignore : Optional [bool ] = None
7577
7678 def __post_init__ (self , constraints : Union [str , Iterable [str ], None ]) -> None :
7779 if constraints is not None :
@@ -98,6 +100,9 @@ def merge(a: "ColumnInfo", b: "ColumnInfo") -> "ColumnInfo":
98100 serialize = b .serialize if b .serialize is not None else a .serialize ,
99101 deserialize = b .deserialize if b .deserialize is not None else a .deserialize ,
100102 insert_only = b .insert_only if b .insert_only is not None else a .insert_only ,
103+ replace_ignore = (
104+ b .replace_ignore if b .replace_ignore is not None else a .replace_ignore
105+ ),
101106 )
102107
103108
@@ -118,6 +123,7 @@ class ConcreteColumnInfo:
118123 serialize: Optional serialization function
119124 deserialize: Optional deserialization function
120125 insert_only: Whether this field should only be set on INSERT, not UPDATE
126+ replace_ignore: Whether this field should be ignored for `replace_multiple`
121127 """
122128
123129 field : Field
@@ -126,9 +132,10 @@ class ConcreteColumnInfo:
126132 create_type : str
127133 nullable : bool
128134 constraints : tuple [str , ...]
129- serialize : Optional [Callable [[Any ], Any ]] = None
130- deserialize : Optional [Callable [[Any ], Any ]] = None
131- insert_only : bool = False
135+ serialize : Optional [Callable [[Any ], Any ]]
136+ deserialize : Optional [Callable [[Any ], Any ]]
137+ insert_only : bool
138+ replace_ignore : bool
132139
133140 @staticmethod
134141 def from_column_info (
@@ -163,6 +170,7 @@ def from_column_info(
163170 serialize = info .serialize ,
164171 deserialize = info .deserialize ,
165172 insert_only = bool (info .insert_only ),
173+ replace_ignore = bool (info .replace_ignore ),
166174 )
167175
168176 def create_table_string (self ) -> str :
@@ -386,6 +394,20 @@ def insert_only_field_names(cls) -> set[str]:
386394 },
387395 )
388396
397+ @classmethod
398+ def replace_ignore_field_names (cls ) -> set [str ]:
399+ """Get set of field names marked as replace_ignore in ColumnInfo.
400+
401+ Returns:
402+ Set of field names that should be ignored for `replace_multiple`
403+ """
404+ return cls ._cached (
405+ ("replace_ignore_field_names" ,),
406+ lambda : {
407+ ci .field .name for ci in cls .column_info ().values () if ci .replace_ignore
408+ },
409+ )
410+
389411 @classmethod
390412 def field_names_sql (
391413 cls ,
@@ -1366,7 +1388,8 @@ async def plan_replace_multiple(
13661388 """
13671389 # For comparison purposes, combine auto-detected insert_only fields with manual ones
13681390 all_insert_only = cls .insert_only_field_names () | set (insert_only )
1369- ignore = sorted (set (ignore ) | all_insert_only )
1391+ default_ignore = cls .replace_ignore_field_names () - set (force_update )
1392+ ignore = sorted (set (ignore ) | default_ignore | all_insert_only )
13701393 equal_ignoring = cls ._cached (
13711394 ("equal_ignoring" , tuple (ignore )),
13721395 lambda : cls ._get_equal_ignoring_fn (ignore ),
0 commit comments