From 14fd85f9353bec07f4b8b3cc4b3b5f46f83bfdc6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 1 Dec 2025 19:42:47 +0000 Subject: [PATCH] json_db: fix StoredDict.__delitem__() to work similarly to .pop() follow-up https://github.com/spesmilo/electrum/pull/10233 ("jsondb pointers") --- electrum/json_db.py | 3 ++ tests/test_jsondb.py | 67 +++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/electrum/json_db.py b/electrum/json_db.py index 144a958f9..e0c0ecbc8 100644 --- a/electrum/json_db.py +++ b/electrum/json_db.py @@ -222,8 +222,11 @@ class StoredDict(dict, BaseStoredObject): @locked def __delitem__(self, key: _FLEX_KEY) -> None: assert isinstance(key, _FLEX_KEY), repr(key) + r = self.get(key, None) dict.__delitem__(self, key) self.db_remove(key) + if isinstance(r, StoredDict): + r._parent = None @locked def pop(self, key: _FLEX_KEY, v=_RaiseKeyError) -> Any: diff --git a/tests/test_jsondb.py b/tests/test_jsondb.py index f0cf30bb5..bb07eaf1a 100644 --- a/tests/test_jsondb.py +++ b/tests/test_jsondb.py @@ -2,6 +2,7 @@ import contextlib import copy import traceback import json +from typing import Any import jsonpatch from jsonpatch import JsonPatchException @@ -88,6 +89,16 @@ class TestJsonpatch(ElectrumTestCase): fail_if_leaking_secret(ctx) +def pop1_from_dict(d: dict, key: str) -> Any: + return d.pop(key) + + +def pop2_from_dict(d: dict, key: str) -> Any: + val = d[key] + del d[key] + return val + + class TestJsonDB(ElectrumTestCase): async def test_jsonpatch_replace_after_remove(self): @@ -109,31 +120,35 @@ class TestJsonDB(ElectrumTestCase): data = jpatch.apply(data) async def test_jsondb_replace_after_remove(self): - data = { 'a': {'b': {'c': 0}}, 'd': 3} - db = JsonDB(repr(data)) - a = db.get_dict('a') - # remove - b = a.pop('b') - self.assertEqual(len(db.pending_changes), 1) - # replace item. this must not been written to db - b['c'] = 42 - self.assertEqual(len(db.pending_changes), 1) - patches = json.loads('[' + ','.join(db.pending_changes) + ']') - jpatch = jsonpatch.JsonPatch(patches) - data = jpatch.apply(data) - self.assertEqual(data, {'a': {}, 'd': 3}) + for pop_from_dict in [pop1_from_dict, pop2_from_dict]: + with self.subTest(pop_from_dict): + data = { 'a': {'b': {'c': 0}}, 'd': 3} + db = JsonDB(repr(data)) + a = db.get_dict('a') + # remove + b = pop_from_dict(a, 'b') + self.assertEqual(len(db.pending_changes), 1) + # replace item. this must not been written to db + b['c'] = 42 + self.assertEqual(len(db.pending_changes), 1) + patches = json.loads('[' + ','.join(db.pending_changes) + ']') + jpatch = jsonpatch.JsonPatch(patches) + data = jpatch.apply(data) + self.assertEqual(data, {'a': {}, 'd': 3}) async def test_jsondb_replace_after_remove_nested(self): - data = { 'a': {'b': {'c': 0}}, 'd': 3} - db = JsonDB(repr(data)) - # remove - a = db.data.pop('a') - self.assertEqual(len(db.pending_changes), 1) - b = a['b'] - # replace item. this must not be written to db - b['c'] = 42 - self.assertEqual(len(db.pending_changes), 1) - patches = json.loads('[' + ','.join(db.pending_changes) + ']') - jpatch = jsonpatch.JsonPatch(patches) - data = jpatch.apply(data) - self.assertEqual(data, {'d': 3}) + for pop_from_dict in [pop1_from_dict, pop2_from_dict]: + with self.subTest(pop_from_dict): + data = { 'a': {'b': {'c': 0}}, 'd': 3} + db = JsonDB(repr(data)) + # remove + a = pop_from_dict(db.data, "a") + self.assertEqual(len(db.pending_changes), 1) + b = a['b'] + # replace item. this must not be written to db + b['c'] = 42 + self.assertEqual(len(db.pending_changes), 1) + patches = json.loads('[' + ','.join(db.pending_changes) + ']') + jpatch = jsonpatch.JsonPatch(patches) + data = jpatch.apply(data) + self.assertEqual(data, {'d': 3})