Ticket #9015: cc63e2f848524b3e5a994a85891f8212158c13b4.patch

File cc63e2f848524b3e5a994a85891f8212158c13b4.patch, 9.1 KB (added by Jannis Leidel, 8 years ago)

Enabling the connect to act as a decorator

  • django/dispatch/dispatcher.py

    From cc63e2f848524b3e5a994a85891f8212158c13b4 Mon Sep 17 00:00:00 2001
    From: Jannis Leidel <jannis@leidel.info>
    Date: Sun, 1 May 2011 23:55:44 +0200
    Subject: [PATCH] Fixed #9015 -- Extended the signal decorator interface by allowing using the Signal.connect method as a decorator.
    
    ---
     django/dispatch/dispatcher.py     |   56 ++++++++++++++++++++++--------------
     docs/topics/signals.txt           |   12 ++++++++
     tests/modeltests/signals/tests.py |   21 ++++++++++++-
     3 files changed, 65 insertions(+), 24 deletions(-)
    
    diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
    index ed9da57..305b2ba 100644
    a b def _make_id(target): 
    1313class Signal(object):
    1414    """
    1515    Base class for all signals
    16    
     16
    1717    Internal attributes:
    18    
     18
    1919        receivers
    2020            { receriverkey (id) : weakref(receiver) }
    2121    """
    22    
     22
    2323    def __init__(self, providing_args=None):
    2424        """
    2525        Create a new signal.
    26        
     26
    2727        providing_args
    2828            A list of the arguments this signal can pass along in a send() call.
    2929        """
    class Signal(object): 
    3333        self.providing_args = set(providing_args)
    3434        self.lock = threading.Lock()
    3535
    36     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
     36    def connect(self, receiver=None, *args, **kwargs):
    3737        """
    3838        Connect receiver to sender for signal.
    39    
     39
    4040        Arguments:
    41        
     41
    4242            receiver
    4343                A function or an instance method which is to receive signals.
    4444                Receivers must be hashable objects.
    class Signal(object): 
    4646                If weak is True, then receiver must be weak-referencable (more
    4747                precisely saferef.safeRef() must be able to create a reference
    4848                to the receiver).
    49        
     49
    5050                Receivers must be able to accept keyword arguments.
    5151
    5252                If receivers have a dispatch_uid attribute, the receiver will
    class Signal(object): 
    6262                module will attempt to use weak references to the receiver
    6363                objects. If this parameter is false, then strong references will
    6464                be used.
    65        
     65
    6666            dispatch_uid
    6767                An identifier used to uniquely identify a particular instance of
    6868                a receiver. This will usually be a string, though it may be
    6969                anything hashable.
    7070        """
     71        # ``connect`` is being called with a function.
     72        if receiver:
     73            self._connect(receiver, *args, **kwargs)
     74            # Return the receiver, if being used for decoration.
     75            return receiver
     76        def wrapper(receiver):
     77            self._connect(receiver, *args, **kwargs)
     78            return receiver
     79        # Return this wrapper so that it can be used to decorate a function.
     80        return wrapper
     81
     82    def _connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
    7183        from django.conf import settings
    72        
     84
    7385        # If DEBUG is on, check that we got a good receiver
    7486        if settings.DEBUG:
    7587            import inspect
    7688            assert callable(receiver), "Signal receivers must be callable."
    77            
     89
    7890            # Check for **kwargs
    7991            # Not all callables are inspectable with getargspec, so we'll
    8092            # try a couple different ways but in the end fall back on assuming
    class Signal(object): 
    90102            if argspec:
    91103                assert argspec[2] is not None, \
    92104                    "Signal receivers must accept keyword arguments (**kwargs)."
    93        
     105
    94106        if dispatch_uid:
    95107            lookup_key = (dispatch_uid, _make_id(sender))
    96108        else:
    class Signal(object): 
    115127
    116128        If weak references are used, disconnect need not be called. The receiver
    117129        will be remove from dispatch automatically.
    118    
     130
    119131        Arguments:
    120        
     132
    121133            receiver
    122134                The registered receiver to disconnect. May be none if
    123135                dispatch_uid is specified.
    124            
     136
    125137            sender
    126138                The registered sender to disconnect
    127            
     139
    128140            weak
    129141                The weakref state to disconnect
    130            
     142
    131143            dispatch_uid
    132144                the unique identifier of the receiver to disconnect
    133145        """
    class Signal(object): 
    135147            lookup_key = (dispatch_uid, _make_id(sender))
    136148        else:
    137149            lookup_key = (_make_id(receiver), _make_id(sender))
    138        
     150
    139151        self.lock.acquire()
    140152        try:
    141153            for index in xrange(len(self.receivers)):
    class Signal(object): 
    155167        receivers called if a raises an error.
    156168
    157169        Arguments:
    158        
     170
    159171            sender
    160172                The sender of the signal Either a specific object or None.
    161    
     173
    162174            named
    163175                Named arguments which will be passed to receivers.
    164176
    class Signal(object): 
    178190        Send signal from sender to all connected receivers catching errors.
    179191
    180192        Arguments:
    181        
     193
    182194            sender
    183195                The sender of the signal. Can be any python object (normally one
    184196                registered with a connect if you actually want something to
    def receiver(signal, **kwargs): 
    265277
    266278    """
    267279    def _decorator(func):
    268         signal.connect(func, **kwargs)
     280        signal._connect(func, **kwargs)
    269281        return func
    270282    return _decorator
  • docs/topics/signals.txt

    diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt
    index 64ce202..00f9b0a 100644
    a b Now, our ``my_callback`` function will be called each time a request finishes. 
    133133
    134134The ``receiver`` decorator was added in Django 1.3.
    135135
     136.. versionadded:: 1.4
     137
     138Optionally you can use the signal as a decorator itself::
     139
     140.. code-block:: python
     141
     142    from django.core.signals import request_finished
     143
     144    @request_finished.connect
     145    def my_callback(sender, **kwargs):
     146        print "Request finished!"
     147
    136148.. admonition:: Where should this code live?
    137149
    138150    You can put signal handling and registration code anywhere you like.
  • tests/modeltests/signals/tests.py

    diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
    index 9b8bce0..e097e1e 100644
    a b class SignalTests(TestCase): 
    6060
    6161        # throw a decorator syntax receiver into the mix
    6262        @receiver(signals.pre_save)
    63         def pre_save_decorator_test(signal, sender, instance, **kwargs):
     63        def pre_save_receiver_decorator_test(signal, sender, instance, **kwargs):
    6464            data.append(instance)
    6565
    6666        @receiver(signals.pre_save, sender=Car)
     67        def pre_save_receiver_decorator_sender_test(signal, sender, instance, **kwargs):
     68            data.append(instance)
     69
     70        @signals.pre_save.connect
     71        def pre_save_decorator_test(signal, sender, instance, **kwargs):
     72            data.append(instance)
     73
     74        @signals.pre_save.connect(sender=Car)
    6775        def pre_save_decorator_sender_test(signal, sender, instance, **kwargs):
    6876            data.append(instance)
    6977
    class SignalTests(TestCase): 
    7381        self.assertEqual(data, [
    7482            (p1, False),
    7583            p1,
     84            p1,
    7685            (p1, True, False),
    7786        ])
    7887        data[:] = []
    class SignalTests(TestCase): 
    8291        self.assertEqual(data, [
    8392            (p1, False),
    8493            p1,
     94            p1,
    8595            (p1, False, False),
    8696        ])
    8797        data[:] = []
    8898
    8999        # Car signal (sender defined)
    90         c1 = Car(make="Volkswagon", model="Passat")
     100        c1 = Car(make="Volkswagen", model="Passat")
    91101        c1.save()
    92102        self.assertEqual(data, [
    93103            (c1, False),
    94104            c1,
    95105            c1,
     106            c1,
     107            c1,
    96108            (c1, True, False),
    97109        ])
    98110        data[:] = []
    class SignalTests(TestCase): 
    102114        self.assertEqual(data, [
    103115            (p1, True),
    104116            p1,
     117            p1,
    105118            (p1, False, True),
    106119        ])
    107120        data[:] = []
    class SignalTests(TestCase): 
    119132        self.assertEqual(data, [
    120133            (p2, False),
    121134            p2,
     135            p2,
    122136            (p2, True, False),
    123137        ])
    124138        data[:] = []
    class SignalTests(TestCase): 
    128142        self.assertEqual(data, [
    129143            (p2, False),
    130144            p2,
     145            p2,
    131146            (p2, True, False),
    132147        ])
    133148        data[:] = []
    class SignalTests(TestCase): 
    151166        signals.pre_save.disconnect(pre_save_test)
    152167        signals.pre_save.disconnect(pre_save_decorator_test)
    153168        signals.pre_save.disconnect(pre_save_decorator_sender_test, sender=Car)
     169        signals.pre_save.disconnect(pre_save_receiver_decorator_test)
     170        signals.pre_save.disconnect(pre_save_receiver_decorator_sender_test, sender=Car)
    154171
    155172        # Check that all our signals got disconnected properly.
    156173        post_signals = (
Back to Top