[BUG] cannot serialize dict subclasses
See original GitHub issueDescribe the bug
Dear evennia developers, I’ve found that I’m not able to persist a dict subclass and after tracking it down a bit I think there’s a generic problem when working w/ base types subclasses.
To Reproduce
A simple PoC using a dict:
- put this in
/typeclasses/foo.py
:
from evennia import CmdSet, Command, DefaultObject
class MaskedDict(dict):
pass
class CmdFoo(Command):
key = "cf"
def func(self):
self.obj.db.foo = MaskedDict({"foo": 1, "bar": 2})
class CmdSetFoo(CmdSet):
def at_cmdset_creation(self):
self.add(CmdFoo())
class FooObject(DefaultObject):
def at_object_creation(self):
self.cmdset.add_default(CmdSetFoo)
- reload and create a foo object
reload
create foo :foo.FooObject
- try to persist a dict subclass in foo obj:
cf
Traceback (most recent call last):
File "/home/oggei/dev/evennia/evennia/commands/cmdhandler.py", line 621, in _run_command
ret = cmd.func()
File "/home/oggei/dev/evennia/dict-bug/typeclasses/foo.py", line 13, in func
self.obj.db.foo = MaskedDict({"foo": 1, "bar": 2})
File "/home/oggei/dev/evennia/evennia/typeclasses/attributes.py", line 1414, in __setattr__
_GA(self, _GA(self, "name")).add(attrname, value)
File "/home/oggei/dev/evennia/evennia/typeclasses/attributes.py", line 1251, in add
self.backend.create_attribute(keystr, category, lockstring, value, strattr)
File "/home/oggei/dev/evennia/evennia/typeclasses/attributes.py", line 697, in create_attribute
attr = self.do_create_attribute(key, category, lockstring, value, strvalue)
File "/home/oggei/dev/evennia/evennia/typeclasses/attributes.py", line 1033, in do_create_attribute
kwargs["db_value"] = to_pickle(value)
File "/home/oggei/dev/evennia/evennia/utils/dbserialize.py", line 663, in to_pickle
return process_item(data)
File "/home/oggei/dev/evennia/evennia/utils/dbserialize.py", line 650, in process_item
return item.__class__([process_item(val) for val in item])
ValueError: dictionary update sequence element #0 has length 3; 2 is required
An untrapped error occurred.
(Traceback was logged 22-07-19 15:38:57).
- confirm that nothing has indeed ben set
examine foo
--------------------------------------------------------------------------------
Name/key: foo (#4)
Typeclass: FooObject (typeclasses.foo.FooObject)
Location: oggei (#1)
Home: oggei (#1)
Locks: call:true(); control:id(1) or perm(Admin); delete:id(1) or perm(Admin);
drop:holds(); edit:perm(Admin); examine:perm(Builder); get:all();
puppet:pperm(Developer); tell:perm(Admin); view:all()
Stored Cmdset(s):
typeclasses.foo.CmdSetFoo [Unnamed CmdSet] (Union, prio 0)
Merged Cmdset(s):
typeclasses.foo.CmdSetFoo [Unnamed CmdSet] (Union, prio 0)
Commands available to foo (result of Merged Cmdset(s)):
cf
Persistent Attributes:
desc=You see nothing special.
--------------------------------------------------------------------------------
Expected behavior
I’d like to be able to persist those subclasses 😃
Environment, Evennia version, OS etc
Evennia 1.0-dev (rev ae5b353ef) (rev ae5b353ef)
OS: posix
Python: 3.9.13
Twisted: 21.7.0
Django: 4.0.6
the problem occurs in 0.9.5 too
Moving toward a solution
At a glance, it’s clear that an eg. dict
subclass will never match the correct condition here.
The first solution that come to mind is to check isinstance
calls rather than type
ones, by reordering the checks following inheritance chains bottom-up, which does not seems to pose particular diamond problems:
classDiagram
int <|-- bool
str <|-- django_utils_safestring_SafeString
django_utils_safestring_SafeData <|-- django_utils_safestring_SafeString
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverList
collections_abc_MutableSequence <|-- evennia_utils_dbserialize__SaverList
collections_abc_Sequence <|-- collections_abc_MutableSequence
collections_abc_Reversible <|-- collections_abc_Sequence
collections_abc_Iterable <|-- collections_abc_Reversible
collections_abc_Collection <|-- collections_abc_Sequence
collections_abc_Sized <|-- collections_abc_Collection
collections_abc_Iterable <|-- collections_abc_Collection
collections_abc_Container <|-- collections_abc_Collection
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverDict
collections_abc_MutableMapping <|-- evennia_utils_dbserialize__SaverDict
collections_abc_Mapping <|-- collections_abc_MutableMapping
collections_abc_Collection <|-- collections_abc_Mapping
collections_abc_Sized <|-- collections_abc_Collection
collections_abc_Iterable <|-- collections_abc_Collection
collections_abc_Container <|-- collections_abc_Collection
dict <|-- collections_defaultdict
evennia_utils_dbserialize__SaverDict <|-- evennia_utils_dbserialize__SaverDefaultDict
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverDict
collections_abc_MutableMapping <|-- evennia_utils_dbserialize__SaverDict
collections_abc_Mapping <|-- collections_abc_MutableMapping
collections_abc_Collection <|-- collections_abc_Mapping
collections_abc_Sized <|-- collections_abc_Collection
collections_abc_Iterable <|-- collections_abc_Collection
collections_abc_Container <|-- collections_abc_Collection
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverSet
collections_abc_MutableSet <|-- evennia_utils_dbserialize__SaverSet
collections_abc_Set <|-- collections_abc_MutableSet
collections_abc_Collection <|-- collections_abc_Set
collections_abc_Sized <|-- collections_abc_Collection
collections_abc_Iterable <|-- collections_abc_Collection
collections_abc_Container <|-- collections_abc_Collection
dict <|-- collections_OrderedDict
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverOrderedDict
collections_abc_MutableMapping <|-- evennia_utils_dbserialize__SaverOrderedDict
collections_abc_Mapping <|-- collections_abc_MutableMapping
collections_abc_Collection <|-- collections_abc_Mapping
collections_abc_Sized <|-- collections_abc_Collection
collections_abc_Iterable <|-- collections_abc_Collection
collections_abc_Container <|-- collections_abc_Collection
evennia_utils_dbserialize__SaverMutable <|-- evennia_utils_dbserialize__SaverDeque
I could work on that but maybe I’m missing an obvious, already existing way of doing things so what do you think?
Many thanks, regards
Issue Analytics
- State:
- Created a year ago
- Comments:19 (19 by maintainers)
Top Results From Across the Web
Serializing returned dict doesn't work · Issue #393 - GitHub
Describe the issue briefly here, including: Serializing returned dict fails, rather than being serialized as if it was a regular dict, ...
Read more >python json module and subclassing dict - Stack Overflow
I would like to define a subclass of dict with, in particular, custom JSON serialization. The problem I'm running into is that if...
Read more >Json does not support Mapping and MutableMapping
Why json module can't serialize Mapping classes by default? ... No, PyDict_Merge can not use PyDict_Iter if other is not subclass of dict....
Read more >Make a Python Class JSON Serializable - PYnative
i.e., The fundamental problem is that the JSON encoder json.dump() and json.dumps() only knows how to serialize the basic set of object types...
Read more >Frequently Asked Questions — jsons documentation
The serialization process of __dict__ cannot easily be tuned. ... It's quite a hassle to (de)serialize custom types: you need to write a...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@Griatch
Methinks we should modify
@examine
so that there’s a settings.py boolean that tells it to respect it.Closed in #2809.