From 8aa76df5a367d1c3c42cb07e4818f92ea881db59 Mon Sep 17 00:00:00 2001 From: Libby Date: Thu, 12 Nov 2026 03:35:17 +0000 Subject: [PATCH] cultcache-py: an owner's removal is one store call for the one record that exists Co-Authored-By: Claude Sonnet 5.5 --- .../cultcache-py/src/cultcache_py/cache.py | 6 ++--- .../cultcache-py/tests/test_slot_readonly.py | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/cultcache-py/src/cultcache_py/cache.py b/packages/cultcache-py/src/cultcache_py/cache.py index aaa4dea4..85b01d89 100644 --- a/packages/cultcache-py/src/cultcache_py/cache.py +++ b/packages/cultcache-py/src/cultcache_py/cache.py @@ -732,9 +732,9 @@ class CultCache: store = self._store_for_type(document.type) legacy_key = self._state.legacy_global_keys.get(document.type) if key == GLOBAL_KEY else None if store is not None: - for other, _ in owned: - store.delete(other, key) - store.delete(document.type, key if legacy_key is None else legacy_key) + # One record per key: the owned record when this document does not hold the key, else its own, in one store call. + holder = owned[0][0] if owned else document.type + store.delete(holder, key if legacy_key is None else legacy_key) if legacy_key is not None: del self._state.legacy_global_keys[document.type] for other, _ in owned: diff --git a/packages/cultcache-py/tests/test_slot_readonly.py b/packages/cultcache-py/tests/test_slot_readonly.py index 338d2cb7..77d4a989 100644 --- a/packages/cultcache-py/tests/test_slot_readonly.py +++ b/packages/cultcache-py/tests/test_slot_readonly.py @@ -441,6 +441,31 @@ class StoreRowTests(unittest.TestCase): self.assertEqual([e.key for e in SingleFileMessagePackBackingStore(path).pull_all()], ["other"]) self.assertTrue(cache.is_writable(b, "k")) + def test_an_owners_removal_is_one_store_call_so_the_cache_and_the_store_agree_when_it_fails(self) -> None: + a = define_database_entry_type("tests.one_a", [database_entry_field("a", 0, codec=t.string)], name="a") + b = define_database_entry_type( + "tests.one_b", [database_entry_field("a", 0, codec=t.string)], compatible_schema_ids=(a.catalog_entry().schema_id,) + ) + path = self.tmp / "one.cc" + _open(path, a).put(a, "k", {"a": "x"}) + + class SecondDeleteFails(SingleFileMessagePackBackingStore): + calls = 0 + + def delete(self, type, key): + self.calls += 1 + if self.calls > 1: + raise OSError("second delete") + super().delete(type, key) + + store = SecondDeleteFails(path) + cache = CultCache.builder().register_document_type(a).register_document_type(b).add_generic_store(store).build() + cache.pull_all_backing_stores() + cache.delete(b, "k") + self.assertEqual(store.calls, 1) + self.assertIsNone(cache.get(a, "k")) + self.assertEqual(SingleFileMessagePackBackingStore(path).pull_all(), []) + def test_writing_another_record_and_flushing_carries_the_marked_one_unchanged(self) -> None: path, cache = self._store("extra-slot-store.cc") original = {e.key: e for e in SingleFileMessagePackBackingStore(path).pull_all()}