From dc91b4d3d689d955bf038fc83c997f6257112b4a Mon Sep 17 00:00:00 2001 From: henddher Date: Fri, 22 Oct 2021 20:02:17 -0500 Subject: [PATCH 1/3] Support defaultdict as db attribute (serialization/deserialization) WIP --- evennia/utils/dbserialize.py | 46 +++++++++++++++++++++++++ evennia/utils/tests/test_dbserialize.py | 10 ++++++ 2 files changed, 56 insertions(+) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index a112c6ada4..c05687557c 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -203,6 +203,10 @@ class _SaverMutable(object): dat = _SaverDict(_parent=parent) dat._data.update((key, process_tree(val, dat)) for key, val in item.items()) return dat + elif dtype == defaultdict: + dat = _SaverDefaultDict(item.default_factory, _parent=parent) + dat._data.update((key, process_tree(val, dat)) for key, val in item.items()) + return dat elif dtype == set: dat = _SaverSet(_parent=parent) dat._data.update(process_tree(val, dat) for val in item) @@ -221,6 +225,7 @@ class _SaverMutable(object): return self._data.__iter__() def __getitem__(self, key): + # breakpoint() return self._data.__getitem__(key) def __eq__(self, other): @@ -305,6 +310,22 @@ class _SaverDict(_SaverMutable, MutableMapping): self._data.update(*args, **kwargs) +class _SaverDefaultDict(_SaverDict): + """ + A defaultdict that stores changes to an attribute when updated + """ + + def __init__(self, factory, *args, **kwargs): + super().__init__(*args, **kwargs) + self._data = defaultdict(factory) + self.default_factory = factory + + # @_save + def __getitem__(self, key): + # breakpoint() + return self._data[key] + + class _SaverSet(_SaverMutable, MutableSet): """ A set that saves to an Attribute when updated @@ -399,6 +420,7 @@ _DESERIALIZE_MAPPING = { _SaverSet.__name__: set, _SaverOrderedDict.__name__: OrderedDict, _SaverDeque.__name__: deque, + _SaverDefaultDict.__name__: defaultdict, } @@ -410,10 +432,13 @@ def deserialize(obj): """ def _iter(obj): + # breakpoint() typ = type(obj) tname = typ.__name__ if tname in ("_SaverDict", "dict"): return {_iter(key): _iter(val) for key, val in obj.items()} + if tname in ("_SaverDefaultDict", "defaultdict"): + return defaultdict({_iter(key): _iter(val) for key, val in obj.items()}) elif tname in _DESERIALIZE_MAPPING: return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj) elif is_iter(obj): @@ -565,6 +590,7 @@ def to_pickle(data): def process_item(item): """Recursive processor and identification of data""" + # breakpoint() dtype = type(item) if dtype in (str, int, float, bool, bytes, SafeString): return item @@ -574,6 +600,8 @@ def to_pickle(data): return [process_item(val) for val in item] elif dtype in (dict, _SaverDict): return dict((process_item(key), process_item(val)) for key, val in item.items()) + elif dtype in (defaultdict, _SaverDefaultDict): + return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items())) elif dtype in (set, _SaverSet): return set(process_item(val) for val in item) elif dtype in (OrderedDict, _SaverOrderedDict): @@ -625,6 +653,7 @@ def from_pickle(data, db_obj=None): def process_item(item): """Recursive processor and identification of data""" + # breakpoint() dtype = type(item) if dtype in (str, int, float, bool, bytes, SafeString): return item @@ -637,6 +666,8 @@ def from_pickle(data, db_obj=None): return tuple(process_item(val) for val in item) elif dtype == dict: return dict((process_item(key), process_item(val)) for key, val in item.items()) + elif dtype == defaultdict: + return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items())) elif dtype == set: return set(process_item(val) for val in item) elif dtype == OrderedDict: @@ -654,6 +685,7 @@ def from_pickle(data, db_obj=None): def process_tree(item, parent): """Recursive processor, building a parent-tree from iterable data""" + # breakpoint() dtype = type(item) if dtype in (str, int, float, bool, bytes, SafeString): return item @@ -672,6 +704,12 @@ def from_pickle(data, db_obj=None): (process_item(key), process_tree(val, dat)) for key, val in item.items() ) return dat + elif dtype == defaultdict: + dat = _SaverDefaultDict(item.default_factory, _parent=parent) + dat._data.update( + (process_item(key), process_tree(val, dat)) for key, val in item.items() + ) + return dat elif dtype == set: dat = _SaverSet(_parent=parent) dat._data.update(set(process_tree(val, dat) for val in item)) @@ -711,6 +749,12 @@ def from_pickle(data, db_obj=None): (process_item(key), process_tree(val, dat)) for key, val in data.items() ) return dat + elif dtype == defaultdict: + dat = _SaverDefaultDict(data.default_factory, _db_obj=db_obj) + dat._data.update( + (process_item(key), process_tree(val, dat)) for key, val in data.items() + ) + return dat elif dtype == set: dat = _SaverSet(_db_obj=db_obj) dat._data.update(process_tree(val, dat) for val in data) @@ -725,6 +769,8 @@ def from_pickle(data, db_obj=None): dat = _SaverDeque(_db_obj=db_obj) dat._data.extend(process_item(val) for val in data) return dat + else: + raise ValueError(f"Unsupported type {dtype}") return process_item(data) diff --git a/evennia/utils/tests/test_dbserialize.py b/evennia/utils/tests/test_dbserialize.py index 896769aa38..d4c1f09df4 100644 --- a/evennia/utils/tests/test_dbserialize.py +++ b/evennia/utils/tests/test_dbserialize.py @@ -15,6 +15,7 @@ class TestDbSerialize(TestCase): def setUp(self): self.obj = DefaultObject(db_key="Tester",) self.obj.save() + print(f"setUp {self.obj}") def test_constants(self): self.obj.db.test = 1 @@ -62,3 +63,12 @@ class TestDbSerialize(TestCase): self.obj.db.test = {'a': True} self.obj.db.test.update({'b': False}) self.assertEqual(self.obj.db.test, {'a': True, 'b': False}) + + def test_defaultdict(self): + from collections import defaultdict + dd = defaultdict(list) + self.obj.db.test = dd + self.obj.db.test['a'] + self.assertEqual(self.obj.db.test, {'a': []}) + self.obj.db.test['a'].append(1) + self.assertEqual(self.obj.db.test, {'a': [1, 2]}) From 405a9cef866f376b6f7773a3adace105c2bb824c Mon Sep 17 00:00:00 2001 From: henddher Date: Fri, 22 Oct 2021 20:27:51 -0500 Subject: [PATCH 2/3] More experimentation --- evennia/utils/dbserialize.py | 2 -- evennia/utils/tests/test_dbserialize.py | 14 +++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index c05687557c..2d28585add 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -769,8 +769,6 @@ def from_pickle(data, db_obj=None): dat = _SaverDeque(_db_obj=db_obj) dat._data.extend(process_item(val) for val in data) return dat - else: - raise ValueError(f"Unsupported type {dtype}") return process_item(data) diff --git a/evennia/utils/tests/test_dbserialize.py b/evennia/utils/tests/test_dbserialize.py index d4c1f09df4..85f6fd35d9 100644 --- a/evennia/utils/tests/test_dbserialize.py +++ b/evennia/utils/tests/test_dbserialize.py @@ -15,7 +15,6 @@ class TestDbSerialize(TestCase): def setUp(self): self.obj = DefaultObject(db_key="Tester",) self.obj.save() - print(f"setUp {self.obj}") def test_constants(self): self.obj.db.test = 1 @@ -66,9 +65,22 @@ class TestDbSerialize(TestCase): def test_defaultdict(self): from collections import defaultdict + # baseline behavior for a defaultdict + _dd = defaultdict(list) + _dd['a'] + self.assertTrue('a' in _dd) + self.assertEqual(_dd, {'a': []}) dd = defaultdict(list) + # behavior after defaultdict is set as attribute self.obj.db.test = dd self.obj.db.test['a'] + self.assertTrue('a' in self.obj.db.test) self.assertEqual(self.obj.db.test, {'a': []}) + + # none of the following matter as test failed by now self.obj.db.test['a'].append(1) + self.assertEqual(self.obj.db.test, {'a': [1]}) + self.obj.db.test['a'].append(2) self.assertEqual(self.obj.db.test, {'a': [1, 2]}) + self.obj.db.test['a'].append(3) + self.assertEqual(self.obj.db.test, {'a': [1, 2, 3]}) From 0add031d59e3a0de4cae619e729c6e1338d5af2a Mon Sep 17 00:00:00 2001 From: henddher Date: Mon, 25 Oct 2021 17:48:36 -0500 Subject: [PATCH 3/3] Specify default_factory. --- evennia/utils/dbserialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 2d28585add..11a2231b24 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -438,7 +438,7 @@ def deserialize(obj): if tname in ("_SaverDict", "dict"): return {_iter(key): _iter(val) for key, val in obj.items()} if tname in ("_SaverDefaultDict", "defaultdict"): - return defaultdict({_iter(key): _iter(val) for key, val in obj.items()}) + return defaultdict(obj.default_factory, {_iter(key): _iter(val) for key, val in obj.items()}) elif tname in _DESERIALIZE_MAPPING: return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj) elif is_iter(obj):