1
0

json_db: fix StoredDict.__delitem__() to work similarly to .pop()

follow-up https://github.com/spesmilo/electrum/pull/10233 ("jsondb pointers")
This commit is contained in:
SomberNight
2025-12-01 19:42:47 +00:00
parent afc87fea9a
commit 14fd85f935
2 changed files with 44 additions and 26 deletions

View File

@@ -222,8 +222,11 @@ class StoredDict(dict, BaseStoredObject):
@locked @locked
def __delitem__(self, key: _FLEX_KEY) -> None: def __delitem__(self, key: _FLEX_KEY) -> None:
assert isinstance(key, _FLEX_KEY), repr(key) assert isinstance(key, _FLEX_KEY), repr(key)
r = self.get(key, None)
dict.__delitem__(self, key) dict.__delitem__(self, key)
self.db_remove(key) self.db_remove(key)
if isinstance(r, StoredDict):
r._parent = None
@locked @locked
def pop(self, key: _FLEX_KEY, v=_RaiseKeyError) -> Any: def pop(self, key: _FLEX_KEY, v=_RaiseKeyError) -> Any:

View File

@@ -2,6 +2,7 @@ import contextlib
import copy import copy
import traceback import traceback
import json import json
from typing import Any
import jsonpatch import jsonpatch
from jsonpatch import JsonPatchException from jsonpatch import JsonPatchException
@@ -88,6 +89,16 @@ class TestJsonpatch(ElectrumTestCase):
fail_if_leaking_secret(ctx) 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): class TestJsonDB(ElectrumTestCase):
async def test_jsonpatch_replace_after_remove(self): async def test_jsonpatch_replace_after_remove(self):
@@ -109,31 +120,35 @@ class TestJsonDB(ElectrumTestCase):
data = jpatch.apply(data) data = jpatch.apply(data)
async def test_jsondb_replace_after_remove(self): async def test_jsondb_replace_after_remove(self):
data = { 'a': {'b': {'c': 0}}, 'd': 3} for pop_from_dict in [pop1_from_dict, pop2_from_dict]:
db = JsonDB(repr(data)) with self.subTest(pop_from_dict):
a = db.get_dict('a') data = { 'a': {'b': {'c': 0}}, 'd': 3}
# remove db = JsonDB(repr(data))
b = a.pop('b') a = db.get_dict('a')
self.assertEqual(len(db.pending_changes), 1) # remove
# replace item. this must not been written to db b = pop_from_dict(a, 'b')
b['c'] = 42 self.assertEqual(len(db.pending_changes), 1)
self.assertEqual(len(db.pending_changes), 1) # replace item. this must not been written to db
patches = json.loads('[' + ','.join(db.pending_changes) + ']') b['c'] = 42
jpatch = jsonpatch.JsonPatch(patches) self.assertEqual(len(db.pending_changes), 1)
data = jpatch.apply(data) patches = json.loads('[' + ','.join(db.pending_changes) + ']')
self.assertEqual(data, {'a': {}, 'd': 3}) jpatch = jsonpatch.JsonPatch(patches)
data = jpatch.apply(data)
self.assertEqual(data, {'a': {}, 'd': 3})
async def test_jsondb_replace_after_remove_nested(self): async def test_jsondb_replace_after_remove_nested(self):
data = { 'a': {'b': {'c': 0}}, 'd': 3} for pop_from_dict in [pop1_from_dict, pop2_from_dict]:
db = JsonDB(repr(data)) with self.subTest(pop_from_dict):
# remove data = { 'a': {'b': {'c': 0}}, 'd': 3}
a = db.data.pop('a') db = JsonDB(repr(data))
self.assertEqual(len(db.pending_changes), 1) # remove
b = a['b'] a = pop_from_dict(db.data, "a")
# replace item. this must not be written to db self.assertEqual(len(db.pending_changes), 1)
b['c'] = 42 b = a['b']
self.assertEqual(len(db.pending_changes), 1) # replace item. this must not be written to db
patches = json.loads('[' + ','.join(db.pending_changes) + ']') b['c'] = 42
jpatch = jsonpatch.JsonPatch(patches) self.assertEqual(len(db.pending_changes), 1)
data = jpatch.apply(data) patches = json.loads('[' + ','.join(db.pending_changes) + ']')
self.assertEqual(data, {'d': 3}) jpatch = jsonpatch.JsonPatch(patches)
data = jpatch.apply(data)
self.assertEqual(data, {'d': 3})