Update from r343 of pubsub trunk/src/pubsub on SF.net

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@75301 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Oliver Schoenborn
2013-11-27 05:45:26 +00:00
parent 6a1bbb55f5
commit ea0f874a40
44 changed files with 657 additions and 546 deletions

View File

@@ -0,0 +1,23 @@
Copyright (c) since 2006, Oliver Schoenborn
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
wx/lib/pubsub/README.txt Normal file
View File

@@ -0,0 +1,21 @@
PyPubSub provides a publish - subscribe API that facilitates the development of
event-based (also known as message-based) applications. PyPubSub supports sending and
receiving messages between objects of an application, as well as a variety of
advanced features that facilitate debugging and maintaining topics and messages
in larger applications.
See the PyPubSub website (http://pubsub.sourceforge.net/) for
further details, and to download.
In order for easy_install to find the correct download links, they are listed here:
* `Windows Installer <http://downloads.sf.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0.win32.exe>`_
* `Egg for Python 2.7 <https://downloads.sourceforge.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0-py2.7.egg>`_
* `Egg for Python 2.6 <https://downloads.sourceforge.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0-py2.6.egg>`_
* `Egg for Python 2.5 <https://downloads.sourceforge.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0-py2.5.egg>`_
* `Egg for Python 2.4 <https://downloads.sourceforge.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0-py2.4.egg>`_
* `Zip (source) <http://downloads.sf.net/project/pubsub/pubsub/3.2.0/PyPubSub-3.2.0.zip>`_
Note: "easy_install pypubsub" will install the egg, which is a zip file, whereas
"easy_install -Z pypubsub" will extract the egg contents to a folder; this option will
make importing pubsub much faster.

View File

@@ -0,0 +1,71 @@
For PyPubSub v3.3.0
^^^^^^^^^^^^^^^^^^^^^
* cleanup low-level API: exception classes, moved some out of pub module that did not
belong there (clutter), move couple modules,
* completed the reference documentation
* support installation via pip
* follow some guidelines in some PEPs such as PEP 396 and PEP 8
* support Python 2.6, 2.7, and 3.2 to 3.4a4 but drop support for Python <= 2.5
For PyPubSub v3.2.0
^^^^^^^^^^^^^^^^^^^
This is a minor release for small improvements made (see docs/CHANGELOG.txt)
based on feedback from user community. In particular an XML reader for
topic specification contributed by Josh English. Also cleaned up the
documentation, updated the examples folder (available in source distribution
as well as `online`_).
.. _online: https://sourceforge.net/p/pubsub/code/HEAD/tree/
Only 3 changes to API (function names):
* renamed pub.getDefaultRootAllTopics to pub.getDefaultTopicTreeRoot
* removed pub.importTopicTree: use pub.addTopicDefnProvider(source, format)
* renamed pub.exportTopicTree to pub.exportTopicTreeSpec
Oliver Schoenborn
September 2013
PyPubSub 3.1.2
^^^^^^^^^^^^^^^^
This is a minor release for small improvements made (see docs/CHANGELOG.txt)
based on feedback from user community. Also extended the documentation. See
pubsub.sourceforge.net for installation and usage. See the examples folder for
some useful examples.
Oliver Schoenborn
Nov 2011
PyPubSub 3.1.1b1
^^^^^^^^^^^^^^^^^^
Docs updated.
Oliver Schoenborn
May 2010
For PyPubSub v3.1.0b1
^^^^^^^^^^^^^^^^^^^^^^
Major cleanup of the API since 3.0 and better support
for the legacy wxPython code. Defining a topic tree
via a text file has been improved drastically, making it
simpler to document topic messages and payload data
required or optional. More examples have been added,
and the messaging protocols clarified.
The included docs are not yet updated, that's what I'm
working on now and will lead to the 3.1.1b1 release.
I'm also working on an add-on module that would allow
two applications to communicate over the network using
pubsub-type messaging (with topics, etc). The design
is almost complete.
Oliver Schoenborn
Jan 2010

View File

@@ -1,14 +1,14 @@
'''
"""
Pubsub package initialization.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
Last change info:
- $Date: 2013-10-10 01:15:37 -0400 (Thu, 10 Oct 2013) $
- $Revision: 329 $
- $Date: 2013-11-17 13:09:24 -0500 (Sun, 17 Nov 2013) $
- $Revision: 340 $
'''
"""
LAST_RELEASE_DATE = "2013-09-15"
LAST_RELEASE_VER = "3.2.1b"

View File

@@ -1,4 +1,4 @@
'''
"""
Core package of pubsub, holding the publisher, listener, and topic
object modules. Functions defined here are used internally by
pubsub so that the right modules can be found later, based on the
@@ -26,13 +26,13 @@ _prependModulePath() at end of this file.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
def _prependModulePath(extra):
'''Insert extra at beginning of package's path list. Should only be
"""Insert extra at beginning of package's path list. Should only be
called once, at package load time, to set the folder used for
implementation specific to the default message protocol.'''
implementation specific to the default message protocol."""
corepath = __path__
initpyLoc = corepath[-1]
import os

View File

@@ -1,4 +1,4 @@
'''
"""
This is not really a package init file, it is only here to simplify the
packaging and installation of pubsub.core's protocol-specific subfolders
by setuptools. The python modules in this folder are automatically made
@@ -9,7 +9,7 @@ protocol is "arg1" (and not usable otherwise).
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
msg = 'Should not import this directly, used by pubsub.core if applicable'

View File

@@ -1,7 +1,7 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .listenerbase import (ListenerBase, ValidatorBase)
from .callables import ListenerMismatchError
@@ -25,7 +25,7 @@ class Message:
class Listener(ListenerBase):
'''
"""
Wraps a callable so it can be stored by weak reference and introspected
to verify that it adheres to a topic's MDS.
@@ -35,12 +35,12 @@ class Listener(ListenerBase):
protocol only one object can be sent via sendMessage, it is put in a
Message object in its "data" field, the listener receives the Message
object.
'''
"""
def __call__(self, actualTopic, data):
'''Call the listener with data. Note that it raises RuntimeError
"""Call the listener with data. Note that it raises RuntimeError
if listener is dead. Should always return True (False would require
the callable_ be dead but self hasn't yet been notified of it...).'''
the callable_ be dead but self hasn't yet been notified of it...)."""
kwargs = {}
if self._autoTopicArgName is not None:
kwargs[self._autoTopicArgName] = actualTopic
@@ -53,11 +53,11 @@ class Listener(ListenerBase):
class ListenerValidator(ValidatorBase):
'''
"""
Accept one arg or *args; accept any **kwarg,
and require that the Listener have at least all the kwargs (can
have extra) of Topic.
'''
"""
def _validateArgs(self, listener, paramsInfo):
# accept **kwargs

View File

@@ -1,32 +1,32 @@
'''
"""
Mixin for publishing messages to a topic's listeners. This will be
mixed into topicobj.Topic so that a user can use a Topic object to
send a message to the topic's listeners via a publish() method.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .publisherbase import PublisherBase
class Publisher(PublisherBase):
'''
"""
Publisher that allows old-style Message.data messages to be sent
to listeners. Listeners take one arg (required, unless there is an
*arg), but can have kwargs (since they have default values).
'''
"""
def sendMessage(self, topicName, data=None):
'''Send message of type topicName to all subscribed listeners,
"""Send message of type topicName to all subscribed listeners,
with message data. If topicName is a subtopic, listeners
of topics more general will also get the message.
Note that any listener that lets a raised exception escape will
interrupt the send operation, unless an exception handler was
specified via pub.setListenerExcHandler().
'''
"""
topicMgr = self.getTopicMgr()
topicObj = topicMgr.getOrCreateTopic(topicName)

View File

@@ -1,4 +1,4 @@
'''
"""
Mixin for publishing messages to a topic's listeners. This will be
mixed into topicobj.Topic so that a user can use a Topic object to
send a message to the topic's listeners via a publish() method.
@@ -12,7 +12,7 @@ loop).
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
class PublisherMixin:
@@ -25,10 +25,10 @@ class PublisherMixin:
############## IMPLEMENTATION ###############
def _mix_prePublish(self, data, topicObj=None, iterState=None):
'''Called just before the __sendMessage, to perform any argument
checking, set iterState, etc'''
"""Called just before the __sendMessage, to perform any argument
checking, set iterState, etc"""
return None
def _mix_callListener(self, listener, data, iterState):
'''Send the data to given listener.'''
"""Send the data to given listener."""
listener(self, data)

View File

@@ -1,29 +1,29 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
import weakref
from .topicutils import WeakNone
class SenderMissingReqdMsgDataError(RuntimeError):
'''
"""
Ignore: Not used for this message protocol.
'''
"""
pass
class SenderUnknownMsgDataError(RuntimeError):
'''
"""
Ignore: Not used for this message protocol.
'''
"""
pass
class ArgsInfo:
'''
"""
Encode the Message Data Specification (MDS) for a given
topic. In the arg1 protocol of pubsub, this MDS is the same for all
topics, so this is quite a simple class, required only because
@@ -35,7 +35,7 @@ class ArgsInfo:
Note that the MDS is always complete because it is known:
it consists of one required argument named 'data' and no
optional arguments.
'''
"""
SPEC_MISSING = 10 # no args given
SPEC_COMPLETE = 12 # all args, but not confirmed via user spec

View File

@@ -1,17 +1,17 @@
'''
"""
The root topic of all topics is different based on messaging protocol.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .. import policies
def getRootTopicSpec():
'''If using "arg1" messaging protocol, then root topic has one arg;
if policies.msgDataArgName is something, then use it as arg name.'''
"""If using "arg1" messaging protocol, then root topic has one arg;
if policies.msgDataArgName is something, then use it as arg name."""
argName = policies.msgDataArgName or 'data'
argsDocs = {argName : 'data for message sent'}
reqdArgs = (argName,)

View File

@@ -1,4 +1,4 @@
'''
"""
Low level functions and classes related to callables.
The AUTO_TOPIC
@@ -10,7 +10,7 @@ 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
@@ -20,9 +20,9 @@ AUTO_TOPIC = '## your listener wants topic name ## (string unlikely to be use
def getModule(obj):
'''Get the module in which an object was defined. Returns '__main__'
"""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). '''
a definition within main script). """
if hasattr(obj, '__module__'):
module = obj.__module__
else:
@@ -31,10 +31,10 @@ def getModule(obj):
def getID(callable_):
'''Get name and module name for a callable, ie function, bound
"""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. '''
defined in module a.b. """
sc = callable_
if ismethod(sc):
module = getModule(sc.__self__)
@@ -50,11 +50,11 @@ def getID(callable_):
def getRawFunction(callable_):
'''Given a callable, return (offset, func) where func is the
"""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).'''
recognized type (function, method or has __call__ method)."""
firstArg = 0
if isfunction(callable_):
#print 'Function', getID(callable_)
@@ -77,13 +77,13 @@ def getRawFunction(callable_):
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)
@@ -99,13 +99,13 @@ class ListenerMismatchError(ValueError):
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:
"""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
@@ -130,7 +130,7 @@ class CallArgsInfo:
self.autoTopicArgName = 'arg2', whereas
listener(self, arg1, arg3=None) will have
self.allParams = (arg1, arg3), self.numRequired=1, and
self.autoTopicArgName = None.'''
self.autoTopicArgName = None."""
#args, firstArgIdx, defaultVals, acceptsAllKwargs
(allParams, varParamName, varOptParamName, defaultVals) = getargspec(func)
@@ -160,13 +160,13 @@ class CallArgsInfo:
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 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.'''
"""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]
@@ -176,8 +176,8 @@ class CallArgsInfo:
def getArgs(callable_):
'''Returns an instance of CallArgsInfo for the given callable_.
Raises ListenerMismatchError if callable_ is not a 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_)

View File

@@ -1,16 +1,16 @@
'''
"""
The _resolve_name and _import_module were taken from the backport of
importlib.import_module from 3.x to 2.7. Thanks to the Python developers
for making this available as a standalone module. This makes it possible
to have an import module that mimics the "import" statement more
closely.
'''
"""
import sys
from .. import py2and3
def _resolve_name(name, package, level):
'''Return the absolute name of the module to be imported.'''
"""Return the absolute name of the module to be imported."""
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
@@ -24,12 +24,12 @@ def _resolve_name(name, package, level):
def _import_module(name, package=None):
'''Import a module.
"""Import a module.
The 'package' argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
'''
"""
if name.startswith('.'):
if not package:
raise TypeError("relative imports require the 'package' argument")
@@ -44,8 +44,8 @@ def _import_module(name, package=None):
def load_module(moduleName, searchPath):
'''Try to import moduleName. If this doesn't work, use the "imp" module
that is part of Python. '''
"""Try to import moduleName. If this doesn't work, use the "imp" module
that is part of Python. """
try:
module = _import_module(moduleName)

View File

@@ -1,4 +1,4 @@
'''
"""
This is not really a package init file, it is only here to simplify the
packaging and installation of pubsub.core's protocol-specific subfolders
by setuptools. The python modules in this folder are automatically made
@@ -9,7 +9,7 @@ protocol is "kwargs" (and not usable otherwise).
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
msg = 'Should not import this directly, used by pubsub.core if applicable'

