How do you get access to the dictionary under traits.api.Dict()?

755 views Asked by At

Here is an example of failure from a shell.

>>> from traits.api import Dict
>>> d=Dict()
>>> d['Foo']='BAR'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Dict' object does not support item assignment

I have been searching all over the web, and there is no indication of how to use Dict.

I am trying to write a simple app that displays the contents of a python dictionary. This link (Defining view elements from dictionary elements in TraitsUI) was moderately helpful except for the fact that the dictionary gets updated on some poll_interval and if I use the solution there (wrapping a normal python dict in a class derived from HasTraits) the display does not update when the underlying dictionary gets updated.

Here are the relevant parts of what I have right now. The last class can pretty much be ignored, the only reason I included it is to help understand how I intend to use the Dict.

pyNetObjDisplay.run_ext() gets called once per loop from the base classes run() method

class DictContainer(HasTraits):
    _dict = {}

    def __getattr__(self, key):
        return self._dict[key]  
    def __getitem__(self, key):
        return self._dict[key]
    def __setitem__(self, key, value):
        self._dict[key] = value
    def __delitem__(self, key, value):
        del self._dict[key]
    def __str__(self):
        return self._dict.__str__()
    def __repr__(self):
        return self._dict.__repr__()
    def has_key(self, key):
        return self._dict.has_key(key)


class displayWindow(HasTraits):
    _remote_data = Instance(DictContainer)
    _messages = Str('', desc='Field to display messages to the user.', label='Messages', multi_line=True)

    def __remote_data_default(self):
        tempDict = DictContainer()
        tempDict._dict = Dict
        #tempDict['FOO'] = 'BAR'
        sys.stderr.write('SETTING DEFAULT DICTIONARY:\t%s\n' % tempDict)
        return tempDict
    def __messages_default(self):
        tempStr = Str()
        tempStr = ''
        return tempStr
    def traits_view(self):
        return View(
            Item('object._remote_data', editor=ValueEditor()),
            Item('object._messages'),
            resizable=True
        )


