Commit 9e6d4dda authored by eric pellegrini's avatar eric pellegrini
Browse files

Refactored pubsub module with absolute import

parent 6ae23a48
Pipeline #1089 failed with stages
in 2 minutes and 23 seconds
......@@ -30,29 +30,15 @@ Created on Mar 30, 2015
:author: Eric C. Pellegrini
import abc
import glob
import imp
import inspect
import os
import sys
class _Meta(type):
Metaclass that allows to use the :py:meth:`__getitem__` method at a class level for the class that has been built.
The class that uses this metaclass must define a class attribute named _registry that will be used
by the :py:meth:`__getitem__` method.
def __getitem__(self, item):
Returns a given item stored in the class registry
return self._registry[item]
from MDANSE.Core.Singleton import Singleton
class ClassRegistry(abc.ABCMeta):
class ClassRegistry(object):
Metaclass that registers the classes that make the MDANSE framework.
......@@ -67,49 +53,41 @@ class ClassRegistry(abc.ABCMeta):
class attribute will not be registered.
__metaclass__ = _Meta
__metaclass__ = Singleton
__interfaces = set()
def __init__(self):
self._registry = {}
self._sections = {}
def __setitem__(self,name,cls):
_registry = {}
def __init__(self, name, bases, namespace):
:param name: the name of the class to be built by this metaclass
:type name: str
:param bases: the base classes of the class to be built by this metaclass
:type bases: tuple
:param namespace: the attributes and methods of the class to be built by this metaclass
:type namespace: dict
# The class to be registered must have a class attribute "_registry" to be registered, otherwise return
clsRegistry = getattr(cls,"_registry")
if clsRegistry is None:
super(ClassRegistry, self).__init__(name, bases, namespace)
# If the class objet does not have a type attribute, it will not be registered.
typ = getattr(self, 'type', None)
if typ is None:
# And this attribute must be a string for the class to be registerable otherwise return
if not isinstance(clsRegistry,basestring):
# Fetch the branch of the registry corresponding the one of the class to be registred, otherwise create a new branch
d = self._registry.setdefault(clsRegistry,{})
# If a class has already been registered with that name return
if d.has_key(name):
# If the class object is metaclassed by ClassRegistry then a new section of the registry will
# be created with its type attribute.
metaClass = namespace.get("__metaclass__", None)
if metaClass is ClassRegistry:
if (ClassRegistry._registry.has_key(typ)):
ClassRegistry._registry[typ] = {}
for interface in ClassRegistry.__interfaces:
if issubclass(self, interface):
ClassRegistry._registry[interface.type][typ] = self
def update_registry(cls,packageDir):
d[name] = cls
def __getitem__(self,name):
return self._registry.get(name,{})
def update(self,packageDir):
Update the classes registry by importing all the modules contained in a given package.
......@@ -141,10 +119,8 @@ class ClassRegistry(abc.ABCMeta):
if os.path.abspath(os.path.dirname(mod.__file__)) != os.path.abspath(moduleDir):
print "A module with name %s is already present in your distribution with %s path." % (moduleName,moduleDir)
def info(cls, interface):
def info(self, interface):
Returns informations about the subclasses of a given base class stored in the registry.
......@@ -155,7 +131,7 @@ class ClassRegistry(abc.ABCMeta):
:rtype: str
if not cls._registry.has_key(interface):
if not self._registry.has_key(interface):
return "The interface " + interface + " is not registered"
words = ["Name", "Class","File"]
......@@ -168,7 +144,7 @@ class ClassRegistry(abc.ABCMeta):
maxlength = -1
# Loop over the registry items.
for i, (k, v) in enumerate(sorted(cls._registry[interface].items())):
for i, (k, v) in enumerate(sorted(self._registry[interface].items())):
# Get the module corresponding to the job class.
mod = inspect.getmodule(v)
......@@ -185,8 +161,8 @@ class ClassRegistry(abc.ABCMeta):
return contents.format(maxlength,*words)
def get_interfaces(cls):
def interfaces(self):
Returns the interfaces that are currently registered.
......@@ -194,4 +170,8 @@ class ClassRegistry(abc.ABCMeta):
:rtype: list of str
return sorted(cls._registry.keys())
\ No newline at end of file
return sorted(self._registry.keys())
REGISTRY = ClassRegistry()
......@@ -35,6 +35,8 @@ import copy
import csv
import numbers
import os
import pkg_resources
import sys
import xml.etree.ElementTree as etree
from MDANSE.Core.Platform import PLATFORM
......@@ -111,8 +113,14 @@ class ElementsDatabase(object):
__metaclass__ = Singleton
# The default path
_DEFAULT_DATABASE = os.path.join(os.path.dirname(__file__), "elements_database.csv")
_DEFAULT_DATABASE = pkg_resources.resource_filename(__name__,"elements_database.csv")
if hasattr(sys,'frozen'):
_DEFAULT_DATABASE = os.path.join(os.path.dirname(sys.executable),"elements_database.csv")
# The user path
_USER_DATABASE = os.path.join(PLATFORM.application_directory(), "elements_database.csv")
Publish-subscribe package.
This package provides the following modules:
- pub: "entry point" module for core pubsub functionality. It provides
functions for sending messages and subscribing listeners and various
- 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 $
__all__ = [
'pub', 'utils',
'printImported', 'setupkwargs', 'setuparg1', 'setupv1',
# 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]
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. '''
# if autosetupv1 is importable, then it means we should
# automatically setup for version 1 API
import imp
imp.find_module('autosetuppubsubv1', __path__)
except ImportError:
from MDANSE.Externals.pubsub import setupv1
assert pub is not None
assert Publisher is pub.Publisher
If this module is named autosetuppubsubv1, and it is in the pubsub
package's folder, it will cause pubsub to default to "version 1" (v1)
of the API when pub is imported. The v1 API was the version originally
developed as part of wxPython's wx.lib library.
If this module is named anything else (such as prefixed with an
underscore) it will not be found by pubsub: the most recent pubsub
API will be loaded upon import of the *pub* module.
\ No newline at end of file
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
selected messaging protocol.
Indeed some of the API depends on the messaging
protocol used. For instance sendMessage(), defined in,
has a different signature (and hence implementation) for the kwargs
protocol than for the arg1 protocol.
The most convenient way to
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
the package's __path__ variable (defined automatically by the Python
interpreter when is executed). For instance, 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
"from pubsub.core import listener", the correct will be
found for the specified protocol. The default protocol is kwargs.
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
_prependModulePath() at end of this file.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
from MDANSE.Externals.pubsub.core import policies
def setMsgProtocol(protocol):
policies.msgDataProtocol = protocol
# add appropriate subdir for protocol-specific implementation
if protocol == 'kwargs':
def setMsgDataArgName(stage, listenerArgName, senderArgNameAny=False):
policies.senderKwargNameAny = senderArgNameAny
policies.msgDataArgName = listenerArgName
policies.msgProtocolTransStage = stage
#print `policies.msgProtocolTransStage`, `policies.msgDataProtocol`, \
# `policies.senderKwargNameAny`, `policies.msgDataArgName`
#print 'override "arg1" protocol arg name:', argName
def _replaceModulePath0(dirname):
'''Replace the first package-path item (in __path__) with dirname.
The dirname will be prepended with the package's path, assumed to
be the last item in __path__.'''
corepath = __path__
assert len(corepath) > 1
initpyLoc = corepath[-1]
import os
corepath[0] = os.path.join(initpyLoc, dirname)
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
implementation specific to the default message protocol.'''
corepath = __path__
initpyLoc = corepath[-1]
import os
corepath.insert(0, os.path.join(initpyLoc, extra))
# default protocol:
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
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
protocol is "arg1" (and not usable otherwise).
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
msg = 'Should not import this directly, used by pubsub.core if applicable'
raise RuntimeError(msg)
\ No newline at end of file
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
from MDANSE.Externals.pubsub.core.listenerbase import ListenerBase, ValidatorBase, ListenerInadequate
from MDANSE.Externals.pubsub.core.callables import ListenerInadequate
from MDANSE.Externals.pubsub.core import policies
class Message:
A simple container object for the two components of a topic messages
in the pubsub API v1: the
topic and the user data. An instance of Message is given to your
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 = data
def __str__(self):
return '[Topic: '+`self.topic`+', Data: '+``+']'
class Listener(ListenerBase):
def __call__(self, actualTopic, data):
'''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...).'''
kwargs = {}
if self._autoTopicArgName is not None:
kwargs[self._autoTopicArgName] = actualTopic
cb = self._callable()
if cb is None:
msg = Message(actualTopic.getNameTuple(), data)
cb(msg, **kwargs)
return True
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
# accept *args
# accept any keyword args
if (paramsInfo.getAllArgs() == ()) and paramsInfo.acceptsAllUnnamedArgs:
if paramsInfo.getAllArgs() == ():
msg = 'Must have at least one parameter (any name, with or without default value, or *arg)'
raise ListenerInadequate(msg, listener, [])
assert paramsInfo.getAllArgs()
#assert not paramsInfo.acceptsAllUnnamedArgs
# verify at most one required arg
numReqdArgs = paramsInfo.numRequired
if numReqdArgs > 1:
allReqd = paramsInfo.getRequiredArgs()
msg = 'only one of %s can be a required agument' % (allReqd,)
raise ListenerInadequate(msg, listener, allReqd)
# if no required args but listener has *args, then we
# don't care about anything else:
if numReqdArgs == 0 and paramsInfo.acceptsAllUnnamedArgs:
# 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 ListenerInadequate(msg, listener, effTopicArgs)
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 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
from MDANSE.Externals.pubsub.core.publisherbase import PublisherBase
class Publisher(PublisherBase):
Publisher that allows old-style 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,
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)
# don't care if topic not final: topicObj.getListeners()
# will return nothing if not final but notification will still work
def getMsgProtocol(self):
return 'arg1'
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.
Note that it is important that the PublisherMixin NOT modify any
state data during message sending, because in principle it could
happen that a listener causes another message of same topic to be
sent (presumably, the listener has a way of preventing infinite
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
class PublisherMixin:
def __init__(self):
def publish(self, data=None):
############## IMPLEMENTATION ###############
def _mix_prePublish(self, data, topicObj=None, iterState=None):
'''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.'''
listener(self, data)
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.
import weakref
from MDANSE.Externals.pubsub.core.topicutils import WeakNone
class SenderMissingReqdArgs(RuntimeError):
Ignore: Not used for this message protocol.
class SenderUnknownOptArgs(RuntimeError):
Ignore: Not used for this message protocol.
class ArgsInfo:
Encode the Listener Function Parameter Specification (LPS) for a given
topic. In the DataMsg API of pubsub, this is the same for all
topics, so this is quite a simple class, required only because
the part of pubsub that uses ArgsInfos supports several API
versions using one common API. So the only difference between
an ArgsInfo and an ArgSpecGiven is that ArgsInfo refers to
parent topic's ArgsInfo; other data members are the same.
Note that the LPS 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
def __init__(self, topicNameTuple, specGiven, parentArgsInfo):
self.__argsDocs = specGiven.argsDocs or {'data':'message data'}
self.argsSpecType = self.SPEC_COMPLETE
self.allOptional = () # list of topic message optional argument names
self.allRequired = ('data',) # list of topic message required argument names
self.parentAI = WeakNone()
if parentArgsInfo is not None:
self.parentAI = weakref.ref(parentArgsInfo)
def isComplete(self):
return True
def getArgs(self):
return self.allOptional + self.allRequired
def numArgs(self):
return len(self.allOptional) + len(self.allRequired)
def getArgsDocs(self):
return self.__argsDocs.copy()
The root topic of all topics is different based on messaging protocol.
:copyright: Copyright 2006-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.