Opened 11 years ago

Closed 10 years ago

Last modified 8 years ago

#22657 closed Bug (worksforme)

Can't disconnect signal if connected using @receiver tag

Reported by: michalsicker@… Owned by: nobody
Component: Database layer (models, ORM) Version: 1.6
Severity: Normal Keywords: @receiver signal disconnect
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I got signal handler function hooked to pre_save signal using @receiver.

When I tried to disconnect that signal handler temporarily in TestCase, I couldn't achieve that. The lookup key generated in dispatcher (lookup_key = (_make_id(receiver), _make_id(sender))) in order to match function which should be disconnected has other value than it should have so the function can't be found in self.receivers and handler doesn't get disconnected.

That's probably because in self.receivers there is a lookup key generated based on "wrapper" function which had been taken to generate that lookup when registering handler, not the original one, that's why importing original function and passing it to .disconnect() doesn't work.

When signal connected without using @receiver but using pre_save.connect it works as expected.

Change History (5)

comment:1 by Tim Graham, 10 years ago

Summary: Can't disconnect signal if connected using @reeiver tagCan't disconnect signal if connected using @receiver tag

comment:2 by Baptiste Mispelon, 10 years ago

Resolution: worksforme
Status: newclosed

Hi,

I've tried to reproduce your issue but couldn't manage to get the results you're describing.

I used an empty Foo model (no fields except for the auto-generated id one) and the following code:

from django.db.models.signals import pre_save
from django.dispatch import receiver

from bug22657.models import Foo

@receiver(pre_save)
def callback(sender, **kwargs):
    print('Hello!')

Foo.objects.create()  # Prints "Hello"
pre_save.disconnect(callback)
Foo.objects.create()  # Prints nothing

I've tried both master and the 1.6 branch and I get the same result in both cases.

Could you show us a piece of code that demonstrates the issue (and reopen this ticket when you do so)?

Thanks

in reply to:  2 ; comment:3 by anonymous, 10 years ago

Replying to bmispelon:

Hi,

I just reproduced the issue. It's related to parameter sender, when I don't provide that parameter, receiver is not disconnected. I know it works for you but maybe something else influence my results, i.e. it occurs in test case. Also the function is imported from different app. But now I'm 100% sure, i'll paste my results.

code part:

pre_save.disconnect(calculate_current_equity)
for s in Subscription.objects.all():

# simulate real world behaviour - closing transactions one by one in correct order
for st in s.subscription_transactions.order_by('mt_close_time'):

st.mt_open_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_close_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_profit = Decimal(randint(1000, 100000) / 100)
st.save()

result:

File "[...]/webapp/signals/models.py", line 149, in calculate_current_equity

raise CustomRuntimeError("Closed transaction saved again, although calculations already done.")

CustomRuntimeError: Closed transaction saved again, although calculations already done.

pre_save.disconnect(calculate_current_equity, SubscriptionTransaction)
for s in Subscription.objects.all():

# simulate real world behaviour - closing transactions one by one in correct order
for st in s.subscription_transactions.order_by('mt_close_time'):

st.mt_open_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_close_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_profit = Decimal(randint(1000, 100000) / 100)
st.save()

result:
test fails from different reasons.

I know it's lame "proof", but what else I can do? Pls try to disconnect receiver in test (and maybe from another app).

Regards,
Mike

Hi,

I've tried to reproduce your issue but couldn't manage to get the results you're describing.

I used an empty Foo model (no fields except for the auto-generated id one) and the following code:

from django.db.models.signals import pre_save
from django.dispatch import receiver

from bug22657.models import Foo

@receiver(pre_save)
def callback(sender, **kwargs):
    print('Hello!')

Foo.objects.create()  # Prints "Hello"
pre_save.disconnect(callback)
Foo.objects.create()  # Prints nothing

I've tried both master and the 1.6 branch and I get the same result in both cases.

Could you show us a piece of code that demonstrates the issue (and reopen this ticket when you do so)?

Thanks

in reply to:  3 comment:4 by Aldrin Navarro, 8 years ago

Replying to anonymous:
Might be good to shed some light in this 2 year old issue.

@Mike

result:
test fails from different reasons.

