1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-09-01 10:52:33 +01:00

pluginloader: Replaced extension loader with WA3 plugin loader

In the process removed modules and boot_strap.py. Also Renamed extensions Plugins.

Louie is now monkey patched rather than containing a modified version in external
This commit is contained in:
Sebastian Goscik
2016-03-17 15:23:39 +00:00
parent a4a428c9ae
commit 1e6763ee9b
68 changed files with 1715 additions and 2917 deletions

View File

@@ -1,12 +0,0 @@
This directory contains Louie package that has been modified by ARM Ltd.
Original Louie package is licensed under BSD license. ARM Ltd. changes are
licensed under Apache version 2 license.
Original Louie package may be found here:
https://pypi.python.org/pypi/Louie/1.1
The text of the BSD License may be viewed here:
http://opensource.org/licenses/bsd-license.php

View File

@@ -1,46 +0,0 @@
__all__ = [
'dispatcher',
'error',
'plugin',
'robustapply',
'saferef',
'sender',
'signal',
'version',
'connect',
'disconnect',
'get_all_receivers',
'reset',
'send',
'send_exact',
'send_minimal',
'send_robust',
'install_plugin',
'remove_plugin',
'Plugin',
'QtWidgetPlugin',
'TwistedDispatchPlugin',
'Anonymous',
'Any',
'All',
'Signal',
]
import louie.dispatcher, louie.error, louie.plugin, louie.robustapply, \
louie.saferef, louie.sender, louie.signal, louie.version
from louie.dispatcher import \
connect, disconnect, get_all_receivers, reset, \
send, send_exact, send_minimal, send_robust
from louie.plugin import \
install_plugin, remove_plugin, Plugin, \
QtWidgetPlugin, TwistedDispatchPlugin
from louie.sender import Anonymous, Any
from louie.signal import All, Signal

View File

