From 5c1487c24af6d3e269f2828c61e22e0bf83aa7bb Mon Sep 17 00:00:00 2001 From: d3vyce <44915747+d3vyce@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:40:18 +0200 Subject: [PATCH] fix: suppress UPDATE callbacks for objects deleted in the same transaction (#205) --- src/fastapi_toolsets/models/watched.py | 7 +++++++ tests/test_models.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/fastapi_toolsets/models/watched.py b/src/fastapi_toolsets/models/watched.py index 35774d1..ce4bed0 100644 --- a/src/fastapi_toolsets/models/watched.py +++ b/src/fastapi_toolsets/models/watched.py @@ -231,6 +231,13 @@ class EventSession(AsyncSession): k: v for k, v in field_changes.items() if k not in transient_ids } + # Suppress updates for deleted objects (row is gone, refresh would fail). + if deletes and field_changes: + deleted_ids = {id(o) for o, _ in deletes} + field_changes = { + k: v for k, v in field_changes.items() if k not in deleted_ids + } + # Suppress updates for newly created objects (CREATE-only semantics). if creates and field_changes: create_ids = {id(o) for o in creates} diff --git a/tests/test_models.py b/tests/test_models.py index b3fd2d8..95741ea 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1041,6 +1041,25 @@ class TestTransientObject: assert len(creates) == 1 assert len(deletes) == 1 + @pytest.mark.anyio + async def test_update_then_delete_suppresses_update_callback(self, mixin_session): + """UPDATE callback is suppressed when the object is also deleted in the same transaction.""" + obj = WatchedModel(status="initial", other="x") + mixin_session.add(obj) + await mixin_session.commit() + + _test_events.clear() + + obj.status = "changed" + await mixin_session.flush() + await mixin_session.delete(obj) + await mixin_session.commit() + + updates = [e for e in _test_events if e["event"] == "update"] + deletes = [e for e in _test_events if e["event"] == "delete"] + assert updates == [] + assert len(deletes) == 1 + class TestPolymorphism: """Event dispatch with STI (Single Table Inheritance)."""