One of the possible causes this signal-handler-dependent test fails is that the TestCase that issued disconnect did not reconnect the muted signal handler and TestCases that follow fail. There are a couple of things that you can do to justify/correct other independent test cases: (cleaner/recommended) reconnect on tearDown; or (works but may have side effects to other tests) reload the signal handler from the other test cases; or import the signal handler from the other test case.

Say we have too models Foo and Bar. We hook pre_save signal with sender Foo to create a Bar instance.

from django.db.models.signals import pre_save
from django.dispatch import receiver

from bug22657.models import Foo, Bar


@receiver(pre_save, sender=Foo)
def callback(sender, **kwargs):
    Bar.objects.create()
    print 'Hello!'

Then, we have two tests: test_model and test_signal.

###  test_model.py ###
from django.test import TestCase

from bug22657.models import Foo, Bar
# a safe re-import/reload of the signal handler
# in expense of an unused import
from bug22657.signals import handlers


class FooModelTestCase(TestCase):

    def test_create_model(self):
        Foo.objects.create()
        self.assertEquals(Foo.objects.count(), 1)
        self.assertEquals(Bar.objects.count(), 1)

###  test_signal.py ###
from django.db.models.signals import pre_save
from django.test import TestCase

from bug22657.models import Foo, Bar
from bug22657.signals.handlers import callback


class FooSignalTestCase(TestCase):

    def setUp(self):
        pre_save.disconnect(callback, sender=Foo)

    def test_create_signal_off(self):
        Foo.objects.create()
        self.assertEquals(Foo.objects.count(), 1)
        self.assertEquals(Bar.objects.count(), 0)
    
    #  comment this tearDown and other tests may fail
    def tearDown(self):
        pre_save.connect(callback, sender=Foo)

For the benefit of it, if you have not noticed the code comments, running the test suite will be successful. However, commenting test_signal.FooSignalTestCase.tearDown will fail the other following test cases, in this particular example the test_model will fail on its second assertion or no Bar object is created.

To avoid further failures that relates to signals, temporary disabling of signals must be use with caution if you have other test cases that depends upon the signal mechanism.

Replying to bmispelon:

Hi,

I just reproduced the issue. It's related to parameter sender, when I don't provide that parameter, receiver is not disconnected. I know it works for you but maybe something else influence my results, i.e. it occurs in test case. Also the function is imported from different app. But now I'm 100% sure, i'll paste my results.

code part:

pre_save.disconnect(calculate_current_equity)
for s in Subscription.objects.all():

# simulate real world behaviour - closing transactions one by one in correct order
for st in s.subscription_transactions.order_by('mt_close_time'):

st.mt_open_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_close_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_profit = Decimal(randint(1000, 100000) / 100)
st.save()

result:

File "[...]/webapp/signals/models.py", line 149, in calculate_current_equity

raise CustomRuntimeError("Closed transaction saved again, although calculations already done.")

CustomRuntimeError: Closed transaction saved again, although calculations already done.

pre_save.disconnect(calculate_current_equity, SubscriptionTransaction)
for s in Subscription.objects.all():

# simulate real world behaviour - closing transactions one by one in correct order
for st in s.subscription_transactions.order_by('mt_close_time'):

st.mt_open_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_close_price = QuoteGenerator.get_symbol_sl_value(st.symbol.name, st.type)
st.mt_profit = Decimal(randint(1000, 100000) / 100)
st.save()

result:
test fails from different reasons.

I know it's lame "proof", but what else I can do? Pls try to disconnect receiver in test (and maybe from another app).

Regards,
Mike

Hi,

I've tried to reproduce your issue but couldn't manage to get the results you're describing.

I used an empty Foo model (no fields except for the auto-generated id one) and the following code:

from django.db.models.signals import pre_save
from django.dispatch import receiver

from bug22657.models import Foo

@receiver(pre_save)
def callback(sender, **kwargs):
    print('Hello!')

Foo.objects.create()  # Prints "Hello"
pre_save.disconnect(callback)
Foo.objects.create()  # Prints nothing

I've tried both master and the 1.6 branch and I get the same result in both cases.

Could you show us a piece of code that demonstrates the issue (and reopen this ticket when you do so)?

Thanks

comment:5 by James Addison, 8 years ago

Note this comment made in #28169: https://code.djangoproject.com/ticket/28169#comment:1 It shows that there are cases where .disconnect() isn't as easy as it 'should' be, but it can be made to work.

Note: See TracTickets for help on using tickets.
Back to Top