@@ -1,591 +0,0 @@
"""Multiple-producer-multiple-consumer signal-dispatching.
``dispatcher`` is the core of Louie, providing the primary API and the
core logic for the system.
Internal attributes:
- ``WEAKREF_TYPES``: Tuple of types/classes which represent weak
references to receivers, and thus must be dereferenced on retrieval
to retrieve the callable object
- ``connections``::
{ senderkey (id) : { signal : [receivers...] } }
- ``senders``: Used for cleaning up sender references on sender
deletion::
{ senderkey (id) : weakref(sender) }
- ``senders_back``: Used for cleaning up receiver references on receiver
deletion::
{ receiverkey (id) : [senderkey (id)...] }
"""
import os
import weakref
try:
set
except NameError:
from sets import Set as set, ImmutableSet as frozenset
from louie import error
from louie import robustapply
from louie import saferef
from louie.sender import Any, Anonymous
from louie.signal import All
from prioritylist import PriorityList
# Support for statistics.
if __debug__:
connects = 0
disconnects = 0
sends = 0
def print_stats():
print ('\n'
'Louie connects: %i\n'
'Louie disconnects: %i\n'
'Louie sends: %i\n'
'\n') % (connects, disconnects, sends)
if 'PYDISPATCH_STATS' in os.environ:
import atexit
atexit.register(print_stats)
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
connections = {}
senders = {}
senders_back = {}
plugins = []
def reset():
"""Reset the state of Louie.
Useful during unit testing. Should be avoided otherwise.
"""
global connections, senders, senders_back, plugins
connections = {}
senders = {}
senders_back = {}
plugins = []
def connect(receiver, signal=All, sender=Any, weak=True, priority=0):
"""Connect ``receiver`` to ``sender`` for ``signal``.
- ``receiver``: A callable Python object which is to receive
messages/signals/events. Receivers must be hashable objects.
If weak is ``True``, then receiver must be weak-referencable (more
precisely ``saferef.safe_ref()`` must be able to create a
reference to the receiver).
Receivers are fairly flexible in their specification, as the
machinery in the ``robustapply`` module takes care of most of the
details regarding figuring out appropriate subsets of the sent
arguments to apply to a given receiver.
Note: If ``receiver`` is itself a weak reference (a callable), it
will be de-referenced by the system's machinery, so *generally*
weak references are not suitable as receivers, though some use
might be found for the facility whereby a higher-level library
passes in pre-weakrefed receiver references.
- ``signal``: The signal to which the receiver should respond.
If ``All``, receiver will receive all signals from the indicated
sender (which might also be ``All``, but is not necessarily
``All``).
Otherwise must be a hashable Python object other than ``None``
(``DispatcherError`` raised on ``None``).
- ``sender``: The sender to which the receiver should respond.
If ``Any``, receiver will receive the indicated signals from any
sender.
If ``Anonymous``, receiver will only receive indicated signals
from ``send``/``send_exact`` which do not specify a sender, or
specify ``Anonymous`` explicitly as the sender.
Otherwise can be any python object.
- ``weak``: Whether to use weak references to the receiver.
By default, the module will attempt to use weak references to
the receiver objects. If this parameter is ``False``, then strong
references will be used.
- ``priority``: specifies the priority by which a reciever should
get notified
Returns ``None``, may raise ``DispatcherTypeError``.
"""
if signal is None:
raise error.DispatcherTypeError(
'Signal cannot be None (receiver=%r sender=%r)'
% (receiver, sender))
if weak:
receiver = saferef.safe_ref(receiver, on_delete=_remove_receiver)
senderkey = id(sender)
if connections.has_key(senderkey):
signals = connections[senderkey]
else:
connections[senderkey] = signals = {}
# Keep track of senders for cleanup.
# Is Anonymous something we want to clean up?
if sender not in (None, Anonymous, Any):
def remove(object, senderkey=senderkey):
_remove_sender(senderkey=senderkey)
# Skip objects that can not be weakly referenced, which means
# they won't be automatically cleaned up, but that's too bad.
try:
weak_sender = weakref.ref(sender, remove)
senders[senderkey] = weak_sender
except:
pass
receiver_id = id(receiver)
# get current set, remove any current references to
# this receiver in the set, including back-references
if signals.has_key(signal):
receivers = signals[signal]
_remove_old_back_refs(senderkey, signal, receiver, receivers)
else:
receivers = signals[signal] = PriorityList()
try:
current = senders_back.get(receiver_id)
if current is None:
senders_back[receiver_id] = current = []
if senderkey not in current:
current.append(senderkey)
except:
pass
receivers.add(receiver, priority)
# Update stats.
if __debug__:
global connects
connects += 1
def disconnect(receiver, signal=All, sender=Any, weak=True):
"""Disconnect ``receiver`` from ``sender`` for ``signal``.
- ``receiver``: The registered receiver to disconnect.
- ``signal``: The registered signal to disconnect.
- ``sender``: The registered sender to disconnect.
- ``weak``: The weakref state to disconnect.
``disconnect`` reverses the process of ``connect``, the semantics for
the individual elements are logically equivalent to a tuple of
``(receiver, signal, sender, weak)`` used as a key to be deleted
from the internal routing tables. (The actual process is slightly
more complex but the semantics are basically the same).
Note: Using ``disconnect`` is not required to cleanup routing when
an object is deleted; the framework will remove routes for deleted
objects automatically. It's only necessary to disconnect if you
want to stop routing to a live object.
Returns ``None``, may raise ``DispatcherTypeError`` or
``DispatcherKeyError``.
"""
if signal is None:
raise error.DispatcherTypeError(
'Signal cannot be None (receiver=%r sender=%r)'
% (receiver, sender))
if weak:
receiver = saferef.safe_ref(receiver)
senderkey = id(sender)
try:
signals = connections[senderkey]
receivers = signals[signal]
except KeyError:
raise error.DispatcherKeyError(
'No receivers found for signal %r from sender %r'
% (signal, sender)
)
try:
# also removes from receivers
_remove_old_back_refs(senderkey, signal, receiver, receivers)
except ValueError:
raise error.DispatcherKeyError(
'No connection to receiver %s for signal %s from sender %s'
% (receiver, signal, sender)
)
_cleanup_connections(senderkey, signal)
# Update stats.
if __debug__:
global disconnects
disconnects += 1
def get_receivers(sender=Any, signal=All):
"""Get list of receivers from global tables.
This function allows you to retrieve the raw list of receivers
from the connections table for the given sender and signal pair.
Note: There is no guarantee that this is the actual list stored in
the connections table, so the value should be treated as a simple
iterable/truth value rather than, for instance a list to which you
might append new records.
Normally you would use ``live_receivers(get_receivers(...))`` to
retrieve the actual receiver objects as an iterable object.
"""
try:
return connections[id(sender)][signal]
except KeyError:
return []
def live_receivers(receivers):
"""Filter sequence of receivers to get resolved, live receivers.
This is a generator which will iterate over the passed sequence,
checking for weak references and resolving them, then returning
all live receivers.
"""
for receiver in receivers:
if isinstance(receiver, WEAKREF_TYPES):
# Dereference the weak reference.
receiver = receiver()
if receiver is not None:
# Check installed plugins to make sure this receiver is
# live.
live = True
for plugin in plugins:
if not plugin.is_live(receiver):
live = False
break
if live:
yield receiver
def get_all_receivers(sender=Any, signal=All):
"""Get list of all receivers from global tables.
This gets all receivers which should receive the given signal from
sender, each receiver should be produced only once by the
resulting generator.
"""
yielded = set()
for receivers in (
# Get receivers that receive *this* signal from *this* sender.
get_receivers(sender, signal),
# Add receivers that receive *all* signals from *this* sender.
get_receivers(sender, All),
# Add receivers that receive *this* signal from *any* sender.
get_receivers(Any, signal),
# Add receivers that receive *all* signals from *any* sender.
get_receivers(Any, All),
):
for receiver in receivers:
if receiver: # filter out dead instance-method weakrefs
try:
if not receiver in yielded:
yielded.add(receiver)
yield receiver
except TypeError:
# dead weakrefs raise TypeError on hash...
pass
def send(signal=All, sender=Anonymous, *arguments, **named):
"""Send ``signal`` from ``sender`` to all connected receivers.
- ``signal``: (Hashable) signal value; see ``connect`` for details.
- ``sender``: The sender of the signal.
If ``Any``, only receivers registered for ``Any`` will receive the
message.
If ``Anonymous``, only receivers registered to receive messages
from ``Anonymous`` or ``Any`` will receive the message.
Otherwise can be any Python object (normally one registered with
a connect if you actually want something to occur).
- ``arguments``: Positional arguments which will be passed to *all*
receivers. Note that this may raise ``TypeError`` if the receivers
do not allow the particular arguments. Note also that arguments
are applied before named arguments, so they should be used with
care.
- ``named``: Named arguments which will be filtered according to the
parameters of the receivers to only provide those acceptable to
the receiver.
Return a list of tuple pairs ``[(receiver, response), ...]``
If any receiver raises an error, the error propagates back through
send, terminating the dispatch loop, so it is quite possible to
not have all receivers called if a raises an error.
"""
# Call each receiver with whatever arguments it can accept.
# Return a list of tuple pairs [(receiver, response), ... ].
responses = []
for receiver in live_receivers(get_all_receivers(sender, signal)):
# Wrap receiver using installed plugins.
original = receiver
for plugin in plugins:
receiver = plugin.wrap_receiver(receiver)
response = robustapply.robust_apply(
receiver, original,
signal=signal,
sender=sender,
*arguments,
**named
)
responses.append((receiver, response))
# Update stats.
if __debug__:
global sends
sends += 1
return responses
def send_minimal(signal=All, sender=Anonymous, *arguments, **named):
"""Like ``send``, but does not attach ``signal`` and ``sender``
arguments to the call to the receiver."""
# Call each receiver with whatever arguments it can accept.
# Return a list of tuple pairs [(receiver, response), ... ].
responses = []
for receiver in live_receivers(get_all_receivers(sender, signal)):
# Wrap receiver using installed plugins.
original = receiver
for plugin in plugins:
receiver = plugin.wrap_receiver(receiver)
response = robustapply.robust_apply(
receiver, original,
*arguments,
**named
)
responses.append((receiver, response))
# Update stats.
if __debug__:
global sends
sends += 1
return responses
def send_exact(signal=All, sender=Anonymous, *arguments, **named):
"""Send ``signal`` only to receivers registered for exact message.
``send_exact`` allows for avoiding ``Any``/``Anonymous`` registered
handlers, sending only to those receivers explicitly registered
for a particular signal on a particular sender.
"""
responses = []
for receiver in live_receivers(get_receivers(sender, signal)):
# Wrap receiver using installed plugins.
original = receiver
for plugin in plugins:
receiver = plugin.wrap_receiver(receiver)
response = robustapply.robust_apply(
receiver, original,
signal=signal,
sender=sender,
*arguments,
**named
)
responses.append((receiver, response))
return responses
def send_robust(signal=All, sender=Anonymous, *arguments, **named):
"""Send ``signal`` from ``sender`` to all connected receivers catching
errors
- ``signal``: (Hashable) signal value, see connect for details
- ``sender``: The sender of the signal.
If ``Any``, only receivers registered for ``Any`` will receive the
message.
If ``Anonymous``, only receivers registered to receive messages
from ``Anonymous`` or ``Any`` will receive the message.
Otherwise can be any Python object (normally one registered with
a connect if you actually want something to occur).
- ``arguments``: Positional arguments which will be passed to *all*
receivers. Note that this may raise ``TypeError`` if the receivers
do not allow the particular arguments. Note also that arguments
are applied before named arguments, so they should be used with
care.
- ``named``: Named arguments which will be filtered according to the
parameters of the receivers to only provide those acceptable to
the receiver.
Return a list of tuple pairs ``[(receiver, response), ... ]``
If any receiver raises an error (specifically, any subclass of
``Exception``), the error instance is returned as the result for
that receiver.
"""
# Call each receiver with whatever arguments it can accept.
# Return a list of tuple pairs [(receiver, response), ... ].
responses = []
for receiver in live_receivers(get_all_receivers(sender, signal)):
original = receiver
for plugin in plugins:
receiver = plugin.wrap_receiver(receiver)
try:
response = robustapply.robust_apply(
receiver, original,
signal=signal,
sender=sender,
*arguments,
**named
)
except Exception, err:
responses.append((receiver, err))
else:
responses.append((receiver, response))
return responses
def _remove_receiver(receiver):
"""Remove ``receiver`` from connections."""
if not senders_back:
# During module cleanup the mapping will be replaced with None.
return False
backKey = id(receiver)
for senderkey in senders_back.get(backKey, ()):
try:
signals = connections[senderkey].keys()
except KeyError:
pass
else:
for signal in signals:
try:
receivers = connections[senderkey][signal]
except KeyError:
pass
else:
try:
receivers.remove(receiver)
except Exception:
pass
_cleanup_connections(senderkey, signal)
try:
del senders_back[backKey]
except KeyError:
pass
def _cleanup_connections(senderkey, signal):
"""Delete empty signals for ``senderkey``. Delete ``senderkey`` if
empty."""
try:
receivers = connections[senderkey][signal]
except:
pass
else:
if not receivers:
# No more connected receivers. Therefore, remove the signal.
try:
signals = connections[senderkey]
except KeyError:
pass
else:
del signals[signal]
if not signals:
# No more signal connections. Therefore, remove the sender.
_remove_sender(senderkey)
def _remove_sender(senderkey):
"""Remove ``senderkey`` from connections."""
_remove_back_refs(senderkey)
try:
del connections[senderkey]
except KeyError:
pass
# Senderkey will only be in senders dictionary if sender
# could be weakly referenced.
try:
del senders[senderkey]
except:
pass
def _remove_back_refs(senderkey):
"""Remove all back-references to this ``senderkey``."""
try:
signals = connections[senderkey]
except KeyError:
signals = None
else:
for signal, receivers in signals.iteritems():
for receiver in receivers:
_kill_back_ref(receiver, senderkey)
def _remove_old_back_refs(senderkey, signal, receiver, receivers):
"""Kill old ``senders_back`` references from ``receiver``.
This guards against multiple registration of the same receiver for
a given signal and sender leaking memory as old back reference
records build up.
Also removes old receiver instance from receivers.
"""
try:
index = receivers.index(receiver)
# need to scan back references here and remove senderkey
except ValueError:
return False
else:
old_receiver = receivers[index]
del receivers[index]
found = 0
signals = connections.get(signal)
if signals is not None:
for sig, recs in connections.get(signal, {}).iteritems():
if sig != signal:
for rec in recs:
if rec is old_receiver:
found = 1
break
if not found:
_kill_back_ref(old_receiver, senderkey)
return True
return False
def _kill_back_ref(receiver, senderkey):
"""Do actual removal of back reference from ``receiver`` to
``senderkey``."""
receiverkey = id(receiver)
senders = senders_back.get(receiverkey, ())
while senderkey in senders:
try:
senders.remove(senderkey)
except:
break
if not senders:
try:
del senders_back[receiverkey]
except KeyError:
pass
return True

