Files
Phoenix/wx/lib/pubsub/core/callables.py
2014-01-14 05:30:19 -06:00

192 lines
7.0 KiB
Python

"""
Low level functions and classes related to callables.
The AUTO_TOPIC
is the "marker" to use in callables to indicate that when a message
is sent to those callables, the topic object for that message should be
added to the data sent via the call arguments. See the docs in
CallArgsInfo regarding its autoTopicArgName data member.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
"""
from inspect import getargspec, ismethod, isfunction
from .. import py2and3
AUTO_TOPIC = '## your listener wants topic name ## (string unlikely to be used by caller)'
def getModule(obj):
"""Get the module in which an object was defined. Returns '__main__'
if no module defined (which usually indicates either a builtin, or
a definition within main script). """
if hasattr(obj, '__module__'):
module = obj.__module__
else:
module = '__main__'
return module
def getID(callable_):
"""Get name and module name for a callable, ie function, bound
method or callable instance, by inspecting the callable. E.g.
getID(Foo.bar) returns ('Foo.bar', 'a.b') if Foo.bar was
defined in module a.b. """
sc = callable_
if ismethod(sc):
module = getModule(sc.__self__)
id = '%s.%s' % (sc.__self__.__class__.__name__, sc.__func__.__name__)
elif isfunction(sc):
module = getModule(sc)
id = sc.__name__
else: # must be a functor (instance of a class that has __call__ method)
module = getModule(sc)
id = sc.__class__.__name__
return id, module
def getRawFunction(callable_):
"""Given a callable, return (offset, func) where func is the
function corresponding to callable, and offset is 0 or 1 to
indicate whether the function's first argument is 'self' (1)
or not (0). Raises ValueError if callable_ is not of a
recognized type (function, method or has __call__ method)."""
firstArg = 0
if isfunction(callable_):
#print('Function %s' % getID(callable_))
func = callable_
elif ismethod(callable_):
#print('Method %s' % getID(callable_))
func = callable_
if func.__self__ is not None:
# Method is bound, don't care about the self arg
firstArg = 1
elif hasattr(callable_, '__call__'):
#print('Functor %s' % getID(callable_))
func = callable_.__call__
firstArg = 1 # don't care about the self arg
else:
msg = 'type "%s" not supported' % type(callable_).__name__
raise ValueError(msg)
return func, firstArg
class ListenerMismatchError(ValueError):
"""
Raised when an attempt is made to subscribe a listener to
a topic, but listener does not satisfy the topic's message data
specification (MDS). This specification is inferred from the first
listener subscribed to a topic, or from an imported topic tree
specification (see pub.addTopicDefnProvider()).
"""
def __init__(self, msg, listener, *args):
idStr, module = getID(listener)
msg = 'Listener "%s" (from module "%s") inadequate: %s' % (idStr, module, msg)
ValueError.__init__(self, msg)
self.msg = msg
self.args = args
self.module = module
self.idStr = idStr
def __str__(self):
return self.msg
class CallArgsInfo:
"""
Represent the "signature" or protocol of a listener in the context of
topics.
"""
def __init__(self, func, firstArgIdx): #args, firstArgIdx, defaultVals, acceptsAllKwargs=False):
"""Inputs:
- Args and defaultVals are the complete set of arguments and
default values as obtained form inspect.getargspec();
- The firstArgIdx points to the first item in
args that is of use, so it is typically 0 if listener is a function,
and 1 if listener is a method.
- The acceptsAllKwargs should be true
if the listener has **kwargs in its protocol.
After construction,
- self.allParams will contain the subset of 'args' without first
firstArgIdx items,
- self.numRequired will indicate number of required arguments
(ie self.allParams[:self.numRequired] are the required args names);
- self.acceptsAllKwargs = acceptsAllKwargs
- self.autoTopicArgName will be the name of argument
in which to put the topic object for which pubsub message is
sent, or None. This is identified by the argument that has a
default value of AUTO_TOPIC.
For instance, listener(self, arg1, arg2=AUTO_TOPIC, arg3=None) will
have self.allParams = (arg1, arg2, arg3), self.numRequired=1, and
self.autoTopicArgName = 'arg2', whereas
listener(self, arg1, arg3=None) will have
self.allParams = (arg1, arg3), self.numRequired=1, and
self.autoTopicArgName = None."""
#args, firstArgIdx, defaultVals, acceptsAllKwargs
(allParams, varParamName, varOptParamName, defaultVals) = getargspec(func)
if defaultVals is None:
defaultVals = []
else:
defaultVals = list(defaultVals)
self.acceptsAllKwargs = (varOptParamName is not None)
self.acceptsAllUnnamedArgs = (varParamName is not None)
self.allParams = allParams
del self.allParams[0:firstArgIdx] # does nothing if firstArgIdx == 0
self.numRequired = len(self.allParams) - len(defaultVals)
assert self.numRequired >= 0
# if listener wants topic, remove that arg from args/defaultVals
self.autoTopicArgName = None
if defaultVals:
self.__setupAutoTopic(defaultVals)
def getAllArgs(self):
return tuple( self.allParams )
def getOptionalArgs(self):
return tuple( self.allParams[self.numRequired:] )
def getRequiredArgs(self):
"""Return a tuple of names indicating which call arguments
are required to be present when pub.sendMessage(...) is called. """
return tuple( self.allParams[:self.numRequired] )
def __setupAutoTopic(self, defaults):
"""Does the listener want topic of message? Returns < 0 if not,
otherwise return index of topic kwarg within args."""
for indx, defaultVal in enumerate(defaults):
if defaultVal == AUTO_TOPIC:
#del self.defaults[indx]
firstKwargIdx = self.numRequired
self.autoTopicArgName = self.allParams.pop(firstKwargIdx + indx)
break
def getArgs(callable_):
"""Returns an instance of CallArgsInfo for the given callable_.
Raises ListenerMismatchError if callable_ is not a callable."""
# figure out what is the actual function object to inspect:
try:
func, firstArgIdx = getRawFunction(callable_)
except ValueError:
from .. import py2and3
exc = py2and3.getexcobj()
raise ListenerMismatchError(str(exc), callable_)
return CallArgsInfo(func, firstArgIdx)