question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] cannot serialize dict subclasses

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:19 (19 by maintainers)

github_iconTop GitHub Comments

2reactions
volundmushcommented, Jul 21, 2022

@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.

1reaction
Griatchcommented, Aug 2, 2022

Closed in #2809.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found