Higher-level classes and functions related to listening of pubsub messages.
Listeners are callable objects that get subscribed to a pubsub
topic. Listeners are deemed "invalid" or "inadequate" (used interchangeably)
by pubsub if they wouldn't be able to receive all message data of a topic
message. This module includes this validation functionality, which varies
based on the pubsub messaging protocol used.
Note that a Listener instance holds its callable listener only by weak
reference so it doesn't prevent the callable from being garbage collected
when callable is no longer in use by the application.
In order for a listener to subscribe to a topic, it must adhere to that
topic's "listener protocol specification" (LPS). A subscription will
fail (via a ListenerInadequate exception) if this is not the case. For
instance, if topic A has an LPS "arg1, arg2=None"", then only listeners
of the form callable([self,] arg1, arg2=something) will be accepted.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
from callables import \
AUTO_TOPIC as _AUTO_ARG
Any listener that is subscribed to pubsub topics is stored by weak
reference into a Listener (derived from this class). This class uses
introspection on the wrapped listener to determine various properties of
interest to pubsub. Anytime a listener is returned from a pubsub
function/method, it is in fact the Listener wrapping it that is
Note that listeners that have 'argName=pub.AUTO_TOPIC' as a kwarg will
be given the Topic object for the message sent by sendMessage().
Such a listener will cause self.wantsTopicObjOnCall() to return True.
AUTO_TOPIC = _AUTO_ARG
def __init__(self, callable_, argsInfo, onDead=None):
'''Use callable_ as a listener of topicName. The argsInfo is the
return value from a Validator, ie an instance of callables.CallArgsInfo.
If given, the onDead will be called with self as parameter, if/when
callable_ gets garbage collected (callable_ is held only by weak
# set call policies
self.acceptsAllKwargs = argsInfo.acceptsAllKwargs
self._autoTopicArgName = argsInfo.autoTopicArgName
self._callable = weakmethod.getWeakRef(callable_, self.__notifyOnDead)
self.__onDead = onDead
# save identity now in case callable dies:
name, mod = getID(callable_) #
self.__nameID = name
self.__module = mod
self.__id = str(id(callable_))[-4:] # only last four digits of id
self.__hash = hash(callable_)
def __call__(self, args, kwargs, actualTopic, allArgs=None):
'''Return a human readable name for listener, based on the
listener's type name and its id (as obtained
from id(listener)). If caller just needs name based on
type info, specify instance=False. Note that the listener's id()
was saved at construction time (since it may get garbage collected
at any time) so the return value of name() is not necessarily unique if the callable has
died (because id's can be re-used after garbage collection).'''
return '%s_%s' % (self.__nameID, self.__id)
'''Get a type name for the listener. This is a class name or
function name, as appropriate. '''
'''Get the module in which the callable was defined.'''
'''Get the listener that was given at initialization. Note that
this could be None if it has been garbage collected (e.g. if it was
created as a wrapper of some other callable, and not stored
'''Return True if this listener died (has been garbage collected)'''
return self._callable() is None
return self._autoTopicArgName is not None
'''Tell self that it is no longer used by a Topic. This allows
to break some cyclical references.'''
self.__onDead = None
raise RuntimeError('BUG: Dead Listener called, still subscribed!')
def __notifyOnDead(self, ref):
'''This gets called when listener weak ref has died. Propagate
info to Topic).'''
notifyDeath = self.__onDead
if notifyDeath is not None:
def __eq__(self, rhs):
'''Compare for equality to rhs. This returns true if rhs has our id id(rhs) is
same as id(self) or id(callable in self). '''
if id(self) == id(rhs):
c1 = self._callable()
c2 = rhs._callable()
# then rhs is not a Listener, compare with c1
return c1 == rhs
# both side of == are Listener, but always compare unequal if both dead
if c2 is None and c1 is None:
return c1 == c2
def __ne__(self, rhs):
'''Counterpart to __eq__ MUST be defined... equivalent to
'not (self == rhs)'.'''
return not self.__eq__(rhs)
"""Hash is an optimization for dict/set searches, it need not
return different numbers for every different object. """
'''String rep is the callable'''
Validates listeners. It checks whether the listener given to
validate() method complies with required and optional arguments
specified for topic.
def __init__(self, topicArgs, topicKwargs):
'''topicArgs is a list of argument names that will be required when sending
a message to listener. Hence order of items in topicArgs matters. The topicKwargs
is a list of argument names that will be optional, ie given as keyword arguments
when sending a message to listener. The list is unordered. '''
self._topicArgs = set(topicArgs)
self._topicKwargs = set(topicKwargs)
def validate(self, listener):
'''Validate that listener satisfies the requirements of
being a topic listener, if topic's kwargs keys are topicKwargKeys
(so only the list of keyword arg names for topic are necessary).
Raises ListenerInadequate if listener not usable for topic.
Otherwise, returns an CallArgsInfo object containing information about
the listener's call arguments, such as whether listener wants topic
name (signified by a kwarg value = AUTO_TOPIC in listener protocol).
E.g. def fn1(msgTopic=Listener.AUTO_TOPIC) would
cause validate(fn1) to return True, whereas any other kwarg name or value
would cause a False to be returned.
paramsInfo = getArgs( listener )
def isValid(self, listener):
'''Return true only if listener can subscribe to messages where
topic has kwargs keys topicKwargKeys. Just calls validate() in
a try-except clause.'''
def _validateArgs(self, listener, paramsInfo):
'''Provide implementation in derived classes'''