View File

@@ -1,10 +1,10 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
class Message:
'''
"""
A simple container object for the two components of a message in the
arg1 messaging protocol: the
topic and the user data. Each listener called by sendMessage(topic, data)
@@ -17,7 +17,7 @@ class Message:
print msg
The example also shows (last line) how a message is convertible to a string.
'''
"""
def __init__(self, topic, data):
self.topic = topic
self.data = data

View File

@@ -1,16 +1,16 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .listenerbase import ListenerBase, ValidatorBase
from .callables import ListenerMismatchError
class Listener(ListenerBase):
'''
"""
Wraps a callable so it can be stored by weak reference and introspected
to verify that it adheres to a topic's MDS.
@@ -24,12 +24,12 @@ class Listener(ListenerBase):
Callables that have a '\**kargs' argument will receive all message
data, not just that for the topic they are subscribed to. Such a listener
will have wantsAllMessageData() True.
'''
"""
def __call__(self, kwargs, actualTopic, allKwargs=None):
'''Call the listener with **kwargs. Note that it raises RuntimeError
"""Call the listener with **kwargs. Note that it raises RuntimeError
if listener is dead. Should always return True (False would require
the callable_ be dead but self hasn't yet been notified of it...).'''
the callable_ be dead but self hasn't yet been notified of it...)."""
if self.acceptsAllKwargs:
kwargs = allKwargs or kwargs # if allKwargs is None then use kwargs
@@ -46,11 +46,11 @@ class Listener(ListenerBase):
class ListenerValidator(ValidatorBase):
'''
"""
Do not accept any required args or *args; accept any **kwarg,
and require that the Listener have at least all the kwargs (can
have extra) of Topic.
'''
"""
def _validateArgs(self, listener, paramsInfo):
# accept **kwargs

View File

@@ -1,7 +1,7 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .publisherbase import PublisherBase
@@ -11,17 +11,17 @@ from .. import (policies, py2and3)
class Publisher(PublisherBase):
'''
"""
Publisher used for kwargs protocol, ie when sending message data
via keyword arguments.
'''
"""
def sendMessage(self, topicName, **kwargs):
'''Send a message.
"""Send a message.
:param topicName: name of message topic (dotted or tuple format)
:param kwargs: message data (must satisfy the topic's MDS)
'''
"""
topicMgr = self.getTopicMgr()
topicObj = topicMgr.getOrCreateTopic(topicName)
topicObj.publish(**kwargs)
@@ -31,10 +31,10 @@ class Publisher(PublisherBase):
class PublisherArg1Stage2(Publisher):
'''
"""
This is used when transitioning from arg1 to kwargs
messaging protocol.
'''
"""
_base = Publisher

View File

@@ -1,11 +1,11 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
class PublisherMixin:
'''
"""
Mixin for publishing messages to a topic's listeners. This will be
mixed into topicobj.Topic so that a user can use a Topic object to
send a message to the topic's listeners via a publish() method.
@@ -15,7 +15,7 @@ class PublisherMixin:
happen that a listener causes another message of same topic to be
sent (presumably, the listener has a way of preventing infinite
loop).
'''
"""
def __init__(self):
pass
@@ -57,9 +57,9 @@ class PublisherMixin:
return iterState
def _mix_callListener(self, listener, msgKwargs, iterState):
'''Send the message for given topic with data in msgKwargs.
"""Send the message for given topic with data in msgKwargs.
This sends message to listeners of parent topics as well.
Note that at each level, msgKwargs is filtered so only those
args that are defined for the topic are sent to listeners. '''
args that are defined for the topic are sent to listeners. """
listener(iterState.filteredArgs, self, msgKwargs)

View File

@@ -1,9 +1,9 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
import weakref
@@ -14,10 +14,10 @@ from .. import py2and3
### Exceptions raised during check() from sendMessage()
class SenderMissingReqdMsgDataError(RuntimeError):
'''
"""
Raised when a sendMessage() is missing arguments tagged as
'required' by pubsub topic of message.
'''
"""
def __init__(self, topicName, argNames, missing):
argsStr = ','.join(argNames)
@@ -28,10 +28,10 @@ class SenderMissingReqdMsgDataError(RuntimeError):
class SenderUnknownMsgDataError(RuntimeError):
'''
"""
Raised when a sendMessage() has arguments not listed among the topic's
message data specification (MDS).
'''
"""
def __init__(self, topicName, argNames, extra):
argsStr = ','.join(argNames)
@@ -42,7 +42,7 @@ class SenderUnknownMsgDataError(RuntimeError):
class ArgsInfo:
'''
"""
Encode the Message Data Specification (MDS) for a given
topic. ArgsInfos form a tree identical to that of Topics in that
ArgInfos have a reference to their parent and children ArgInfos,
@@ -58,7 +58,7 @@ class ArgsInfo:
The MDS can be created "empty", ie "incomplete", meaning it cannot
yet be used to validate listener subscriptions to topics.
'''
"""
SPEC_MISSING = 10 # no args given
SPEC_COMPLETE = 12 # all args, but not confirmed via user spec
@@ -98,17 +98,17 @@ class ArgsInfo:
return self.allDocs.copy()
def setArgsDocs(self, docs):
'''docs is a mapping from arg names to their documentation'''
"""docs is a mapping from arg names to their documentation"""
if not self.isComplete():
raise
for arg, doc in py2and3.iteritems(docs):
self.allDocs[arg] = doc
def check(self, msgKwargs):
'''Check that the message arguments given satisfy the topic message
"""Check that the message arguments given satisfy the topic message
data specification (MDS). Raises SenderMissingReqdMsgDataError if some required
args are missing or not known, and raises SenderUnknownMsgDataError if some
optional args are unknown. '''
optional args are unknown. """
all = set(msgKwargs)
# check that it has all required args
needReqd = set(self.allRequired)
@@ -125,11 +125,11 @@ class ArgsInfo:
py2and3.keys(msgKwargs), optional - set(self.allOptional) )
def filterArgs(self, msgKwargs):
'''Returns a dict which contains only those items of msgKwargs
"""Returns a dict which contains only those items of msgKwargs
which are defined for topic. E.g. if msgKwargs is {a:1, b:'b'}
and topic arg spec is ('a',) then return {a:1}. The returned dict
is valid only if check(msgKwargs) was called (or
check(superset of msgKwargs) was called).'''
check(superset of msgKwargs) was called)."""
assert self.isComplete()
if len(msgKwargs) == self.numArgs():
return msgKwargs
@@ -150,20 +150,20 @@ class ArgsInfo:
return newKwargs
def hasSameArgs(self, *argNames):
'''Returns true if self has all the message arguments given, no
"""Returns true if self has all the message arguments given, no
more and no less. Order does not matter. So if getArgs()
returns ('arg1', 'arg2') then self.hasSameArgs('arg2', 'arg1')
will return true. '''
will return true. """
return set(argNames) == set( self.getArgs() )
def hasParent(self, argsInfo):
'''return True if self has argsInfo object as parent'''
"""return True if self has argsInfo object as parent"""
return self.parentAI() is argsInfo
def getCompleteAI(self):
'''Get the closest arg spec, starting from self and moving to parent,
"""Get the closest arg spec, starting from self and moving to parent,
that is complete. So if self.isComplete() is True, then returns self,
otherwise returns parent (if parent.isComplete()), etc. '''
otherwise returns parent (if parent.isComplete()), etc. """
AI = self
while AI is not None:
if AI.isComplete():
@@ -172,8 +172,8 @@ class ArgsInfo:
return None
def updateAllArgsFinal(self, topicDefn):
'''This can only be called once, if the construction was done
with ArgSpecGiven.SPEC_GIVEN_NONE'''
"""This can only be called once, if the construction was done
with ArgSpecGiven.SPEC_GIVEN_NONE"""
assert not self.isComplete()
assert topicDefn.isComplete()
self.__setAllArgs(topicDefn)
@@ -183,7 +183,7 @@ class ArgsInfo:
self.childrenAI.append(childAI)
def __notifyParentCompleted(self):
'''Parent should call this when parent ArgsInfo has been completed'''
"""Parent should call this when parent ArgsInfo has been completed"""
assert self.parentAI().isComplete()
if self.isComplete():
# verify that our spec is compatible with parent's

View File

@@ -1,12 +1,12 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
def getRootTopicSpec():
'''If using kwargs protocol, then root topic takes no args.'''
"""If using kwargs protocol, then root topic takes no args."""
argsDocs = {}
reqdArgs = ()
return argsDocs, reqdArgs

View File

@@ -1,11 +1,11 @@
'''
"""
Top-level functionality related to message listeners.
'''
"""
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .callables import (
getID,
@@ -21,7 +21,7 @@ from .listenerimpl import (
)
class IListenerExcHandler:
'''
"""
Interface class base class for any handler given to pub.setListenerExcHandler()
Such handler is called whenever a listener raises an exception during a
pub.sendMessage(). Example::
@@ -33,7 +33,7 @@ class IListenerExcHandler:
... do something with listenerID ...
pub.setListenerExcHandler(MyHandler())
'''
"""
def __call__(self, listenerID, topicObj):
raise NotImplementedError('%s must override __call__()' % self.__class__)

View File

