@@ -89,39 +89,29 @@ def decorator(func):
8989 return decorator
9090
9191
92- def _walk_path (d , path ):
93- for k in path :
94- if k in d :
95- d = d [k ]
96- elif '*' in d :
97- d = d ['*' ]
98- else :
99- return None
100- return d
101-
102- def _convert_dict_key (path : List [str ], key : str ) -> _FLEX_KEY :
103- """Maybe convert key from str to python type (typically int or IntEnum)"""
104- assert all (isinstance (x , str ) for x in path ), repr (path )
105- r = _walk_path (registered_keys , path )
106- if r :
107- if func := r .get ('self' ):
108- key = func (key )
109- assert isinstance (key , _FLEX_KEY ), f"unexpected type for { key = !r} at { path = } "
110- return key
111-
112- def _convert_dict_value (path : List [str ], v ) -> Any :
113- assert all (isinstance (x , str ) for x in path ), repr (path )
114- r = _walk_path (registered_names , path )
115- if r and type (r ) is tuple :
116- _type , constructor = r
117- if _type == dict :
118- v = constructor (** v )
119- elif _type == tuple :
120- v = constructor (* v )
121- else :
122- v = constructor (v )
123- return v
124-
92+ def to_default (obj ):
93+ """Convert user-defined classes to python built-in types.
94+ Also convert bytes to hex, so that the result is json serializable.
95+ """
96+ if obj is None or isinstance (obj , (str , int , float )):
97+ return obj
98+ if isinstance (obj , bytes ):
99+ return obj .hex ()
100+ if hasattr (obj , 'as_str' ) and callable (obj .as_str ):
101+ return obj .as_str ()
102+ if hasattr (obj , 'as_dict' ) and callable (obj .as_dict ):
103+ obj = obj .as_dict ()
104+ if hasattr (obj , 'as_tuple' ) and callable (obj .as_tuple ):
105+ obj = obj .as_tuple ()
106+ if isinstance (obj , (set , frozenset )):
107+ return [to_default (x ) for x in list (obj )]
108+ if isinstance (obj , dict ):
109+ return dict ([(key_to_str (k ), to_default (v )) for k , v in obj .items ()])
110+ if isinstance (obj , list ):
111+ return [to_default (x ) for x in obj ]
112+ if isinstance (obj , tuple ):
113+ return tuple ([to_default (x ) for x in list (obj )])
114+ raise Exception ('unsupported type' , type (obj ))
125115
126116
127117
@@ -131,6 +121,7 @@ def __init__(self, path):
131121 Logger .__init__ (self )
132122 self ._write_batch = None
133123 self .path = path
124+ self ._should_convert = True
134125
135126 def file_exists (self ):
136127 raise NotImplementedError ()
@@ -178,9 +169,9 @@ def _to_stored_dict_or_list(self, key, value):
178169 value = StoredList (self ._db , key = key , parent = self )
179170 elif isinstance (value , dict ):
180171 value = StoredDict (self ._db , key = key , parent = self )
181- # elif isinstance(value, tuple):
182- # value = StoredList(self._db, key=key, parent=self)
183- # value = tuple(value[:]) # do not expose StoredTuple to callers
172+ elif isinstance (value , tuple ):
173+ value = StoredList (self ._db , key = key , parent = self )
174+ value = tuple (value [:]) # do not expose StoredTuple to callers
184175 return value
185176
186177 @property
@@ -193,12 +184,37 @@ def hint(self):
193184 def db_get (self , key ):
194185 value = self ._db .get (self .hint , key )
195186 value = self ._to_stored_dict_or_list (key , value )
187+ if not self .should_convert ():
188+ return value
189+ value = self ._convert_value (key , value )
196190 # set db for StoredObject, because it is not set in the constructor
197191 if isinstance (value , StoredObject ):
198192 value .set_db (self ._db )
199193 value .set_parent (key = key , parent = self )
200194 return value
201195
196+ def _convert_key (self , key : str ) -> _FLEX_KEY :
197+ """Maybe convert key from str to python type (typically int or IntEnum)"""
198+ if self ._key_converters :
199+ if func := self ._key_converters .get ('self' ):
200+ key = func (key )
201+ assert isinstance (key , _FLEX_KEY ), f"unexpected type for { key = !r} at { self ._path } "
202+ return key
203+
204+ def _convert_value (self , key , v ) -> Any :
205+ reg = self .get_constructor (key )
206+ if reg :
207+ if isinstance (v , (StoredDict , StoredList )):
208+ v = v .dump ()
209+ _type , constructor = reg
210+ if _type == dict :
211+ v = constructor (** v )
212+ elif _type == tuple :
213+ v = constructor (* v )
214+ else :
215+ v = constructor (v )
216+ return v
217+
202218 def get_constructor (self , key ):
203219 if self ._constructor :
204220 r = self ._constructor .get (key , self ._constructor .get ('*' , None ))
@@ -245,10 +261,10 @@ def __setattr__(self, key: str, value):
245261 assert isinstance (key , str ), repr (key )
246262 if not key .startswith ('_' ) and self ._path :
247263 if value != getattr (self , key ):
248- self ._db .replace (self .hint , self ._path , key , value )
264+ self ._db .replace (self .hint , self ._path , key , to_default ( value ) )
249265 object .__setattr__ (self , key , value )
250266
251- def to_json (self ):
267+ def as_dict (self ):
252268 d = dict (vars (self ))
253269 # don't expose/store private stuff
254270 d = {k : v for k , v in d .items ()
@@ -277,6 +293,9 @@ def __init__(self, db: BaseDB, key: _FLEX_KEY, parent):
277293 self .init_constructor ()
278294 self .init_key_converters ()
279295
296+ def should_convert (self ):
297+ return self ._db ._should_convert
298+
280299 def dump (self ) -> dict :
281300 data = {}
282301 for k , v in self .items ():
@@ -286,18 +305,23 @@ def dump(self) -> dict:
286305 return data
287306
288307 def __getitem__ (self , key : _FLEX_KEY ) -> Any :
308+ key = key_to_str (key )
289309 return self .db_get (key )
290310
291311 def __setitem__ (self , key : _FLEX_KEY , value : Any ) -> None :
312+ key = key_to_str (key )
292313 if isinstance (value , StoredObject ):
293314 # side effect
294315 value .set_db (self ._db )
295316 value .set_parent (key = key , parent = self )
296317 if isinstance (value , (StoredList , StoredDict )):
297318 value = value .dump ()
319+ # convert to python
320+ value = to_default (value )
298321 self ._db .put (self .hint , self ._path , key , value )
299322
300323 def __delitem__ (self , key : _FLEX_KEY ) -> None :
324+ key = key_to_str (key )
301325 self ._db .remove (self .hint , self ._path , key )
302326
303327 def __iter__ (self ) -> Iterator [str ]:
@@ -309,19 +333,20 @@ def __len__(self) -> int:
309333 # ---- Dict-like extras ----
310334
311335 def __contains__ (self , key : object ) -> bool :
336+ key = key_to_str (key )
312337 return self ._db .dict_contains (self .hint , self ._path , key )
313338
314339 def keys (self ) -> Iterable [str ]:
315340 for k in self ._db .iter_keys (self .hint , self ._path ):
316- yield k
341+ yield self . _convert_key ( k )
317342
318343 def values (self ) -> Iterator [Any ]:
319344 for k in self ._db .iter_keys (self .hint , self ._path ):
320345 yield self [k ]
321346
322347 def items (self ) -> Iterator [Tuple [str , Any ]]:
323348 for k in self ._db .iter_keys (self .hint , self ._path ):
324- yield (k , self [k ])
349+ yield (self . _convert_key ( k ) , self [k ])
325350
326351 def get (self , key : _FLEX_KEY , default : Any = None , add_if_missing = False ) -> Any :
327352 # If add_if_missing is True, create DB entry if it does not exist.
@@ -383,6 +408,9 @@ def __init__(self, db: BaseDB, key: _FLEX_KEY, parent):
383408 self .init_constructor ()
384409 self .init_key_converters ()
385410
411+ def should_convert (self ):
412+ return self ._db ._should_convert
413+
386414 def _get_list_item (self , key : int ):
387415 key = int (key )
388416 return self .db_get (key )
@@ -408,16 +436,19 @@ def __iter__(self) -> Iterator[str]:
408436 yield self ._get_list_item (i )
409437
410438 def append (self , value ):
439+ value = to_default (value )
411440 self ._db .list_append (self .hint , self ._path , value )
412441
413442 def clear (self ):
414443 self ._db .list_clear (self .hint , self ._path )
415444 assert len (self ) == 0
416445
417446 def index (self , item ) -> int :
447+ item = to_default (item )
418448 return self ._db .list_index (self .hint , self ._path , item )
419449
420450 def remove (self , item ):
451+ item = to_default (item )
421452 self ._db .list_remove (self .hint , self ._path , item )
422453
423454 def dump (self ) -> list :
0 commit comments