Ticket #4561: dispatch-smart-signals.patch

File dispatch-smart-signals.patch, 24.8 KB (added by (removed), 17 years ago)

smart signals v1

  • django/dispatch/weaksets.py

    === added file 'django/dispatch/weaksets.py'
     
     1from weakref import WeakKeyDictionary
     2
     3class WeakSet(object):
     4    def __init__(self, initial_vals=None):
     5        self._map = WeakKeyDictionary()
     6        if initial_vals is not None:
     7            self.update(initial_vals)
     8        self.remove = self._map.__delitem__
     9        self.add = self._map.setdefault
     10
     11    def update(self, iterable):
     12        map(self._map.setdefault, iterable)
     13
     14    def __len__(self):
     15        return len(self._map)
     16
     17    def __iter__(self):
     18        return iter(self._map)
     19
     20    def discard(self, key):
     21        self._map.pop(key, None)
     22
     23    def __nonzero__(self):
     24        return bool(self._map)
     25
     26    def __repr__(self):
     27        return "WeakSet(%r)" % self._map.keys()
     28
     29    def __str__(self):
     30        return "WeakSet(%s)" % self._map.keys()
     31
     32    def __contains__(self, key):
     33        return key in self._map
  • tests/regressiontests/dispatch/tests/test_new_signals.py

    === added file 'tests/regressiontests/dispatch/tests/test_new_signals.py'
     
     1from django import dispatch
     2from django.dispatch import dispatcher
     3from unittest import TestCase
     4
     5class base(TestCase):
     6
     7    def assertIn(self, needle, haystack, msg=None):
     8        if msg is None:
     9            msg = "%s wasn't in %s" % (needle, haystack)
     10        self.assertTrue((needle in haystack), msg=msg)
     11
     12class test_signal(base):
     13
     14    kls = staticmethod(dispatch.signal)
     15
     16    def test_str(self):
     17        self.assertEqual(str(self.kls("asdf")), "asdf")
     18
     19    def test_repr(self):
     20        o = self.kls("foon")
     21        self.assertEqual(repr(o), "<signal signal='foon' @#%x>" % id(o))
     22
     23class test_mk_signal(base):
     24
     25    func = staticmethod(dispatch.mk_signal)
     26
     27    def test_it(self):
     28        o = self.func("sig1")
     29        self.assertEqual(o.__doc__, dispatch.signal.__doc__)
     30        self.assertEqual(o.signal_name, "sig1")
     31        docstring="mr. super-happy docstring says gimme candy"
     32        o = self.func("sig2", docstring=docstring)
     33        self.assertEqual(o.__doc__, docstring)
     34        self.assertEqual(o.signal_name, 'sig2')
     35        class dar(dispatch.signal):pass
     36        self.assertEqual(self.func('sig3', signal_kls=dar).__class__, dar)
     37
     38class protect_signals_state_mixin(object):
     39
     40    """mixin basically wiping the smart_signals state information clean after backing
     41    up a copy; upon completion of a fixture, restores the state.
     42   
     43    Note you cannot rely on existing connections/listeners in any tests using this mixin-
     44    as said, the state is reset, then restored around the fixture
     45    """
     46
     47    def setUp(self):
     48        self._known_sigs = dispatch.smart_signal.known_signals.items()
     49        self._any_any = dispatch.smart_signal.any_any_count
     50        self._all_known_senders = dispatch.smart_signal.all_known_senders.items()
     51        dispatch.smart_signal.known_signals.clear()
     52        dispatch.smart_signal.any_any_count = 0
     53        dispatch.smart_signal.all_known_senders.clear()
     54
     55    def tearDown(self):
     56        dispatch.smart_signal.known_signals.clear()
     57        dispatch.smart_signal.known_signals.update(self._known_sigs)
     58        dispatch.smart_signal.any_any_count = self._any_any
     59        dispatch.smart_signal.all_known_senders.clear()
     60        dispatch.smart_signal.all_known_senders.update(self._all_known_senders)
     61
     62class smart_signals_mixin(protect_signals_state_mixin):
     63
     64    def mk_signal(self, name="test-signal"):
     65        class tracking_sig(dispatch.smart_signal):
     66            def __init__(self, *args):
     67                dispatch.smart_signal.__init__(self, *args)
     68                self.enables, self.disables = [], []
     69
     70            def _enable_sender(self, sender, incr):
     71                self.enables.append((sender, incr))
     72
     73            def _disable_sender(self, sender, incr):
     74                self.disables.append((sender, incr))
     75
     76        return tracking_sig(name)
     77
     78    def mk_kls(self):
     79        class foo(object):
     80            def __init__(self, *args):
     81                self.args = args
     82
     83        return foo
     84
     85class test_smart_signal(smart_signals_mixin, base):
     86
     87    def test_behaviour(self):
     88        kls, sig = self.mk_kls(), self.mk_signal()
     89        self.assertEqual([], list(sig.known_senders))
     90        obj = kls()
     91        sig.register_sender(obj)
     92        self.assertEqual([obj], list(sig.known_senders))
     93        del obj
     94        # assert weakref'd; jython probably won't like this, but oh well
     95        self.assertEqual([], list(sig.known_senders))
     96        self.assertFalse(sig.enables)
     97        self.assertFalse(sig.disables)
     98       
     99        # force an Any in to force enables to track that it gets properly enabled, and that
     100        # _enable_sender is invoked once with the higher incr count
     101        obj = kls()
     102        sig.enable_sender(dispatch.Any)
     103        sig.register_sender(obj)
     104        self.assertEqual([(obj, 1)], sig.enables)
     105        obj2 = kls()
     106        del obj
     107        sig.enables = []
     108        sig.enable_sender(dispatch.Any)
     109
     110        # register a new instance; should be a single _enable_sender call, incr=2 due to
     111        # 2 Any senders register.
     112       
     113        sig.register_sender(obj2)
     114        self.assertEqual([(obj2, 2)], sig.enables)
     115        self.assertFalse(sig.disables)
     116
     117class test_Any(smart_signals_mixin, base):
     118
     119    def test_register_sender(self):
     120        self.assertRaises(TypeError, dispatch.Any.register_sender, self.mk_kls()())
     121
     122    def test_enable_disable_sender(self):
     123        kls = self.mk_kls()
     124        obj1, obj2 = kls(), kls()
     125        sig1, sig2 = self.mk_signal("sig1"), self.mk_signal("sig2")
     126        sig1.register_sender(obj1)
     127        sig2.register_sender(obj2)
     128
     129        self.assertFalse(sig1.enables)
     130        self.assertFalse(sig2.enables)
     131
     132        # first test it's behaviour for a signal=Any, sender=specific
     133        dispatch.Any.enable_sender(obj1)
     134        self.assertEqual(sig1.enables, [(obj1, 1)])
     135        self.assertFalse(sig2.enables), self.assertFalse(sig2.disables)
     136        dispatch.Any.disable_sender(obj1)
     137        self.assertEqual(sig1.enables, [(obj1, 1)])
     138        self.assertEqual(sig1.disables, [(obj1, 1)])
     139        self.assertFalse(sig2.enables), self.assertFalse(sig2.disables)
     140
     141        dispatch.Any.enable_sender(obj2)
     142        self.assertEqual(sig2.enables, [(obj2, 1)])       
     143        dispatch.Any.disable_sender(obj2)
     144        self.assertEqual(sig2.disables, [(obj2, 1)])       
     145        sig1.enables, sig1.disables = [], []
     146        sig2.enables, sig2.disables = [], []
     147
     148        # now test signal=Any, sender=Any behaviour.
     149        dispatch.Any.enable_sender(dispatch.Any)
     150        self.assertEqual(sig1.enables, [(obj1, 1)])
     151        self.assertEqual(sig2.enables, [(obj2, 1)])
     152        self.assertFalse(sig1.disables), self.assertFalse(sig2.disables)
     153
     154        dispatch.Any.enable_sender(dispatch.Any)
     155        self.assertEqual(sig1.enables, [(obj1, 1)]*2)
     156        self.assertEqual(sig2.enables, [(obj2, 1)]*2)
     157
     158        dispatch.Any.disable_sender(dispatch.Any, 2)
     159        self.assertEqual(sig1.disables, [(obj1, 2)])
     160        self.assertEqual(sig2.disables, [(obj2, 2)])
  • tests/regressiontests/dispatch/tests/test_weakset.py

    === added file 'tests/regressiontests/dispatch/tests/test_weakset.py'
     
     1from django.dispatch.weaksets import WeakSet
     2from unittest import TestCase
     3
     4if not hasattr(__builtins__, 'set'):
     5    from sets import Set as set
     6
     7class test_WeakSet(TestCase):
     8
     9    def mk_obj(self):
     10        # can't weakref base object instances, hence subclassing
     11        class foon(object):
     12            pass
     13        return foon()
     14
     15    def test_basics(self):
     16        ws = WeakSet()
     17        self.assertEqual(len(ws), 0)
     18        ws.add(self.mk_obj())
     19        # should've gone out of scope immediately, since the only referant would be ws,
     20        # which *better* be weakref'ing it.
     21        self.assertEqual(len(ws), 0)
     22        self.assertFalse(ws)
     23        obj = self.mk_obj()
     24        ws.add(obj)
     25        self.assertEqual(len(ws), 1)
     26        self.assertTrue(ws)
     27        self.assertEqual(list(ws), [obj])
     28        obj2 = self.mk_obj()
     29        ws.add(obj2)
     30        self.assertEqual(set(ws), set([obj, obj2]))
     31       
     32        del obj
     33        self.assertEqual(list(ws), [obj2])
     34        del obj2
     35        self.assertFalse(ws)
     36
     37        obj = self.mk_obj()
     38        # check that it accepts an iterable
     39        self.assertEqual(list(WeakSet([obj])), [obj])
     40        ws.discard(obj)
     41        self.assertFalse(ws)
     42        # no exception
     43        ws.discard(obj)
     44        ws.add(obj)
     45        self.assertTrue(obj)
     46        ws.remove(obj)
     47        self.assertFalse(ws)
     48        # keyerror
     49        self.assertRaises(KeyError, ws.remove, obj)
     50
     51        # finally, update
     52        objs = [self.mk_obj() for x in xrange(3)]
     53        ws.update(objs)
     54        self.assertEqual(set(ws), set(objs))
     55
     56        self.assertTrue(objs[0] in ws)
     57        self.assertFalse(self in ws)
  • django/db/models/base.py

    === modified file 'django/db/models/base.py'
     
    6969
    7070        new_class._prepare()
    7171
     72        # now we register the class with the signals it can have turned on;
     73        signals.pre_init.register_sender(new_class)
     74        signals.post_init.register_sender(new_class)
     75        signals.pre_save.register_sender(new_class)
     76        signals.post_save.register_sender(new_class)
     77
    7278        register_models(new_class._meta.app_label, new_class)
    7379        # Because of the way imports happen (recursively), we may or may not be
    7480        # the first class for this model to register with the framework. There
     
    95101        return not self.__eq__(other)
    96102
    97103    def __init__(self, *args, **kwargs):
    98         dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
    99104       
    100105        # There is a rather weird disparity here; if kwargs, it's set, then args
    101106        # overrides it. It should be one or the other; don't duplicate the work
     
    164169                    pass
    165170            if kwargs:
    166171                raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
    167         dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self)
    168172
    169173    def add_to_class(cls, name, value):
    170174        if name == 'Admin':
     
    199203    _prepare = classmethod(_prepare)
    200204
    201205    def save(self):
    202         dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
    203 
    204206        non_pks = [f for f in self._meta.fields if not f.primary_key]
    205207        cursor = connection.cursor()
    206208
     
    252254        transaction.commit_unless_managed()
    253255
    254256        # Run any post-save hooks.
    255         dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
    256257
    257258    save.alters_data = True
    258259
  • django/db/models/signals.py

    === modified file 'django/db/models/signals.py'
     
    1 class_prepared = object()
    2 
    3 pre_init= object()
    4 post_init = object()
    5 
    6 pre_save = object()
    7 post_save = object()
    8 
    9 pre_delete = object()
    10 post_delete = object()
    11 
    12 post_syncdb = object()
     1from django.dispatch import mk_signal, mk_pre_post_signals
     2from django.dispatch.dispatcher import send
     3
     4class_prepared = mk_signal('class_prepared')
     5
     6class _init_mixin(object):
     7    """one of two signals fired when a new model instance is created
     8
     9    pre_init is fired when the instance is being created, and is passed two keyword args:
     10      @param args: positional args passed to __init__
     11      @param kwargs: dictionary of keyword args passed to __init__
     12   
     13    post_init is fired after the instance is fully created, and is passed a signal keyword arg:
     14      @param instance: newly created instance
     15    """
     16
     17    def _pre_replacement(self, func, sender):
     18        def wrapped(internal_self, *args, **kwargs):
     19            send(signal=pre_init, sender=internal_self.__class__, args=args, kwargs=kwargs)
     20            func(internal_self, *args, **kwargs)
     21        return wrapped
     22
     23    def _post_replacement(self, func, sender):
     24        def wrapped(internal_self, *args, **kwargs):
     25            func(internal_self, *args, **kwargs)
     26            send(signal=post_init, sender=internal_self.__class__, instance=internal_self)
     27        return wrapped
     28       
     29    def _both_replacement(self, func, sender):
     30        def wrapped(internal_self, *args, **kwargs):
     31            send(signal=pre_init, sender=internal_self.__class__, args=args, kwargs=kwargs)
     32            func(internal_self, *args, **kwargs)
     33            send(signal=post_init, sender=internal_self.__class__, instance=internal_self)
     34        return wrapped
     35
     36pre_init, post_init = mk_pre_post_signals('__init__', 'pre_init', 'post_init', _init_mixin)
     37
     38class _save_mixin(object):
     39
     40    def _pre_replacement(self, func, sender):
     41        def wrapped(internal_self, *args, **kwargs):
     42            send(signal=pre_save, sender=internal_self.__class__, instance=internal_self)
     43            func(internal_self, *args, **kwargs)
     44        wrapped.alters_data = True
     45        return wrapped
     46
     47    def _post_replacement(self, func, sender):
     48        def wrapped(internal_self, *args, **kwargs):
     49            func(internal_self, *args, **kwargs)
     50            send(signal=post_save, sender=internal_self.__class__, instance=internal_self)
     51        wrapped.alters_data = True
     52        return wrapped
     53
     54    def _both_replacement(self, func, sender):
     55        def wrapped(internal_self, *args, **kwargs):
     56            send(signal=pre_save, sender=internal_self.__class__, instance=internal_self)
     57            func(internal_self, *args, **kwargs)
     58            send(signal=post_save, sender=internal_self.__class__, instance=internal_self)
     59        wrapped.alters_data = True
     60        return wrapped
     61
     62pre_save, post_save = mk_pre_post_signals('save', 'pre_save', 'post_save', _save_mixin)
     63
     64pre_delete = mk_signal('pre_delete')
     65post_delete = mk_signal('post_delete')
     66
     67post_syncdb = mk_signal('post_syncdb')
  • django/dispatch/__init__.py

    === modified file 'django/dispatch/__init__.py'
     
    44__author__ = "Patrick K. O'Brien"
    55__license__ = "BSD-style, see license.txt for details"
    66
     7from weakref import WeakKeyDictionary
     8from weaksets import WeakSet
     9
     10class signal(object):
     11
     12    def __init__(self, signal_name):
     13        self.signal_name = signal_name
     14
     15    def __str__(self):
     16        return self.signal_name
     17
     18    def __repr__(self):
     19        return "<signal signal=%r @#%x>" % (self.signal_name, id(self))
     20
     21def mk_signal(signal_name, *args, **kwargs):
     22    docstring = kwargs.pop("docstring", None)
     23    signal_kls = kwargs.pop("signal_kls", signal)
     24    if docstring is not None:
     25        class signal_kls(signal_kls):
     26            __doc__ = docstring
     27    return signal_kls(signal_name, *args, **kwargs)
     28
     29
     30class smart_signal(signal):
     31
     32    # tracks known senders (keys), and the 'Any signal' count per sender
     33    # signal=Any, sender=sender
     34    all_known_senders = WeakKeyDictionary()
     35   
     36    # 'Any signal, Any sender' count; tracked for new signal/sender initialization
     37    any_any_count = 0
     38
     39    # tracks known signals, and the # of signal=signal, sender=Any
     40    # one exemption to this; Any is left out of known_signals.
     41    known_signals = WeakKeyDictionary()
     42   
     43    # note per instance, there is a 'known_senders'; tracks the known senders
     44    # of a specific signal (just that).  This is required to be tracked so
     45    # that a sender can't be registered twice (will trash the ref cnts)
     46
     47    def __init__(self, signal_name):
     48        signal.__init__(self, signal_name)
     49        self.known_senders = WeakSet()
     50        # set the # of sender=any, signal=self count.
     51        self.known_signals[self] = self.any_any_count
     52
     53    def register_sender(self, sender):
     54        if sender in self.known_senders:
     55            # already known; can occur for instances that have a shared known_senders.
     56            return
     57        self.known_senders.add(sender)
     58
     59        # find the incref count from the misc Any currently listening
     60        count = self.all_known_senders.setdefault(sender, 0) + self.known_signals[self]
     61        if count:
     62            self.enable_sender(sender, incr=count)
     63
     64    def enable_sender(self, sender, incr=1):
     65        if sender is Any:
     66            self.known_signals[self] += 1
     67            for sender in self.known_senders:
     68                self._enable_sender(sender, incr)
     69            return
     70        self._enable_sender(sender, incr)
     71
     72    def disable_sender(self, sender, decr=1):
     73        if sender is Any:
     74            self.known_signals[self] -= 1
     75            # on the offchance a sender goes away when nothing is listening.
     76            # *probably* not sanely possible
     77            for sender in tuple(self.known_senders):
     78                self._disable_sender(sender, decr)
     79            return
     80
     81        self._disable_sender(sender, decr)
     82
     83    def _enable_sender(self, sender, incr):
     84        raise NotImplementedError(self, '_enable_sender')
     85
     86    def _disable_sender(self, sender, decr):
     87        raise NotImplementedError(self, '_disable_sender')
     88
     89class Any(smart_signal):
     90
     91    """Singleton used to signal either "Any Sender" or "Any Signal"
     92   
     93    The Any object can be used with connect, disconnect,
     94    send, or sendExact to signal that the parameter given
     95    Any should react to all senders/signals, not just
     96    a particular sender/signal.
     97    """
     98
     99    def __init__(self):
     100        signal.__init__(self, "Any")
     101        self.known_senders = smart_signal.all_known_senders
     102
     103    def register_sender(self, sender):
     104        raise TypeError("you cannot register a sender for Any")
     105
     106    def enable_sender(self, sender, incr=1):
     107        if sender is self:
     108            for signal in self.known_signals.iterkeys():
     109                signal.enable_sender(self, incr=incr)
     110            smart_signal.any_any_count += incr
     111        else:
     112            for signal in self.known_signals.iterkeys():
     113                if sender in signal.known_senders:
     114                    signal.enable_sender(sender, incr=incr)
     115           
     116    def disable_sender(self, sender, decr=1):
     117        if sender is self:
     118            for signal in self.known_signals.iterkeys():
     119                signal.disable_sender(self, decr=decr)
     120            smart_signal.any_any_count -= decr
     121            assert smart_signal.any_any_count >= 0
     122        else:
     123            for signal in self.known_signals.iterkeys():
     124                if sender in signal.known_senders:
     125                    signal.disable_sender(sender, decr=decr)
     126
     127Any = Any()
     128
     129class _pre_post_signal(smart_signal):
     130
     131    def __init__(self, signal_name, method_name, is_pre, known_senders=None):
     132        smart_signal.__init__(self, signal_name)
     133        self._method_name = method_name
     134        if known_senders is not None:
     135            self.known_senders = known_senders
     136        self.is_pre = is_pre
     137
     138    def _enable_sender(self, sender, incr):
     139        original_func = func = getattr(sender, self._method_name)
     140        precount = getattr(original_func, "_listening_pre_refcnt", 0)
     141        postcount = getattr(original_func, "_listening_post_refcnt", 0)
     142
     143        if self.is_pre:
     144            if postcount:
     145                if not precount:
     146                    func = self._both_replacement(func._listening_wrapped, sender)
     147                    original_func = original_func._listening_wrapped
     148            elif not precount:
     149                func = self._pre_replacement(func, sender)
     150            precount += incr
     151        else:
     152            if precount:
     153                if not postcount:
     154                    func = self._both_replacement(func._listening_wrapped, sender)
     155                    original_func = original_func._listening_wrapped
     156            elif not postcount:
     157                func = self._post_replacement(func, sender)
     158            postcount += incr
     159
     160        func._listening_pre_refcnt = precount
     161        func._listening_post_refcnt = postcount
     162        func._listening_refcnt = precount + postcount
     163
     164        if func is not original_func:
     165            setattr(sender, self._method_name, func)
     166            func._listening_wrapped = original_func
     167
     168    def _disable_sender(self, sender, decr):
     169        func = original_func = getattr(sender, self._method_name)
     170
     171        # shortcut to simplify logic.
     172        if func._listening_refcnt <= decr:
     173            setattr(sender, self._method_name, func._listening_wrapped)
     174            return
     175       
     176        precount = func._listening_pre_refcnt
     177        postcount = func._listening_post_refcnt
     178       
     179        if self.is_pre:
     180            precount -= decr
     181            if not precount:
     182                func = self._post_replacement(func, sender)
     183                func._listening_refcnt = func._listening_post_refcnt = postcount
     184            func._listening_pre_refcnt = precount
     185        else:
     186            postcount -= decr
     187            if not postcount:
     188                func = self._pre_replacement(func, sender)
     189                func._listening_refcnt = func._listening_pre_refcnt = precount
     190            func._listening_post_refcnt = postcount
     191
     192        if func is not original_func:
     193            seattr(sender, self._method_name, func)
     194            func._listening_func = original_func._listening_wrapped
     195
     196    def _pre_replacement(self, original_func, sender):
     197        raise NotImplementedError(self, "_pre_replacement")
     198
     199    def _post_replacement(self, original_func, sender):
     200        raise NotImplementedError(self, "_post_replacement")
     201
     202    def _both_replacement(self, original_func, sender):
     203        raise NotImplementedError(self, "_both_replacement")
     204
     205def mk_pre_post_signals(method_name, pre_signal_name, post_signal_name, mixin_kls):
     206    class mixin(mixin_kls, _pre_post_signal):
     207        __doc__ = mixin_kls.__doc__
     208        pass
     209    pre = mixin(pre_signal_name, method_name, True)
     210    pre.__name__ = pre_signal_name
     211    post = mixin(post_signal_name, method_name, False)#
     212    post.__name__ = post_signal_name
     213    return pre, post
  • django/dispatch/dispatcher.py

    === modified file 'django/dispatch/dispatcher.py'
     
    2626        vs. the original code.)
    2727"""
    2828import types, weakref
    29 from django.dispatch import saferef, robustapply, errors
     29# Any is imported for backwards compatibility API wise
     30from django.dispatch import saferef, robustapply, errors, mk_signal, Any
    3031
    3132__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
    3233__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
    3334__version__ = "$Revision: 1.9 $"[11:-2]
    3435
    35 
    36 class _Parameter:
    37     """Used to represent default parameter values."""
    38     def __repr__(self):
    39         return self.__class__.__name__
    40 
    41 class _Any(_Parameter):
    42     """Singleton used to signal either "Any Sender" or "Any Signal"
    43 
    44     The Any object can be used with connect, disconnect,
    45     send, or sendExact to signal that the parameter given
    46     Any should react to all senders/signals, not just
    47     a particular sender/signal.
    48     """
    49 Any = _Any()
    50 
    51 class _Anonymous(_Parameter):
     36Anonymous = mk_signal('Anonymous', docstring=
    5237    """Singleton used to signal "Anonymous Sender"
    5338
    5439    The Anonymous object is used to signal that the sender
     
    6550        in either function then all messages are routed
    6651        as though there was a single sender (Anonymous)
    6752        being used everywhere.
    68     """
    69 Anonymous = _Anonymous()
     53    """)
    7054
    7155WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
    7256
     
    137121
    138122    signals = connections.setdefault(senderkey, {})
    139123
     124    enabler = getattr(signal, 'enable_sender', None)
     125    if enabler is not None:
     126        enabler(sender)
    140127    # Keep track of senders for cleanup.
    141128    # Is Anonymous something we want to clean up?
    142129    if sender not in (None, Anonymous, Any):
     
    203190        )
    204191    if weak: receiver = saferef.safeRef(receiver)
    205192    senderkey = id(sender)
     193    disabler = getattr(signal, 'disable_sender', None)
     194    if disabler is not None:
     195        disabler(sender)
    206196    try:
    207197        signals = connections[senderkey]
    208198        receivers = signals[signal]
  • tests/regressiontests/dispatch/tests/__init__.py

    === modified file 'tests/regressiontests/dispatch/tests/__init__.py'
     
    55from test_dispatcher import *
    66from test_robustapply import *
    77from test_saferef import *
     8from test_new_signals import *
     9from test_weakset import *
Back to Top