#22657 closed Bug (worksforme)
Can't disconnect signal if connected using @receiver tag
Reported by: | 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 , 10 years ago
Summary: | Can't disconnect signal if connected using @reeiver tag → Can't disconnect signal if connected using @receiver tag |
---|
follow-up: 3 comment:2 by , 10 years ago
Resolution: | → worksforme |
---|---|
Status: | new → closed |
follow-up: 4 comment:3 by , 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-generatedid
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 nothingI'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:4 by , 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-generatedid
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 nothingI'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 , 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.
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-generatedid
one) and the following code: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