View File

@@ -1,22 +0,0 @@
"""Error types for Louie."""
class LouieError(Exception):
"""Base class for all Louie errors"""
class DispatcherError(LouieError):
"""Base class for all Dispatcher errors"""
class DispatcherKeyError(KeyError, DispatcherError):
"""Error raised when unknown (sender, signal) specified"""
class DispatcherTypeError(TypeError, DispatcherError):
"""Error raised when inappropriate signal-type specified (None)"""
class PluginTypeError(TypeError, LouieError):
"""Error raise when trying to install more than one plugin of a
certain type."""

View File

@@ -1,108 +0,0 @@
"""Common plugins for Louie."""
from louie import dispatcher
from louie import error
def install_plugin(plugin):
cls = plugin.__class__
for p in dispatcher.plugins:
if p.__class__ is cls:
raise error.PluginTypeError(
'Plugin of type %r already installed.' % cls)
dispatcher.plugins.append(plugin)
def remove_plugin(plugin):
dispatcher.plugins.remove(plugin)
class Plugin(object):
"""Base class for Louie plugins.
Plugins are used to extend or alter the behavior of Louie
in a uniform way without having to modify the Louie code
itself.
"""
def is_live(self, receiver):
"""Return True if the receiver is still live.
Only called for receivers who have already been determined to
be live by default Louie semantics.
"""
return True
def wrap_receiver(self, receiver):
"""Return a callable that passes arguments to the receiver.
Useful when you want to change the behavior of all receivers.
"""
return receiver
class QtWidgetPlugin(Plugin):
"""A Plugin for Louie that knows how to handle Qt widgets
when using PyQt built with SIP 4 or higher.
Weak references are not useful when dealing with QWidget
instances, because even after a QWidget is closed and destroyed,
only the C++ object is destroyed. The Python 'shell' object
remains, but raises a RuntimeError when an attempt is made to call
an underlying QWidget method.
This plugin alleviates this behavior, and if a QWidget instance is
found that is just an empty shell, it prevents Louie from
dispatching to any methods on those objects.
"""
def __init__(self):
try:
import qt
except ImportError:
self.is_live = self._is_live_no_qt
else:
self.qt = qt
def is_live(self, receiver):
"""If receiver is a method on a QWidget, only return True if
it hasn't been destroyed."""
if (hasattr(receiver, 'im_self') and
isinstance(receiver.im_self, self.qt.QWidget)
):
try:
receiver.im_self.x()
except RuntimeError:
return False
return True
def _is_live_no_qt(self, receiver):
return True
class TwistedDispatchPlugin(Plugin):
"""Plugin for Louie that wraps all receivers in callables
that return Twisted Deferred objects.
When the wrapped receiver is called, it adds a call to the actual
receiver to the reactor event loop, and returns a Deferred that is
called back with the result.
"""
def __init__(self):
# Don't import reactor ourselves, but make access to it
# easier.
from twisted import internet
from twisted.internet.defer import Deferred
self._internet = internet
self._Deferred = Deferred
def wrap_receiver(self, receiver):
def wrapper(*args, **kw):
d = self._Deferred()
def called(dummy):
return receiver(*args, **kw)
d.addCallback(called)
self._internet.reactor.callLater(0, d.callback, None)
return d
return wrapper

