Commits

jason kirtland committed d644ad0

Depend on blinker; flatland.util.signals has graduated.

  • Participants
  • Parent commits 5afb6da

Comments (0)

Files changed (8)

File flatland/signals.py

-from flatland.util import signal
+from blinker import signal
 
 
 validator_validated = signal('validator_validated', doc="""\

File flatland/util/__init__.py

         'deferred': (
             'deferred_module',
             ),
-        'signals': (
-            'Signal',
-            'signal',
-            ),
       })

File flatland/util/_saferef.py

-# extracted from Louie, http://pylouie.org/
-#
-# Copyright (c) 2006 Patrick K. O'Brien, Mike C. Fletcher,
-#                    Matthew R. Scott
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#
-#     * 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.
-#
-#     * Neither the name of the <ORGANIZATION> nor the names of its
-#       contributors may be used to endorse or promote products derived
-#       from this software without specific prior written permission.
-#
-# 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.
-#
-"""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

File flatland/util/signals.py

-# -*- coding: utf-8 -*-
-"""Signals and events.
-
-A small implementation of signals, inspired by a snippet of Django signal API
-client code seen in a blog post.  Signals are first-class objects and each
-manages its own receivers and message emission.
-
-The :func:`signal` function provides singleton behavior for named
-signals.
-
-"""
-from collections import defaultdict
-import weakref
-from . base import symbol, threading
-from . import weakrefs
-
-
-ANY = symbol('ANY')
-ANY_ID = 0
-
-class Signal(object):
-    """A generic notification emitter."""
-
-    # convenience for importers, allows Signal.ANY
-    ANY = ANY
-
-    def __init__(self, doc=None):
-        if doc:
-            self.__doc__ = doc
-        self.receivers = {}
-        self._by_receiver = defaultdict(set)
-        self._by_sender = defaultdict(set)
-        self._weak_senders = {}
-
-    def connect(self, receiver, sender=ANY, weak=True):
-        """Connect *receiver* to signal events send by *sender*.
-
-        :param receiver: A callable.  Will be invoked by :meth:`send`.
-          Will be invoked with `sender=` as a named argument and any
-          \*\*kwargs that were provided to a call to :meth:`send`.
-
-        :param sender: Any object or :attr:`Signal.ANY`.  Restricts
-          notifications to *receiver* to only those :meth:`send`
-          emissions sent by *sender*.  If ``ANY``, the receiver will
-          always be notified.  A *receiver* may be connected to
-          multiple *sender* on the same Signal.  Defaults to ``ANY``.
-
-        :param weak: If true, the Signal will hold a weakref to
-          *receiver* and automatically disconnect when *receiver* goes
-          out of scope or is garbage collected.  Defaults to True.
-
-        """
-        receiver_id = _hashable_identity(receiver)
-        if weak:
-            receiver_ref = weakrefs.reference(receiver, self._cleanup_receiver)
-            receiver_ref.receiver_id = receiver_id
-        else:
-            receiver_ref = receiver
-        sender_id = ANY_ID if sender is ANY else _hashable_identity(sender)
-
-        self.receivers.setdefault(receiver_id, receiver_ref)
-        self._by_sender[sender_id].add(receiver_id)
-        self._by_receiver[receiver_id].add(sender_id)
-        del receiver_ref
-
-        if sender is not ANY and sender_id not in self._weak_senders:
-            # wire together a cleanup for weakref-able senders
-            try:
-                sender_ref = weakrefs.reference(sender, self._cleanup_sender)
-                sender_ref.sender_id = sender_id
-            except TypeError:
-                pass
-            else:
-                self._weak_senders.setdefault(sender_id, sender_ref)
-                del sender_ref
-
-        # broadcast this connection.  if receivers raise, disconnect.
-        if receiver_connected.receivers and self is not receiver_connected:
-            try:
-                receiver_connected.send(self,
-                                        receiver_arg=receiver,
-                                        sender_arg=sender,
-                                        weak_arg=weak)
-            except:
-                self.disconnect(receiver, sender)
-                raise
-        return receiver
-
-    def send(self, sender=None, **kwargs):
-        """Emit this signal on behalf of *sender*, passing on \*\*kwargs.
-
-        Returns a list of 2-tuples, pairing receivers with their return
-        value. The ordering of receiver notification is undefined.
-
-        """
-        if not self.receivers:
-            return []
-        else:
-            return [(receiver, receiver(sender=sender, **kwargs))
-                    for receiver in self.receivers_for(sender)]
-
-    def has_receivers_for(self, sender):
-        """True if there is probably a receiver for *sender*.
-
-        Performs an optimistic check for receivers only.  Does not guarantee
-        that all weakly referenced receivers are still alive.  See
-        :meth:`receivers_for` for a stronger search.
-
-        """
-        if not self.receivers:
-            return False
-        if self._by_sender[ANY_ID]:
-            return True
-        if sender is ANY:
-            return False
-        return _hashable_identity(sender) in self._by_sender
-
-    def receivers_for(self, sender):
-        """Iterate all live receivers listening for *sender*."""
-        if self.receivers:
-            sender_id = _hashable_identity(sender)
-            if sender_id in self._by_sender:
-                ids = (self._by_sender[ANY_ID] |
-                       self._by_sender[sender_id])
-            else:
-                ids = self._by_sender[ANY_ID].copy()
-            for receiver_id in ids:
-                receiver = self.receivers.get(receiver_id)
-                if receiver is None:
-                    continue
-                if isinstance(receiver, weakrefs.WeakTypes):
-                    strong = receiver()
-                    if strong is None:
-                        self._disconnect(receiver_id, ANY_ID)
-                        continue
-                    receiver = strong
-                yield receiver
-
-    def disconnect(self, receiver, sender=ANY):
-        """Disconnect *receiver* from this signal's events."""
-        sender_id = ANY_ID if sender is ANY else _hashable_identity(sender)
-        receiver_id = _hashable_identity(receiver)
-        self._disconnect(receiver_id, sender_id)
-
-    def _disconnect(self, receiver_id, sender_id):
-        if sender_id == ANY_ID:
-            if self._by_receiver.pop(receiver_id, False):
-                for bucket in self._by_sender.values():
-                    bucket.discard(receiver_id)
-            self.receivers.pop(receiver_id, None)
-        else:
-            self._by_sender[sender_id].discard(receiver_id)
-
-    def _cleanup_receiver(self, receiver_ref):
-        """Disconnect a receiver from all senders."""
-        self._disconnect(receiver_ref.receiver_id, ANY_ID)
-
-    def _cleanup_sender(self, sender_ref):
-        """Disconnect all receivers from a sender."""
-        sender_id = sender_ref.sender_id
-        assert sender_id != ANY_ID
-        self._weak_senders.pop(sender_id, None)
-        for receiver_id in self._by_sender.pop(sender_id, ()):
-            self._by_receiver[receiver_id].discard(sender_id)
-
-    def _clear_state(self):
-        """Throw away all signal state.  Useful for unit tests."""
-        self._weak_senders.clear()
-        self.receivers.clear()
-        self._by_sender.clear()
-        self._by_receiver.clear()
-
-
-receiver_connected = Signal()
-
-class NamedSignal(Signal):
-    """A named generic notification emitter."""
-
-    def __init__(self, name, doc=None):
-        Signal.__init__(self, doc)
-        self.name = name
-
-    def __repr__(self):
-        return '<NamedSignal 0x%x; %r>' % (id(self), self.name)
-
-
-class Namespace(object):
-    """Manages NamedSignals as singletons."""
-
-    storage_factory = weakref.WeakValueDictionary
-    signal_factory = NamedSignal
-
-    def __init__(self):
-        self.lock = threading.Lock()
-        self.signals = self.storage_factory()
-
-    def signal(self, name, doc=None):
-        """Return the :class:`NamedSignal` *name*, creating it if required."""
-        lock = self.lock
-        lock.acquire()
-        try:
-            return self.signals[name]
-        except KeyError:
-            self.signals[name] = instance = self.signal_factory(name, doc)
-            return instance
-        finally:
-            lock.release()
-
-signal = Namespace().signal
-
-def _hashable_identity(obj):
-    if hasattr(obj, 'im_func'):
-        return (id(obj.im_func), id(obj.im_self))
-    else:
-        return id(obj)

File flatland/util/weakrefs.py

-"""Weak reference helpers."""
-import weakref
-from . import _saferef
-
-
-WeakTypes = weakref.ref, _saferef.BoundMethodWeakref
-
-class annotatable_weakref(weakref.ref):
-    """A weakref.ref that supports custom instance attributes."""
-
-
-def reference(object, callback=None, **annotations):
-    """Return an annotated weak ref."""
-    if callable(object):
-        weak = callable_reference(object, callback)
-    else:
-        weak = annotatable_weakref(object, callback)
-    for key, value in annotations.items():
-        setattr(weak, key, value)
-    return weak
-
-def callable_reference(object, callback=None):
-    """Return an annotated weak ref, supporting bound instance methods."""
-    if hasattr(object, 'im_self') and object.im_self is not None:
-        return _saferef.BoundMethodWeakref(target=object, on_delete=callback)
-    return annotatable_weakref(object, callback)
                    'Programming Language :: Python :: 2.7',
                    'Topic :: Internet :: WWW/HTTP :: WSGI',
                    'Topic :: Software Development :: Libraries'],