@@ -1,7 +1,7 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from . import weakmethod
@@ -15,18 +15,18 @@ from .callables import (
class ListenerBase:
'''
"""
Base class for listeners, ie. callables subscribed to pubsub.
'''
"""
AUTO_TOPIC = _AUTO_ARG
def __init__(self, callable_, argsInfo, onDead=None):
'''Use callable_ as a listener of topicName. The argsInfo is the
"""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
reference). '''
reference). """
# set call policies
self.acceptsAllKwargs = argsInfo.acceptsAllKwargs
@@ -45,62 +45,62 @@ class ListenerBase:
raise NotImplementedError
def name(self):
'''Return a human readable name for listener, based on the
"""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).'''
id's can be re-used after garbage collection)."""
return '%s_%s' % (self.__nameID, self.__id)
def typeName(self):
'''Get a type name for the listener. This is a class name or
function name, as appropriate. '''
"""Get a type name for the listener. This is a class name or
function name, as appropriate. """
return self.__nameID
def module(self):
'''Get the module in which the callable was defined.'''
"""Get the module in which the callable was defined."""
return self.__module
def getCallable(self):
'''Get the listener that was given at initialization. Note that
"""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
locally).'''
locally)."""
return self._callable()
def isDead(self):
'''Return True if this listener died (has been garbage collected)'''
"""Return True if this listener died (has been garbage collected)"""
return self._callable() is None
def wantsTopicObjOnCall(self):
'''True if this listener wants topic object: it has a arg=pub.AUTO_TOPIC'''
"""True if this listener wants topic object: it has a arg=pub.AUTO_TOPIC"""
return self._autoTopicArgName is not None
def wantsAllMessageData(self):
'''True if this listener wants all message data: it has a \**kwargs argument'''
"""True if this listener wants all message data: it has a \**kwargs argument"""
return self.acceptsAllKwargs
def _unlinkFromTopic_(self):
'''Tell self that it is no longer used by a Topic. This allows
to break some cyclical references.'''
"""Tell self that it is no longer used by a Topic. This allows
to break some cyclical references."""
self.__onDead = None
def _calledWhenDead(self):
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).'''
"""This gets called when listener weak ref has died. Propagate
info to Topic)."""
notifyDeath = self.__onDead
self._unlinkFromTopic_()
if notifyDeath is not None:
notifyDeath(self)
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). '''
"""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):
return True
@@ -119,8 +119,8 @@ class ListenerBase:
return c1 == c2
def __ne__(self, rhs):
'''Counterpart to __eq__ MUST be defined... equivalent to
'not (self == rhs)'.'''
"""Counterpart to __eq__ MUST be defined... equivalent to
'not (self == rhs)'."""
return not self.__eq__(rhs)
def __hash__(self):
@@ -129,28 +129,28 @@ class ListenerBase:
return self.__hash
def __str__(self):
'''String rep is the callable'''
"""String rep is the callable"""
return self.__nameID
class ValidatorBase:
'''
"""
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
"""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. '''
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
"""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 ListenerMismatchError if listener not usable for topic.
@@ -161,16 +161,16 @@ class ValidatorBase:
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 )
self._validateArgs(listener, paramsInfo)
return paramsInfo
def isValid(self, listener):
'''Return true only if listener can subscribe to messages where
"""Return true only if listener can subscribe to messages where
topic has kwargs keys topicKwargKeys. Just calls validate() in
a try-except clause.'''
a try-except clause."""
try:
self.validate(listener)
return True
@@ -179,7 +179,7 @@ class ValidatorBase:
def _validateArgs(self, listener, paramsInfo):
'''Provide implementation in derived classes'''
"""Provide implementation in derived classes"""
raise NotImplementedError

View File

@@ -1,11 +1,11 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
import sys
class NotificationMgr:
'''
"""
Manages notifications for tracing pubsub activity. When pubsub takes a
certain action such as sending a message or creating a topic, and
the notification flag for that activity is True, all registered
@@ -23,7 +23,7 @@ class NotificationMgr:
collecting everything, which could lead to various pubsub notifications
-- by then they should be of no interest -- such as dead
listeners, etc.
'''
"""
def __init__(self, notificationHandler = None):
self.__notifyOnSend = False
@@ -82,7 +82,7 @@ class NotificationMgr:
handler.notifyDeadListener(*args, **kwargs)
def getFlagStates(self):
'''Return state of each notification flag, as a dict.'''
"""Return state of each notification flag, as a dict."""
return dict(
subscribe = self.__notifyOnSubscribe,
unsubscribe = self.__notifyOnUnsubscribe,
@@ -95,7 +95,7 @@ class NotificationMgr:
def setFlagStates(self, subscribe=None, unsubscribe=None,
deadListener=None, sendMessage=None, newTopic=None,
delTopic=None, all=None):
'''Set the notification flag on/off for various aspects of pubsub.
"""Set the notification flag on/off for various aspects of pubsub.
The kwargs that are None are left at their current value. The 'all',
if not None, is set first. E.g.
@@ -103,7 +103,7 @@ class NotificationMgr:
will toggle all notifications on, but will turn off the 'delTopic'
notification.
'''
"""
if all is not None:
# ignore all other arg settings, and set all of them to true:
numArgs = 7 # how many args in this method
@@ -132,54 +132,54 @@ class NotificationMgr:
class INotificationHandler:
'''
"""
Defines the interface expected by pubsub for pubsub activity
notifications. Any instance that supports the same methods, or
derives from this class, will work as a notification handler
for pubsub events (see pub.addNotificationHandler).
'''
"""
def notifySubscribe(self, pubListener, topicObj, newSub):
'''Called when a listener is subscribed to a topic.
"""Called when a listener is subscribed to a topic.
:param pubListener: the pubsub.core.Listener that wraps subscribed listener.
:param topicObj: the pubsub.core.Topic object subscribed to.
:param newSub: false if pubListener was already subscribed. '''
:param newSub: false if pubListener was already subscribed. """
raise NotImplementedError
def notifyUnsubscribe(self, pubListener, topicObj):
'''Called when a listener is unsubscribed from given topic.
"""Called when a listener is unsubscribed from given topic.
:param pubListener: the pubsub.core.Listener that wraps unsubscribed listener.
:param topicObj: the pubsub.core.Topic object unsubscribed from.'''
:param topicObj: the pubsub.core.Topic object unsubscribed from."""
raise NotImplementedError
def notifyDeadListener(self, pubListener, topicObj):
'''Called when a listener has been garbage collected.
"""Called when a listener has been garbage collected.
:param pubListener: the pubsub.core.Listener that wraps GC'd listener.
:param topicObj: the pubsub.core.Topic object it was subscribed to.'''
:param topicObj: the pubsub.core.Topic object it was subscribed to."""
raise NotImplementedError
def notifySend(self, stage, topicObj, pubListener=None):
'''Called multiple times during a sendMessage: once before message
"""Called multiple times during a sendMessage: once before message
sending has started (pre), once for each listener about to be sent the
message, and once after all listeners have received the message (post).
:param stage: 'pre', 'post', or 'loop'.
:param topicObj: the Topic object for the message.
:param pubListener: None for pre and post stages; for loop, the listener
that is about to be sent the message.'''
that is about to be sent the message."""
raise NotImplementedError
def notifyNewTopic(self, topicObj, description, required, argsDocs):
'''Called whenever a new topic is added to the topic tree.
"""Called whenever a new topic is added to the topic tree.
:param topicObj: the Topic object for the message.
:param description: docstring for the topic.
:param required: list of message data names (keys in argsDocs) that are required.
:param argsDocs: dictionary of all message data names, with the
corresponding docstring. '''
corresponding docstring. """
raise NotImplementedError
def notifyDelTopic(self, topicName):
'''Called whenever a topic is removed from topic tree.
:param topicName: name of topic removed.'''
"""Called whenever a topic is removed from topic tree.
:param topicName: name of topic removed."""
raise NotImplementedError

View File

@@ -1,7 +1,7 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .topicmgr import (
TopicManager,
@@ -12,45 +12,45 @@ from .. import py2and3
class PublisherBase:
'''
"""
Represent the class that send messages to listeners of given
topics and that knows how to subscribe/unsubscribe listeners
from topics.
'''
"""
def __init__(self, treeConfig = None):
'''If treeConfig is None, a default one is created from an
instance of TreeConfig.'''
"""If treeConfig is None, a default one is created from an
instance of TreeConfig."""
self.__treeConfig = treeConfig or TreeConfig()
self.__topicMgr = TopicManager(self.__treeConfig)
def getTopicMgr(self):
'''Get the topic manager created for this publisher.'''
"""Get the topic manager created for this publisher."""
return self.__topicMgr
def getListenerExcHandler(self):
'''Get the listener exception handler that was registered
via setListenerExcHandler(), or None of none registered.'''
"""Get the listener exception handler that was registered
via setListenerExcHandler(), or None of none registered."""
return self.__treeConfig.listenerExcHandler
def setListenerExcHandler(self, handler):
'''Set the function to call when a listener raises an exception
"""Set the function to call when a listener raises an exception
during a sendMessage(). The handler must adhere to the
IListenerExcHandler API. '''
IListenerExcHandler API. """
self.__treeConfig.listenerExcHandler = handler
def addNotificationHandler(self, handler):
'''Add a handler for tracing pubsub activity. The handler should be
a class that adheres to the API of INotificationHandler. '''
"""Add a handler for tracing pubsub activity. The handler should be
a class that adheres to the API of INotificationHandler. """
self.__treeConfig.notificationMgr.addHandler(handler)
def clearNotificationHandlers(self):
'''Remove all notification handlers that were added via
self.addNotificationHandler(). '''
"""Remove all notification handlers that were added via
self.addNotificationHandler(). """
self.__treeConfig.notificationMgr.clearHandlers()
def setNotificationFlags(self, **kwargs):
'''Set the notification flags on or off for each type of
"""Set the notification flags on or off for each type of
pubsub activity. The kwargs keys can be any of the following:
- subscribe: if True, get notified whenever a listener subscribes to a topic;
@@ -69,15 +69,15 @@ class PublisherBase:
will toggle all notifications on, but will turn off the 'delTopic'
notification.
'''
"""
self.__treeConfig.notificationMgr.setFlagStates(**kwargs)
def getNotificationFlags(self):
'''Return a dictionary with the notification flag states.'''
"""Return a dictionary with the notification flag states."""
return self.__treeConfig.notificationMgr.getFlagStates()
def setTopicUnspecifiedFatal(self, newVal=True, checkExisting=True):
'''Changes the creation policy for topics.
"""Changes the creation policy for topics.
By default, pubsub will accept topic names for topics that
don't have a message data specification (MDS). This default behavior
@@ -114,7 +114,7 @@ class PublisherBase:
3. Use it as in #1 during app development, and once stable, use
#2. This is easiest to do in combination with
pub.exportTopicTreeSpec().
'''
"""
oldVal = self.__treeConfig.raiseOnTopicUnspecified
self.__treeConfig.raiseOnTopicUnspecified = newVal
@@ -124,14 +124,14 @@ class PublisherBase:
return oldVal
def sendMessage(self, topicName, *args, **kwargs):
'''Send a message for topic name with given data (args and kwargs).
"""Send a message for topic name with given data (args and kwargs).
This will be overridden by derived classes that implement
message-sending for different messaging protocols; not all
parameters may be accepted.'''
parameters may be accepted."""
raise NotImplementedError
def subscribe(self, listener, topicName):
'''Subscribe listener to named topic. Raises ListenerMismatchError
"""Subscribe listener to named topic. Raises ListenerMismatchError
if listener isn't compatible with the topic's MDS. Returns
(pubsub.core.Listener, success), where success is False if listener
was already subscribed. The pub.core.Listener wraps the callable
@@ -139,18 +139,18 @@ class PublisherBase:
the callable.
Note that if 'subscribe' notification is on, the handler's
'notifySubscribe' method is called after subscription.'''
'notifySubscribe' method is called after subscription."""
topicObj = self.__topicMgr.getOrCreateTopic(topicName)
subscribedListener, success = topicObj.subscribe(listener)
return subscribedListener, success
def unsubscribe(self, listener, topicName):
'''Unsubscribe from given topic. Returns the pubsub.core.Listener
"""Unsubscribe from given topic. Returns the pubsub.core.Listener
instance that was used to wrap listener at subscription
time. Raises an TopicNameError if topicName doesn't exist.
Note that if 'unsubscribe' notification is on, the handler's
notifyUnsubscribe() method will be called after unsubscribing. '''
notifyUnsubscribe() method will be called after unsubscribing. """
topicObj = self.__topicMgr.getTopic(topicName)
unsubdLisnr = topicObj.unsubscribe(listener)
@@ -158,7 +158,7 @@ class PublisherBase:
def unsubAll(self, topicName = None,
listenerFilter = None, topicFilter = None):
'''By default (no args given), unsubscribe all listeners from all
"""By default (no args given), unsubscribe all listeners from all
topics. A listenerFilter can be given so that only the listeners
that satisfy listenerFilter(listener) == True will be unsubscribed
(with listener being a pub.Listener wrapper instance for each listener
@@ -171,7 +171,7 @@ class PublisherBase:
were unsubscribed from the topic tree).
Note: this method will generate one 'unsubcribe' notification message
(see pub.setNotificationFlags()) for each listener unsubscribed.'''
(see pub.setNotificationFlags()) for each listener unsubscribed."""
unsubdListeners = []
if topicName is None:

View File

@@ -1,10 +1,10 @@
'''
"""
Definitions related to message data specification.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .listener import getArgs as getListenerArgs
@@ -17,10 +17,10 @@ from .topicargspecimpl import (
def topicArgsFromCallable(_callable):
'''Get the topic message data names and list of those that are required,
"""Get the topic message data names and list of those that are required,
by introspecting given callable. Returns a pair, (args, required)
where args is a dictionary of allowed message data names vs docstring,
and required states which ones are required rather than optional.'''
and required states which ones are required rather than optional."""
argsInfo = getListenerArgs(_callable)
required = argsInfo.getRequiredArgs()
defaultDoc = 'UNDOCUMENTED'
@@ -29,7 +29,7 @@ def topicArgsFromCallable(_callable):
class ArgSpecGiven:
'''
"""
The message data specification (MDS) for a topic.
This consists of each argument name that listener should have in its
call protocol, plus which ones are required in any sendMessage(), and a
@@ -37,7 +37,7 @@ class ArgSpecGiven:
into an ArgsInfo object which is basically a superset of that information,
needed to ensure that the arguments specifications satisfy
pubsub policies for chosen API version.
'''
"""
SPEC_GIVEN_NONE = 1 # specification not given
SPEC_GIVEN_ALL = 3 # all args specified
@@ -64,7 +64,7 @@ class ArgSpecGiven:
self.argsSpecType = ArgSpecGiven.SPEC_GIVEN_ALL
def isComplete(self):
'''Returns True if the definition is usable, false otherwise.'''
"""Returns True if the definition is usable, false otherwise."""
return self.argsSpecType == ArgSpecGiven.SPEC_GIVEN_ALL
def getOptional(self):

View File

@@ -1,7 +1,7 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
import os, re, inspect
@@ -20,34 +20,34 @@ from .topicexc import UnrecognizedSourceFormatError
class ITopicDefnProvider:
'''
"""
All topic definition providers added via pub.addTopicDefnProvider()
must have this interface. Derived classes must override the getDefn(),
getTreeDoc() and topicNames() methods.
'''
"""
def getDefn(self, topicNameTuple):
'''Must return a pair (string, ArgSpecGiven) for given topic.
"""Must return a pair (string, ArgSpecGiven) for given topic.
The first item is a description for topic, the second item
contains the message data specification (MDS). Note topic name
is in tuple format ('a', 'b', 'c') rather than 'a.b.c'. '''
is in tuple format ('a', 'b', 'c') rather than 'a.b.c'. """
msg = 'Must return (string, ArgSpecGiven), or (None, None)'
raise NotImplementedError(msg)
def topicNames(self):
'''Return an iterator over topic names available from this provider.
"""Return an iterator over topic names available from this provider.
Note that the topic names should be in tuple rather than dotted-string
format so as to be compatible with getDefn().'''
format so as to be compatible with getDefn()."""
msg = 'Must return a list of topic names available from this provider'
raise NotImplementedError(msg)
def getTreeDoc(self):
'''Get the docstring for the topic tree.'''
"""Get the docstring for the topic tree."""
msg = 'Must return documentation string for root topic (tree)'
raise NotImplementedError(msg)
def __iter__(self):
'''Same as self.topicNames(), do NOT override.'''
"""Same as self.topicNames(), do NOT override."""
return self.topicNames()
@@ -57,15 +57,15 @@ SPEC_METHOD_NAME = 'msgDataSpec'
class ITopicDefnDeserializer:
'''
"""
Interface class for all topic definition de-serializers that can be
accepted by TopicDefnProvider. A deserializer
creates a topic tree from something such as file, module, or string.
'''
"""
class TopicDefn:
'''Encapsulate date for a topic definition. Used by
getNextTopic().'''
"""Encapsulate date for a topic definition. Used by
getNextTopic()."""
def __init__(self, nameTuple, description, argsDocs, required):
self.nameTuple = nameTuple
@@ -77,41 +77,41 @@ class ITopicDefnDeserializer:
return (self.description is not None) and (self.argsDocs is not None)
def getTreeDoc(self):
'''Get the docstring for the topic tree.'''
"""Get the docstring for the topic tree."""
raise NotImplementedError
def getNextTopic(self):
'''Get the next topic definition available from the data. The return
"""Get the next topic definition available from the data. The return
must be an instance of TopicDefn. Must return None when no topics
are left.'''
are left."""
raise NotImplementedError
def doneIter(self):
'''Called automatically by TopicDefnProvider once
"""Called automatically by TopicDefnProvider once
it considers the iteration completed. Override this only if
deserializer needs to take action, such as closing a file.'''
deserializer needs to take action, such as closing a file."""
pass
def resetIter(self):
'''Called by the TopicDefnProvider if it needs to
"""Called by the TopicDefnProvider if it needs to
restart the topic iteration. Override this only if special action needed,
such as resetting a file pointer to beginning of file.'''
such as resetting a file pointer to beginning of file."""
pass
class TopicDefnDeserialClass(ITopicDefnDeserializer):
'''
"""
Convert a nested class tree as a topic definition tree. Format: the class
name is the topic name, its doc string is its description. The topic's
message data specification is determined by inspecting a class method called
the same as SPEC_METHOD_NAME. The doc string of that method is parsed to
extract the description for each message data.
'''
"""
def __init__(self, pyClassObj=None):
'''If pyClassObj is given, it is an object that contains nested
"""If pyClassObj is given, it is an object that contains nested
classes defining root topics; the root topics contain nested
classes defining subtopics; etc.'''
classes defining subtopics; etc."""
self.__rootTopics = []
self.__iterStarted = False
self.__nextTopic = iter(self.__rootTopics)
@@ -156,10 +156,10 @@ class TopicDefnDeserialClass(ITopicDefnDeserializer):
return [nt for (nt, defn) in self.__rootTopics]
def __addDefnFromClassObj(self, pyClassObj):
'''Extract a topic definition from a Python class: topic name,
"""Extract a topic definition from a Python class: topic name,
docstring, and MDS, and docstring for each message data.
The class name is the topic name, assumed to be a root topic, and
descends recursively into nested classes to define subtopic etc. '''
descends recursively into nested classes to define subtopic etc. """
if self.__iterStarted:
raise RuntimeError('addDefnFromClassObj must be called before iteration started!')
@@ -193,7 +193,7 @@ class TopicDefnDeserialClass(ITopicDefnDeserializer):
self.__findTopics(topicClassObj, parentNameTuple2)
def __getTopicClasses(self, pyClassObj, parentNameTuple=()):
'''Returns a list of pairs, (topicNameTuple, memberClassObj)'''
"""Returns a list of pairs, (topicNameTuple, memberClassObj)"""
memberNames = dir(pyClassObj)
topicClasses = []
for memberName in memberNames:
@@ -232,15 +232,14 @@ class TopicDefnDeserialClass(ITopicDefnDeserializer):
class TopicDefnDeserialModule(ITopicDefnDeserializer):
'''
"""
Deserialize a module containing Python source code defining a topic tree.
This loads the module and gives it to an instance of TopicDefnDeserialClass.
'''
"""
def __init__(self, moduleName, searchPath=None):
'''Load the given named module, searched for in searchPath or, if not
specified, in sys.path. Give it to a TopicDefnDeserialClass.
'''
"""Load the given named module, searched for in searchPath or, if not
specified, in sys.path. Give it to a TopicDefnDeserialClass."""
from . import imp2
module = imp2.load_module(moduleName, searchPath)
self.__classDeserial = TopicDefnDeserialClass(module)
@@ -262,16 +261,16 @@ class TopicDefnDeserialModule(ITopicDefnDeserializer):
class TopicDefnDeserialString(ITopicDefnDeserializer):
'''
"""
Deserialize a string containing Python source code defining a topic tree.
The string has the same format as expected by TopicDefnDeserialModule.
'''
"""
def __init__(self, source):
'''This just saves the string into a temporary file created in
"""This just saves the string into a temporary file created in
os.getcwd(), and the rest is delegated to TopicDefnDeserialModule.
The temporary file (module -- as well as its byte-compiled
version) will be deleted when the doneIter() method is called.'''
version) will be deleted when the doneIter() method is called."""
def createTmpModule():
moduleNamePre = 'tmp_export_topics_'
@@ -316,7 +315,7 @@ TOPIC_TREE_FROM_CLASS = 'class'
class TopicDefnProvider(ITopicDefnProvider):
'''
"""
Default implementation of the ITopicDefnProvider API. This
implementation accepts several formats for the topic tree
source data and delegates to a registered ITopicDefnDeserializer
@@ -327,14 +326,14 @@ class TopicDefnProvider(ITopicDefnProvider):
when source is *not* an ITopicDefnProvider.
Additional de-serializers can be registered via registerTypeForImport().
'''
"""
_typeRegistry = {}
def __init__(self, source, format, **providerKwargs):
'''Find the correct de-serializer class from registry for the given
"""Find the correct de-serializer class from registry for the given
format; instantiate it with given source and providerKwargs; get
all available topic definitions.'''
all available topic definitions."""
if format not in self._typeRegistry:
raise UnrecognizedSourceFormatError()
providerClassObj = self._typeRegistry[format]
@@ -366,7 +365,7 @@ class TopicDefnProvider(ITopicDefnProvider):
@classmethod
def registerTypeForImport(cls, typeName, providerClassObj):
'''If a new type of importer is defined for topic definitions, it
"""If a new type of importer is defined for topic definitions, it
can be registered with pubsub by providing a name for the new
importer (typeName), and the class to instantiate when
pub.addTopicDefnProvider(obj, typeName) is called. For instance, ::
@@ -377,7 +376,7 @@ class TopicDefnProvider(ITopicDefnProvider):
TopicDefnProvider.registerTypeForImport('some name', SomeNewImporter)
# will instantiate SomeNewImporter(source)
pub.addTopicDefnProvider(source, 'some name')
'''
"""
assert issubclass(providerClassObj, ITopicDefnDeserializer)
cls._typeRegistry[typeName] = providerClassObj
@@ -412,7 +411,7 @@ defaultTopicTreeSpecFooter = \
def exportTopicTreeSpec(moduleName = None, rootTopic=None, bak='bak', moduleDoc=None):
'''Using TopicTreeSpecPrinter, exports the topic tree rooted at rootTopic to a
"""Using TopicTreeSpecPrinter, exports the topic tree rooted at rootTopic to a
Python module (.py) file. This module will define module-level classes
representing root topics, nested classes for subtopics etc. Returns a string
representing the contents of the file. Parameters:
@@ -425,7 +424,7 @@ def exportTopicTreeSpec(moduleName = None, rootTopic=None, bak='bak', moduleDoc=
corresponding topic. Otherwise, complete tree, using
pub.getDefaultTopicTreeRoot() as starting point.
- The moduleDoc is the doc string for the module ie topic tree.
'''
"""
if rootTopic is None:
from .. import pub
@@ -453,24 +452,24 @@ def exportTopicTreeSpec(moduleName = None, rootTopic=None, bak='bak', moduleDoc=
##############################################################
class TopicTreeSpecPrinter:
'''
"""
Helper class to print the topic tree using the Python class
syntax. The "printout" can be sent to any file object (object that has a
write() method). If printed to a module, the module can be imported and
given to pub.addTopicDefnProvider(module, 'module'). Importing the module
also provides code completion of topic names (rootTopic.subTopic can be
given to any pubsub function requiring a topic name).
'''
"""
INDENT_CH = ' '
#INDENT_CH = '.'
def __init__(self, rootTopic=None, fileObj=None, width=70, indentStep=4,
treeDoc = defaultTopicTreeSpecHeader, footer = defaultTopicTreeSpecFooter):
'''For formatting, can specify the width of output, the indent step, the
"""For formatting, can specify the width of output, the indent step, the
header and footer to print to override defaults. The destination is fileObj;
if none is given, then sys.stdout is used. If rootTopic is given, calls
writeAll(rootTopic) at end of __init__.'''
writeAll(rootTopic) at end of __init__."""
self.__traverser = TopicTreeTraverser(self)
import sys
@@ -510,13 +509,13 @@ class TopicTreeSpecPrinter:
self.writeAll(rootTopic)
def getOutput(self):
'''Each line that was sent to fileObj was saved in a list; returns a
string which is ``'\\n'.join(list)``.'''
"""Each line that was sent to fileObj was saved in a list; returns a
string which is ``'\\n'.join(list)``."""
return '\n'.join( self.__output )
def writeAll(self, topicObj):
'''Traverse each topic of topic tree, starting at topicObj, printing
each topic definition as the tree gets traversed. '''
"""Traverse each topic of topic tree, starting at topicObj, printing
each topic definition as the tree gets traversed. """
self.__traverser.traverse(topicObj)
def _accept(self, topicObj):
@@ -545,7 +544,7 @@ class TopicTreeSpecPrinter:
self.__destination.write(self.getOutput())
def _onTopic(self, topicObj):
'''This gets called for each topic. Print as per specified content.'''
"""This gets called for each topic. Print as per specified content."""
# don't print root of tree, it is the ALL_TOPICS builtin topic
if topicObj.isAll():
self.__lastWasAll = True
@@ -564,12 +563,12 @@ class TopicTreeSpecPrinter:
self.__printTopicArgSpec(topicObj)
def _startChildren(self):
'''Increase the indent'''
"""Increase the indent"""
if not self.__lastWasAll:
self.__indent += self.__indentStep
def _endChildren(self):
'''Decrease the indent'''
"""Decrease the indent"""
if not self.__lastWasAll:
self.__indent -= self.__indentStep
@@ -578,14 +577,14 @@ class TopicTreeSpecPrinter:
return msg
if msg.startswith("'''") or msg.startswith('"""'):
return msg
return "'''\n%s\n'''" % msg.strip()
return '"""\n%s\n"""' % msg.strip()
def __printTopicDescription(self, topicObj):
if topicObj.getDescription():
extraIndent = self.__indentStep
self.__formatItem("'''", extraIndent)
self.__formatItem('"""', extraIndent)
self.__formatItem( topicObj.getDescription(), extraIndent )
self.__formatItem("'''", extraIndent)
self.__formatItem('"""', extraIndent)
def __printTopicArgSpec(self, topicObj):
extraIndent = self.__indentStep
@@ -609,7 +608,7 @@ class TopicTreeSpecPrinter:
# and finally, the args docs
extraIndent += self.__indentStep
self.__formatItem("'''", extraIndent)
self.__formatItem('"""', extraIndent)
# but ignore the arg keys that are in parent args docs:
parentMsgKeys = ()
if topicObj.getParent() is not None:
@@ -620,7 +619,7 @@ class TopicTreeSpecPrinter:
argDesc = argsDocs[key]
msg = "- %s: %s" % (key, argDesc)
self.__formatItem(msg, extraIndent)
self.__formatItem("'''", extraIndent)
self.__formatItem('"""', extraIndent)
def __formatItem(self, item, extraIndent=0):
indent = extraIndent + self.__indent

View File

@@ -1,22 +1,22 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
class TopicNameError(ValueError):
'''Raised when the topic name is not properly formatted or
no corresponding Topic object found. '''
"""Raised when the topic name is not properly formatted or
no corresponding Topic object found. """
def __init__(self, name, msg):
ValueError.__init__(self, 'Topic name "%s": %s' % (name, msg))
class TopicDefnError(RuntimeError):
'''
"""
Raised when an operation requires a topic have an MDS, but it doesn't.
See also pub.setTopicUnspecifiedFatal().
'''
"""
def __init__(self, topicNameTuple):
msg = "No topic specification for topic '%s'." \
% '.'.join(topicNameTuple)
@@ -25,14 +25,14 @@ class TopicDefnError(RuntimeError):
class MessageDataSpecError(RuntimeError):
'''
"""
Raised when an attempt is made to define a topic's Message Data
Specification (MDS) to something that is not valid.
The keyword names for invalid data go in the 'args' list,
and the msg should state the problem and contain "%s" for the
args, such as MessageDataSpecError('duplicate args %s', ('arg1', 'arg2')).
'''
"""
def __init__(self, msg, args):
argsMsg = msg % ','.join(args)
@@ -40,17 +40,17 @@ class MessageDataSpecError(RuntimeError):
class ExcHandlerError(RuntimeError):
'''
"""
Raised when a listener exception handler (see pub.setListenerExcHandler())
raises an exception. The original exception is contained.
'''
"""
def __init__(self, badExcListenerID, topicObj, origExc=None):
'''The badExcListenerID is the name of the listener that raised
"""The badExcListenerID is the name of the listener that raised
the original exception that handler was attempting to handle.
The topicObj is the Topic object for the topic of the
sendMessage that had an exception raised.
The origExc is currently not used. '''
The origExc is currently not used. """
self.badExcListenerID = badExcListenerID
import traceback
self.exc = traceback.format_exc()
@@ -62,10 +62,10 @@ class ExcHandlerError(RuntimeError):
class UnrecognizedSourceFormatError(ValueError):
'''
"""
Raised when a topic definition provider doesn't recognize the format
of source input it was given.
'''
"""
def __init__(self):
ValueError.__init__(self, 'Source format not recognized')

View File

@@ -1,10 +1,10 @@
'''
"""
Code related to the concept of topic tree and its management: creating
and removing topics, getting info about a particular topic, etc.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
__all__ = [
'TopicManager',
@@ -52,7 +52,7 @@ ARGS_SPEC_NONE = ArgSpecGiven.SPEC_GIVEN_NONE
# ---------------------------------------------------------
class TopicManager:
'''
"""
Manages the registry of all topics and creation/deletion
of topics.
@@ -60,7 +60,7 @@ class TopicManager:
'dotted' format such as ``'a.b.c.'`` or in tuple format such as
``('a', 'b', 'c')``. Any such method will raise a ValueError
if name not valid (empty, invalid characters, etc).
'''
"""
# Allowed return values for isTopicSpecified()
TOPIC_SPEC_NOT_SPECIFIED = 0 # false
@@ -69,10 +69,10 @@ class TopicManager:
def __init__(self, treeConfig=None):
'''The optional treeConfig is an instance of TreeConfig, used to
"""The optional treeConfig is an instance of TreeConfig, used to
configure the topic tree such as notification settings, etc. A
default config is created if not given. This method should only be
called by an instance of Publisher (see Publisher.getTopicManager()).'''
called by an instance of Publisher (see Publisher.getTopicManager())."""
self.__allTopics = None # root of topic tree
self._topicsMap = {} # registry of all topics
self.__treeConfig = treeConfig or TreeConfig()
@@ -86,7 +86,7 @@ class TopicManager:
self.__allTopics = self.__createTopic((ALL_TOPICS,), desc, specGiven=specGiven)
def getRootAllTopics(self):
'''Get the topic that is parent of all root (ie top-level) topics,
"""Get the topic that is parent of all root (ie top-level) topics,
for default TopicManager instance created when this module is imported.
Some notes:
@@ -94,11 +94,11 @@ class TopicManager:
getParent() is None;
- all root-level topics satisfy isAll()==False, isRoot()==True, and
getParent() is getDefaultTopicTreeRoot();
- all other topics satisfy neither. '''
- all other topics satisfy neither. """
return self.__allTopics
def addDefnProvider(self, providerOrSource, format=None):
'''Register a topic definition provider. After this method is called, whenever a topic must be created,
"""Register a topic definition provider. After this method is called, whenever a topic must be created,
the first definition provider that has a definition
for the required topic is used to instantiate the topic.
@@ -107,7 +107,7 @@ class TopicManager:
instance of TopicDefnProvider(providerOrSource, format). In that case,
if format is not given, it defaults to TOPIC_TREE_FROM_MODULE. Either
way, returns the instance of ITopicDefnProvider registered.
'''
"""
if isinstance(providerOrSource, ITopicDefnProvider):
provider = providerOrSource
else:
@@ -118,17 +118,17 @@ class TopicManager:
return provider
def clearDefnProviders(self):
'''Remove all registered topic definition providers'''
"""Remove all registered topic definition providers"""
self.__defnProvider.clear()
def getNumDefnProviders(self):
'''Get how many topic definitions providers are registered.'''
"""Get how many topic definitions providers are registered."""
return self.__defnProvider.getNumProviders()
def getTopic(self, name, okIfNone=False):
'''Get the Topic instance for the given topic name. By default, raises
"""Get the Topic instance for the given topic name. By default, raises
an TopicNameError exception if a topic with given name doesn't exist. If
okIfNone=True, returns None instead of raising an exception.'''
okIfNone=True, returns None instead of raising an exception."""
topicNameDotted = stringize(name)
#if not name:
# raise TopicNameError(name, 'Empty topic name not allowed')
@@ -152,11 +152,11 @@ class TopicManager:
raise TopicNameError(name, msg)
def newTopic(self, _name, _desc, _required=(), **_argDocs):
'''Deprecated legacy method.
"""Deprecated legacy method.
If topic _name already exists, just returns it and does nothing else.
Otherwise, uses getOrCreateTopic() to create it, then sets its
description (_desc) and its message data specification (_argDocs
and _required). Replaced by getOrCreateTopic().'''
and _required). Replaced by getOrCreateTopic()."""
topic = self.getTopic(_name, True)
if topic is None:
topic = self.getOrCreateTopic(_name)
@@ -165,7 +165,7 @@ class TopicManager:
return topic
def getOrCreateTopic(self, name, protoListener=None):
'''Get the Topic instance for topic of given name, creating it
"""Get the Topic instance for topic of given name, creating it
(and any of its missing parent topics) as necessary. Pubsub
functions such as subscribe() use this to obtain the Topic object
corresponding to a topic name.
@@ -201,7 +201,7 @@ class TopicManager:
The MDS can also be defined via a call to subscribe(listener, topicName),
which indirectly calls getOrCreateTopic(topicName, listener).
'''
"""
obj = self.getTopic(name, okIfNone=True)
if obj:
# if object is not sendable but a proto listener was given,
@@ -229,17 +229,17 @@ class TopicManager:
return self.__createTopic(nameTuple, desc, parent = parentObj, specGiven = specGiven)
def isTopicInUse(self, name):
'''Determine if topic 'name' is in use. True if a Topic object exists
"""Determine if topic 'name' is in use. True if a Topic object exists
for topic name (i.e. message has already been sent for that topic, or a
least one listener subscribed), false otherwise. Note: a topic may be in use
but not have a definition (MDS and docstring); or a topic may have a
definition, but not be in use.'''
definition, but not be in use."""
return self.getTopic(name, okIfNone=True) is not None
def hasTopicDefinition(self, name):
'''Determine if there is a definition avaiable for topic 'name'. Return
"""Determine if there is a definition avaiable for topic 'name'. Return
true if there is, false otherwise. Note: a topic may have a
definition without being in use, and vice versa.'''
definition without being in use, and vice versa."""
# in already existing Topic object:
alreadyCreated = self.getTopic(name, okIfNone=True)
if alreadyCreated is not None and alreadyCreated.hasMDS():
@@ -253,16 +253,16 @@ class TopicManager:
return False
def checkAllTopicsHaveMDS(self):
'''Check that all topics that have been created for their MDS.
Raise a TopicDefnError if one is found that does not have one.'''
"""Check that all topics that have been created for their MDS.
Raise a TopicDefnError if one is found that does not have one."""
for topic in py2and3.itervalues(self._topicsMap):
if not topic.hasMDS():
raise TopicDefnError(topic.getNameTuple())
def delTopic(self, name):
'''Delete the named topic, including all sub-topics. Returns False
"""Delete the named topic, including all sub-topics. Returns False
if topic does not exist; True otherwise. Also unsubscribe any listeners
of topic and all subtopics. '''
of topic and all subtopics. """
# find from which parent the topic object should be removed
dottedName = stringize(name)
try:
@@ -283,9 +283,9 @@ class TopicManager:
return True
def getTopicsSubscribed(self, listener):
'''Get the list of Topic objects that have given listener
"""Get the list of Topic objects that have given listener
subscribed. Note: the listener can also get messages from any
sub-topic of returned list.'''
sub-topic of returned list."""
assocTopics = []
for topicObj in py2and3.itervalues(self._topicsMap):
if topicObj.hasListener(listener):
@@ -293,7 +293,7 @@ class TopicManager:
return assocTopics
def __getClosestParent(self, topicNameDotted):
'''Returns a pair, (closest parent, tuple path from parent). The
"""Returns a pair, (closest parent, tuple path from parent). The
first item is the closest parent Topic that exists.
The second one is the list of topic name elements that have to be
created to create the given topic.
@@ -303,7 +303,7 @@ class TopicManager:
Note that if none of the branch exists (not even A), then return
will be [root topic, ['A',B','C','D']). Note also that if A.B.C
exists, the return will be (A.B.C, ['D']) regardless of whether
A.B.C.D exists. '''
A.B.C.D exists. """
subtopicNames = []
headTail = topicNameDotted.rsplit('.', 1)
while len(headTail) > 1:
@@ -319,9 +319,9 @@ class TopicManager:
return self.__allTopics, subtopicNames
def __createParentTopics(self, topicName):
'''This will find which parents need to be created such that
"""This will find which parents need to be created such that
topicName can be created (but doesn't create given topic),
and creates them. Returns the parent object.'''
and creates them. Returns the parent object."""
assert self.getTopic(topicName, okIfNone=True) is None
parentObj, subtopicNames = self.__getClosestParent(stringize(topicName))
@@ -341,9 +341,9 @@ class TopicManager:
return parentObj
def __createTopic(self, nameTuple, desc, specGiven, parent=None):
'''Actual topic creation step. Adds new Topic instance to topic map,
"""Actual topic creation step. Adds new Topic instance to topic map,
and sends notification message (see ``Publisher.addNotificationMgr()``)
regarding topic creation.'''
regarding topic creation."""
if specGiven is None:
specGiven = ArgSpecGiven()
parentAI = None
@@ -373,9 +373,9 @@ class TopicManager:
def validateNameHierarchy(topicTuple):
'''Check that names in topicTuple are valid: no spaces, not empty.
"""Check that names in topicTuple are valid: no spaces, not empty.
Raise ValueError if fails check. E.g. ('',) and ('a',' ') would
both fail, but ('a','b') would be ok. '''
both fail, but ('a','b') would be ok. """
if not topicTuple:
topicName = stringize(topicTuple)
errMsg = 'empty topic name'
@@ -401,38 +401,38 @@ def validateNameHierarchy(topicTuple):
class _MasterTopicDefnProvider:
'''
"""
Stores a list of topic definition providers. When queried for a topic
definition, queries each provider (registered via addProvider()) and
returns the first complete definition provided, or (None,None).
The providers must follow the ITopicDefnProvider protocol.
'''
"""
def __init__(self, treeConfig):
self.__providers = []
self.__treeConfig = treeConfig
def addProvider(self, provider):
'''Add given provider IF not already added. '''
"""Add given provider IF not already added. """
assert(isinstance(provider, ITopicDefnProvider))
if provider not in self.__providers:
self.__providers.append(provider)
def clear(self):
'''Remove all providers added.'''
"""Remove all providers added."""
self.__providers = []
def getNumProviders(self):
'''Return how many providers added.'''
"""Return how many providers added."""
return len(self.__providers)
def getDefn(self, topicNameTuple):
'''Returns a pair (docstring, MDS) for the topic. The first item is
"""Returns a pair (docstring, MDS) for the topic. The first item is
a string containing the topic's "docstring", i.e. a description string
for the topic, or None if no docstring available for the topic. The
second item is None or an instance of ArgSpecGiven specifying the
required and optional message data for listeners of this topic. '''
required and optional message data for listeners of this topic. """
desc, defn = None, None
for provider in self.__providers:
tmpDesc, tmpDefn = provider.getDefn(topicNameTuple)
@@ -444,8 +444,8 @@ class _MasterTopicDefnProvider:
return desc, defn
def isDefined(self, topicNameTuple):
'''Returns True only if a complete definition exists, ie topic
has a description and a complete message data specification (MDS).'''
"""Returns True only if a complete definition exists, ie topic
has a description and a complete message data specification (MDS)."""
desc, defn = self.getDefn(topicNameTuple)
if desc is None or defn is None:
return False

View File

@@ -1,9 +1,9 @@
'''
"""
Provide the Topic class.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from weakref import ref as weakref
@@ -42,16 +42,16 @@ from .. import py2and3
class Topic(PublisherMixin):
'''
"""
Represent topics in pubsub. Contains information about a topic,
including topic's message data specification (MDS), the list of
subscribed listeners, docstring for the topic. It allows Python-like
access to subtopics (e.g. A.B is subtopic B of topic A).
'''
"""
def __init__(self, treeConfig, nameTuple, description,
msgArgsInfo, parent=None):
'''Create a topic. Should only be called by TopicManager via its
"""Create a topic. Should only be called by TopicManager via its
getOrCreateTopic() method (which gets called in several places
in pubsub, such as sendMessage, subscribe, and newTopic).
@@ -62,7 +62,7 @@ class Topic(PublisherMixin):
:param parent: parent of topic
:raises ValueError: invalid topic name
'''
"""
if parent is None:
if nameTuple != (ALL_TOPICS,):
msg = 'Only one topic, named %s, can be root of topic tree'
@@ -111,24 +111,24 @@ class Topic(PublisherMixin):
parent.__adoptSubtopic( self )
def setDescription(self, desc):
'''Set the 'docstring' of topic'''
"""Set the 'docstring' of topic"""
self.__description = desc
def getDescription(self):
'''Return the 'docstring' of topic'''
"""Return the 'docstring' of topic"""
if self.__description is None:
return None
return smartDedent(self.__description)
def setMsgArgSpec(self, argsDocs, required=()):
'''Specify the message data for topic messages.
"""Specify the message data for topic messages.
:param argsDocs: a dictionary of keyword names (message data name) and data 'docstring'; cannot be None
:param required: a list of those keyword names, appearing in argsDocs,
which are required (all others are assumed optional)
Can only be called if this info has not been already set at construction
or in a previous call.
:raise RuntimeError: if MDS already set at construction or previous call.'''
:raise RuntimeError: if MDS already set at construction or previous call."""
assert self.__parentTopic is not None # for root of tree, this method never called!
if argsDocs is None:
raise ValueError('Cannot set listener spec to None')
@@ -148,10 +148,10 @@ class Topic(PublisherMixin):
raise RuntimeError('Not allowed to call this: msg spec already set!')
def getArgs(self):
'''Returns a pair (reqdArgs, optArgs) where reqdArgs is tuple
"""Returns a pair (reqdArgs, optArgs) where reqdArgs is tuple
of names of required message arguments, optArgs is tuple
of names for optional arguments. If topic args not specified
yet, returns (None, None).'''
yet, returns (None, None)."""
sendable = self.__msgArgs.isComplete()
assert sendable == self.hasMDS()
if sendable:
@@ -160,19 +160,19 @@ class Topic(PublisherMixin):
return None, None
def getArgDescriptions(self):
'''Get a map of keyword names to docstrings: documents each MDS element. '''
"""Get a map of keyword names to docstrings: documents each MDS element. """
return self.__msgArgs.getArgsDocs()
def setArgDescriptions(self, **docs):
'''Set the docstring for each MDS datum.'''
"""Set the docstring for each MDS datum."""
self.__msgArgs.setArgsDocs(docs)
def hasMDS(self):
'''Return true if this topic has a message data specification (MDS).'''
"""Return true if this topic has a message data specification (MDS)."""
return self.__validator is not None
def filterMsgArgs(self, msgKwargs, check=False):
'''Get the MDS docstrings for each of the spedified kwargs.'''
"""Get the MDS docstrings for each of the spedified kwargs."""
filteredArgs = self.__msgArgs.filterArgs(msgKwargs)
# if no check of args yet, do it now:
if check:
@@ -180,14 +180,14 @@ class Topic(PublisherMixin):
return filteredArgs
def isAll(self):
'''Returns true if this topic is the 'all topics' topic. All root
topics behave as though they are child of that topic. '''
"""Returns true if this topic is the 'all topics' topic. All root
topics behave as though they are child of that topic. """
return self.__tupleName == (ALL_TOPICS,)
def isRoot(self):
'''Returns true if this is a "root" topic, false otherwise. A
"""Returns true if this is a "root" topic, false otherwise. A
root topic is a topic whose name contains no dots and which
has pub.ALL_TOPICS as parent.'''
has pub.ALL_TOPICS as parent."""
parent = self.getParent()
if parent:
return parent.isAll()
@@ -195,36 +195,36 @@ class Topic(PublisherMixin):
return False
def getName(self):
'''Return dotted form of full topic name'''
"""Return dotted form of full topic name"""
return stringize(self.__tupleName)
def getNameTuple(self):
'''Return tuple form of full topic name'''
"""Return tuple form of full topic name"""
return self.__tupleName
def getNodeName(self):
'''Return the last part of the topic name (has no dots)'''
"""Return the last part of the topic name (has no dots)"""
name = self.__tupleName[-1]
return name
def getParent(self):
'''Get Topic object that is parent of self (i.e. self is a subtopic
of parent). Return none if self is the "all topics" topic.'''
"""Get Topic object that is parent of self (i.e. self is a subtopic
of parent). Return none if self is the "all topics" topic."""
if self.__parentTopic is None:
return None
return self.__parentTopic()
def hasSubtopic(self, name=None):
'''Return true only if name is a subtopic of self. If name not
specified, return true only if self has at least one subtopic.'''
"""Return true only if name is a subtopic of self. If name not
specified, return true only if self has at least one subtopic."""
if name is None:
return len(self.__subTopics) > 0
return name in self.__subTopics
def getSubtopic(self, relName):
'''Get the specified subtopic object. The relName can be a valid
subtopic name, a dotted-name string, or a tuple. '''
"""Get the specified subtopic object. The relName can be a valid
subtopic name, a dotted-name string, or a tuple. """
if not relName:
raise ValueError("getSubtopic() arg can't be empty")
topicTuple = tupleize(relName)
@@ -241,50 +241,49 @@ class Topic(PublisherMixin):
return topicObj
def getSubtopics(self):
'''Get a list of Topic instances that are subtopics of self.'''
"""Get a list of Topic instances that are subtopics of self."""
return py2and3.values(self.__subTopics)
def getNumListeners(self):
'''Return number of listeners currently subscribed to topic. This is
"""Return number of listeners currently subscribed to topic. This is
different from number of listeners that will get notified since more
general topics up the topic tree may have listeners.'''
general topics up the topic tree may have listeners."""
return len(self.__listeners)
def hasListener(self, listener):
'''Return true if listener is subscribed to this topic.'''
"""Return true if listener is subscribed to this topic."""
return listener in self.__listeners
def hasListeners(self):
'''Return true if there are any listeners subscribed to
this topic, false otherwise.'''
"""Return true if there are any listeners subscribed to
this topic, false otherwise."""
return bool(self.__listeners)
def getListeners(self):
'''Get a list of Listener objects for listeners
subscribed to this topic.'''
return py2and3.keys(self.__listeners)
"""Get an iterator of listeners subscribed to this topic."""
return py2and3.iterkeys(self.__listeners)
def validate(self, listener):
'''Checks whether listener could be subscribed to this topic:
"""Checks whether listener could be subscribed to this topic:
if yes, just returns; if not, raises ListenerMismatchError.
Note that method raises TopicDefnError if self not
hasMDS().'''
hasMDS()."""
if not self.hasMDS():
raise TopicDefnError(self.__tupleName)
return self.__validator.validate(listener)
def isValid(self, listener):
'''Return True only if listener could be subscribed to this topic,
"""Return True only if listener could be subscribed to this topic,
otherwise returns False. Note that method raises TopicDefnError
if self not hasMDS().'''
if self not hasMDS()."""
if not self.hasMDS():
raise TopicDefnError(self.__tupleName)
return self.__validator.isValid(listener)
def subscribe(self, listener):
'''Subscribe listener to this topic. Returns a pair
"""Subscribe listener to this topic. Returns a pair
(pub.Listener, success). The success is true only if listener
was not already subscribed and is now subscribed. '''
was not already subscribed and is now subscribed. """
if listener in self.__listeners:
assert self.hasMDS()
subdLisnr, newSub = self.__listeners[listener], False
@@ -305,12 +304,12 @@ class Topic(PublisherMixin):
return subdLisnr, newSub
def unsubscribe(self, listener):
'''Unsubscribe the specified listener from this topic. Returns
"""Unsubscribe the specified listener from this topic. Returns
the pub.Listener object associated with the listener that was
unsubscribed, or None if the specified listener was not
subscribed to this topic. Note that this method calls
``notifyUnsubscribe(listener, self)`` on all registered notification
handlers (see pub.addNotificationHandler).'''
handlers (see pub.addNotificationHandler)."""
unsubdLisnr = self.__listeners.pop(listener, None)
if unsubdLisnr is None:
return None
@@ -324,10 +323,10 @@ class Topic(PublisherMixin):
return unsubdLisnr
def unsubscribeAllListeners(self, filter=None):
'''Clears list of subscribed listeners. If filter is given, it must
"""Clears list of subscribed listeners. If filter is given, it must
be a function that takes a listener and returns true if the listener
should be unsubscribed. Returns the list of Listener for listeners
that were unsubscribed.'''
that were unsubscribed."""
unsubd = []
if filter is None:
for listener in self.__listeners:
@@ -335,10 +334,12 @@ class Topic(PublisherMixin):
unsubd = py2and3.keys(self.__listeners)
self.__listeners = {}
else:
unsubd = [listener for listener in self.__listeners if filter(listener)]
for listener in unsubd:
listener._unlinkFromTopic_()
del self.__listeners[listener]
unsubd = []
for listener in py2and3.keys(self.__listeners):
if filter(listener):
unsubd.append(listener)
listener._unlinkFromTopic_()
del self.__listeners[listener]
# send notification regarding all listeners actually unsubscribed
notificationMgr = self._treeConfig.notificationMgr
@@ -354,14 +355,14 @@ class Topic(PublisherMixin):
#############################################################
def _getListenerSpec(self):
'''Only to be called by pubsub package'''
"""Only to be called by pubsub package"""
return self.__msgArgs
def _publish(self, data):
'''This sends message to listeners of parent topics as well.
"""This sends message to listeners of parent topics as well.
If an exception is raised in a listener, the publish is
aborted, except if there is a handler (see
pub.setListenerExcHandler).'''
pub.setListenerExcHandler)."""
self._treeConfig.notificationMgr.notifySend('pre', self)
# send to ourself
@@ -405,10 +406,10 @@ class Topic(PublisherMixin):
raise ExcHandlerError(listener.name(), topicObj, exc)
def __finalize(self):
'''Finalize the topic specification, which currently means
"""Finalize the topic specification, which currently means
creating the listener validator for this topic. This allows
calls to subscribe() to validate that listener adheres to
topic's message data specification (MDS).'''
topic's message data specification (MDS)."""
assert self.__msgArgs.isComplete()
assert not self.hasMDS()
@@ -419,15 +420,15 @@ class Topic(PublisherMixin):
assert not self.__listeners
def _undefineSelf_(self, topicsMap):
'''Called by topic manager when deleting a topic.'''
"""Called by topic manager when deleting a topic."""
if self.__parentTopic is not None:
self.__parentTopic().__abandonSubtopic(self.__tupleName[-1])
self.__undefineBranch(topicsMap)
def __undefineBranch(self, topicsMap):
'''Unsubscribe all our listeners, remove all subtopics from self,
"""Unsubscribe all our listeners, remove all subtopics from self,
then detach from parent. Parent is not notified, because method
assumes it has been called by parent'''
assumes it has been called by parent"""
#print 'Remove %s listeners (%s)' % (self.getName(), self.getNumListeners())
self.unsubscribeAllListeners()
self.__parentTopic = None
@@ -441,18 +442,18 @@ class Topic(PublisherMixin):
del topicsMap[self.getName()]
def __adoptSubtopic(self, topicObj):
'''Add topicObj as child topic.'''
"""Add topicObj as child topic."""
assert topicObj.__parentTopic() is self
attrName = topicObj.getNodeName()
self.__subTopics[attrName] = topicObj
def __abandonSubtopic(self, name):
'''The given subtopic becomes orphan (no parent).'''
"""The given subtopic becomes orphan (no parent)."""
topicObj = self.__subTopics.pop(name)
assert topicObj.__parentTopic() is self
def __onDeadListener(self, weakListener):
'''One of our subscribed listeners has died, so remove it and notify'''
"""One of our subscribed listeners has died, so remove it and notify"""
pubListener = self.__listeners.pop(weakListener)
# notify:
self._treeConfig.notificationMgr.notifyDeadListener(pubListener, self)

View File

@@ -1,12 +1,12 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
class TopicTreeTraverser:
'''
"""
Supports taking action on every topic in the topic tree. The traverse() method
traverses a topic tree and calls visitor._onTopic() for each topic in the tree
that satisfies visitor._accept(). Additionally it calls visitor._startChildren()
@@ -14,29 +14,29 @@ class TopicTreeTraverser:
visitor._endChildren() when it is done with the subtopics. Finally, it calls
visitor._doneTraversal() when traversal has been completed. The visitor must
therefore adhere to the ITopicTreeVisitor interface.
'''
"""
DEPTH = 'Depth first through topic tree'
BREADTH = 'Breadth first through topic tree'
MAP = 'Sequential through topic manager\'s topics map'
def __init__(self, visitor = None):
'''The visitor, if given, must adhere to API of
"""The visitor, if given, must adhere to API of
ITopicTreeVisitor. The visitor can be changed or
set via setVisitor(visitor) before calling traverse().'''
set via setVisitor(visitor) before calling traverse()."""
self.__handler = visitor
def setVisitor(self, visitor):
'''The visitor must adhere to API of ITopicTreeVisitor. '''
"""The visitor must adhere to API of ITopicTreeVisitor. """
self.__handler = visitor
def traverse(self, topicObj, how=DEPTH, onlyFiltered=True):
'''Start traversing tree at topicObj. Note that topicObj is a
"""Start traversing tree at topicObj. Note that topicObj is a
Topic object, not a topic name. The how defines if tree should
be traversed breadth or depth first. If onlyFiltered is
False, then all nodes are accepted (_accept(node) not called).
This method can be called multiple times.
'''
"""
if how == self.MAP:
raise NotImplementedError('not yet available')
@@ -104,40 +104,40 @@ class TopicTreeTraverser:
class ITopicTreeVisitor:
'''
"""
Derive from ITopicTreeVisitor and override one or more of the
self._*() methods. Give an instance to an instance of
TopicTreeTraverser.
'''
"""
def _accept(self, topicObj):
'''Override this to filter nodes of topic tree. Must return
"""Override this to filter nodes of topic tree. Must return
True (accept node) of False (reject node). Note that rejected
nodes cause traversal to move to next branch (no children
traversed).'''
traversed)."""
return True
def _startTraversal(self):
'''Override this to define what to do when traversal() starts.'''
"""Override this to define what to do when traversal() starts."""
pass
def _onTopic(self, topicObj):
'''Override this to define what to do for each node.'''
"""Override this to define what to do for each node."""
pass
def _startChildren(self):
'''Override this to take special action whenever a
"""Override this to take special action whenever a
new level of the topic hierarchy is started (e.g., indent
some output). '''
some output). """
pass
def _endChildren(self):
'''Override this to take special action whenever a
"""Override this to take special action whenever a
level of the topic hierarchy is completed (e.g., dedent
some output). '''
some output). """
pass
def _doneTraversal(self):
'''Override this to take special action when traversal done.'''
"""Override this to take special action when traversal done."""
pass

View File

@@ -1,10 +1,10 @@
'''
"""
Various utilities used by topic-related modules.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from textwrap import TextWrapper, dedent
@@ -21,19 +21,19 @@ ALL_TOPICS = 'ALL_TOPICS'
class WeakNone:
'''Pretend to be a weak reference to nothing. Used by ArgsInfos to
refer to parent when None so no if-else blocks needed. '''
"""Pretend to be a weak reference to nothing. Used by ArgsInfos to
refer to parent when None so no if-else blocks needed. """
def __call__(self):
return None
def smartDedent(paragraph):
'''Dedent paragraph using textwrap.dedent(), but properly dedents
"""Dedent paragraph using textwrap.dedent(), but properly dedents
even if the first line of paragraph does not contain blanks.
This handles the case where a user types a documentation string as
"""A long string spanning
several lines."""
'''
'''A long string spanning
several lines.'''
"""
if paragraph.startswith(' '):
para = dedent(paragraph)
else:
@@ -48,7 +48,7 @@ _validNameRE = re.compile(r'[-0-9a-zA-Z]\w*')
def validateName(topicName):
'''Raise TopicNameError if nameTuple not valid as topic name.'''
"""Raise TopicNameError if nameTuple not valid as topic name."""
topicNameTuple = tupleize(topicName)
if not topicNameTuple:
reason = 'name tuple must have at least one item!'
@@ -75,12 +75,12 @@ def validateName(topicName):
def stringize(topicName):
'''If topicName is a string, just return it
"""If topicName is a string, just return it
as is. If it is a topic definition object (ie an object that has
'msgDataSpec' as data member), return the dotted name of corresponding
topic. Otherwise, assume topicName is a tuple and convert it to to a
dotted name i.e. ('a','b','c') => 'a.b.c'. Empty name is not allowed
(ValueError). The reverse operation is tupleize(topicName).'''
(ValueError). The reverse operation is tupleize(topicName)."""
if py2and3.isstring(topicName):
return topicName
@@ -97,10 +97,10 @@ def stringize(topicName):
def tupleize(topicName):
'''If topicName is a tuple of strings, just return it as is. Otherwise,
"""If topicName is a tuple of strings, just return it as is. Otherwise,
convert it to tuple, assuming dotted notation used for topicName. I.e.
'a.b.c' => ('a','b','c'). Empty topicName is not allowed (ValueError).
The reverse operation is stringize(topicNameTuple).'''
The reverse operation is stringize(topicNameTuple)."""
# assume name is most often str; if more often tuple,
# then better use isinstance(name, tuple)
if hasattr(topicName, "msgDataSpec"):

View File

@@ -1,17 +1,17 @@
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .notificationmgr import NotificationMgr
class TreeConfig:
'''
"""
Each topic tree has its own topic manager and configuration,
such as notification and exception handling.
'''
"""
def __init__(self, notificationHandler=None, listenerExcHandler=None):
self.notificationMgr = NotificationMgr(notificationHandler)

View File

@@ -1,16 +1,16 @@
'''
"""
Some topic definition validation functions.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from .topicexc import MessageDataSpecError
def verifyArgsDifferent(allArgs, allParentArgs, topicName):
'''Verify that allArgs does not contain any of allParentArgs. Raise
MessageDataSpecError if fail. '''
"""Verify that allArgs does not contain any of allParentArgs. Raise
MessageDataSpecError if fail. """
extra = set(allArgs).intersection(allParentArgs)
if extra:
msg = 'Args %%s already used in parent of "%s"' % topicName
@@ -18,8 +18,8 @@ def verifyArgsDifferent(allArgs, allParentArgs, topicName):
def verifySubset(all, sub, topicName, extraMsg=''):
'''Verify that sub is a subset of all for topicName. Raise
MessageDataSpecError if fail. '''
"""Verify that sub is a subset of all for topicName. Raise
MessageDataSpecError if fail. """
notInAll = set(sub).difference(all)
if notInAll:
args = ','.join(all)

View File

@@ -1,4 +1,4 @@
'''
"""
This module provides a basic "weak method" implementation, WeakMethod. It uses
weakref.WeakRef which, used on its own, produces weak methods that are dead on
creation, not very useful. Use the getWeakRef(object) module function to create the
@@ -7,7 +7,7 @@ proper type of weak reference (weakref.WeakRef or WeakMethod) for given object.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
# for function and method parameter counting:
from inspect import ismethod

View File

@@ -1,9 +1,9 @@
'''
"""
Aggregates policies for pubsub. Mainly, related to messaging protocol.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
msgProtocolTransStage = None

View File

@@ -1,4 +1,4 @@
'''
"""
This is the main entry-point to pubsub's core functionality. The :mod:`~pubsub.pub`
module supports:
@@ -21,14 +21,14 @@ and those of the pubsub.core.TopicManager instance that it contains. However, an
application may create as many independent instances of Publisher as
required (for instance, one in each thread; with a custom queue to mediate
message transfer between threads).
'''
"""
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
VERSION_API = 3 #: major API version
VERSION_API = 3 #: major API version
VERSION_SVN = "$Rev: 243 $".split()[1] # DO NOT CHANGE: automatically updated by VCS
@@ -119,7 +119,7 @@ __all__ = [
'TopicTreeTraverser',
]
]
# --------- Publisher singleton and bound methods ------------------------------------
@@ -144,8 +144,8 @@ setTopicUnspecifiedFatal = _publisher.setTopicUnspecifiedFatal
getMsgProtocol = _publisher.getMsgProtocol
def getDefaultPublisher():
'''Get the Publisher instance created by default when this module
is imported. See the module doc for details about this instance.'''
"""Get the Publisher instance created by default when this module
is imported. See the module doc for details about this instance."""
return _publisher
@@ -158,28 +158,28 @@ topicsMap = _topicMgr._topicsMap
def isValid(listener, topicName):
'''Return true only if listener can subscribe to messages of given topic.'''
"""Return true only if listener can subscribe to messages of given topic."""
return _topicMgr.getTopic(topicName).isValid(listener)
def validate(listener, topicName):
'''Checks if listener can subscribe to topicName. If not, raises
ListenerMismatchError, otherwise just returns.'''
"""Checks if listener can subscribe to topicName. If not, raises
ListenerMismatchError, otherwise just returns."""
_topicMgr.getTopic(topicName).validate(listener)
def isSubscribed(listener, topicName):
'''Returns true if listener has subscribed to topicName, false otherwise.
"""Returns true if listener has subscribed to topicName, false otherwise.
WARNING: a false return is not a guarantee that listener won't get
messages of topicName: it could receive messages of a subtopic of
topicName. '''
topicName. """
return _topicMgr.getTopic(topicName).hasListener(listener)
def getDefaultTopicMgr():
'''Get the TopicManager instance created by default when this
"""Get the TopicManager instance created by default when this
module is imported. This function is a shortcut for
``pub.getDefaultPublisher().getTopicMgr()``.'''
``pub.getDefaultPublisher().getTopicMgr()``."""
return _topicMgr
@@ -188,11 +188,11 @@ clearTopicDefnProviders = _topicMgr.clearDefnProviders
getNumTopicDefnProviders = _topicMgr.getNumDefnProviders
def instantiateAllDefinedTopics(provider):
'''Loop over all topics of given provider and "instantiate" each topic, thus
"""Loop over all topics of given provider and "instantiate" each topic, thus
forcing a parse of the topics documentation, message data specification (MDS),
comparison with parent MDS, and MDS documentation. Without this function call,
an error among any of those characteristics will manifest only if the a
listener is registered on it. '''
listener is registered on it. """
for topicName in provider:
_topicMgr.getOrCreateTopic(topicName)

