Traits and Orbited

How does all of this work?

The main moving parts of this application are:

Orbited

Orbited is a Python "Comet" (persitent AJAX) server that exposes standard TCP sockets to javascript.

STOMP

STOMP is a simple TCP messaging protocol. Orbited includes a STOMP server called MorbidQ.

Twisted

Twisted handles the Python end of the TCP socket.

Traits

Traits is a system that allows reactive programming, type-checking and rapid GUI development.

Chaco

Chaco is an interactive plotting library for Python which uses Traits.

The following diagram shows how all of this fits together:

orbited_architecture.png

The Basic Code Ideas

The core of the system is a subclass of stompservice.StompClientFactory, which handles sending and receiving messages. Simplifying the actual code to remove the control messages like log-ins, this looks like:

class TraitsWeb(StompClientFactory):

    def recv_connected(self, msg):
        print 'Connected; producing data'
        self.subscribe(CHANNEL_IN_NAME)
        self.subscribe(CHANNEL_CONTROL_NAME)

    def recv_message(self, msg):
        message = json.decode(msg['body'])
        if msg['headers']['destination'] == CHANNEL_IN_NAME:
            self.handlers[message['id']].setattr(message['trait'], message['value'])
        elif msg['headers']['destination'] == CHANNEL_CONTROL_NAME:
            # code to handle log-in and disconnection which manages
            # self.handlers

    def send_data(self, data):
        if hasattr(self, 'send'):
            data['type'] = 'trait_change'
            self.send(CHANNEL_OUT_NAME, json.encode(data))

The key to the recv_message code is a dictionary of handlers, one for each web client. These handlers look like this:

class ChatWebHandler(HasTraits):
    """ This class handles interactions with particular web-based clients
    """
    id = Str

    model = Instance(Chat)

    def setattr(self, name, value):
        if name == 'entry_field':
            self.model.add_message(self.id, value)
        elif name == 'rating_selector':
            self.model.connection_ratings[self.id] = value

The setattr method makes calls to the main model object, and not much else. These changes within the main model object cause trait events to fire and trait on_trait_change listeners on those events to generate new data.

This new data is then passed back to the factory as a send_data call containing a dictionary of the form {'trait': name, 'value': value}.

The Tricky Stuff

Getting the Traits wxWindows UI and Twisted to cohabit can be tricky. You need to carefully import things in the correct order (thanks to Bryce Hendrix at Enthought for working this out):

# Now get the wxApp object from Traits UI and register it with the reactor
from enthought.traits.ui.wx.toolkit import _app
from twisted.internet import reactor

reactor.registerWxApp(_app)

# Now we import orbited and stomp libraries
from orbited import json
from stompservice import StompClientFactory

# And finally our Traits and Traits UI stuff
from enthought.traits.api import HasTraits, Range, Bool, Str, Float, Any
from enthought.traits.ui.api import View, Group, Item

# your code goes here
...

# connect up factories and start the reactor
if __name__ == '__main__':
    # connect client factories
    reactor.connectTCP('localhost', 61613, your_client_factory)

    # do edit_traits() on any Traits objects you want a GUI for
    traited_object.edit_traits()

    # run Twisted
    reactor.run()