Ticket #29: sugar-dispatcher.patch

File sugar-dispatcher.patch, 22.7 KB (added by erikos, 14 years ago)

the sugar dispatcher is used by the NM connections

  • configure.ac

    diff --git a/configure.ac b/configure.ac
    index 9400a88..a0f4330 100644
    a b AC_OUTPUT([ 
    3131Makefile
    3232src/Makefile
    3333src/sugar/Makefile
     34src/sugar/dispatch/Makefile
    3435po/Makefile.in
    3536])
  • new file po/.gitignore

    diff --git a/po/.gitignore b/po/.gitignore
    new file mode 100644
    index 0000000..becd153
    - +  
     1*.gmo
  • src/sugar/Makefile.am

    diff --git a/src/sugar/Makefile.am b/src/sugar/Makefile.am
    index 4da4a1e..e6db230 100644
    a b  
     1SUBDIRS = dispatch
     2
    13INCLUDES = -DXDG_PREFIX=sugar_mime
    24
    35sugardir = $(pythondir)/sugar
  • new file src/sugar/dispatch/Makefile.am

    diff --git a/src/sugar/dispatch/Makefile.am b/src/sugar/dispatch/Makefile.am
    new file mode 100644
    index 0000000..8b0e9e6
    - +  
     1sugardir = $(pythondir)/sugar/dispatch
     2sugar_PYTHON =          \
     3        __init__.py     \
     4        dispatcher.py   \
     5        saferef.py
  • new file src/sugar/dispatch/__init__.py

    diff --git a/src/sugar/dispatch/__init__.py b/src/sugar/dispatch/__init__.py
    new file mode 100644
    index 0000000..776b1bc
    - +  
     1"""Multi-consumer multi-producer dispatching mechanism
     2
     3Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
     4See license.txt for original license.
     5
     6Heavily modified for Django's purposes.
     7"""
     8
     9from sugar.dispatch.dispatcher import Signal
  • new file src/sugar/dispatch/dispatcher.py

    diff --git a/src/sugar/dispatch/dispatcher.py b/src/sugar/dispatch/dispatcher.py
    new file mode 100644
    index 0000000..9e7efc1
    - +  
     1import weakref
     2try:
     3    set
     4except NameError:
     5    from sets import Set as set # Python 2.3 fallback
     6
     7from sugar.dispatch import saferef
     8
     9WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
     10
     11def _make_id(target):
     12    if hasattr(target, 'im_func'):
     13        return (id(target.im_self), id(target.im_func))
     14    return id(target)
     15
     16class Signal(object):
     17    """Base class for all signals
     18   
     19    Internal attributes:
     20        receivers -- { receriverkey (id) : weakref(receiver) }
     21    """
     22   
     23    def __init__(self, providing_args=None):
     24        """providing_args -- A list of the arguments this signal can pass along in
     25                       a send() call.
     26        """
     27        self.receivers = []
     28        if providing_args is None:
     29            providing_args = []
     30        self.providing_args = set(providing_args)
     31
     32    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
     33        """Connect receiver to sender for signal
     34   
     35        receiver -- a function or an instance method which is to
     36            receive signals.  Receivers must be
     37            hashable objects.
     38
     39            if weak is True, then receiver must be weak-referencable
     40            (more precisely saferef.safeRef() must be able to create
     41            a reference to the receiver).
     42       
     43            Receivers must be able to accept keyword arguments.
     44
     45            If receivers have a dispatch_uid attribute, the receiver will
     46              not be added if another receiver already exists with that
     47              dispatch_uid.
     48
     49        sender -- the sender to which the receiver should respond
     50            Must either be of type Signal, or None to receive events
     51            from any sender.
     52
     53        weak -- whether to use weak references to the receiver
     54            By default, the module will attempt to use weak
     55            references to the receiver objects.  If this parameter
     56            is false, then strong references will be used.
     57       
     58        dispatch_uid -- an identifier used to uniquely identify a particular
     59            instance of a receiver. This will usually be a string, though it
     60            may be anything hashable.
     61
     62        returns None
     63        """
     64
     65        if dispatch_uid:
     66            lookup_key = (dispatch_uid, _make_id(sender))
     67        else:
     68            lookup_key = (_make_id(receiver), _make_id(sender))
     69
     70        if weak:
     71            receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
     72
     73        for r_key, _ in self.receivers:
     74            if r_key == lookup_key:
     75                break
     76        else:
     77            self.receivers.append((lookup_key, receiver))
     78
     79    def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
     80        """Disconnect receiver from sender for signal
     81   
     82        receiver -- the registered receiver to disconnect. May be none if
     83            dispatch_uid is specified.
     84        sender -- the registered sender to disconnect
     85        weak -- the weakref state to disconnect
     86        dispatch_uid -- the unique identifier of the receiver to disconnect
     87   
     88        disconnect reverses the process of connect.
     89
     90        If weak references are used, disconnect need not be called.
     91          The receiver will be remove from dispatch automatically.
     92
     93        returns None
     94        """
     95
     96        if dispatch_uid:
     97            lookup_key = (dispatch_uid, _make_id(sender))
     98        else:
     99            lookup_key = (_make_id(receiver), _make_id(sender))
     100
     101        for idx, (r_key, _) in enumerate(self.receivers):
     102            if r_key == lookup_key:
     103                del self.receivers[idx]
     104
     105    def send(self, sender, **named):
     106        """Send signal from sender to all connected receivers.
     107
     108        sender -- the sender of the signal
     109            Either a specific object or None.
     110   
     111        named -- named arguments which will be passed to receivers.
     112
     113        Returns a list of tuple pairs [(receiver, response), ... ].
     114
     115        If any receiver raises an error, the error propagates back
     116        through send, terminating the dispatch loop, so it is quite
     117        possible to not have all receivers called if a raises an
     118        error.
     119        """
     120
     121        responses = []
     122        if not self.receivers:
     123            return responses
     124
     125        for receiver in self._live_receivers(_make_id(sender)):
     126            response = receiver(signal=self, sender=sender, **named)
     127            responses.append((receiver, response))
     128        return responses
     129
     130    def send_robust(self, sender, **named):
     131        """Send signal from sender to all connected receivers catching errors
     132
     133        sender -- the sender of the signal
     134            Can be any python object (normally one registered with
     135            a connect if you actually want something to occur).
     136
     137        named -- named arguments which will be passed to receivers.
     138            These arguments must be a subset of the argument names
     139            defined in providing_args.
     140
     141        Return a list of tuple pairs [(receiver, response), ... ],
     142        may raise DispatcherKeyError
     143
     144        if any receiver raises an error (specifically any subclass of Exception),
     145        the error instance is returned as the result for that receiver.
     146        """
     147
     148        responses = []
     149        if not self.receivers:
     150            return responses
     151
     152        # Call each receiver with whatever arguments it can accept.
     153        # Return a list of tuple pairs [(receiver, response), ... ].
     154        for receiver in self._live_receivers(_make_id(sender)):
     155            try:
     156                response = receiver(signal=self, sender=sender, **named)
     157            except Exception, err:
     158                responses.append((receiver, err))
     159            else:
     160                responses.append((receiver, response))
     161        return responses
     162
     163    def _live_receivers(self, senderkey):
     164        """Filter sequence of receivers to get resolved, live receivers
     165
     166        This checks for weak references
     167        and resolves them, then returning only live
     168        receivers.
     169        """
     170        none_senderkey = _make_id(None)
     171
     172        for (receiverkey, r_senderkey), receiver in self.receivers:
     173            if r_senderkey == none_senderkey or r_senderkey == senderkey:
     174                if isinstance(receiver, WEAKREF_TYPES):
     175                    # Dereference the weak reference.
     176                    receiver = receiver()
     177                    if receiver is not None:
     178                        yield receiver
     179                else:
     180                    yield receiver
     181
     182    def _remove_receiver(self, receiver):
     183        """Remove dead receivers from connections."""
     184
     185        to_remove = []
     186        for key, connected_receiver in self.receivers:
     187            if connected_receiver == receiver:
     188                to_remove.append(key)
     189        for key in to_remove:
     190            for idx, (r_key, _) in enumerate(self.receivers):
     191                if r_key == key:
     192                    del self.receivers[idx]
  • new file src/sugar/dispatch/license.txt

    diff --git a/src/sugar/dispatch/license.txt b/src/sugar/dispatch/license.txt
    new file mode 100644
    index 0000000..0272c28
    - +  
     1sugar.dispatch was originally forked from django.dispatch
     2
     3Copyright (c) Django Software Foundation and individual contributors.
     4All rights reserved.
     5
     6Redistribution and use in source and binary forms, with or without modification,
     7are permitted provided that the following conditions are met:
     8
     9    1. Redistributions of source code must retain the above copyright notice,
     10       this list of conditions and the following disclaimer.
     11   
     12    2. Redistributions in binary form must reproduce the above copyright
     13       notice, this list of conditions and the following disclaimer in the
     14       documentation and/or other materials provided with the distribution.
     15
     16    3. Neither the name of Django nor the names of its contributors may be used
     17       to endorse or promote products derived from this software without
     18       specific prior written permission.
     19
     20THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     21ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     22WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     24ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     27ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     29SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30
     31django.dispatch was originally forked from PyDispatcher.
     32
     33PyDispatcher License:
     34
     35    Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
     36    All rights reserved.
     37   
     38    Redistribution and use in source and binary forms, with or without
     39    modification, are permitted provided that the following conditions
     40    are met:
     41   
     42        Redistributions of source code must retain the above copyright
     43        notice, this list of conditions and the following disclaimer.
     44   
     45        Redistributions in binary form must reproduce the above
     46        copyright notice, this list of conditions and the following
     47        disclaimer in the documentation and/or other materials
     48        provided with the distribution.
     49   
     50        The name of Patrick K. O'Brien, or the name of any Contributor,
     51        may not be used to endorse or promote products derived from this
     52        software without specific prior written permission.
     53   
     54    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     55    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     56    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
     57    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
     58    COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
     59    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     60    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     61    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     62    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
     63    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     64    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
     65    OF THE POSSIBILITY OF SUCH DAMAGE.
     66
  • new file src/sugar/dispatch/saferef.py

    diff --git a/src/sugar/dispatch/saferef.py b/src/sugar/dispatch/saferef.py
    new file mode 100644
    index 0000000..8bcfd8a
    - +  
     1"""
     2"Safe weakrefs", originally from pyDispatcher.
     3
     4Provides a way to safely weakref any function, including bound methods (which
     5aren't handled by the core weakref module).
     6"""
     7
     8import weakref, traceback
     9
     10def safeRef(target, onDelete = None):
     11    """Return a *safe* weak reference to a callable target
     12
     13    target -- the object to be weakly referenced, if it's a
     14        bound method reference, will create a BoundMethodWeakref,
     15        otherwise creates a simple weakref.
     16    onDelete -- if provided, will have a hard reference stored
     17        to the callable to be called after the safe reference
     18        goes out of scope with the reference object, (either a
     19        weakref or a BoundMethodWeakref) as argument.
     20    """
     21    if hasattr(target, 'im_self'):
     22        if target.im_self is not None:
     23            # Turn a bound method into a BoundMethodWeakref instance.
     24            # Keep track of these instances for lookup by disconnect().
     25            assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,)
     26            reference = get_bound_method_weakref(
     27                target=target,
     28                onDelete=onDelete
     29            )
     30            return reference
     31    if callable(onDelete):
     32        return weakref.ref(target, onDelete)
     33    else:
     34        return weakref.ref( target )
     35
     36class BoundMethodWeakref(object):
     37    """'Safe' and reusable weak references to instance methods
     38
     39    BoundMethodWeakref objects provide a mechanism for
     40    referencing a bound method without requiring that the
     41    method object itself (which is normally a transient
     42    object) is kept alive.  Instead, the BoundMethodWeakref
     43    object keeps weak references to both the object and the
     44    function which together define the instance method.
     45
     46    Attributes:
     47        key -- the identity key for the reference, calculated
     48            by the class's calculateKey method applied to the
     49            target instance method
     50        deletionMethods -- sequence of callable objects taking
     51            single argument, a reference to this object which
     52            will be called when *either* the target object or
     53            target function is garbage collected (i.e. when
     54            this object becomes invalid).  These are specified
     55            as the onDelete parameters of safeRef calls.
     56        weakSelf -- weak reference to the target object
     57        weakFunc -- weak reference to the target function
     58
     59    Class Attributes:
     60        _allInstances -- class attribute pointing to all live
     61            BoundMethodWeakref objects indexed by the class's
     62            calculateKey(target) method applied to the target
     63            objects.  This weak value dictionary is used to
     64            short-circuit creation so that multiple references
     65            to the same (object, function) pair produce the
     66            same BoundMethodWeakref instance.
     67
     68    """
     69   
     70    _allInstances = weakref.WeakValueDictionary()
     71   
     72    def __new__( cls, target, onDelete=None, *arguments,**named ):
     73        """Create new instance or return current instance
     74
     75        Basically this method of construction allows us to
     76        short-circuit creation of references to already-
     77        referenced instance methods.  The key corresponding
     78        to the target is calculated, and if there is already
     79        an existing reference, that is returned, with its
     80        deletionMethods attribute updated.  Otherwise the
     81        new instance is created and registered in the table
     82        of already-referenced methods.
     83        """
     84        key = cls.calculateKey(target)
     85        current =cls._allInstances.get(key)
     86        if current is not None:
     87            current.deletionMethods.append( onDelete)
     88            return current
     89        else:
     90            base = super( BoundMethodWeakref, cls).__new__( cls )
     91            cls._allInstances[key] = base
     92            base.__init__( target, onDelete, *arguments,**named)
     93            return base
     94   
     95    def __init__(self, target, onDelete=None):
     96        """Return a weak-reference-like instance for a bound method
     97
     98        target -- the instance-method target for the weak
     99            reference, must have im_self and im_func attributes
     100            and be reconstructable via:
     101                target.im_func.__get__( target.im_self )
     102            which is true of built-in instance methods.
     103        onDelete -- optional callback which will be called
     104            when this weak reference ceases to be valid
     105            (i.e. either the object or the function is garbage
     106            collected).  Should take a single argument,
     107            which will be passed a pointer to this object.
     108        """
     109        def remove(weak, self=self):
     110            """Set self.isDead to true when method or instance is destroyed"""
     111            methods = self.deletionMethods[:]
     112            del self.deletionMethods[:]
     113            try:
     114                del self.__class__._allInstances[ self.key ]
     115            except KeyError:
     116                pass
     117            for function in methods:
     118                try:
     119                    if callable( function ):
     120                        function( self )
     121                except Exception, e:
     122                    try:
     123                        traceback.print_exc()
     124                    except AttributeError, err:
     125                        print '''Exception during saferef %s cleanup function %s: %s'''%(
     126                            self, function, e
     127                        )
     128        self.deletionMethods = [onDelete]
     129        self.key = self.calculateKey( target )
     130        self.weakSelf = weakref.ref(target.im_self, remove)
     131        self.weakFunc = weakref.ref(target.im_func, remove)
     132        self.selfName = str(target.im_self)
     133        self.funcName = str(target.im_func.__name__)
     134   
     135    def calculateKey( cls, target ):
     136        """Calculate the reference key for this reference
     137
     138        Currently this is a two-tuple of the id()'s of the
     139        target object and the target function respectively.
     140        """
     141        return (id(target.im_self),id(target.im_func))
     142    calculateKey = classmethod( calculateKey )
     143   
     144    def __str__(self):
     145        """Give a friendly representation of the object"""
     146        return """%s( %s.%s )"""%(
     147            self.__class__.__name__,
     148            self.selfName,
     149            self.funcName,
     150        )
     151   
     152    __repr__ = __str__
     153   
     154    def __nonzero__( self ):
     155        """Whether we are still a valid reference"""
     156        return self() is not None
     157   
     158    def __cmp__( self, other ):
     159        """Compare with another reference"""
     160        if not isinstance (other,self.__class__):
     161            return cmp( self.__class__, type(other) )
     162        return cmp( self.key, other.key)
     163   
     164    def __call__(self):
     165        """Return a strong reference to the bound method
     166
     167        If the target cannot be retrieved, then will
     168        return None, otherwise returns a bound instance
     169        method for our object and function.
     170
     171        Note:
     172            You may call this method any number of times,
     173            as it does not invalidate the reference.
     174        """
     175        target = self.weakSelf()
     176        if target is not None:
     177            function = self.weakFunc()
     178            if function is not None:
     179                return function.__get__(target)
     180        return None
     181
     182class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
     183    """A specialized BoundMethodWeakref, for platforms where instance methods
     184    are not descriptors.
     185
     186    It assumes that the function name and the target attribute name are the
     187    same, instead of assuming that the function is a descriptor. This approach
     188    is equally fast, but not 100% reliable because functions can be stored on an
     189    attribute named differenty than the function's name such as in:
     190
     191    class A: pass
     192    def foo(self): return "foo"
     193    A.bar = foo
     194
     195    But this shouldn't be a common use case. So, on platforms where methods
     196    aren't descriptors (such as Jython) this implementation has the advantage
     197    of working in the most cases.
     198    """
     199    def __init__(self, target, onDelete=None):
     200        """Return a weak-reference-like instance for a bound method
     201
     202        target -- the instance-method target for the weak
     203            reference, must have im_self and im_func attributes
     204            and be reconstructable via:
     205                target.im_func.__get__( target.im_self )
     206            which is true of built-in instance methods.
     207        onDelete -- optional callback which will be called
     208            when this weak reference ceases to be valid
     209            (i.e. either the object or the function is garbage
     210            collected).  Should take a single argument,
     211            which will be passed a pointer to this object.
     212        """
     213        assert getattr(target.im_self, target.__name__) == target, \
     214               ("method %s isn't available as the attribute %s of %s" %
     215                (target, target.__name__, target.im_self))
     216        super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete)
     217
     218    def __call__(self):
     219        """Return a strong reference to the bound method
     220
     221        If the target cannot be retrieved, then will
     222        return None, otherwise returns a bound instance
     223        method for our object and function.
     224
     225        Note:
     226            You may call this method any number of times,
     227            as it does not invalidate the reference.
     228        """
     229        target = self.weakSelf()
     230        if target is not None:
     231            function = self.weakFunc()
     232            if function is not None:
     233                # Using curry() would be another option, but it erases the
     234                # "signature" of the function. That is, after a function is
     235                # curried, the inspect module can't be used to determine how
     236                # many arguments the function expects, nor what keyword
     237                # arguments it supports, and pydispatcher needs this
     238                # information.
     239                return getattr(target, function.__name__)
     240        return None
     241
     242def get_bound_method_weakref(target, onDelete):
     243    """Instantiates the appropiate BoundMethodWeakRef, depending on the details of
     244    the underlying class method implementation"""
     245    if hasattr(target, '__get__'):
     246        # target method is a descriptor, so the default implementation works:
     247        return BoundMethodWeakref(target=target, onDelete=onDelete)
     248    else:
     249        # no luck, use the alternative implementation:
     250        return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)