evennia: [BUG] cannot serialize dict subclasses
Describe 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. dictsubclass 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
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 19 (19 by maintainers)
Commits related to this issue
- fix https://github.com/evennia/evennia/issues/2808 — committed to evennia/evennia by aogier 2 years ago
@Griatch
Methinks we should modify
@examineso that there’s a settings.py boolean that tells it to respect it.Closed in #2809.