sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

🐛 Fix `RuntimeError: dictionary changed size during iteration` in `sqlmodel_update()`

Open BartSchuurmans opened this issue 1 year ago • 3 comments

Fixes the issue described in https://github.com/tiangolo/sqlmodel/discussions/982

BartSchuurmans avatar Jul 01 '24 15:07 BartSchuurmans

@alejsdev

May this PR be merged?

birdhackor avatar Aug 15 '24 09:08 birdhackor

Problems

  • RuntimeError risk: previous implementation mutated dictionaries during iteration, leading to
    RuntimeError: dictionary changed size during iteration.
  • Inconsistent paths: separate logic for dict vs. BaseModel increased 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.

AlePiccin avatar Aug 21 '25 15:08 AlePiccin

@YuriiMotov Thanks for your response, I've added (and slightly modified) the test you suggested.

BartSchuurmans avatar Aug 21 '25 16:08 BartSchuurmans