View File

@@ -1,128 +0,0 @@
"""OrderedList class
This class keeps its elements ordered according to their priority.
"""
from collections import defaultdict
import numbers
from bisect import insort
class PriorityList(object):
def __init__(self):
"""
Returns an OrderedReceivers object that externaly behaves
like a list but it maintains the order of its elements
according to their priority.
"""
self.elements = defaultdict(list)
self.is_ordered = True
self.priorities = []
self.size = 0
self._cached_elements = None
def __del__(self):
pass
def __iter__(self):
"""
this method makes PriorityList class iterable
"""
self._order_elements()
for priority in reversed(self.priorities): # highest priority first
for element in self.elements[priority]:
yield element
def __getitem__(self, index):
self._order_elements()
return self._to_list()[index]
def __delitem__(self, index):
self._order_elements()
if isinstance(index, numbers.Integral):
index = int(index)
if index < 0:
index_range = [len(self)+index]
else:
index_range = [index]
elif isinstance(index, slice):
index_range = range(index.start or 0, index.stop, index.step or 1)
else:
raise ValueError('Invalid index {}'.format(index))
current_global_offset = 0
priority_counts = {priority : count for (priority, count) in
zip(self.priorities, [len(self.elements[p]) for p in self.priorities])}
for priority in self.priorities:
if not index_range:
break
priority_offset = 0
while index_range:
del_index = index_range[0]
if priority_counts[priority] + current_global_offset <= del_index:
current_global_offset += priority_counts[priority]
break
within_priority_index = del_index - (current_global_offset + priority_offset)
self._delete(priority, within_priority_index)
priority_offset += 1
index_range.pop(0)
def __len__(self):
return self.size
def add(self, new_element, priority=0, force_ordering=True):
"""
adds a new item in the list.
- ``new_element`` the element to be inserted in the PriorityList
- ``priority`` is the priority of the element which specifies its
order withing the List
- ``force_ordering`` indicates whether elements should be ordered
right now. If set to False, ordering happens on demand (lazy)
"""
self._add_element(new_element, priority)
if priority not in self.priorities:
self._add_priority(priority, force_ordering)
def index(self, element):
return self._to_list().index(element)
def remove(self, element):
index = self.index(element)
self.__delitem__(index)
def _order_elements(self):
if not self.is_ordered:
self.priorities = sorted(self.priorities)
self.is_ordered = True
def _to_list(self):
if self._cached_elements == None:
self._order_elements()
self._cached_elements = []
for priority in self.priorities:
self._cached_elements += self.elements[priority]
return self._cached_elements
def _add_element(self, element, priority):
self.elements[priority].append(element)
self.size += 1
self._cached_elements = None
def _delete(self, priority, priority_index):
del self.elements[priority][priority_index]
self.size -= 1
if len(self.elements[priority]) == 0:
self.priorities.remove(priority)
self._cached_elements = None
def _add_priority(self, priority, force_ordering):
if force_ordering and self.is_ordered:
insort(self.priorities, priority)
elif not force_ordering:
self.priorities.append(priority)
self.is_ordered = False
elif not self.is_ordered:
self.priorities.append(priority)
self._order_elements()
else:
raise AssertionError('Should never get here.')

