Logo Search packages:      
Sourcecode: whyteboard version File versions  Download package

Classes | Functions | Variables

whyteboard::lib::pubsub::core::pubsub2 Namespace Reference

Classes

class  ExcInfo
class  Listener
class  ListenerError
class  Message

Functions

def logToStdOut
def sendMessage
def setLog
def setupMsgTree
def subscribe
def unsubscribe

Variables

list __all__
 _log = None
int PUBSUB_VERSION = 2

Detailed Description

This module provides publish-subscribe functions that allow
your methods, functions, and any other callable object to subscribe to 
messages of a given topic, sent from anywhere in your application. 
It therefore provides a powerful decoupling mechanism, e.g. between 
GUI and application logic: senders and listeners don't need to know about 
each other. 

E.g. the following sends a message of type 'MsgType' to a listener, 
carrying data 'some data' (in this case, a string, but could be anything)::

    import pubsub2 as ps
    class MsgType(ps.Message):
pass
    def listener(msg, data):
print 'got msg', data
    ps.subscribe(listener, MsgType)
    ps.sendMessage(MsgType('some data'))

The only requirement on your listener is that it be a callable that takes
the message instance as the first argument, and any args/kwargs come after. 
Contrary to pubsub, with pubsub2 the data sent with your message is 
specified in the message instance constructor, and those parameters are
passed on directly to your listener via its parameter list. 

The important concepts of pubsub2 are: 

- topic: the message type. This is a 'dotted' sequence of class names, 
  defined in your messaging module e.g. yourmsgs.py. The sequence
  denotes a hierarchy of topics from most general to least. 
  For example, a listener of this topic::

      Sports.Baseball

  would receive messages for these topics::

      Sports.Baseball              # because same
      Sports.Baseball.Highscores   # because more specific

  but not these::

      Sports     # because more general
      News       # because different topic
    
  Defining a topic hierarchy is trivial: in yourmsgs.py you would do e.g.::

      import pubsub2 as ps
      class Sports(ps.Message):
class Baseball(ps.Message):
    class Highscores(ps.Message): pass
    class Lowscores(ps.Message):  pass
class Hockey(ps.Message): 
    class Highscores(ps.Message): pass
    
      ps.setupMsgTree(Sports) # don't forget this!
    
  Note that the above allows you to document your message topic tree 
  using standard Python techniques, and to define specific __init__()
  for your data. 

- listener: a function, bound method or callable object. The first 
  argument will be a reference to a Message object. 
  The order of call of the listeners is not specified. Here are 
  examples of valid listeners (see the Sports.subscribe() calls)::
      
      class Foo:
  def __call__(self, m):       pass
  def meth(self,  m):          pass
  def meth2(self, m, arg1=''): pass # arg1 is optional so valid
      foo = Foo()
    
      def func(m, arg1=None, arg2=''): pass # both arg args are optional
      
      from yourmsgs import Sports
      Sports.Hockey.subscribe(func)       # function
      Sports.Baseball.subscribe(foo.meth) # bound method
      Sports.Hockey.subscribe(foo.meth2)  # bound method
      Sports.Hockey.subscribe(foo)        # functor (Foo.__call__)
      
  In every case, the parameter `m` will contain the message instance, 
  and the remaining arguments are those given to the message constructor.

- message: an instance of a message of a certain type. You create the 
  instance, giving it data via keyword arguments, which become instance
  attributes. E.g. ::

      from yourmsgs import sendMessage, Sports
      sendMessage( Sports.Hockey(a=1, b='c') )
    
  will cause the previous example's `func` listener to get an instance 
  m of Sports.Hockey, with m.a==1 and m.b=='c'. 

  Note that every message instance has a subTopic attribute. If this 
  attribute is not None, it means that the message instance is 
  not for the topic given to the sendMessage(), but for a more 
  generic topic (closer to the root of the message type tree)::

      def handleSports(msg):
assert msg.subTopic == Sports.Hockey
      def handleHockey(msg):
assert msg.subTopic == None
      Sports.Hockey.subscribe(handleHockey)
      Sports.subscribe(handleSports)
      sendMessage(Sports.Hockey())

- sender: the part of your code that calls send()::

    # Sports.Hockey is defined in yourmsgs.py, so:
    from yourmsgs import sendMessage, Sports
    # now send something:
    msg = Sports.Hockey(arg1)
    sendMessage( msg ) 

  Note that the above will cause your listeners to be called as 
  f(msg, arg1). 

- log output: using a messaging system has the disadvantage that 
  "tracking" data/events can be more difficult. As an aid, 
  information is sent to a log function, which by default just 
  discards the information. You can set your own logger via 
  setLog() or logToStdOut().  

  An extra string can be given in the send() or 
  subscribe() calls. For send(), this string allows you to identify 
  the "send point": if you don't see it on your log output, then
  you know that your code doesn't reach the call to send(). For 
  subscribe(), it identifies the listener with a string of your choice, 
  otherwise it would be the (rather cryptic) Python name for the listener 
  callable. 

- exceptions while sending: what should happen if a listener (or something
  it calls) raises an exception? The listeners must be independent of each 
  other because the order of calls is not specified. Certain types of 
  exceptions might be handlable by the sender, so simply stopping the 
  send loop is rather extreme. Instead, the send() aggregates the exception
  objects and when it has sent to all listeners, raises a ListenerError 
  exception. This has an attribute `exceptions` that is a list of 
  ExcInfo instances, one for each exception raised during the send(). 

- infinite recursion: it is possible, though not likely, that one of your
  messages causes another message to get sent, which in turn causes the 
  first type of message to get sent again, thereby leading to an infinite
  loop. There is currently no guard against this, though adding one would
  not be difficult.

To summarize: 

- First, create a file e.g. yourmsgs.py in which you define and document
  your message topics tree and in which you call setupMsgTree();
- Subscribe your listeners to some of those topics by importing yourmsgs.py, 
  and calling subscribe() on the message topic to listen for;
- Anywhere in your code, you can send a message by importing yourmsgs.py, 
  and calling `sendMessage( MsgTopicSeq(data) )` or MsgTopic(data).send()
- Debugging your messaging: 
  - If you are not seeing all the messages that you expect, add some 
    identifiers to the send/subscribe calls. 
  - Turn logging on with logToStdOut() (or use setLog(yourLogFunction)
  - The class mechanism will lead to runtime exception if msg topic doesn't
    exist. 

Note: Listeners (callbacks) are held only by weak reference, which in 
general is adequate (this prevents the messaging system from keeping alive
callables that are no longer used by anyone). However, if you want the 
callback to be a wrapper around one of your functions, that wrapper must 
be stored somewhere so that the weak reference isn't the only reference 
to it (which will cause it to die). 


:Author:      Oliver Schoenborn
:Since:       Apr 2004
:Version:     2.01
:Copyright:   \(c) 2007 Oliver Schoenborn
:License:     Python Software Foundation


Generated by  Doxygen 1.6.0   Back to index