Skip to content

Commit 3e07c4d

Browse files
committed
stored_dict: convert objects to simple python types before passing them to db
1 parent dcd7662 commit 3e07c4d

3 files changed

Lines changed: 48 additions & 26 deletions

File tree

electrum/json_db.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
from .util import WalletFileException, profiler, sticky_property
3535
from .logging import Logger
36-
from .stored_dict import _FLEX_KEY, BaseDB, json_default
36+
from .stored_dict import _FLEX_KEY, BaseDB
3737
from .storage import FileStorage
3838

3939

@@ -174,7 +174,6 @@ def replace(self, path, key, value):
174174
@modifier
175175
def put(self, path, key, value):
176176
d = self._subdict(path)
177-
value = json.loads(json.dumps(value, default=json_default)) # default() is applied recursively
178177
is_new = key not in d
179178
d[key] = value
180179
self.db_add(path, key, value) if is_new else self.db_replace(path, key, value)
@@ -293,7 +292,7 @@ def modified(self):
293292

294293
@locked
295294
def add_patch(self, patch):
296-
self.pending_changes.append(json.dumps(patch, default=json_default))
295+
self.pending_changes.append(json.dumps(patch))
297296
self.set_modified(True)
298297

299298
def db_add(self, path, key: _FLEX_KEY, value) -> None:
@@ -317,7 +316,6 @@ def dump(self, *, human_readable: bool = True) -> str:
317316
self.json_data,
318317
indent=4 if human_readable else None,
319318
sort_keys=bool(human_readable),
320-
default=json_default,
321319
)
322320

323321
@contextmanager

electrum/level_db.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import plyvel
88
from contextlib import contextmanager
99

10-
from .stored_dict import BaseDB, _FLEX_KEY, json_default
10+
from .stored_dict import BaseDB, _FLEX_KEY
1111