View File

@@ -1,58 +0,0 @@
"""Robust apply mechanism.
Provides a function 'call', which can sort out what arguments a given
callable object can take, and subset the given arguments to match only
those which are acceptable.
"""
def function(receiver):
"""Get function-like callable object for given receiver.
returns (function_or_method, codeObject, fromMethod)
If fromMethod is true, then the callable already has its first
argument bound.
"""
if hasattr(receiver, '__call__'):
# receiver is a class instance; assume it is callable.
# Reassign receiver to the actual method that will be called.
c = receiver.__call__
if hasattr(c, 'im_func') or hasattr(c, 'im_code'):
receiver = c
if hasattr(receiver, 'im_func'):
# receiver is an instance-method.
return receiver, receiver.im_func.func_code, 1
elif not hasattr(receiver, 'func_code'):
raise ValueError(
'unknown reciever type %s %s' % (receiver, type(receiver)))
return receiver, receiver.func_code, 0
def robust_apply(receiver, signature, *arguments, **named):
"""Call receiver with arguments and appropriate subset of named.
``signature`` is the callable used to determine the call signature
of the receiver, in case ``receiver`` is a callable wrapper of the
actual receiver."""
signature, code_object, startIndex = function(signature)
acceptable = code_object.co_varnames[
startIndex + len(arguments):
code_object.co_argcount
]
for name in code_object.co_varnames[
startIndex:startIndex + len(arguments)
]:
if named.has_key(name):
raise TypeError(
'Argument %r specified both positionally '
'and as a keyword for calling %r'
% (name, signature)
)
if not (code_object.co_flags & 8):
# fc does not have a **kwds type parameter, therefore
# remove unacceptable arguments.
for arg in named.keys():
if arg not in acceptable:
del named[arg]
return receiver(*arguments, **named)

View File