View File

@@ -1,4 +1,4 @@
'''
"""
Setup pubsub for the *arg1* message protocol. In a default pubsub installation
the default protocol is *kargs*.
@@ -14,20 +14,20 @@ protocol cannot be changed (i.e., importing it after the first
The *arg1* protocol is identical to the legacy messaging protocol from
first version of pubsub (when it was still part of wxPython) and
is *deprecated*. This module is therefore *deprecated*.
'''
"""
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from . import policies
policies.msgDataProtocol = 'arg1'
def enforceArgName(commonName):
'''This will configure pubsub to require that all listeners use
"""This will configure pubsub to require that all listeners use
the same argument name (*commonName*) as first parameter. This
is a ueful first step in migrating an application that has been
using *arg1* protocol to the more powerful *kwargs* protocol. '''
using *arg1* protocol to the more powerful *kwargs* protocol. """
policies.setMsgDataArgName(1, commonName)

View File

@@ -1,4 +1,4 @@
'''
"""
Setup pubsub for the kwargs message protocol. In a default installation
this is the default protocol so this module is only needed if setupkargs
utility functions are used, or in a custom installation where kwargs
@@ -9,21 +9,21 @@ This module must be imported before the first ``from pubsub import pub``
statement in the application. Once :mod:pub has been imported, the messaging
protocol cannot be changed (i.e., importing it after the first
``from pubsub import pub`` statement has undefined behavior).
'''
"""
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from . import policies
policies.msgDataProtocol = 'kwargs'
def transitionFromArg1(commonName):
'''Utility function to assist migrating an application from using
"""Utility function to assist migrating an application from using
the arg1 messaging protocol to using the kwargs protocol. Call this
after having run and debugged your application with ``setuparg1.enforceArgName(commonName)``. See the migration docs
for more detais.
'''
"""
policies.setMsgDataArgName(2, commonName)

View File

@@ -1,16 +1,12 @@
'''
"""
Provides utility functions and classes that are not required for using
pubsub but are likely to be very useful.
'''
"""
'''
"""
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
from .intraimport import intraImport
intraImport(__path__)
"""
from .topictreeprinter import printTreeDocs

