Skip to content

Commit 1119cb9

Browse files
committed
json_db: add some type hints and asserts
- fix some incorrect type hints - add many new type hints - add asserts re types no functional changes intended
1 parent 828fc56 commit 1119cb9

File tree

1 file changed

+47
-26
lines changed

1 file changed

+47
-26
lines changed

electrum/json_db.py

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import threading
2626
import copy
2727
import json
28-
from typing import TYPE_CHECKING, Optional, Sequence, List, Union
28+
from typing import TYPE_CHECKING, Optional, Sequence, List, Union, Any
2929

3030
import jsonpatch
3131
import jsonpointer
@@ -97,13 +97,16 @@ def decorator(func):
9797
return func
9898
return decorator
9999

100+
_FLEX_KEY = str | int | None
100101

101-
def key_path(path: Sequence[Union[str, int]], key: Optional[str]) -> str:
102-
def to_str(x):
102+
def key_path(path: Sequence[_FLEX_KEY], key: _FLEX_KEY) -> str:
103+
def to_str(x: _FLEX_KEY) -> str:
104+
assert isinstance(x, _FLEX_KEY), repr(x)
105+
assert x is not None
103106
if isinstance(x, int):
104107
return str(int(x))
105108
else:
106-
assert isinstance(x, str)
109+
assert isinstance(x, str), f"unexpected key type for: {x!r}"
107110
return x
108111
items = [to_str(x) for x in path]
109112
if key is not None:
@@ -113,15 +116,17 @@ def to_str(x):
113116
class BaseStoredObject:
114117

115118
_db: 'JsonDB' = None
116-
_key = None
117-
_parent = None
118-
_lock = None
119+
_key: _FLEX_KEY = None
120+
_parent: Optional['BaseStoredObject'] = None
121+
_lock: threading.RLock = None
119122

120123
def set_db(self, db):
121124
self._db = db
122125
self._lock = self._db.lock if self._db else threading.RLock()
123126

124-
def set_parent(self, key, parent):
127+
def set_parent(self, *, key: _FLEX_KEY, parent: Optional['BaseStoredObject']) -> None:
128+
assert (key == "") == (parent is None), f"{key=!r}, {parent=!r}"
129+
assert isinstance(key, _FLEX_KEY), repr(key)
125130
self._key = key
126131
self._parent = parent
127132

@@ -130,7 +135,7 @@ def lock(self):
130135
return self._lock
131136

132137
@property
133-
def path(self) -> Sequence[str]:
138+
def path(self) -> Sequence[_FLEX_KEY] | None:
134139
# return None iff we are pruned from root
135140
x = self
136141
s = [x._key]
@@ -142,23 +147,27 @@ def path(self) -> Sequence[str]:
142147
assert self._db is not None
143148
return s
144149

145-
def db_add(self, key, value):
150+
def db_add(self, key: _FLEX_KEY, value) -> None:
151+
assert isinstance(key, _FLEX_KEY), repr(key)
146152
if self.path:
147153
self._db.add(self.path, key, value)
148154

149-
def db_replace(self, key, value):
155+
def db_replace(self, key: _FLEX_KEY, value) -> None:
156+
assert isinstance(key, _FLEX_KEY), repr(key)
150157
if self.path:
151158
self._db.replace(self.path, key, value)
152159

153-
def db_remove(self, key):
160+
def db_remove(self, key: _FLEX_KEY) -> None:
161+
assert isinstance(key, _FLEX_KEY), repr(key)
154162
if self.path:
155163
self._db.remove(self.path, key)
156164

157165

158166
class StoredObject(BaseStoredObject):
159167
"""for attr.s objects """
160168

161-
def __setattr__(self, key, value):
169+
def __setattr__(self, key: str, value):
170+
assert isinstance(key, str), repr(key)
162171
if self.path and not key.startswith('_'):
163172
if value != getattr(self, key):
164173
self.db_replace(key, value)
@@ -185,7 +194,8 @@ def __init__(self, data: dict, db: 'JsonDB'):
185194
self.__setitem__(k, v)
186195

187196
@locked
188-
def __setitem__(self, key, v):
197+
def __setitem__(self, key: _FLEX_KEY, v) -> None:
198+
assert isinstance(key, _FLEX_KEY), repr(key)
189199
is_new = key not in self
190200
# early return to prevent unnecessary disk writes
191201
if not is_new and self._db and json.dumps(v, cls=self._db.encoder) == json.dumps(self[key], cls=self._db.encoder):
@@ -204,18 +214,20 @@ def __setitem__(self, key, v):
204214
v.set_db(self._db)
205215
# set parent
206216
if isinstance(v, BaseStoredObject):
207-
v.set_parent(key, self)
217+
v.set_parent(key=key, parent=self)
208218
# set item
209219
dict.__setitem__(self, key, v)
210220
self.db_add(key, v) if is_new else self.db_replace(key, v)
211221

