Customizing traits.api.Dict methods

27 views
Skip to first unread message

Michael Dudley

unread,
Sep 24, 2021, 9:48:57 AM9/24/21
to Enthought Tool Suite users
Hi all,

I am trying to customize methods on a traits.api.Dict object, like __setitem__(). I'm using traits 6.2.0.

I have gotten pretty close with the code below, but I am missing something important, because the default object doesn't get initialized correctly (it doesn't use my customized class).

Is there a better way to accomplish this?

import copy

import traits.api


class TraitRegistryObject(traits.api.TraitDictObject):
    def __setitem__(self, key, item):
        print("Customized __setitem__!")
        super().__setitem__(key, item)

    def __deepcopy__(self, memo):
        result = TraitRegistryObject(
            self.trait,
            lambda: None,
            self.name,
            dict(copy.deepcopy(x, memo) for x in self.items()),
        )

        return result


class Registry(traits.api.Dict):
    info_text = "object registry"

    def validate(self, object, name, value):
        if isinstance(value, dict):
            if object is None:
                return value

            return TraitRegistryObject(self, object, name, value)

        self.error(object, name, value)


class Model(traits.api.HasTraits):
    registry = Registry()


if __name__ == "__main__":
    model = Model()
    model.registry = {}  # A TraitRegistryObject() doesn't get created without this.
    model.registry["abc"] = 123

Corran Webster

unread,
Sep 29, 2021, 6:09:18 AM9/29/21
to Enthought Tool Suite users
Hi,

I think the missing thing in what you are doing is that you also need to tell your new Trait that the default value should be a TraitRegistryObject - otherwise it will inherit from Dict and give you a TraitDictObject, which is what you are seeing.

Unfortunately Traits currently special-cases the default value for Dict/TraitDictObject.  The simple solution is to make sure that you always supply a TraitRegistryObject instance manually as the default (eg. by a _*_default() method).  The more complex method is that you need to specify a different default_value_type and default_value for your new trait (not sure what the right combination is, because TraitDictObject has an annoying __init__ signature).  But this is a bit hypothetical, because there is a lot of magic around this stuff in Traits.  If you don't care about on_trait_change (eg. you are using observe exclusively) or *_items events firing there may be simpler approaches.

There's a longer-term plan to make it easier to do this sort of thing, but Traits isn't there yet.

It looks like what you're trying to build is a registry which does something when a value is set into a dictionary.  When we're doing something like this we usually just use a HasTraits class which has a Dict trait, and then depending on the exact use-case we'll either implement accessor methods on the HasTraits class (if you need to do something before you add into the registry) or an observer (if you are fine with just reacting to the changes).  For example, something like the ServiceRegistry in Envisage illustrates this sort of pattern (https://github.com/enthought/envisage/blob/main/envisage/service_registry.py)

There is also the DataContext class in Codetools which is a traitified dict-like on steroids (see http://docs.enthought.com/codetools/tut_datacontexts.html and https://github.com/enthought/codetools/blob/ae7b54e055221fe907b1cca4e4fc83ac5b3a6878/codetools/contexts/data_context.py#L235).  However we mainly use those for code execution contexts rather than registries.

Hope this helps.

-- Corran
Reply all
Reply to author
Forward
0 new messages