class pyNetObjDisplay(pyNetObject.pyNetObjPubClient):
    '''A derived pyNetObjPubClient that stores remote data in a dictionary and displays it using traitsui.'''

    def __init__(self, hostname='localhost', port=54322, service='pyNetObject', poll_int=10.0):
        self._display = displayWindow()

        self.poll_int = poll_int
        super(pyNetObjDisplay, self).__init__(hostname, port, service)
        self._ui_running = False
        self._ui_pid = 0

        ### For Testing Only, REMOVE THESE LINES ###
        self.connect()
        self.ns_subscribe(service, 'FOO', poll_int)
        self.ns_subscribe(service, 'BAR', poll_int)
        self.ns_subscribe(service, 'BAZ', poll_int)
        ############################################

    def run_ext(self):
        if not self._ui_running:
            self._ui_running = True
            self._ui_pid = os.fork()
            if not self._ui_pid:
                time.sleep(1.25*self.poll_int)
                self._display.configure_traits()
        for ((service, namespace, key), value) in self._object_buffer:
            sys.stderr.write('TEST:\t' + str(self._display._remote_data) + '\n')
            if not self._display._remote_data.has_key(service):
                self._display._remote_data[service] = {}
            if not self._display._remote_data[service].has_key(namespace):
                #self._remote_data[service][namespace] = {}
                self._display._remote_data[service][namespace] = {}
            self._display._remote_data[service][namespace][key] = value

            msg = 'Got Published ((service, namespace, key), value) pair:\t((%s, %s, %s), %s)\n' % (service, namespace, key, value)
            sys.stderr.write(msg)
            self._display._messages += msg
            sys.stderr.write('REMOTE DATA:\n' + str(self._display._remote_data)
        self._object_buffer = []
2

There are 2 answers

3
aestrivex On BEST ANSWER

I think your basic problem has to do with notification issues for traits that live outside the model object, and not with "how to access those objects" per se [edit: actually no this is not your problem at all! But it is what I thought you were trying to do when I read your question with my biased mentality towards problems I have seen before and in any case my suggested solution will still work]. I have run into this sort of problem recently because of how I decided to design my program (with code describing a GUI separated modularly from the very complex sets of data that it can contain). You may have found my other questions, as you found the first one.

Having lots of data live in a complex data hierarchy away from the GUI is not the design that traitsui has in mind for your application and it causes all kinds of problems with notifications. Having a flatter design where GUI information is integrated into the different parts of your program more directly is the design solution.

I think that various workarounds might be possible for this in general (I have used some for instance in enabled_when listening outside model object) that don't involve dictionaries. I'm not sure what the most design friendly solution to your problem with dictionaries is, but one thing that works and doesn't interfere a lot with your design (but it is still a "somewhat annoying" solution) is to make everything in a dictionary be a HasTraits and thus tag it as listenable. Like so:

from traits.api import *
from traitsui.api import *
from traitsui.ui_editors.array_view_editor import ArrayViewEditor
import numpy as np

class DContainer(HasTraits):
    _dict=Dict
    def __getattr__(self, k):
        if k in self._dict:
            return self._dict[k]

class DItem(HasTraits):
    _item=Any

    def __init__(self,item):
        super(DItem,self).__init__()
        self._item=item
    def setitem(self,val):
        self._item=val
    def getitem(self):
        return self._item
    def traits_view(self):
        return View(Item('_item',editor=ArrayViewEditor()))

class LargeApplication(HasTraits):
  d=Instance(DContainer)
  stupid_listener=Any
  bn=Button('CLICKME')

  def _d_default(self):
    d=DContainer()
    d._dict={'a_stat':DItem(np.random.random((10,1))),
           'b_stat':DItem(np.random.random((10,10)))}
    return d

  def traits_view(self):
    v=View(
        Item('object.d.a_stat',editor=InstanceEditor(),style='custom'),
        Item('bn'),
        height=500,width=500)
    return v

  def _bn_fired(self):
    self.d.a_stat.setitem(np.random.random((10,1)))

LargeApplication().configure_traits()
1
user2121874 On

Okay, I found the answer (kindof) in this question: Traits List not reporting items added or removed

when including Dict or List objects as attributes in a class one should NOT do it this way:

class Foo(HasTraits):
    def __init__(self):
        ### This will not work as expected!
        self.bar = Dict(desc='Description.', label='Name', value={})

Instead do this:

class Foo(HasTraits):
    def __init__(self):
        self.add_trait('bar', Dict(desc='Description.', label='Name', value={}) )

Now the following will work:

>>> f = Foo()
>>> f.bar['baz']='boo'
>>> f.bar['baz']
'boo'

Unfortunately for some reason the GUI generated with configure_traits() does not update it's view when the underlying data changes. Here is some test code that demonstrates the problem:

import os
import time
import sys
from traits.api import HasTraits, Str, Dict
from traitsui.api import View, Item, ValueEditor

class displayWindow(HasTraits):
    def __init__(self, **traits):
        super(displayWindow, self).__init__(**traits)
        self.add_trait('_remote_data', Dict(desc='Dictionary to store remote data in.', label='Data', value={}) )
        self.add_trait('_messages', Str(desc='Field to display messages to the user.', label='Messages', multi_line=True, value='') )

    def traits_view(self):
        return View(
            Item('object._remote_data', editor=ValueEditor()),
            Item('object._messages'),
            resizable=True
        )

class testObj(object):
    def __init__(self):
        super(testObj, self).__init__()
        self._display = displayWindow()
        self._ui_pid = 0
    def run(self):
        ### Run the GUI in the background
        self._ui_pid = os.fork()
        if not self._ui_pid:
            self._display.configure_traits()
        i = 0
        while True:
            self._display._remote_data[str(i)] = i
            msg = 'Added (key,value):\t("%s", %s)\n' % (str(i), i, )
            self._display._messages += msg
            sys.stderr.write(msg)
            time.sleep(5.0)
            i+=1
if __name__ == '__main__':
    f = testObj()
    f.run()