212222
@locked
213-
def __delitem__(self, key):
223+
def __delitem__(self, key: _FLEX_KEY) -> None:
224+
assert isinstance(key, _FLEX_KEY), repr(key)
214225
dict.__delitem__(self, key)
215226
self.db_remove(key)
216227

217228
@locked
218-
def pop(self, key, v=_RaiseKeyError):
229+
def pop(self, key: _FLEX_KEY, v=_RaiseKeyError) -> Any:
230+
assert isinstance(key, _FLEX_KEY), repr(key)
219231
if key not in self:
220232
if v is _RaiseKeyError:
221233
raise KeyError(key)
@@ -227,7 +239,8 @@ def pop(self, key, v=_RaiseKeyError):
227239
r._parent = None
228240
return r
229241

230-
def setdefault(self, key, default = None, /):
242+
def setdefault(self, key: _FLEX_KEY, default = None, /):
243+
assert isinstance(key, _FLEX_KEY), repr(key)
231244
if key not in self:
232245
self.__setitem__(key, default)
233246
return self[key]
@@ -283,7 +296,7 @@ def __init__(
283296
data = self._convert_dict([], data)
284297
# convert dict to StoredDict
285298
self.data = StoredDict(data, self)
286-
self.data.set_parent('', None)
299+
self.data.set_parent(key='', parent=None)
287300
# write file in case there was a db upgrade
288301
if self.storage and self.storage.file_exists():
289302
self.write_and_force_consolidation()
@@ -357,13 +370,16 @@ def add_patch(self, patch):
357370
self.pending_changes.append(json.dumps(patch, cls=self.encoder))
358371
self.set_modified(True)
359372

360-
def add(self, path, key, value):
373+
def add(self, path, key: _FLEX_KEY, value) -> None:
374+
assert isinstance(key, _FLEX_KEY), repr(key)
361375
self.add_patch({'op': 'add', 'path': key_path(path, key), 'value': value})
362376

363-
def replace(self, path, key, value):
377+
def replace(self, path, key: _FLEX_KEY, value) -> None:
378+
assert isinstance(key, _FLEX_KEY), repr(key)
364379
self.add_patch({'op': 'replace', 'path': key_path(path, key), 'value': value})
365380

366-
def remove(self, path, key):
381+
def remove(self, path, key: _FLEX_KEY) -> None:
382+
assert isinstance(key, _FLEX_KEY), repr(key)
367383
self.add_patch({'op': 'remove', 'path': key_path(path, key)})
368384

369385
@locked
@@ -419,7 +435,9 @@ def dump(self, *, human_readable: bool = True) -> str:
419435
def _should_convert_to_stored_dict(self, key) -> bool:
420436
return True
421437

422-
def _convert_dict_key(self, path):
438+
def _convert_dict_key(self, path: List[str]) -> _FLEX_KEY:
439+
"""Maybe convert key from str to python type (typically int or IntEnum)"""
440+
assert all(isinstance(x, str) for x in path), repr(path)
423441
key = path[-1]
424442
parent_key = path[-2] if len(path) > 1 else None
425443
gp_key = path[-3] if len(path) > 2 else None
@@ -431,9 +449,11 @@ def _convert_dict_key(self, path):
431449
convert_key = None
432450
if convert_key:
433451
key = convert_key(key)
452+
assert isinstance(key, _FLEX_KEY), f"unexpected type for {key=!r} at {path=}"
434453
return key
435454

436-
def _convert_dict_value(self, path, v):
455+
def _convert_dict_value(self, path: List[str], v) -> Any:
456+
assert all(isinstance(x, str) for x in path), repr(path)
437457
key = path[-1]
438458
if key in registered_dicts:
439459
constructor, _type = registered_dicts[key]
@@ -453,8 +473,9 @@ def _convert_dict_value(self, path, v):
453473
v = self._convert_dict(path, v)
454474
return v
455475

456-
def _convert_dict(self, path, data: dict):
457-
# recursively convert dict to StoredDict
476+
def _convert_dict(self, path: List[str], data: dict):
477+
# recursively convert json dict to StoredDict
478+
assert all(isinstance(x, str) for x in path), repr(path)
458479
d = {}
459480
for k, v in list(data.items()):
460481
child_path = path + [k]

0 commit comments

Comments
 (0)