🐛 Fix `RuntimeError: dictionary changed size during iteration` in `sqlmodel_update()`
Fixes the issue described in https://github.com/tiangolo/sqlmodel/discussions/982
@alejsdev
May this PR be merged?
Problems
- RuntimeError risk: previous implementation mutated dictionaries during iteration, leading to
RuntimeError: dictionary changed size during iteration. - Inconsistent paths: separate logic for
dictvs.BaseModelincreased code complexity and risk of subtle bugs. - Over-filtering blocked relationships: early field filtering prevented relationship attributes from being updated.
- No opt-in safety: there was no simple way to restrict updates strictly to model fields when desired.
- Recursive models issue: as discussed in sqlmodel#823, previous approaches could fail or behave inconsistently when dealing with nested/recursive models.
The Change
def sqlmodel_update(
self: _TSQLModel,
obj: Union[Dict[str, Any], BaseModel],
*,
update: Union[Dict[str, Any], None] = None,
update_only_fields: bool = False,
) -> _TSQLModel:
update_dict = {**dict(obj), **(update or {})}
if update_only_fields:
update_dict = {k: v for k, v in update_dict.items() if k in get_model_fields(self)}
for key, value in update_dict.items():
setattr(self, key, value)
return self
Why it fixes
Removes mutation during iteration: builds a single update_dict before looping, eliminating RuntimeError.
Unified update logic: normalizes inputs and applies changes in a single pass.
Relationship-friendly: by default, allows updating relationship attributes without unnecessary filtering.
Optional strict mode: update_only_fields=True ensures only valid model fields are updated, providing safe opt-in filtering.
@YuriiMotov Thanks for your response, I've added (and slightly modified) the test you suggested.