@@ -1,179 +0,0 @@
"""Refactored 'safe reference from dispatcher.py"""
import weakref
import traceback
def safe_ref(target, on_delete=None):
"""Return a *safe* weak reference to a callable target.
- ``target``: The object to be weakly referenced, if it's a bound
method reference, will create a BoundMethodWeakref, otherwise
creates a simple weakref.
- ``on_delete``: If provided, will have a hard reference stored to
the callable to be called after the safe reference goes out of
scope with the reference object, (either a weakref or a
BoundMethodWeakref) as argument.
"""
if hasattr(target, 'im_self'):
if target.im_self is not None:
# Turn a bound method into a BoundMethodWeakref instance.
# Keep track of these instances for lookup by disconnect().
assert hasattr(target, 'im_func'), (
"safe_ref target %r has im_self, but no im_func, "
"don't know how to create reference"
% target
)
reference = BoundMethodWeakref(target=target, on_delete=on_delete)
return reference
if callable(on_delete):
return weakref.ref(target, on_delete)
else:
return weakref.ref(target)
class BoundMethodWeakref(object):
"""'Safe' and reusable weak references to instance methods.
BoundMethodWeakref objects provide a mechanism for referencing a
bound method without requiring that the method object itself
(which is normally a transient object) is kept alive. Instead,
the BoundMethodWeakref object keeps weak references to both the
object and the function which together define the instance method.
Attributes:
- ``key``: The identity key for the reference, calculated by the
class's calculate_key method applied to the target instance method.
- ``deletion_methods``: Sequence of callable objects taking single
argument, a reference to this object which will be called when
*either* the target object or target function is garbage
collected (i.e. when this object becomes invalid). These are
specified as the on_delete parameters of safe_ref calls.
- ``weak_self``: Weak reference to the target object.
- ``weak_func``: Weak reference to the target function.
Class Attributes:
- ``_all_instances``: Class attribute pointing to all live
BoundMethodWeakref objects indexed by the class's
calculate_key(target) method applied to the target objects.
This weak value dictionary is used to short-circuit creation so
that multiple references to the same (object, function) pair
produce the same BoundMethodWeakref instance.
"""
_all_instances = weakref.WeakValueDictionary()
def __new__(cls, target, on_delete=None, *arguments, **named):
"""Create new instance or return current instance.
Basically this method of construction allows us to
short-circuit creation of references to already- referenced
instance methods. The key corresponding to the target is
calculated, and if there is already an existing reference,
that is returned, with its deletion_methods attribute updated.
Otherwise the new instance is created and registered in the
table of already-referenced methods.
"""
key = cls.calculate_key(target)
current = cls._all_instances.get(key)
if current is not None:
current.deletion_methods.append(on_delete)
return current
else:
base = super(BoundMethodWeakref, cls).__new__(cls)
cls._all_instances[key] = base
base.__init__(target, on_delete, *arguments, **named)
return base
def __init__(self, target, on_delete=None):
"""Return a weak-reference-like instance for a bound method.
- ``target``: The instance-method target for the weak reference,
must have im_self and im_func attributes and be
reconstructable via the following, which is true of built-in
instance methods::
target.im_func.__get__( target.im_self )
- ``on_delete``: Optional callback which will be called when
this weak reference ceases to be valid (i.e. either the
object or the function is garbage collected). Should take a
single argument, which will be passed a pointer to this
object.
"""
def remove(weak, self=self):
"""Set self.isDead to True when method or instance is destroyed."""
methods = self.deletion_methods[:]
del self.deletion_methods[:]
try:
del self.__class__._all_instances[self.key]
except KeyError:
pass
for function in methods:
try:
if callable(function):
function(self)
except Exception:
try:
traceback.print_exc()
except AttributeError, e:
print ('Exception during saferef %s '
'cleanup function %s: %s' % (self, function, e))
self.deletion_methods = [on_delete]
self.key = self.calculate_key(target)
self.weak_self = weakref.ref(target.im_self, remove)
self.weak_func = weakref.ref(target.im_func, remove)
self.self_name = str(target.im_self)
self.func_name = str(target.im_func.__name__)
def calculate_key(cls, target):
"""Calculate the reference key for this reference.
Currently this is a two-tuple of the id()'s of the target
object and the target function respectively.
"""
return (id(target.im_self), id(target.im_func))
calculate_key = classmethod(calculate_key)
def __str__(self):
"""Give a friendly representation of the object."""
return "%s(%s.%s)" % (
self.__class__.__name__,
self.self_name,
self.func_name,
)
__repr__ = __str__
def __nonzero__(self):
"""Whether we are still a valid reference."""
return self() is not None
def __cmp__(self, other):
"""Compare with another reference."""
if not isinstance(other, self.__class__):
return cmp(self.__class__, type(other))
return cmp(self.key, other.key)
def __call__(self):
"""Return a strong reference to the bound method.
If the target cannot be retrieved, then will return None,
otherwise returns a bound instance method for our object and
function.
Note: You may call this method any number of times, as it does
not invalidate the reference.
"""
target = self.weak_self()
if target is not None:
function = self.weak_func()
if function is not None:
return function.__get__(target)
return None

View File

@@ -1,39 +0,0 @@
"""Sender classes."""
class _SENDER(type):
"""Base metaclass for sender classes."""
def __str__(cls):
return '<Sender: %s>' % (cls.__name__, )
class Any(object):
"""Used to represent either 'any sender'.
The Any class can be used with connect, disconnect, send, or
sendExact to denote that the sender paramater should react to any
sender, not just a particular sender.
"""
__metaclass__ = _SENDER
class Anonymous(object):
"""Singleton used to signal 'anonymous sender'.
The Anonymous class is used to signal that the sender of a message
is not specified (as distinct from being 'any sender').
Registering callbacks for Anonymous will only receive messages
sent without senders. Sending with anonymous will only send
messages to those receivers registered for Any or Anonymous.
Note: The default sender for connect is Any, while the default
sender for send is Anonymous. This has the effect that if you do
not specify any senders in either function then all messages are
routed as though there was a single sender (Anonymous) being used
everywhere.
"""
__metaclass__ = _SENDER

View File

@@ -1,30 +0,0 @@
"""Signal class.
This class is provided as a way to consistently define and document
signal types. Signal classes also have a useful string
representation.
Louie does not require you to use a subclass of Signal for signals.
"""
class _SIGNAL(type):
"""Base metaclass for signal classes."""
def __str__(cls):
return '<Signal: %s>' % (cls.__name__, )
class Signal(object):
__metaclass__ = _SIGNAL
class All(Signal):
"""Used to represent 'all signals'.
The All class can be used with connect, disconnect, send, or
sendExact to denote that the signal should react to all signals,
not just a particular signal.
"""

View File

@@ -1,5 +0,0 @@
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

View File

