Commit baab0d4f authored by eric pellegrini's avatar eric pellegrini
Browse files

Merge tag '1.0.3.a' into develop

Hotfix 1.0.3.a
parents 01bf9448 361639ef
Pipeline #1135 passed with stages
in 16 minutes and 41 seconds
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.
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
\ No newline at end of file
''' """
Publish-subscribe package. Pubsub package initialization.
This package provides the following modules: :copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
"""
- pub: "entry point" module for core pubsub functionality. It provides _PREVIOUS_RELEASE_DATE = "2013-09-15"
functions for sending messages and subscribing listeners and various _PREVIOUS_RELEASE_VER = "3.2.1b"
others.
- utils: subpackage of utility functions and classes for debugging
messages, handling exceptions raised in listeners, and more.
- setupv1: (deprecated) module to force pubsub to use the old,
"version 1" (aka v1) API (should only be useful to wxPython users
for legacy code).
- setuparg1: module to setup pubsub to use "arg1" messaging protocol
- setupkwargs: module to setup pubsub to use "kwargs" messaging protocol
For instance::
from pubsub import pub
pub.sendMessage('topic', data1=123)
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
Last known commit:
- $Date: 2010-02-13 08:57:21 -0500 (Sat, 13 Feb 2010) $
- $Revision: 249 $
'''
__version__ = "3.3.0"
__all__ = [ __all__ = [
'pub', 'utils', 'pub',
'printImported', 'setupkwargs', 'setuparg1', 'setupv1', 'utils',
'setupkwargs',
'setuparg1',
'__version__'
] ]
# set our module search path in globalsettings so setup*.py modules
# can find and modify
from MDANSE.Externals.pubsub import pubsubconf
pubsubconf.setPubsubInfo(__path__, globals())
#del pubsubconf # prevent circular ref
def printImported():
'''Output a list of pubsub modules imported so far'''
import sys
ll = [mod for mod in sys.modules.keys() if mod.find('pubsub') >= 0]
ll.sort()
print '\n'.join(ll)
def _tryAutoSetupV1():
'''This function is called automatically when the pubsub module is
imported. It determines if the legacy "version 1" API of pubsub should
be used automatically, by looking for a module called 'autosetuppubsubv1'
on the module's search path. If this module is found, setupv1 is imported,
so your application will get v1 API just by doing "from pubsub import ...".
If that module is not found then nothing happens and the function
returns; your application will get the "default" API unless you
explicitly choose a different one. Note that autosetuppubsubv1 is never
actually imported, just searched. '''
try:
# if autosetupv1 is importable, then it means we should
# automatically setup for version 1 API
import imp
imp.find_module('autosetuppubsubv1', __path__)
except ImportError:
pass
else:
from MDANSE.Externals.pubsub import setupv1
assert pub is not None
assert Publisher is pub.Publisher
_tryAutoSetupV1()
''' """
Core package of pubsub, holding the publisher, listener, and topic Core package of pubsub, holding the publisher, listener, and topic
object modules. Functions defined here are used internally by object modules. Functions defined here are used internally by
pubsub so that the right modules can be found later, based on the pubsub so that the right modules can be found later, based on the
selected messaging protocol. selected messaging protocol.
Indeed some of the API depends on the messaging Indeed some of the API depends on the messaging
protocol used. For instance sendMessage(), defined in publisher.py, protocol used. For instance sendMessage(), defined in publisher.py,
has a different signature (and hence implementation) for the kwargs has a different signature (and hence implementation) for the kwargs
protocol than for the arg1 protocol. protocol than for the arg1 protocol.
The most convenient way to The most convenient way to
support this is to put the parts of the package that differ based support this is to put the parts of the package that differ based
on protocol in separate folders, and add one of those folders to on protocol in separate folder, and add one of those folders to
the package's __path__ variable (defined automatically by the Python the package's __path__ variable (defined automatically by the Python
interpreter when __init__.py is executed). For instance, code interpreter when __init__.py is executed). For instance, code
specific to the kwargs protocol goes in the kwargs folder, and code specific to the kwargs protocol goes in the kwargs folder, and code
specific to the arg1 protocol in the arg1 folder. Then when doing specific to the arg1 protocol in the arg1 folder. Then when doing
"from pubsub.core import listener", the correct listener.py will be "from pubsub.core import listener", the correct listener.py will be
found for the specified protocol. The default protocol is kwargs. found for the specified protocol. The default protocol is kwargs.
Only one protocol can be used in an application. The default protocol, Only one protocol can be used in an application. The default protocol,
if none is chosen by user, is kwargs, as selected by the call to if none is chosen by user, is kwargs, as selected by the call to
_prependModulePath() at end of this file. _prependModulePath() at end of this file.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved. :copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details. :license: BSD, see LICENSE_BSD_Simple.txt for details.
''' """
from MDANSE.Externals.pubsub.core import policies
def _prependModulePath(extra):
def setMsgProtocol(protocol): """Insert extra at beginning of package's path list. Should only be
called once, at package load time, to set the folder used for
policies.msgDataProtocol = protocol implementation specific to the default message protocol."""
corepath = __path__
# add appropriate subdir for protocol-specific implementation initpyLoc = corepath[-1]
if protocol == 'kwargs': import os
_replaceModulePath0('kwargs') corepath.insert(0, os.path.join(initpyLoc, extra))
else:
_replaceModulePath0('arg1') # add appropriate subdir for protocol-specific implementation
from .. import policies
_prependModulePath(policies.msgDataProtocol)
def setMsgDataArgName(stage, listenerArgName, senderArgNameAny=False):
from .publisher import Publisher
policies.senderKwargNameAny = senderArgNameAny
policies.msgDataArgName = listenerArgName from .callables import (
policies.msgProtocolTransStage = stage AUTO_TOPIC,
#print `policies.msgProtocolTransStage`, `policies.msgDataProtocol`, \ )
# `policies.senderKwargNameAny`, `policies.msgDataArgName`
#print 'override "arg1" protocol arg name:', argName from .listener import (
Listener,
getID as getListenerID,
def _replaceModulePath0(dirname): ListenerMismatchError,
'''Replace the first package-path item (in __path__) with dirname. IListenerExcHandler,
The dirname will be prepended with the package's path, assumed to )
be the last item in __path__.'''
corepath = __path__ from .topicobj import (
assert len(corepath) > 1 Topic,
initpyLoc = corepath[-1] SenderMissingReqdMsgDataError,
import os SenderUnknownMsgDataError,
corepath[0] = os.path.join(initpyLoc, dirname) MessageDataSpecError,
TopicDefnError,
ExcHandlerError,
def _prependModulePath(extra): )
'''Insert extra at beginning of package's path list. Should only be
called once, at package load time, to set the folder used for from .topicmgr import (
implementation specific to the default message protocol.''' TopicManager,
corepath = __path__ TopicDefnError,
initpyLoc = corepath[-1] TopicNameError,
import os ALL_TOPICS,
corepath.insert(0, os.path.join(initpyLoc, extra)) )
from .topicdefnprovider import (
# default protocol: ITopicDefnProvider,
_prependModulePath('kwargs') TopicDefnProvider,
ITopicDefnDeserializer,
UnrecognizedSourceFormatError,
exportTopicTreeSpec,
TOPIC_TREE_FROM_MODULE,
TOPIC_TREE_FROM_STRING,
TOPIC_TREE_FROM_CLASS,
)
from .topictreetraverser import (
TopicTreeTraverser,
)
from .notificationmgr import (
INotificationHandler,
)
\ No newline at end of file
''' """
This is not really a package init file, it is only here to simplify the 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 packaging and installation of pubsub.core's protocol-specific subfolders
by setuptools. The python modules in this folder are automatically made by setuptools. The python modules in this folder are automatically made
part of pubsub.core via pubsub.core's __path__. Hence, this should not part of pubsub.core via pubsub.core's __path__. Hence, this should not
be imported directly, it is part of pubsub.core when the messaging be imported directly, it is part of pubsub.core when the messaging
protocol is "arg1" (and not usable otherwise). protocol is "arg1" (and not usable otherwise).
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved. :copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details. :license: BSD, see LICENSE_BSD_Simple.txt for details.
''' """
msg = 'Should not import this directly, used by pubsub.core if applicable' msg = 'Should not import this directly, used by pubsub.core if applicable'
raise RuntimeError(msg) raise RuntimeError(msg)
\ No newline at end of file
''' """
:copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved. :license: BSD, see LICENSE_BSD_Simple.txt for details.
:license: BSD, see LICENSE.txt for details. """
''' from .listenerbase import (ListenerBase, ValidatorBase)
from .callables import ListenerMismatchError
from MDANSE.Externals.pubsub.core.listenerbase import ListenerBase, ValidatorBase, ListenerInadequate from .. import policies
from MDANSE.Externals.pubsub.core.callables import ListenerInadequate
from MDANSE.Externals.pubsub.core import policies
class Message:
"""
class Message: A simple container object for the two components of a topic messages
""" in the pubsub legacy API: the
A simple container object for the two components of a topic messages topic and the user data. An instance of Message is given to your
in the pubsub API v1: the listener when called by sendMessage(topic). The data is accessed
topic and the user data. An instance of Message is given to your via the 'data' attribute, and can be type of object.
listener when called by sendMessage(topic). The data is accessed """
via the 'data' attribute, and can be type of object. def __init__(self, topicNameTuple, data):
""" self.topic = topicNameTuple
def __init__(self, topicNameTuple, data): self.data = data
self.topic = topicNameTuple
self.data = data def __str__(self):
return '[Topic: '+repr(self.topic)+', Data: '+repr(self.data)+']'
def __str__(self):
return '[Topic: '+`self.topic`+', Data: '+`self.data`+']'
class Listener(ListenerBase):
"""
class Listener(ListenerBase): Wraps a callable so it can be stored by weak reference and introspected
def __call__(self, actualTopic, data): to verify that it adheres to a topic's MDS.
'''Call the listener with data. Note that it raises RuntimeError
if listener is dead. Should always return True (False would require A Listener instance has the same hash value as the callable that it wraps.
the callable_ be dead but self hasn't yet been notified of it...).'''
kwargs = {} A callable will be given data when a message is sent to it. In the arg1
if self._autoTopicArgName is not None: protocol only one object can be sent via sendMessage, it is put in a
kwargs[self._autoTopicArgName] = actualTopic Message object in its "data" field, the listener receives the Message
cb = self._callable() object.
if cb is None: """
self._calledWhenDead()
msg = Message(actualTopic.getNameTuple(), data) def __call__(self, actualTopic, data):
cb(msg, **kwargs) """Call the listener with data. Note that it raises RuntimeError
return True if listener is dead. Should always return True (False would require
the callable_ be dead but self hasn't yet been notified of it...)."""
kwargs = {}
class ListenerValidator(ValidatorBase): if self._autoTopicArgName is not None:
''' kwargs[self._autoTopicArgName] = actualTopic
Accept one arg or *args; accept any **kwarg, cb = self._callable()
and require that the Listener have at least all the kwargs (can if cb is None:
have extra) of Topic. self._calledWhenDead()
''' msg = Message(actualTopic.getNameTuple(), data)
cb(msg, **kwargs)
def _validateArgs(self, listener, paramsInfo): return True
# accept **kwargs
# accept *args
# accept any keyword args class ListenerValidator(ValidatorBase):
"""
if (paramsInfo.getAllArgs() == ()) and paramsInfo.acceptsAllUnnamedArgs: Accept one arg or *args; accept any **kwarg,
return and require that the Listener have at least all the kwargs (can
have extra) of Topic.
if paramsInfo.getAllArgs() == (): """
msg = 'Must have at least one parameter (any name, with or without default value, or *arg)'
raise ListenerInadequate(msg, listener, []) def _validateArgs(self, listener, paramsInfo):
# accept **kwargs
assert paramsInfo.getAllArgs() # accept *args
#assert not paramsInfo.acceptsAllUnnamedArgs # accept any keyword args
# verify at most one required arg if (paramsInfo.getAllArgs() == ()) and paramsInfo.acceptsAllUnnamedArgs:
numReqdArgs = paramsInfo.numRequired return
if numReqdArgs > 1:
allReqd = paramsInfo.getRequiredArgs() if paramsInfo.getAllArgs() == ():
msg = 'only one of %s can be a required agument' % (allReqd,) msg = 'Must have at least one parameter (any name, with or without default value, or *arg)'
raise ListenerInadequate(msg, listener, allReqd) raise ListenerMismatchError(msg, listener, [])
# if no required args but listener has *args, then we assert paramsInfo.getAllArgs()
# don't care about anything else: #assert not paramsInfo.acceptsAllUnnamedArgs
if numReqdArgs == 0 and paramsInfo.acceptsAllUnnamedArgs:
return # verify at most one required arg
numReqdArgs = paramsInfo.numRequired
# if no policy set, any name ok; otherwise validate name: if numReqdArgs > 1:
needArgName = policies.msgDataArgName allReqd = paramsInfo.getRequiredArgs()
firstArgName = paramsInfo.allParams[0] msg = 'only one of %s can be a required agument' % (allReqd,)
if (needArgName is not None) and firstArgName != needArgName: raise ListenerMismatchError(msg, listener, allReqd)
msg = 'listener arg name must be "%s" (is "%s")' % (needArgName, firstArgName)
effTopicArgs = [needArgName] # if no required args but listener has *args, then we
raise ListenerInadequate(msg, listener, effTopicArgs) # don't care about anything else:
if numReqdArgs == 0 and paramsInfo.acceptsAllUnnamedArgs:
return
# if no policy set, any name ok; otherwise validate name:
needArgName = policies.msgDataArgName
firstArgName = paramsInfo.allParams[0]
if (needArgName is not None) and firstArgName != needArgName:
msg = 'listener arg name must be "%s" (is "%s")' % (needArgName, firstArgName)
effTopicArgs = [needArgName]
raise ListenerMismatchError(msg, listener, effTopicArgs)
''' """
Mixin for publishing messages to a topic's listeners. This will be 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 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. send a message to the topic's listeners via a publish() method.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved. :copyright: Copyright since 2006 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details. :license: BSD, see LICENSE_BSD_Simple.txt for details.
"""
'''
from .publisherbase import PublisherBase
from MDANSE.Externals.pubsub.core.publisherbase import PublisherBase
class Publisher(PublisherBase): class Publisher(PublisherBase):
''' """
Publisher that allows old-style Message.data messages to be sent Publisher that allows old-style Message.data messages to be sent
to listeners. Listeners take one arg (required, unless there is an to listeners. Listeners take one arg (required, unless there is an
*arg), but can have kwargs (since they have default values). *arg), but can have kwargs (since they have default values).
''' """
def sendMessage(self, topicName, data=None): 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 with message data. If topicName is a subtopic, listeners
of topics more general will also get the message. of topics more general will also get the message.
Note that any listener that lets a raised exception escape will Note that any listener that lets a raised exception escape will
interrupt the send operation, unless an exception handler was interrupt the send operation, unless an exception handler was
specified via pub.setListenerExcHandler(). specified via pub.setListenerExcHandler().
''' """
topicMgr = self.getTopicMgr() topicMgr = self.getTopicMgr()
topicObj = topicMgr.getOrCreateTopic(topicName) topicObj = topicMgr.getOrCreateTopic(topicName)
......
''' """
Mixin for publishing messages to a topic's listeners. This will be 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 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. send a message to the topic's listeners via a publish() method.