View File

@@ -1,4 +1,4 @@
'''
"""
Some utility classes for exception handling of exceptions raised
within listeners:
@@ -11,7 +11,7 @@ within listeners:
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
import sys, traceback
@@ -20,7 +20,7 @@ from ..core.listener import IListenerExcHandler
class TracebackInfo:
'''
"""
Represent the traceback information for when an exception is
raised -- but not caught -- in a listener. The complete
traceback cannot be stored since this leads to circular
@@ -38,7 +38,7 @@ class TracebackInfo:
* self.traceback: list of quadruples as returned by traceback.extract_tb()
Normally you just need to call one of the two getFormatted() methods.
'''
"""
def __init__(self):
tmpInfo = sys.exc_info()
self.ExcClass = tmpInfo[0]
@@ -50,15 +50,15 @@ class TracebackInfo:
del tmpInfo
def getFormattedList(self):
'''Get a list of strings as returned by the traceback module's
format_list() and format_exception_only() functions.'''
"""Get a list of strings as returned by the traceback module's
format_list() and format_exception_only() functions."""
tmp = traceback.format_list(self.traceback)
tmp.extend( traceback.format_exception_only(self.ExcClass, self.excArg) )
return tmp
def getFormattedString(self):
'''Get a string similar to the stack trace that gets printed
to stdout by Python interpreter when an exception is not caught.'''
"""Get a string similar to the stack trace that gets printed
to stdout by Python interpreter when an exception is not caught."""
return ''.join(self.getFormattedList())
def __str__(self):
@@ -66,24 +66,24 @@ class TracebackInfo:
class ExcPublisher(IListenerExcHandler):
'''
"""
Example exception handler that simply publishes the exception traceback
as a message of topic name given by topicUncaughtExc.
'''
"""
# name of the topic
topicUncaughtExc = 'uncaughtExcInListener'
def __init__(self, topicMgr=None):
'''If topic manager is specified, will automatically call init().
"""If topic manager is specified, will automatically call init().
Otherwise, caller must call init() after pubsub imported. See
pub.setListenerExcHandler().'''
pub.setListenerExcHandler()."""
if topicMgr is not None:
self.init(topicMgr)
def init(self, topicMgr):
'''Must be called only after pubsub has been imported since this
handler creates a pubsub topic.'''
"""Must be called only after pubsub has been imported since this
handler creates a pubsub topic."""
obj = topicMgr.getOrCreateTopic(self.topicUncaughtExc)
obj.setDescription('generated when a listener raises an exception')
obj.setMsgArgSpec( dict(
@@ -92,8 +92,8 @@ class ExcPublisher(IListenerExcHandler):
self.__topicObj = obj
def __call__(self, listenerID, topicObj):
'''Handle the exception raised by given listener. Send the
Traceback to all subscribers of topic self.topicUncaughtExc. '''
"""Handle the exception raised by given listener. Send the
Traceback to all subscribers of topic self.topicUncaughtExc. """
tbInfo = TracebackInfo()
self.__topicObj.publish(listenerStr=listenerID, excTraceback=tbInfo)

View File

@@ -13,21 +13,21 @@ __all__ = ('printImported', 'StructMsg', 'Callback', 'Enum' )
def printImported():
'''Output a list of pubsub modules imported so far'''
"""Output a list of pubsub modules imported so far"""
ll = [mod for mod in sys.modules.keys() if mod.find('pubsub') >= 0] # iter keys ok
ll.sort()
py2and3.print_('\n'.join(ll))
class StructMsg:
'''
"""
This *can* be used to package message data. Each of the keyword
args given at construction will be stored as a member of the 'data'
member of instance. E.g. "m=Message2(a=1, b='b')" would succeed
"assert m.data.a==1" and "assert m.data.b=='b'". However, use of
Message2 makes your messaging code less documented and harder to
debug.
'''
"""
def __init__(self, **kwargs):
class Data: pass
@@ -36,7 +36,7 @@ class StructMsg:
class Callback:
'''This can be used to wrap functions that are referenced by class
"""This can be used to wrap functions that are referenced by class
data if the data should be called as a function. E.g. given
>>> def func(): pass
>>> class A:
@@ -46,7 +46,7 @@ class Callback:
will fail since Python will try to call a() as a method of boo,
whereas a() is a free function. But if you have instead
"self.a = Callback(func)", then "boo.a()" works as expected.
'''
"""
def __init__(self, callable_):
self.__callable = callable_
def __call__(self, *args, **kwargs):
@@ -54,7 +54,7 @@ class Callback:
class Enum:
'''Used only internally. Represent one value out of an enumeration
"""Used only internally. Represent one value out of an enumeration
set. It is meant to be used as::
class YourAllowedValues:
@@ -70,12 +70,12 @@ class Enum:
...
if val is YourAllowedValues.enum1:
...
'''
"""
nextValue = 0
values = set()
def __init__(self, value=None, *desc):
'''Use value if given, otherwise use next integer.'''
"""Use value if given, otherwise use next integer."""
self.desc = '\n'.join(desc)
if value is None:
assert Enum.nextValue not in Enum.values

View File

@@ -1,4 +1,4 @@
'''
"""
Provide an interface class for handling pubsub notification messages,
and an example class (though very useful in practice) showing how to
use it.
@@ -17,19 +17,19 @@ relevant information.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from ..core import callables
from ..core.notificationmgr import INotificationHandler
class IgnoreNotificationsMixin(INotificationHandler):
'''
"""
Derive your Notifications handler from this class if your handler
just wants to be notified of one or two types of pubsub events.
Then just override the desired methods. The rest of the notifications
will automatically be ignored.
'''
"""
def notifySubscribe(self, pubListener, topicObj, newSub):
pass
@@ -47,16 +47,16 @@ class IgnoreNotificationsMixin(INotificationHandler):
class NotifyByWriteFile(INotificationHandler):
'''
"""
Print a message to stdout when a notification is received.
'''
"""
defaultPrefix = 'PUBSUB:'
def __init__(self, fileObj = None, prefix = None):
'''Will write to stdout unless fileObj given. Will use
"""Will write to stdout unless fileObj given. Will use
defaultPrefix as prefix for each line output, unless prefix
specified. '''
specified. """
self.__pre = prefix or self.defaultPrefix
if fileObj is None:
@@ -107,7 +107,7 @@ class NotifyByWriteFile(INotificationHandler):
class NotifyByPubsubMessage(INotificationHandler):
'''
"""
Handle pubsub notification messages by generating
messages of a 'pubsub.' subtopic. Also provides
an example of how to create a notification handler.
@@ -123,7 +123,7 @@ class NotifyByPubsubMessage(INotificationHandler):
message is generated. If you have subscribed a listener of
this topic, your listener will be notified of what listener
unsubscribed from what topic.
'''
"""
topicRoot = 'pubsub'
@@ -142,8 +142,8 @@ class NotifyByPubsubMessage(INotificationHandler):
self.createNotificationTopics(topicMgr)
def createNotificationTopics(self, topicMgr):
'''Create the notification topics. The root of the topics created
is self.topicRoot. The topicMgr is (usually) pub.topicMgr.'''
"""Create the notification topics. The root of the topics created
is self.topicRoot. The topicMgr is (usually) pub.topicMgr."""
# see if the special topics have already been defined
try:
topicMgr.getTopic(self.topicRoot)
@@ -185,10 +185,10 @@ class NotifyByPubsubMessage(INotificationHandler):
self.__doNotification(pubTopic, kwargs)
def notifySend(self, stage, topicObj, pubListener=None):
'''Stage must be 'pre' or 'post'. Note that any pubsub sendMessage
"""Stage must be 'pre' or 'post'. Note that any pubsub sendMessage
operation resulting from this notification (which sends a message;
listener could handle by sending another message!) will NOT themselves
lead to a send notification. '''
lead to a send notification. """
if (self._pubTopic is None) or self.__sending:
return
@@ -223,14 +223,14 @@ class NotifyByPubsubMessage(INotificationHandler):
def _createTopics(topicMap, topicMgr):
'''
"""
Create notification topics. These are used when
some of the notification flags have been set to True (see
pub.setNotificationFlags(). The topicMap is a dict where key is
the notification type, and value is the topic name to create.
Notification type is a string in ('send', 'subscribe',
'unsubscribe', 'newTopic', 'delTopic', 'deadListener'.
'''
"""
def newTopic(_name, _desc, _required=None, **argsDocs):
topic = topicMgr.getOrCreateTopic(_name)
topic.setDescription(_desc)
@@ -278,7 +278,7 @@ def _createTopics(topicMap, topicMgr):
def useNotifyByPubsubMessage(publisher=None, all=True, **kwargs):
'''Will cause all of pubsub's notifications of pubsub "actions" (such as
"""Will cause all of pubsub's notifications of pubsub "actions" (such as
new topic created, message sent, listener subscribed, etc) to be sent
out as messages. Topic will be 'pubsub' subtopics, such as
'pubsub.newTopic', 'pubsub.delTopic', 'pubsub.sendMessage', etc.
@@ -297,7 +297,7 @@ def useNotifyByPubsubMessage(publisher=None, all=True, **kwargs):
from wx.lib.pubsub.utils import notification
notification.useNotifyByPubsubMessage()
'''
"""
if publisher is None:
from .. import pub
publisher = pub.getDefaultPublisher()
@@ -310,7 +310,7 @@ def useNotifyByPubsubMessage(publisher=None, all=True, **kwargs):
def useNotifyByWriteFile(fileObj=None, prefix=None,
publisher=None, all=True, **kwargs):
'''Will cause all pubsub notifications of pubsub "actions" (such as
"""Will cause all pubsub notifications of pubsub "actions" (such as
new topic created, message sent, listener died etc) to be written to
specified file (or stdout if none given). The fileObj need only
provide a 'write(string)' method.
@@ -319,7 +319,7 @@ def useNotifyByWriteFile(fileObj=None, prefix=None,
constructor. The 'all' and kwargs arguments are those of pubsub's
setNotificationFlags(), except that 'all' defaults to True. See
useNotifyByPubsubMessage() for an explanation of pubModule (typically
only if pubsub inside wxPython's wx.lib)'''
only if pubsub inside wxPython's wx.lib)"""
notifHandler = NotifyByWriteFile(fileObj, prefix)
if publisher is None:

View File

@@ -1,9 +1,9 @@
'''
"""
Output various aspects of topic tree to string or file.
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
from textwrap import TextWrapper
@@ -11,7 +11,7 @@ from ..core.topictreetraverser import (ITopicTreeVisitor, TopicTreeTraverser)
class TopicTreePrinter(ITopicTreeVisitor):
'''
"""
Example topic tree visitor that prints a prettified representation
of topic tree by doing a depth-first traversal of topic tree and
print information at each (topic) node of tree. Extra info to be
@@ -38,19 +38,19 @@ class TopicTreePrinter(ITopicTreeVisitor):
> arg1: (required) its description
> arg2: some other description
'''
"""
allowedExtras = frozenset('DAaL') # must NOT change
ALL_TOPICS_NAME = 'ALL_TOPICS' # output for name of 'all topics' topic
def __init__(self, extra=None, width=70, indentStep=4,
bulletTopic='\\--', bulletTopicItem='|==', bulletTopicArg='-', fileObj=None):
'''Topic tree printer will print listeners for each topic only
"""Topic tree printer will print listeners for each topic only
if printListeners is True. The width will be used to limit
the width of text output, while indentStep is the number of
spaces added each time the text is indented further. The
three bullet parameters define the strings used for each
item (topic, topic items, and kwargs). '''
item (topic, topic items, and kwargs). """
self.__contentMeth = dict(
D = self.__printTopicDescription,
A = self.__printTopicArgsAll,
@@ -83,7 +83,7 @@ class TopicTreePrinter(ITopicTreeVisitor):
self.__destination.write(self.getOutput())
def _onTopic(self, topicObj):
'''This gets called for each topic. Print as per specified content.'''
"""This gets called for each topic. Print as per specified content."""
# topic name
self.__wrapper.width = self.__width
@@ -102,17 +102,17 @@ class TopicTreePrinter(ITopicTreeVisitor):
function(indent, topicObj)
def _startChildren(self):
'''Increase the indent'''
"""Increase the indent"""
self.__indent += self.__indentStep
def _endChildren(self):
'''Decrease the indent'''
"""Decrease the indent"""
self.__indent -= self.__indentStep
def __formatDefn(self, indent, item, defn='', sep=': '):
'''Print a definition: a block of text at a certain indent,
"""Print a definition: a block of text at a certain indent,
has item name, and an optional definition separated from
item by sep. '''
item by sep. """
if defn:
prefix = '%s%s%s' % (' '*indent, item, sep)
self.__wrapper.initial_indent = prefix
@@ -162,7 +162,7 @@ class TopicTreePrinter(ITopicTreeVisitor):
def printTreeDocs(rootTopic=None, topicMgr=None, **kwargs):
'''Print out the topic tree to a file (or file-like object like a
"""Print out the topic tree to a file (or file-like object like a
StringIO), starting at rootTopic. If root topic should be root of
whole tree, get it from pub.getDefaultTopicTreeRoot().
The treeVisitor is an instance of pub.TopicTreeTraverser.
@@ -182,7 +182,7 @@ def printTreeDocs(rootTopic=None, topicMgr=None, **kwargs):
The kwargs are the same as for TopicTreePrinter constructor:
extra(None), width(70), indentStep(4), bulletTopic, bulletTopicItem,
bulletTopicArg, fileObj(stdout). If fileObj not given, stdout is used.'''
bulletTopicArg, fileObj(stdout). If fileObj not given, stdout is used."""
if rootTopic is None:
if topicMgr is None:
from .. import pub

View File

@@ -1,4 +1,4 @@
'''
"""
Contributed by Joshua R English, adapted by Oliver Schoenborn to be
consistent with pubsub API.
@@ -8,7 +8,7 @@ Python nested class format.
To use:
xml = """
xml = '''
<topicdefntree>
<description>Test showing topic hierarchy and inheritance</description>
<topic id="parent">
@@ -26,7 +26,7 @@ To use:
</topic>
</topic>
</topicdefntree>
"""
'''
These topic definitions are loaded through an XmlTopicDefnProvider:
@@ -43,7 +43,7 @@ of the XML tree.
:copyright: Copyright since 2013 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
'''
"""
__author__ = 'Joshua R English'
__revision__ = 6
@@ -249,9 +249,9 @@ def indent(elem, level=0):
def exportTopicTreeSpecXml(moduleName=None, rootTopic=None, bak='bak', moduleDoc=None):
'''
"""
If rootTopic is None, then pub.getDefaultTopicTreeRoot() is assumed.
'''
"""
if rootTopic is None:
from .. import pub