@@ -1,154 +0,0 @@
import unittest
import louie
from louie import dispatcher
def x(a):
return a
class Dummy(object):
pass
class Callable(object):
def __call__(self, a):
return a
def a(self, a):
return a
class TestDispatcher(unittest.TestCase):
def setUp(self):
louie.reset()
def _isclean(self):
"""Assert that everything has been cleaned up automatically"""
assert len(dispatcher.senders_back) == 0, dispatcher.senders_back
assert len(dispatcher.connections) == 0, dispatcher.connections
assert len(dispatcher.senders) == 0, dispatcher.senders
def test_Exact(self):
a = Dummy()
signal = 'this'
louie.connect(x, signal, a)
expected = [(x, a)]
result = louie.send('this', a, a=a)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
louie.disconnect(x, signal, a)
assert len(list(louie.get_all_receivers(a, signal))) == 0
self._isclean()
def test_AnonymousSend(self):
a = Dummy()
signal = 'this'
louie.connect(x, signal)
expected = [(x, a)]
result = louie.send(signal, None, a=a)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
louie.disconnect(x, signal)
assert len(list(louie.get_all_receivers(None, signal))) == 0
self._isclean()
def test_AnyRegistration(self):
a = Dummy()
signal = 'this'
louie.connect(x, signal, louie.Any)
expected = [(x, a)]
result = louie.send('this', object(), a=a)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
louie.disconnect(x, signal, louie.Any)
expected = []
result = louie.send('this', object(), a=a)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
assert len(list(louie.get_all_receivers(louie.Any, signal))) == 0
self._isclean()
def test_AllRegistration(self):
a = Dummy()
signal = 'this'
louie.connect(x, louie.All, a)
expected = [(x, a)]
result = louie.send('this', a, a=a)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
louie.disconnect(x, louie.All, a)
assert len(list(louie.get_all_receivers(a, louie.All))) == 0
self._isclean()
def test_GarbageCollected(self):
a = Callable()
b = Dummy()
signal = 'this'
louie.connect(a.a, signal, b)
expected = []
del a
result = louie.send('this', b, a=b)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
assert len(list(louie.get_all_receivers(b, signal))) == 0, (
"Remaining handlers: %s" % (louie.get_all_receivers(b, signal),))
self._isclean()
def test_GarbageCollectedObj(self):
class x:
def __call__(self, a):
return a
a = Callable()
b = Dummy()
signal = 'this'
louie.connect(a, signal, b)
expected = []
del a
result = louie.send('this', b, a=b)
assert result == expected, (
"Send didn't return expected result:\n\texpected:%s\n\tgot:%s"
% (expected, result))
assert len(list(louie.get_all_receivers(b, signal))) == 0, (
"Remaining handlers: %s" % (louie.get_all_receivers(b, signal),))
self._isclean()
def test_MultipleRegistration(self):
a = Callable()
b = Dummy()
signal = 'this'
louie.connect(a, signal, b)
louie.connect(a, signal, b)
louie.connect(a, signal, b)
louie.connect(a, signal, b)
louie.connect(a, signal, b)
louie.connect(a, signal, b)
result = louie.send('this', b, a=b)
assert len(result) == 1, result
assert len(list(louie.get_all_receivers(b, signal))) == 1, (
"Remaining handlers: %s" % (louie.get_all_receivers(b, signal),))
del a
del b
del result
self._isclean()
def test_robust(self):
"""Test the sendRobust function."""
def fails():
raise ValueError('this')
a = object()
signal = 'this'
louie.connect(fails, louie.All, a)
result = louie.send_robust('this', a, a=a)
err = result[0][1]
assert isinstance(err, ValueError)
assert err.args == ('this', )

View File

@@ -1,145 +0,0 @@
"""Louie plugin tests."""
import unittest
import louie
try:
import qt
if not hasattr(qt.qApp, 'for_testing'):
_app = qt.QApplication([])
_app.for_testing = True
qt.qApp = _app
except ImportError:
qt = None
class ReceiverBase(object):
def __init__(self):
self.args = []
self.live = True
def __call__(self, arg):
self.args.append(arg)
class Receiver1(ReceiverBase):
pass
class Receiver2(ReceiverBase):
pass
class Plugin1(louie.Plugin):
def is_live(self, receiver):
"""ReceiverBase instances are only live if their `live`
attribute is True"""
if isinstance(receiver, ReceiverBase):
return receiver.live
return True
class Plugin2(louie.Plugin):
def is_live(self, receiver):
"""Pretend all Receiver2 instances are not live."""
if isinstance(receiver, Receiver2):
return False
return True
def test_only_one_instance():
louie.reset()
plugin1a = Plugin1()
plugin1b = Plugin1()
louie.install_plugin(plugin1a)
# XXX: Move these tests into test cases so we can use unittest's
# 'assertRaises' method.
try:
louie.install_plugin(plugin1b)
except louie.error.PluginTypeError:
pass
else:
raise Exception('PluginTypeError not raised')
def test_is_live():
louie.reset()
# Create receivers.
receiver1a = Receiver1()
receiver1b = Receiver1()
receiver2a = Receiver2()
receiver2b = Receiver2()
# Connect signals.
louie.connect(receiver1a, 'sig')
louie.connect(receiver1b, 'sig')
louie.connect(receiver2a, 'sig')
louie.connect(receiver2b, 'sig')
# Check reception without plugins.
louie.send('sig', arg='foo')
assert receiver1a.args == ['foo']
assert receiver1b.args == ['foo']
assert receiver2a.args == ['foo']
assert receiver2b.args == ['foo']
# Install plugin 1.
plugin1 = Plugin1()
louie.install_plugin(plugin1)
# Make some receivers not live.
receiver1a.live = False
receiver2b.live = False
# Check reception.
louie.send('sig', arg='bar')
assert receiver1a.args == ['foo']
assert receiver1b.args == ['foo', 'bar']
assert receiver2a.args == ['foo', 'bar']
assert receiver2b.args == ['foo']
# Remove plugin 1, install plugin 2.
plugin2 = Plugin2()
louie.remove_plugin(plugin1)
louie.install_plugin(plugin2)
# Check reception.
louie.send('sig', arg='baz')
assert receiver1a.args == ['foo', 'baz']
assert receiver1b.args == ['foo', 'bar', 'baz']
assert receiver2a.args == ['foo', 'bar']
assert receiver2b.args == ['foo']
# Install plugin 1 alongside plugin 2.
louie.install_plugin(plugin1)
# Check reception.
louie.send('sig', arg='fob')
assert receiver1a.args == ['foo', 'baz']
assert receiver1b.args == ['foo', 'bar', 'baz', 'fob']
assert receiver2a.args == ['foo', 'bar']
assert receiver2b.args == ['foo']
if qt is not None:
def test_qt_plugin():
louie.reset()
# Create receivers.
class Receiver(qt.QWidget):
def __init__(self):
qt.QObject.__init__(self)
self.args = []
def receive(self, arg):
self.args.append(arg)
receiver1 = Receiver()
receiver2 = Receiver()
# Connect signals.
louie.connect(receiver1.receive, 'sig')
louie.connect(receiver2.receive, 'sig')
# Destroy receiver2 so only a shell is left.
receiver2.close(True)
# Check reception without plugins.
louie.send('sig', arg='foo')
assert receiver1.args == ['foo']
assert receiver2.args == ['foo']
# Install plugin.
plugin = louie.QtWidgetPlugin()
louie.install_plugin(plugin)
# Check reception with plugins.
louie.send('sig', arg='bar')
assert receiver1.args == ['foo', 'bar']
assert receiver2.args == ['foo']

