The main moving parts of this application are:
Orbited is a Python "Comet" (persitent AJAX) server that exposes standard TCP sockets to javascript.STOMP is a simple TCP messaging protocol. Orbited includes a STOMP server called MorbidQ.Twisted handles the Python end of the TCP socket.Traits is a system that allows reactive programming, type-checking and rapid GUI development.Chaco is an interactive plotting library for Python which uses Traits.
The following diagram shows how all of this fits together:
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}.
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()