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:

  1. 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)
  1. reload and create a foo object
reload
create foo :foo.FooObject
  1. 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).
  1. 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

Most upvoted comments

@Griatch

The examine command does not check attribute locks, this is deliberately done, so as to make this command as useful as possible (you can’t edit Attributes via examine). That said, if you explicitly want to hide data from your builders in Attributes, you can of course override examine to do just that.

Methinks we should modify @examine so that there’s a settings.py boolean that tells it to respect it.

Closed in #2809.