View File

@@ -1,41 +0,0 @@
import unittest
import louie
from louie import dispatcher
class Callable(object):
def __init__(self, val):
self.val = val
def __call__(self):
return self.val
one = Callable(1)
two = Callable(2)
three = Callable(3)
class TestPriorityDispatcher(unittest.TestCase):
def test_ConnectNotify(self):
louie.connect(
two,
'one',
priority=200
)
louie.connect(
one,
'one',
priority=100
)
louie.connect(
three,
'one',
priority=300
)
result = [ i[1] for i in louie.send('one')]
if not result == [1, 2, 3]:
print result
assert(False)

View File

@@ -1,62 +0,0 @@
import unittest
import louie.prioritylist
from louie.prioritylist import PriorityList
#def populate_list(plist):
class TestPriorityList(unittest.TestCase):
def test_Insert(self):
pl = PriorityList()
elements = {3: "element 3",
2: "element 2",
1: "element 1",
5: "element 5",
4: "element 4"
}
for key in elements:
pl.add(elements[key], priority=key)
match = zip(sorted(elements.values()), pl[:])
for pair in match:
assert(pair[0]==pair[1])
def test_Delete(self):
pl = PriorityList()
elements = {2: "element 3",
1: "element 2",
0: "element 1",
4: "element 5",
3: "element 4"
}
for key in elements:
pl.add(elements[key], priority=key)
del elements[2]
del pl[2]
match = zip(sorted(elements.values()) , pl[:])
for pair in match:
assert(pair[0]==pair[1])
def test_Multiple(self):
pl = PriorityList()
pl.add('1', 1)
pl.add('2.1', 2)
pl.add('3', 3)
pl.add('2.2', 2)
it = iter(pl)
assert(it.next() == '1')
assert(it.next() == '2.1')
assert(it.next() == '2.2')
assert(it.next() == '3')
def test_IteratorBreak(self):
pl = PriorityList()
pl.add('1', 1)
pl.add('2.1', 2)
pl.add('3', 3)
pl.add('2.2', 2)
for i in pl:
if i == '2.1':
break
assert(pl.index('3') == 3)

View File

@@ -1,34 +0,0 @@
import unittest
from louie.robustapply import robust_apply
def no_argument():
pass
def one_argument(blah):
pass
def two_arguments(blah, other):
pass
class TestRobustApply(unittest.TestCase):
def test_01(self):
robust_apply(no_argument, no_argument)
def test_02(self):
self.assertRaises(TypeError, robust_apply, no_argument, no_argument,
'this' )
def test_03(self):
self.assertRaises(TypeError, robust_apply, one_argument, one_argument)
def test_04(self):
"""Raise error on duplication of a particular argument"""
self.assertRaises(TypeError, robust_apply, one_argument, one_argument,
'this', blah='that')

View File

@@ -1,83 +0,0 @@
import unittest
from louie.saferef import safe_ref
class _Sample1(object):
def x(self):
pass
def _sample2(obj):
pass
class _Sample3(object):
def __call__(self, obj):
pass
class TestSaferef(unittest.TestCase):
# XXX: The original tests had a test for closure, and it had an
# off-by-one problem, perhaps due to scope issues. It has been
# removed from this test suite.
def setUp(self):
ts = []
ss = []
for x in xrange(5000):
t = _Sample1()
ts.append(t)
s = safe_ref(t.x, self._closure)
ss.append(s)
ts.append(_sample2)
ss.append(safe_ref(_sample2, self._closure))
for x in xrange(30):
t = _Sample3()
ts.append(t)
s = safe_ref(t, self._closure)
ss.append(s)
self.ts = ts
self.ss = ss
self.closure_count = 0
def tearDown(self):
if hasattr(self, 'ts'):
del self.ts
if hasattr(self, 'ss'):
del self.ss
def test_In(self):
"""Test the `in` operator for safe references (cmp)"""
for t in self.ts[:50]:
assert safe_ref(t.x) in self.ss
def test_Valid(self):
"""Test that the references are valid (return instance methods)"""
for s in self.ss:
assert s()
def test_ShortCircuit(self):
"""Test that creation short-circuits to reuse existing references"""
sd = {}
for s in self.ss:
sd[s] = 1
for t in self.ts:
if hasattr(t, 'x'):
assert sd.has_key(safe_ref(t.x))
else:
assert sd.has_key(safe_ref(t))
def test_Representation(self):
"""Test that the reference object's representation works
XXX Doesn't currently check the results, just that no error
is raised
"""
repr(self.ss[-1])
def _closure(self, ref):
"""Dumb utility mechanism to increment deletion counter"""
self.closure_count += 1

View File

@@ -1,8 +0,0 @@
"""Louie version information."""
NAME = 'Louie'
DESCRIPTION = 'Signal dispatching mechanism'
VERSION = '1.1'