]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/pubsub.py
move wxPython to new trunk
[wxWidgets.git] / wxPython / wx / lib / pubsub.py
diff --git a/wxPython/wx/lib/pubsub.py b/wxPython/wx/lib/pubsub.py
deleted file mode 100644 (file)
index 6973df1..0000000
+++ /dev/null
@@ -1,1243 +0,0 @@
-
-#---------------------------------------------------------------------------
-"""
-This module provides a publish-subscribe component that allows
-listeners to subcribe to messages of a given topic. Contrary to the
-original wxPython.lib.pubsub module (which it is based on), it uses 
-weak referencing to the subscribers so the lifetime of subscribers 
-is not affected by Publisher. Also, callable objects can be used in 
-addition to functions and bound methods. See Publisher class docs for 
-more details. 
-
-Thanks to Robb Shecter and Robin Dunn for having provided 
-the basis for this module (which now shares most of the concepts but
-very little design or implementation with the original 
-wxPython.lib.pubsub).
-
-The publisher is a singleton instance of the PublisherClass class. You 
-access the instance via the Publisher object available from the module::
-
-    from wx.lib.pubsub import Publisher
-    Publisher().subscribe(...)
-    Publisher().sendMessage(...)
-    ...
-
-:Author:      Oliver Schoenborn
-:Since:       Apr 2004
-:Version:     $Id$
-:Copyright:   \(c) 2004 Oliver Schoenborn
-:License:     wxWidgets
-"""
-
-_implNotes = """
-Implementation notes
---------------------
-
-In class Publisher, I represent the topics-listener set as a tree
-where each node is a topic, and contains a list of listeners of that
-topic, and a dictionary of subtopics of that topic. When the Publisher
-is told to send a message for a given topic, it traverses the tree
-down to the topic for which a message is being generated, all
-listeners on the way get sent the message.
-
-Publisher currently uses a weak listener topic tree to store the
-topics for each listener, and if a listener dies before being
-unsubscribed, the tree is notified, and the tree eliminates the
-listener from itself.
-
-Ideally, _TopicTreeNode would be a generic _TreeNode with named
-subnodes, and _TopicTreeRoot would be a generic _Tree with named
-nodes, and Publisher would store listeners in each node and a topic
-tuple would be converted to a path in the tree.  This would lead to a
-much cleaner separation of concerns. But time is over, time to move on.
-"""
-#---------------------------------------------------------------------------
-
-# for function and method parameter counting:
-from types   import InstanceType
-from inspect import getargspec, ismethod, isfunction
-# for weakly bound methods:
-from new     import instancemethod as InstanceMethod
-from weakref import ref as WeakRef
-
-# -----------------------------------------------------------------------------
-
-def _isbound(method):
-    """Return true if method is a bound method, false otherwise"""
-    assert ismethod(method)
-    return method.im_self is not None
-
-
-def _paramMinCountFunc(function):
-    """Given a function, return pair (min,d) where min is minimum # of
-    args required, and d is number of default arguments."""
-    assert isfunction(function)
-    (args, va, kwa, dflt) = getargspec(function)
-    lenDef = len(dflt or ())
-    lenArgs = len(args or ())
-    lenVA = int(va is not None)
-    return (lenArgs - lenDef + lenVA, lenDef)
-
-
-def _paramMinCount(callableObject):
-    """
-    Given a callable object (function, method or callable instance),
-    return pair (min,d) where min is minimum # of args required, and d
-    is number of default arguments. The 'self' parameter, in the case
-    of methods, is not counted.
-    """
-    if type(callableObject) is InstanceType:
-        min, d = _paramMinCountFunc(callableObject.__call__.im_func)
-        return min-1, d
-    elif ismethod(callableObject):
-        min, d = _paramMinCountFunc(callableObject.im_func)
-        return min-1, d
-    elif isfunction(callableObject):
-        return _paramMinCountFunc(callableObject)
-    else:
-        raise 'Cannot determine type of callable: '+repr(callableObject)
-
-
-def _tupleize(items):
-    """Convert items to tuple if not already one, 
-    so items must be a list, tuple or non-sequence"""
-    if isinstance(items, list):
-        raise TypeError, 'Not allowed to tuple-ize a list'
-    elif isinstance(items, (str, unicode)) and items.find('.') != -1:
-        items = tuple(items.split('.'))
-    elif not isinstance(items, tuple):
-        items = (items,)
-    return items
-
-
-def _getCallableName(callable):
-    """Get name for a callable, ie function, bound 
-    method or callable instance"""
-    if ismethod(callable):
-        return '%s.%s ' % (callable.im_self, callable.im_func.func_name)
-    elif isfunction(callable):
-        return '%s ' % callable.__name__
-    else:
-        return '%s ' % callable
-    
-    
-def _removeItem(item, fromList):
-    """Attempt to remove item from fromList, return true 
-    if successful, false otherwise."""
-    try: 
-        fromList.remove(item)
-        return True
-    except ValueError:
-        return False
-        
-        
-# -----------------------------------------------------------------------------
-
-class _WeakMethod:
-    """Represent a weak bound method, i.e. a method doesn't keep alive the 
-    object that it is bound to. It uses WeakRef which, used on its own, 
-    produces weak methods that are dead on creation, not very useful. 
-    Typically, you will use the getRef() function instead of using
-    this class directly. """
-    
-    def __init__(self, method, notifyDead = None):
-        """The method must be bound. notifyDead will be called when 
-        object that method is bound to dies. """
-        assert ismethod(method)
-        if method.im_self is None:
-            raise ValueError, "We need a bound method!"
-        if notifyDead is None:
-            self.objRef = WeakRef(method.im_self)
-        else:
-            self.objRef = WeakRef(method.im_self, notifyDead)
-        self.fun = method.im_func
-        self.cls = method.im_class
-        
-    def __call__(self):
-        """Returns a new.instancemethod if object for method still alive. 
-        Otherwise return None. Note that instancemethod causes a 
-        strong reference to object to be created, so shouldn't save 
-        the return value of this call. Note also that this __call__
-        is required only for compatibility with WeakRef.ref(), otherwise
-        there would be more efficient ways of providing this functionality."""
-        if self.objRef() is None:
-            return None
-        else:
-            return InstanceMethod(self.fun, self.objRef(), self.cls)
-        
-    def __eq__(self, method2):
-        """Two WeakMethod objects compare equal if they refer to the same method
-        of the same instance. Thanks to Josiah Carlson for patch and clarifications
-        on how dict uses eq/cmp and hashing. """
-        if not isinstance(method2, _WeakMethod):
-            return False 
-        return      self.fun      is method2.fun \
-                and self.objRef() is method2.objRef() \
-                and self.objRef() is not None
-    
-    def __hash__(self):
-        """Hash is an optimization for dict searches, it need not 
-        return different numbers for every different object. Some objects
-        are not hashable (eg objects of classes derived from dict) so no
-        hash(objRef()) in there, and hash(self.cls) would only be useful
-        in the rare case where instance method was rebound. """
-        return hash(self.fun)
-    
-    def __repr__(self):
-        dead = ''
-        if self.objRef() is None: 
-            dead = '; DEAD'
-        obj = '<%s at %s%s>' % (self.__class__, id(self), dead)
-        return obj
-        
-    def refs(self, weakRef):
-        """Return true if we are storing same object referred to by weakRef."""
-        return self.objRef == weakRef
-
-
-def _getWeakRef(obj, notifyDead=None):
-    """Get a weak reference to obj. If obj is a bound method, a _WeakMethod
-    object, that behaves like a WeakRef, is returned, if it is
-    anything else a WeakRef is returned. If obj is an unbound method,
-    a ValueError will be raised."""
-    if ismethod(obj):
-        createRef = _WeakMethod
-    else:
-        createRef = WeakRef
-        
-    if notifyDead is None:
-        return createRef(obj)
-    else:
-        return createRef(obj, notifyDead)
-    
-    
-# -----------------------------------------------------------------------------
-
-def getStrAllTopics():
-    """Function to call if, for whatever reason, you need to know 
-    explicitely what is the string to use to indicate 'all topics'."""
-    return ''
-
-
-# alias, easier to see where used
-ALL_TOPICS = getStrAllTopics()
-
-# -----------------------------------------------------------------------------
-
-
-class _NodeCallback:
-    """Encapsulate a weak reference to a method of a TopicTreeNode
-    in such a way that the method can be called, if the node is 
-    still alive, but the callback does not *keep* the node alive.
-    Also, define two methods, preNotify() and noNotify(), which can 
-    be redefined to something else, very useful for testing. 
-    """
-    
-    def __init__(self, obj):
-        self.objRef = _getWeakRef(obj)
-        
-    def __call__(self, weakCB):
-        notify = self.objRef()
-        if notify is not None: 
-            self.preNotify(weakCB)
-            notify(weakCB)
-        else: 
-            self.noNotify()
-            
-    def preNotify(self, dead):
-        """'Gets called just before our callback (self.objRef) is called"""
-        pass
-        
-    def noNotify(self):
-        """Gets called if the TopicTreeNode for this callback is dead"""
-        pass
-
-
-class _TopicTreeNode:
-    """A node in the topic tree. This contains a list of callables
-    that are interested in the topic that this node is associated
-    with, and contains a dictionary of subtopics, whose associated
-    values are other _TopicTreeNodes. The topic of a node is not stored
-    in the node, so that the tree can be implemented as a dictionary
-    rather than a list, for ease of use (and, likely, performance).
-    
-    Note that it uses _NodeCallback to encapsulate a callback for 
-    when a registered listener dies, possible thanks to WeakRef.
-    Whenever this callback is called, the onDeadListener() function, 
-    passed in at construction time, is called (unless it is None).
-    """
-    
-    def __init__(self, topicPath, onDeadListenerWeakCB):
-        self.__subtopics = {}
-        self.__callables = []
-        self.__topicPath = topicPath
-        self.__onDeadListenerWeakCB = onDeadListenerWeakCB
-        
-    def getPathname(self): 
-        """The complete node path to us, ie., the topic tuple that would lead to us"""
-        return self.__topicPath
-    
-    def createSubtopic(self, subtopic, topicPath):
-        """Create a child node for subtopic"""
-        return self.__subtopics.setdefault(subtopic,
-                    _TopicTreeNode(topicPath, self.__onDeadListenerWeakCB))
-    
-    def hasSubtopic(self, subtopic):
-        """Return true only if topic string is one of subtopics of this node"""
-        return self.__subtopics.has_key(subtopic)
-    
-    def getNode(self, subtopic):
-        """Return ref to node associated with subtopic"""
-        return self.__subtopics[subtopic]
-    
-    def addCallable(self, callable):
-        """Add a callable to list of callables for this topic node"""
-        try:
-            id = self.__callables.index(_getWeakRef(callable))
-            return self.__callables[id]
-        except ValueError:
-            wrCall = _getWeakRef(callable, _NodeCallback(self.__notifyDead))
-            self.__callables.append(wrCall)
-            return wrCall
-            
-    def getCallables(self):
-        """Get callables associated with this topic node"""
-        return [cb() for cb in self.__callables if cb() is not None]
-    
-    def hasCallable(self, callable):
-        """Return true if callable in this node"""
-        try: 
-            self.__callables.index(_getWeakRef(callable))
-            return True
-        except ValueError:
-            return False
-    
-    def sendMessage(self, message):
-        """Send a message to our callables"""
-        deliveryCount = 0
-        for cb in self.__callables:
-            listener = cb()
-            if listener is not None:
-                listener(message)
-                deliveryCount += 1
-        return deliveryCount
-    
-    def removeCallable(self, callable):
-        """Remove weak callable from our node (and return True). 
-        Does nothing if not here (and returns False)."""
-        try: 
-            self.__callables.remove(_getWeakRef(callable))
-            return True
-        except ValueError:
-            return False
-        
-    def clearCallables(self):
-        """Abandon list of callables to caller. We no longer have 
-        any callables after this method is called."""
-        tmpList = [cb for cb in self.__callables if cb() is not None]
-        self.__callables = []
-        return tmpList
-        
-    def __notifyDead(self, dead):
-        """Gets called when a listener dies, thanks to WeakRef"""
-        #print 'TreeNODE', `self`, 'received death certificate for ', dead
-        self.__cleanupDead()
-        if self.__onDeadListenerWeakCB is not None:
-            cb = self.__onDeadListenerWeakCB()
-            if cb is not None: 
-                cb(dead)
-        
-    def __cleanupDead(self):
-        """Remove all dead objects from list of callables"""
-        self.__callables = [cb for cb in self.__callables if cb() is not None]
-        
-    def __str__(self):
-        """Print us in a not-so-friendly, but readable way, good for debugging."""
-        strVal = []
-        for callable in self.getCallables():
-            strVal.append(_getCallableName(callable))
-        for topic, node in self.__subtopics.iteritems():
-            strVal.append(' (%s: %s)' %(topic, node))
-        return ''.join(strVal)
-      
-      
-class _TopicTreeRoot(_TopicTreeNode):
-    """
-    The root of the tree knows how to access other node of the 
-    tree and is the gateway of the tree user to the tree nodes. 
-    It can create topics, and and remove callbacks, etc. 
-    
-    For efficiency, it stores a dictionary of listener-topics, 
-    so that unsubscribing a listener just requires finding the 
-    topics associated to a listener, and finding the corresponding
-    nodes of the tree. Without it, unsubscribing would require 
-    that we search the whole tree for all nodes that contain 
-    given listener. Since Publisher is a singleton, it will 
-    contain all topics in the system so it is likely to be a large
-    tree. However, it is possible that in some runs, unsubscribe()
-    is called very little by the user, in which case most unsubscriptions
-    are automatic, ie caused by the listeners dying. In this case, 
-    a flag is set to indicate that the dictionary should be cleaned up
-    at the next opportunity. This is not necessary, it is just an 
-    optimization.
-    """
-    
-    def __init__(self):
-        self.__callbackDict  = {}
-        self.__callbackDictCleanup = 0
-        # all child nodes will call our __rootNotifyDead method
-        # when one of their registered listeners dies 
-        _TopicTreeNode.__init__(self, (ALL_TOPICS,), 
-                                _getWeakRef(self.__rootNotifyDead))
-        
-    def addTopic(self, topic, listener):
-        """Add topic to tree if doesnt exist, and add listener to topic node"""
-        assert isinstance(topic, tuple)
-        topicNode = self.__getTreeNode(topic, make=True)
-        weakCB = topicNode.addCallable(listener)
-        assert topicNode.hasCallable(listener)
-
-        theList = self.__callbackDict.setdefault(weakCB, [])
-        assert self.__callbackDict.has_key(weakCB)
-        # add it only if we don't already have it
-        try:
-            weakTopicNode = WeakRef(topicNode)
-            theList.index(weakTopicNode)
-        except ValueError:
-            theList.append(weakTopicNode)
-        assert self.__callbackDict[weakCB].index(weakTopicNode) >= 0
-        
-    def getTopics(self, listener):
-        """Return the list of topics for given listener"""
-        weakNodes = self.__callbackDict.get(_getWeakRef(listener), [])
-        return [weakNode().getPathname() for weakNode in weakNodes 
-                if weakNode() is not None]
-
-    def isSubscribed(self, listener, topic=None):
-        """Return true if listener is registered for topic specified. 
-        If no topic specified, return true if subscribed to something.
-        Use topic=getStrAllTopics() to determine if a listener will receive 
-        messages for all topics."""
-        weakCB = _getWeakRef(listener)
-        if topic is None: 
-            return self.__callbackDict.has_key(weakCB)
-        else:
-            topicPath = _tupleize(topic)
-            for weakNode in self.__callbackDict[weakCB]:
-                if topicPath == weakNode().getPathname():
-                    return True
-            return False
-            
-    def unsubscribe(self, listener, topicList):
-        """Remove listener from given list of topics. If topicList
-        doesn't have any topics for which listener has subscribed,
-        nothing happens."""
-        weakCB = _getWeakRef(listener)
-        if not self.__callbackDict.has_key(weakCB):
-            return
-        
-        cbNodes = self.__callbackDict[weakCB] 
-        if topicList is None:
-            for weakNode in cbNodes:
-                weakNode().removeCallable(listener)
-            del self.__callbackDict[weakCB] 
-            return
-
-        for weakNode in cbNodes:
-            node = weakNode()
-            if node is not None and node.getPathname() in topicList:
-                success = node.removeCallable(listener)
-                assert success == True
-                cbNodes.remove(weakNode)
-                assert not self.isSubscribed(listener, node.getPathname())
-
-    def unsubAll(self, topicList, onNoSuchTopic):
-        """Unsubscribe all listeners registered for any topic in 
-        topicList. If a topic in the list does not exist, and 
-        onNoSuchTopic is not None, a call
-        to onNoSuchTopic(topic) is done for that topic."""
-        for topic in topicList:
-            node = self.__getTreeNode(topic)
-            if node is not None:
-                weakCallables = node.clearCallables()
-                for callable in weakCallables:
-                    weakNodes = self.__callbackDict[callable]
-                    success = _removeItem(WeakRef(node), weakNodes)
-                    assert success == True
-                    if weakNodes == []:
-                        del self.__callbackDict[callable]
-            elif onNoSuchTopic is not None: 
-                onNoSuchTopic(topic)
-            
-    def sendMessage(self, topic, message, onTopicNeverCreated):
-        """Send a message for given topic to all registered listeners. If 
-        topic doesn't exist, call onTopicNeverCreated(topic)."""
-        # send to the all-toipcs listeners
-        deliveryCount = _TopicTreeNode.sendMessage(self, message)
-        # send to those who listen to given topic or any of its supertopics
-        node = self
-        for topicItem in topic:
-            assert topicItem != ''
-            if node.hasSubtopic(topicItem):
-                node = node.getNode(topicItem)
-                deliveryCount += node.sendMessage(message)
-            else: # topic never created, don't bother continuing
-                if onTopicNeverCreated is not None:
-                    onTopicNeverCreated(topic)
-                break
-        return deliveryCount
-
-    def numListeners(self):
-        """Return a pair (live, dead) with count of live and dead listeners in tree"""
-        dead, live = 0, 0
-        for cb in self.__callbackDict:
-            if cb() is None: 
-                dead += 1
-            else:
-                live += 1
-        return live, dead
-    
-    # clean up the callback dictionary after how many dead listeners
-    callbackDeadLimit = 10
-
-    def __rootNotifyDead(self, dead):
-        #print 'TreeROOT received death certificate for ', dead
-        self.__callbackDictCleanup += 1
-        if self.__callbackDictCleanup > _TopicTreeRoot.callbackDeadLimit:
-            self.__callbackDictCleanup = 0
-            oldDict = self.__callbackDict
-            self.__callbackDict = {}
-            for weakCB, weakNodes in oldDict.iteritems():
-                if weakCB() is not None:
-                    self.__callbackDict[weakCB] = weakNodes
-        
-    def __getTreeNode(self, topic, make=False):
-        """Return the tree node for 'topic' from the topic tree. If it 
-        doesnt exist and make=True, create it first."""
-        # if the all-topics, give root; 
-        if topic == (ALL_TOPICS,):
-            return self
-            
-        # not root, so traverse tree
-        node = self
-        path = ()
-        for topicItem in topic:
-            path += (topicItem,)
-            if topicItem == ALL_TOPICS:
-                raise ValueError, 'Topic tuple must not contain ""'
-            if make: 
-                node = node.createSubtopic(topicItem, path)
-            elif node.hasSubtopic(topicItem):
-                node = node.getNode(topicItem)
-            else:
-                return None
-        # done
-        return node
-        
-    def printCallbacks(self):
-        strVal = ['Callbacks:\n']
-        for listener, weakTopicNodes in self.__callbackDict.iteritems():
-            topics = [topic() for topic in weakTopicNodes if topic() is not None]
-            strVal.append('  %s: %s\n' % (_getCallableName(listener()), topics))
-        return ''.join(strVal)
-        
-    def __str__(self):
-        return 'all: %s' % _TopicTreeNode.__str__(self)
-    
-    
-# -----------------------------------------------------------------------------
-
-class _SingletonKey: pass
-
-class PublisherClass:
-    """
-    The publish/subscribe manager.  It keeps track of which listeners
-    are interested in which topics (see subscribe()), and sends a
-    Message for a given topic to listeners that have subscribed to
-    that topic, with optional user data (see sendMessage()).
-    
-    The three important concepts for Publisher are:
-        
-    - listener: a function, bound method or
-      callable object that can be called with one parameter
-      (not counting 'self' in the case of methods). The parameter
-      will be a reference to a Message object. E.g., these listeners
-      are ok::
-          
-          class Foo:
-              def __call__(self, a, b=1): pass # can be called with only one arg
-              def meth(self,  a):         pass # takes only one arg
-              def meth2(self, a=2, b=''): pass # can be called with one arg
-        
-          def func(a, b=''): pass
-          
-          Foo foo
-          Publisher().subscribe(foo)           # functor
-          Publisher().subscribe(foo.meth)      # bound method
-          Publisher().subscribe(foo.meth2)     # bound method
-          Publisher().subscribe(func)          # function
-          
-      The three types of callables all have arguments that allow a call 
-      with only one argument. In every case, the parameter 'a' will contain
-      the message. 
-
-    - topic: a single word, a tuple of words, or a string containing a
-      set of words separated by dots, for example: 'sports.baseball'.
-      A tuple or a dotted notation string 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
-          ('sports',)    # because more general
-          () or ('')     # because only for those listening to 'all' topics
-          ('news')       # because different topic
-          
-    - message: this is an instance of Message, containing the topic for 
-      which the message was sent, and any data the sender specified. 
-      
-    :note: This class is visible to importers of pubsub only as a
-           Singleton. I.e., every time you execute 'Publisher()', it's
-           actually the same instance of PublisherClass that is
-           returned. So to use, just do'Publisher().method()'.
-        
-    """
-    
-    __ALL_TOPICS_TPL = (ALL_TOPICS, )
-    
-    def __init__(self, singletonKey):
-        """Construct a Publisher. This can only be done by the pubsub 
-        module. You just use pubsub.Publisher()."""
-        if not isinstance(singletonKey, _SingletonKey):
-            raise invalid_argument("Use Publisher() to get access to singleton")
-        self.__messageCount  = 0
-        self.__deliveryCount = 0
-        self.__topicTree     = _TopicTreeRoot()
-
-    #
-    # Public API
-    #
-
-    def getDeliveryCount(self):
-        """How many listeners have received a message since beginning of run"""
-        return self.__deliveryCount
-    
-    def getMessageCount(self):
-        """How many times sendMessage() was called since beginning of run"""
-        return self.__messageCount
-    
-    def subscribe(self, listener, topic = ALL_TOPICS):
-        """
-        Subscribe listener for given topic. If topic is not specified,
-        listener will be subscribed for all topics (that listener will 
-        receive a Message for any topic for which a message is generated). 
-        
-        This method may be called multiple times for one listener,
-        registering it with many topics.  It can also be invoked many
-        times for a particular topic, each time with a different
-        listener.  See the class doc for requirements on listener and
-        topic.
-
-        :note: The listener is held by Publisher() only by *weak*
-               reference.  This means you must ensure you have at
-               least one strong reference to listener, otherwise it
-               will be DOA ("dead on arrival"). This is particularly
-               easy to forget when wrapping a listener method in a
-               proxy object (e.g. to bind some of its parameters),
-               e.g.::
-        
-                  class Foo: 
-                      def listener(self, event): pass
-                  class Wrapper:
-                      def __init__(self, fun): self.fun = fun
-                      def __call__(self, *args): self.fun(*args)
-                  foo = Foo()
-                  Publisher().subscribe( Wrapper(foo.listener) ) # whoops: DOA!
-                  wrapper = Wrapper(foo.listener)
-                  Publisher().subscribe(wrapper) # good!
-        
-        :note: Calling this method for the same listener, with two
-               topics in the same branch of the topic hierarchy, will
-               cause the listener to be notified twice when a message
-               for the deepest topic is sent. E.g.
-               subscribe(listener, 't1') and then subscribe(listener,
-               ('t1','t2')) means that when calling sendMessage('t1'),
-               listener gets one message, but when calling
-               sendMessage(('t1','t2')), listener gets message twice.
-        
-        """
-        self.validate(listener)
-
-        if topic is None: 
-            raise TypeError, 'Topic must be either a word, tuple of '\
-                             'words, or getStrAllTopics()'
-            
-        self.__topicTree.addTopic(_tupleize(topic), listener)
-
-    def isSubscribed(self, listener, topic=None):
-        """Return true if listener has subscribed to topic specified. 
-        If no topic specified, return true if subscribed to something.
-        Use topic=getStrAllTopics() to determine if a listener will receive 
-        messages for all topics."""
-        return self.__topicTree.isSubscribed(listener, topic)
-            
-    def validate(self, listener):
-        """Similar to isValid(), but raises a TypeError exception if not valid"""
-        # check callable
-        if not callable(listener):
-            raise TypeError, 'Listener '+`listener`+' must be a '\
-                             'function, bound method or instance.'
-        # ok, callable, but if method, is it bound:
-        elif ismethod(listener) and not _isbound(listener):
-            raise TypeError, 'Listener '+`listener`+\
-                             ' is a method but it is unbound!'
-                             
-        # check that it takes the right number of parameters
-        min, d = _paramMinCount(listener)
-        if min > 1:
-            raise TypeError, 'Listener '+`listener`+" can't"\
-                             ' require more than one parameter!'
-        if min <= 0 and d == 0:
-            raise TypeError, 'Listener '+`listener`+' lacking arguments!'
-                             
-        assert (min == 0 and d>0) or (min == 1)
-
-    def isValid(self, listener):
-        """Return true only if listener will be able to subscribe to 
-        Publisher."""
-        try: 
-            self.validate(listener)
-            return True
-        except TypeError:
-            return False
-
-    def unsubAll(self, topics=None, onNoSuchTopic=None):
-        """Unsubscribe all listeners subscribed for topics. Topics can 
-        be a single topic (string or tuple) or a list of topics (ie 
-        list containing strings and/or tuples). If topics is not 
-        specified, all listeners for all topics will be unsubscribed, 
-        ie. the Publisher singleton will have no topics and no listeners
-        left. If onNoSuchTopic is given, it will be called as 
-        onNoSuchTopic(topic) for each topic that is unknown.
-        """
-        if topics is None: 
-            del self.__topicTree
-            self.__topicTree = _TopicTreeRoot()
-            return
-        
-        # make sure every topics are in tuple form
-        if isinstance(topics, list):
-            topicList = [_tupleize(x) for x in topics]
-        else:
-            topicList = [_tupleize(topics)]
-            
-        # unsub every listener of topics
-        self.__topicTree.unsubAll(topicList, onNoSuchTopic)
-            
-    def unsubscribe(self, listener, topics=None):
-        """Unsubscribe listener. If topics not specified, listener is
-        completely unsubscribed. Otherwise, it is unsubscribed only 
-        for the topic (the usual tuple) or list of topics (ie a list
-        of tuples) specified. Nothing happens if listener is not actually
-        subscribed to any of the topics.
-        
-        Note that if listener subscribed for two topics (a,b) and (a,c), 
-        then unsubscribing for topic (a) will do nothing. You must 
-        use getAssociatedTopics(listener) and give unsubscribe() the returned 
-        list (or a subset thereof).
-        """
-        self.validate(listener)
-        topicList = None
-        if topics is not None:
-            if isinstance(topics, list):
-                topicList = [_tupleize(x) for x in topics]
-            else:
-                topicList = [_tupleize(topics)]
-            
-        self.__topicTree.unsubscribe(listener, topicList)
-        
-    def getAssociatedTopics(self, listener):
-        """Return a list of topics the given listener is registered with. 
-        Returns [] if listener never subscribed.
-        
-        :attention: when using the return of this method to compare to
-                expected list of topics, remember that topics that are
-                not in the form of a tuple appear as a one-tuple in
-                the return. E.g. if you have subscribed a listener to
-                'topic1' and ('topic2','subtopic2'), this method
-                returns::
-            
-                associatedTopics = [('topic1',), ('topic2','subtopic2')]
-        """
-        return self.__topicTree.getTopics(listener)
-    
-    def sendMessage(self, topic=ALL_TOPICS, data=None, onTopicNeverCreated=None):
-        """Send a message for given topic, with optional data, to
-        subscribed listeners. If topic is not specified, only the
-        listeners that are interested in all topics will receive message. 
-        The onTopicNeverCreated is an optional callback of your choice that 
-        will be called if the topic given was never created (i.e. it, or 
-        one of its subtopics, was never subscribed to by any listener). 
-        It will be called as onTopicNeverCreated(topic)."""
-        aTopic  = _tupleize(topic)
-        message = Message(aTopic, data)
-        self.__messageCount += 1
-        
-        # send to those who listen to all topics
-        self.__deliveryCount += \
-            self.__topicTree.sendMessage(aTopic, message, onTopicNeverCreated)
-        
-    #
-    # Private methods
-    #
-
-    def __call__(self):
-        """Allows for singleton"""
-        return self
-    
-    def __str__(self):
-        return str(self.__topicTree)
-
-# Create the Publisher singleton. We prevent users from (inadvertently)
-# instantiating more than one object, by requiring a key that is 
-# accessible only to module.  From
-# this point forward any calls to Publisher() will invoke the __call__
-# of this instance which just returns itself.
-#
-# The only flaw with this approach is that you can't derive a new
-# class from Publisher without jumping through hoops.  If this ever
-# becomes an issue then a new Singleton implementaion will need to be
-# employed.
-_key = _SingletonKey()
-Publisher = PublisherClass(_key)
-
-
-#---------------------------------------------------------------------------
-
-class Message:
-    """
-    A simple container object for the two components of a message: the 
-    topic and the user data. An instance of Message is given to your 
-    listener when called by Publisher().sendMessage(topic) (if your
-    listener callback was registered for that topic).
-    """
-    def __init__(self, topic, data):
-        self.topic = topic
-        self.data  = data
-
-    def __str__(self):
-        return '[Topic: '+`self.topic`+',  Data: '+`self.data`+']'
-
-
-#---------------------------------------------------------------------------
-
-
-#
-# Code for a simple command-line test
-#
-def test():
-    def done(funcName):
-        print '----------- Done %s -----------' % funcName
-        
-    def testParam():
-        def testFunc00(): pass
-        def testFunc21(a,b,c=1): pass
-        def testFuncA(*args): pass
-        def testFuncAK(*args,**kwds): pass
-        def testFuncK(**kwds): pass
-        
-        class Foo:
-            def testMeth(self,a,b): pass
-            def __call__(self, a): pass
-        class Foo2:
-            def __call__(self, *args): pass
-            
-        assert _paramMinCount(testFunc00)==(0,0)
-        assert _paramMinCount(testFunc21)==(2,1)
-        assert _paramMinCount(testFuncA) ==(1,0)
-        assert _paramMinCount(testFuncAK)==(1,0)
-        assert _paramMinCount(testFuncK) ==(0,0)
-        foo = Foo()
-        assert _paramMinCount(Foo.testMeth)==(2,0)
-        assert _paramMinCount(foo.testMeth)==(2,0)
-        assert _paramMinCount(foo)==(1,0)
-        assert _paramMinCount(Foo2())==(1,0)
-    
-        done('testParam')
-
-    testParam()
-    #------------------------
-
-    _NodeCallback.notified = 0
-    def testPreNotifyNode(self, dead):
-        _NodeCallback.notified += 1
-        print 'testPreNotifyNODE heard notification of', `dead`
-    _NodeCallback.preNotify = testPreNotifyNode
-    
-    def testTreeNode():
-
-        class WS:
-            def __init__(self, s):
-                self.s = s
-            def __call__(self, msg):
-                print 'WS#', self.s, ' received msg ', msg
-            def __str__(self):
-                return self.s
-            
-        def testPreNotifyRoot(dead):
-            print 'testPreNotifyROOT heard notification of', `dead`
-    
-        node = _TopicTreeNode((ALL_TOPICS,), WeakRef(testPreNotifyRoot))
-        boo, baz, bid = WS('boo'), WS('baz'), WS('bid')
-        node.addCallable(boo)
-        node.addCallable(baz)
-        node.addCallable(boo)
-        assert node.getCallables() == [boo,baz]
-        assert node.hasCallable(boo)
-        
-        node.removeCallable(bid) # no-op
-        assert node.hasCallable(baz)
-        assert node.getCallables() == [boo,baz]
-        
-        node.removeCallable(boo)
-        assert node.getCallables() == [baz]
-        assert node.hasCallable(baz)
-        assert not node.hasCallable(boo)
-        
-        node.removeCallable(baz)
-        assert node.getCallables() == []
-        assert not node.hasCallable(baz)
-
-        node2 = node.createSubtopic('st1', ('st1',))
-        node3 = node.createSubtopic('st2', ('st2',))
-        cb1, cb2, cb = WS('st1_cb1'), WS('st1_cb2'), WS('st2_cb')
-        node2.addCallable(cb1)
-        node2.addCallable(cb2)
-        node3.addCallable(cb)
-        node2.createSubtopic('st3', ('st1','st3'))
-        node2.createSubtopic('st4', ('st1','st4'))
-       
-        print str(node)
-        assert str(node) == ' (st1: st1_cb1 st1_cb2  (st4: ) (st3: )) (st2: st2_cb )'
-    
-        # verify send message, and that a dead listener does not get sent one
-        delivered = node2.sendMessage('hello')
-        assert delivered == 2
-        del cb1
-        delivered = node2.sendMessage('hello')
-        assert delivered == 1
-        assert _NodeCallback.notified == 1
-        
-        done('testTreeNode')
-        
-    testTreeNode()
-    #------------------------
-    
-    def testValidate():
-        class Foo:
-            def __call__(self, a):   pass
-            def fun(self, b):        pass
-            def fun2(self, b=1):     pass
-            def fun3(self, a, b=2):  pass
-            def badFun(self):        pass
-            def badFun2():           pass
-            def badFun3(self, a, b): pass
-            
-        server = Publisher()
-        foo = Foo()
-        server.validate(foo)
-        server.validate(foo.fun)
-        server.validate(foo.fun2)
-        server.validate(foo.fun3)
-        assert not server.isValid(foo.badFun)
-        assert not server.isValid(foo.badFun2)
-        assert not server.isValid(foo.badFun3)
-    
-        done('testValidate')
-
-    testValidate()
-    #------------------------
-    
-    class SimpleListener:
-        def __init__(self, number):
-            self.number = number
-        def __call__(self, message = ''): 
-            print 'Callable #%s got the message "%s"' %(self.number, message)
-        def notify(self, message):
-            print '%s.notify() got the message "%s"' %(self.number, message)
-        def __str__(self):
-            return "SimpleListener_%s" % self.number
-
-    def testSubscribe():
-        publisher = Publisher()
-        
-        topic1 = 'politics'
-        topic2 = ('history','middle age')
-        topic3 = ('politics','UN')
-        topic4 = ('politics','NATO')
-        topic5 = ('politics','NATO','US')
-        
-        lisnr1 = SimpleListener(1)
-        lisnr2 = SimpleListener(2)
-        def func(message, a=1): 
-            print 'Func received message "%s"' % message
-        lisnr3 = func
-        lisnr4 = lambda x: 'Lambda received message "%s"' % x
-
-        assert not publisher.isSubscribed(lisnr1)
-        assert not publisher.isSubscribed(lisnr2)
-        assert not publisher.isSubscribed(lisnr3)
-        assert not publisher.isSubscribed(lisnr4)
-        
-        publisher.subscribe(lisnr1, topic1)
-        assert publisher.getAssociatedTopics(lisnr1) == [(topic1,)]
-        publisher.subscribe(lisnr1, topic2)
-        publisher.subscribe(lisnr1, topic1) # do it again, should be no-op
-        assert publisher.getAssociatedTopics(lisnr1) == [(topic1,),topic2]
-        publisher.subscribe(lisnr2.notify, topic3)
-        assert publisher.getAssociatedTopics(lisnr2.notify) == [topic3]
-        assert publisher.getAssociatedTopics(lisnr1) == [(topic1,),topic2]
-        publisher.subscribe(lisnr3, topic5)
-        assert publisher.getAssociatedTopics(lisnr3) == [topic5]
-        assert publisher.getAssociatedTopics(lisnr2.notify) == [topic3]
-        assert publisher.getAssociatedTopics(lisnr1) == [(topic1,),topic2]
-        publisher.subscribe(lisnr4)
-        
-        print "Publisher tree: ", publisher
-        assert publisher.isSubscribed(lisnr1)
-        assert publisher.isSubscribed(lisnr1, topic1)
-        assert publisher.isSubscribed(lisnr1, topic2)
-        assert publisher.isSubscribed(lisnr2.notify)
-        assert publisher.isSubscribed(lisnr3, topic5)
-        assert publisher.isSubscribed(lisnr4, ALL_TOPICS)
-        expectTopicTree = 'all: <lambda>  (politics: SimpleListener_1  (UN: SimpleListener_2.notify ) (NATO:  (US: func ))) (history:  (middle age: SimpleListener_1 ))'
-        print "Publisher tree: ", publisher
-        assert str(publisher) == expectTopicTree
-        
-        publisher.unsubscribe(lisnr1, 'booboo') # should do nothing
-        assert publisher.getAssociatedTopics(lisnr1) == [(topic1,),topic2]
-        assert publisher.getAssociatedTopics(lisnr2.notify) == [topic3]
-        assert publisher.getAssociatedTopics(lisnr3) == [topic5]
-        publisher.unsubscribe(lisnr1, topic1)
-        assert publisher.getAssociatedTopics(lisnr1) == [topic2]
-        assert publisher.getAssociatedTopics(lisnr2.notify) == [topic3]
-        assert publisher.getAssociatedTopics(lisnr3) == [topic5]
-        publisher.unsubscribe(lisnr1, topic2)
-        publisher.unsubscribe(lisnr1, topic2)
-        publisher.unsubscribe(lisnr2.notify, topic3)
-        publisher.unsubscribe(lisnr3, topic5)
-        assert publisher.getAssociatedTopics(lisnr1) == []
-        assert publisher.getAssociatedTopics(lisnr2.notify) == []
-        assert publisher.getAssociatedTopics(lisnr3) == []
-        publisher.unsubscribe(lisnr4)
-        
-        expectTopicTree = 'all:  (politics:  (UN: ) (NATO:  (US: ))) (history:  (middle age: ))'
-        print "Publisher tree: ", publisher
-        assert str(publisher) == expectTopicTree
-        assert publisher.getDeliveryCount() == 0
-        assert publisher.getMessageCount() == 0
-        
-        publisher.unsubAll()
-        assert str(publisher) == 'all: '
-        
-        done('testSubscribe')
-    
-    testSubscribe()
-    #------------------------
-    
-    def testUnsubAll():
-        publisher = Publisher()
-        
-        topic1 = 'politics'
-        topic2 = ('history','middle age')
-        topic3 = ('politics','UN')
-        topic4 = ('politics','NATO')
-        topic5 = ('politics','NATO','US')
-        
-        lisnr1 = SimpleListener(1)
-        lisnr2 = SimpleListener(2)
-        def func(message, a=1): 
-            print 'Func received message "%s"' % message
-        lisnr3 = func
-        lisnr4 = lambda x: 'Lambda received message "%s"' % x
-
-        publisher.subscribe(lisnr1, topic1)
-        publisher.subscribe(lisnr1, topic2)
-        publisher.subscribe(lisnr2.notify, topic3)
-        publisher.subscribe(lisnr3, topic2)
-        publisher.subscribe(lisnr3, topic5)
-        publisher.subscribe(lisnr4)
-        
-        expectTopicTree = 'all: <lambda>  (politics: SimpleListener_1  (UN: SimpleListener_2.notify ) (NATO:  (US: func ))) (history:  (middle age: SimpleListener_1 func ))'
-        print "Publisher tree: ", publisher
-        assert str(publisher) == expectTopicTree
-    
-        publisher.unsubAll(topic1)
-        assert publisher.getAssociatedTopics(lisnr1) == [topic2]
-        assert not publisher.isSubscribed(lisnr1, topic1)
-        
-        publisher.unsubAll(topic2)
-        print publisher
-        assert publisher.getAssociatedTopics(lisnr1) == []
-        assert publisher.getAssociatedTopics(lisnr3) == [topic5]
-        assert not publisher.isSubscribed(lisnr1)
-        assert publisher.isSubscribed(lisnr3, topic5)
-        
-        #print "Publisher tree: ", publisher
-        expectTopicTree = 'all: <lambda>  (politics:  (UN: SimpleListener_2.notify ) (NATO:  (US: func ))) (history:  (middle age: ))'
-        assert str(publisher) == expectTopicTree
-        publisher.unsubAll(ALL_TOPICS)
-        #print "Publisher tree: ", publisher
-        expectTopicTree = 'all:  (politics:  (UN: SimpleListener_2.notify ) (NATO:  (US: func ))) (history:  (middle age: ))'
-        assert str(publisher) == expectTopicTree
-        
-        publisher.unsubAll()
-        done('testUnsubAll')
-    
-    testUnsubAll()
-    #------------------------
-    
-    def testSend():
-        publisher = Publisher()
-        called = []
-        
-        class TestListener:
-            def __init__(self, num):
-                self.number = num
-            def __call__(self, b): 
-                called.append( 'TL%scb' % self.number )
-            def notify(self, b):
-                called.append( 'TL%sm' % self.number )
-        def funcListener(b):
-            called.append('func')
-            
-        lisnr1 = TestListener(1)
-        lisnr2 = TestListener(2)
-        lisnr3 = funcListener
-        lisnr4 = lambda x: called.append('lambda')
-
-        topic1 = 'politics'
-        topic2 = 'history'
-        topic3 = ('politics','UN')
-        topic4 = ('politics','NATO','US')
-        topic5 = ('politics','NATO')
-        
-        publisher.subscribe(lisnr1, topic1)
-        publisher.subscribe(lisnr2, topic2)
-        publisher.subscribe(lisnr2.notify, topic2)
-        publisher.subscribe(lisnr3, topic4)
-        publisher.subscribe(lisnr4)
-        
-        print publisher
-        
-        # setup ok, now test send/receipt
-        publisher.sendMessage(topic1)
-        assert called == ['lambda','TL1cb']
-        called = []
-        publisher.sendMessage(topic2)
-        assert called == ['lambda','TL2cb','TL2m']
-        called = []
-        publisher.sendMessage(topic3)
-        assert called == ['lambda','TL1cb']
-        called = []
-        publisher.sendMessage(topic4)
-        assert called == ['lambda','TL1cb','func']
-        called = []
-        publisher.sendMessage(topic5)
-        assert called == ['lambda','TL1cb']
-        assert publisher.getDeliveryCount() == 12
-        assert publisher.getMessageCount() == 5
-    
-        # test weak referencing works:
-        _NodeCallback.notified = 0
-        del lisnr2
-        called = []
-        publisher.sendMessage(topic2)
-        assert called == ['lambda']
-        assert _NodeCallback.notified == 2
-        
-        done('testSend')
-        
-    testSend()
-    assert _NodeCallback.notified == 5
-    
-    def testDead():
-        # verify if weak references work as expected
-        print '------ Starting testDead ----------'
-        node = _TopicTreeNode('t1', None)
-        lisnr1 = SimpleListener(1)
-        lisnr2 = SimpleListener(2)
-        lisnr3 = SimpleListener(3)
-        lisnr4 = SimpleListener(4)
-
-        node.addCallable(lisnr1)
-        node.addCallable(lisnr2)
-        node.addCallable(lisnr3)
-        node.addCallable(lisnr4)
-        
-        print 'Deleting listeners first'
-        _NodeCallback.notified = 0
-        del lisnr1
-        del lisnr2
-        assert _NodeCallback.notified == 2
-        
-        print 'Deleting node first'
-        _NodeCallback.notified = 0
-        del node
-        del lisnr3
-        del lisnr4
-        assert _NodeCallback.notified == 0
-        
-        lisnr1 = SimpleListener(1)
-        lisnr2 = SimpleListener(2)
-        lisnr3 = SimpleListener(3)
-        lisnr4 = SimpleListener(4)
-        
-        # try same with root of tree
-        node = _TopicTreeRoot()
-        node.addTopic(('',), lisnr1)
-        node.addTopic(('',), lisnr2)
-        node.addTopic(('',), lisnr3)
-        node.addTopic(('',), lisnr4)
-        # add objects that will die immediately to see if cleanup occurs
-        # this must be done visually as it is a low-level detail
-        _NodeCallback.notified = 0
-        _TopicTreeRoot.callbackDeadLimit = 3
-        node.addTopic(('',), SimpleListener(5))
-        node.addTopic(('',), SimpleListener(6))
-        node.addTopic(('',), SimpleListener(7))
-        print node.numListeners()
-        assert node.numListeners() == (4, 3)
-        node.addTopic(('',), SimpleListener(8))
-        assert node.numListeners() == (4, 0)
-        assert _NodeCallback.notified == 4
-        
-        print 'Deleting listeners first'
-        _NodeCallback.notified = 0
-        del lisnr1
-        del lisnr2
-        assert _NodeCallback.notified == 2
-        print 'Deleting node first'
-        _NodeCallback.notified = 0
-        del node
-        del lisnr3
-        del lisnr4
-        assert _NodeCallback.notified == 0
-        
-        done('testDead')
-    
-    testDead()
-    
-    print 'Exiting tests'
-#---------------------------------------------------------------------------
-
-if __name__ == '__main__':
-    test()