1212
# Todo:
1313
# - simplify path: first element is unused
@@ -24,7 +24,7 @@ class JsonCodec:
2424
"""Default value codec: JSON (utf-8)."""
2525
@staticmethod
2626
def dumps(value: Any) -> bytes:
27-
return json.dumps(value, separators=(",", ":"), ensure_ascii=False, default=json_default).encode("utf-8")
27+
return json.dumps(value, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
2828

2929
@staticmethod
3030
def loads(data: bytes) -> Any:
@@ -60,6 +60,7 @@ def __init__(
6060
self.codec = JsonCodec
6161
if init_db:
6262
self.init_db()
63+
self._debug()
6364

6465
def is_encrypted(self):
6566
return False
@@ -224,7 +225,11 @@ def get(self, path, key: _FLEX_KEY) -> Any:
224225
raw = db.get(self._full_key(path, key))
225226
if raw is None:
226227
raise KeyError((path, key, self._full_key(path, key)))
227-
return self.codec.loads(raw) # json to python
228+
r = self.codec.loads(raw)
229+
# internally we store tuples as non-empty lists
230+
if type(r) is list and len(r) > 0:
231+
r = tuple(r)
232+
return r
228233

229234
@contextmanager
230235
def write_batch(self):
@@ -278,13 +283,7 @@ def put(self, path, key: _FLEX_KEY, value: Any) -> None:
278283
@locked
279284
def replace(self, path, key: _FLEX_KEY, value: Any) -> None:
280285
# called by StoredObject in setattr
281-
db = self.db
282-
if db is None:
283-
raise RuntimeError("DB is closed")
284-
fullkey = self._full_key(path[:-1], path[-1])
285-
d = self.codec.loads(db.get(fullkey))
286-
d[key] = value
287-
db.put(fullkey, self.codec.dumps(d))
286+
self.put(path, key, value)
288287

289288
@locked
290289
def contains(self, path, key: object) -> bool:

electrum/stored_dict.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,37 @@ class StorageEncryptionVersion(IntEnum):
8989

9090

9191
def json_default(obj):
92+
"""Convert object to simple python types.
93+
The result must be json serializable.
94+
"""
9295
if isinstance(obj, (set, frozenset)):
93-
return list(obj)
96+
return [json_default(x) for x in list(obj)]
9497
if isinstance(obj, bytes):
9598
return obj.hex()
9699
if hasattr(obj, 'as_str') and callable(obj.as_str):
97100
return obj.as_str()
98101
if hasattr(obj, 'as_dict') and callable(obj.as_dict):
99-
return obj.as_dict()
102+
obj = obj.as_dict()
103+
return dict([(key_to_str(k), json_default(v)) for k, v in obj.items()])
100104
if hasattr(obj, 'as_tuple') and callable(obj.as_tuple):
101-
return obj.as_tuple()
105+
obj = obj.as_tuple()
106+
return tuple([json_default(x) for x in list(obj)])
107+
if hasattr(obj, 'to_json') and callable(obj.to_json): # TxOutpoint
108+
return obj.to_json()
109+
if isinstance(obj, dict):
110+
return dict([(key_to_str(k), json_default(v)) for k, v in obj.items()])
111+
if isinstance(obj, list):
112+
return [json_default(x) for x in obj]
113+
if isinstance(obj, tuple):
114+
return tuple([json_default(x) for x in list(obj)])
102115
if isinstance(obj, StoredDict):
103-
return dict(obj)
116+
raise Exception('trying to convert StoredDict')
104117
if isinstance(obj, StoredList):
105-
return list(obj)
118+
raise Exception('trying to convert StoredList')
106119
return obj
107120

108121

122+
109123
class BaseDB(Logger):
110124

111125
def __init__(self, path):
@@ -136,6 +150,7 @@ def _convert_dict_key(path: List[str], key: str) -> _FLEX_KEY:
136150
assert isinstance(key, _FLEX_KEY), f"unexpected type for {key=!r} at {path=}"
137151
return key
138152

153+
139154
def _convert_dict_value(path: List[str], v) -> Any:
140155
assert all(isinstance(x, _FLEX_KEY) for x in path)
141156
n = len(path)
@@ -145,8 +160,13 @@ def _convert_dict_value(path: List[str], v) -> Any:
145160
if reg:
146161
_type, constructor = reg
147162
if _type == dict:
163+
if isinstance(v, StoredDict):
164+
v = v._dump()
165+
#v = {k:_convert_dict_value(path + [k], x) for k, x in v.items()}
148166
v = constructor(**v)
149167
elif _type == tuple:
168+
if isinstance(v, StoredList):
169+
v = v._dump()
150170
v = constructor(*v)
151171
else:
152172
v = constructor(v)
@@ -207,7 +227,7 @@ def __setattr__(self, key: str, value):
207227
assert isinstance(key, str), repr(key)
208228
if not key.startswith('_') and self.path:
209229
if value != getattr(self, key):
210-
self._db.replace(self.path, key, value)
230+
self._db.replace(self.path, key, json_default(value))
211231
object.__setattr__(self, key, value)
212232

213233
def as_dict(self):
@@ -218,7 +238,6 @@ def as_dict(self):
218238
return d
219239

220240

221-
222241
class StoredDict(BaseStoredObject):
223242
"""
224243
dict-like object that queries the DB
@@ -255,14 +274,15 @@ def _dump(self) -> dict:
255274
def __getitem__(self, key: _FLEX_KEY) -> Any:
256275
key = key_to_str(key) # fixme: assumes that db keys are str
257276
value = self._db.get(self.path, key)
277+
value = self._to_stored_dict_or_list(key, value)
258278
if not self.should_convert():
259-
return self._to_stored_dict_or_list(key, value)
279+
return value
260280
value = _convert_dict_value(self.path + [key], value)
261281
# set db for StoredObject, because it is not set in the constructor
262282
if isinstance(value, StoredObject):
263283
value.set_db(self._db)
264284
value.set_parent(key=key, parent=self)
265-
return self._to_stored_dict_or_list(key, value)
285+
return value
266286

267287
def __setitem__(self, key: _FLEX_KEY, value: Any) -> None:
268288
key = key_to_str(key)
@@ -273,6 +293,8 @@ def __setitem__(self, key: _FLEX_KEY, value: Any) -> None:
273293
if isinstance(value, StoredDict):
274294
value = value._dump()
275295
assert isinstance(value, dict)
296+
# convert here
297+
value = json_default(value)
276298
self._db.put(self.path, key, value)
277299

278300
def __delitem__(self, key: _FLEX_KEY) -> None:
@@ -365,9 +387,9 @@ def should_convert(self):
365387
def _get_list_item(self, key: int):
366388
key = int(key)
367389
value = self._db.get(self.path, key)
390+
value = self._to_stored_dict_or_list(key, value)
368391
if self.should_convert():
369392
value = _convert_dict_value(self.path + [key], value)
370-
value = self._to_stored_dict_or_list(key, value)
371393
return value
372394

373395
def __getitem__(self, s: slice) -> Any:
@@ -390,17 +412,20 @@ def __iter__(self) -> Iterator[str]:
390412
for i in range(self._db.list_len(self.path)):
391413
yield self._get_list_item(i)
392414

393-
def append(self, item):
394-
self._db.list_append(self.path, item)
415+
def append(self, value):
416+
value = json_default(value)
417+
self._db.list_append(self.path, value)
395418

396419
def clear(self):
397420
self._db.list_clear(self.path)
398421
assert len(self) == 0
399422

400423
def index(self, item) -> int:
424+
item = json_default(item)
401425
return self._db.list_index(self.path, item)
402426

403427
def remove(self, item):
428+
item = json_default(item)
404429
self._db.list_remove(self.path, item)
405430

406431
def _dump(self) -> list:

0 commit comments

Comments
 (0)