+      install_requires=[
+          'blinker',
+          ]
       **extra_setup)

File tests/test_signals.py

-from flatland.util.signals import (
-    ANY,
-    ANY_ID,
-    NamedSignal,
-    Namespace,
-    Signal,
-    receiver_connected,
-    )
-
-from tests._util import eq_, assert_raises
-
-
-def test_meta_connect():
-    sentinel = []
-
-    def meta_received(**kw):
-        sentinel.append(kw)
-
-    assert not receiver_connected.receivers
-    receiver_connected.connect(meta_received)
-    assert not sentinel
-
-    def receiver(**kw):
-        pass
-    sig = Signal()
-    sig.connect(receiver)
-
-    eq_(sentinel, [dict(sender=sig,
-                        receiver_arg=receiver,
-                        sender_arg=ANY,
-                        weak_arg=True)])
-
-    receiver_connected._clear_state()
-
-
-def test_meta_connect_failure():
-
-    def meta_received(**kw):
-        raise TypeError('boom')
-
-    assert not receiver_connected.receivers
-    receiver_connected.connect(meta_received)
-
-    def receiver(**kw):
-        pass
-    sig = Signal()
-
-    assert_raises(TypeError, sig.connect, receiver)
-    assert not sig.receivers
-    assert not sig._by_receiver
-    eq_(sig._by_sender, {ANY_ID: set()})
-
-    receiver_connected._clear_state()
-
-
-def test_singletons():
-    ns = Namespace()
-    assert not ns.signals
-    s1 = ns.signal('abc')
-    assert s1 is ns.signal('abc')
-    assert s1 is not ns.signal('def')
-    assert 'abc' in ns.signals
-    # weak by default, already out of scope
-    assert 'def' not in ns.signals
-    del s1
-    assert 'abc' not in ns.signals
-
-
-def test_weak_receiver():
-    sentinel = []
-
-    def received(**kw):
-        sentinel.append(kw)
-
-    sig = Signal()
-    sig.connect(received, weak=True)
-    del received
-
-    assert not sentinel
-    sig.send()
-    assert not sentinel
-    assert not sig.receivers
-    values_are_empty_sets_(sig._by_receiver)
-    values_are_empty_sets_(sig._by_sender)
-
-
-def test_strong_receiver():
-    sentinel = []
-
-    def received(**kw):
-        sentinel.append(kw)
-    fn_id = id(received)
-
-    sig = Signal()
-    sig.connect(received, weak=False)
-    del received
-
-    assert not sentinel
-    sig.send()
-    assert sentinel
-    eq_([id(fn) for fn in sig.receivers.values()], [fn_id])
-
-
-def test_filtered_receiver():
-    sentinel = []
-
-    def received(sender):
-        sentinel.append(sender)
-
-    sig = Signal()
-
-    sig.connect(received, 123)
-
-    assert not sentinel
-    sig.send()
-    assert not sentinel
-    sig.send(123)
-    assert sentinel == [123]
-    sig.send()
-    assert sentinel == [123]
-
-
-def test_filtered_receiver_weakref():
-    sentinel = []
-
-    def received(sender):
-        sentinel.append(sender)
-
-    class Object(object):
-        pass
-    obj = Object()
-
-    sig = Signal()
-    sig.connect(received, obj)
-
-    assert not sentinel
-    sig.send(obj)
-    assert sentinel == [obj]
-    del sentinel[:]
-    del obj
-
-    # general index isn't cleaned up
-    assert sig.receivers
-    # but receiver/sender pairs are
-    values_are_empty_sets_(sig._by_receiver)
-    values_are_empty_sets_(sig._by_sender)
-
-
-def test_no_double_send():
-    sentinel = []
-
-    def received(sender):
-        sentinel.append(sender)
-
-    sig = Signal()
-
-    sig.connect(received, 123)
-    sig.connect(received)
-
-    assert not sentinel
-    sig.send()
-    assert sentinel == [None]
-    sig.send(123)
-    assert sentinel == [None, 123]
-    sig.send()
-    assert sentinel == [None, 123, None]
-
-
-def test_has_receivers():
-    received = lambda sender: None
-
-    sig = Signal()
-    assert not sig.has_receivers_for(None)
-    assert not sig.has_receivers_for(ANY)
-
-    sig.connect(received, 'xyz')
-    assert not sig.has_receivers_for(None)
-    assert not sig.has_receivers_for(ANY)
-    assert sig.has_receivers_for('xyz')
-
-    class Object(object):
-        pass
-    o = Object()
-
-    sig.connect(received, o)
-    assert sig.has_receivers_for(o)
-
-    del received
-    assert not sig.has_receivers_for('xyz')
-    assert list(sig.receivers_for('xyz')) == []
-    assert list(sig.receivers_for(o)) == []
-
-    sig.connect(lambda sender: None, weak=False)
-    assert sig.has_receivers_for('xyz')
-    assert sig.has_receivers_for(o)
-    assert sig.has_receivers_for(None)
-    assert sig.has_receivers_for(ANY)
-    assert sig.has_receivers_for('xyz')
-
-
-def test_instance_doc():
-    sig = Signal(doc='x')
-    assert sig.__doc__ == 'x'
-
-    sig = Signal('x')
-    assert sig.__doc__ == 'x'
-
-
-def test_named_signals():
-    sig = NamedSignal('squiznart')
-    assert 'squiznart' in repr(sig)
-
-
-def values_are_empty_sets_(dictionary):
-    for val in dictionary.itervalues():
-        eq_(val, set())

File tests/validation/test_signals.py

 def test_validator_validated():
     sentinel = []
 
-    def listener(**kw):
+    def listener(sender, **kw):
+        kw['sender'] = sender
         sentinel.append(kw)
 
     signals.validator_validated.connect(listener)
     eq_(sentinel, [dict(sender=NotEmpty, element=el,
                         state=None, result=True)])
 
-    del listener
-    del sentinel[:]
-    el = schema('squiznart')
-    assert not el.validate()
-    assert not sentinel
-
     signals.validator_validated._clear_state()