Description
Work around
Decorate the class with mute_signals
, and additionally override _after_postgeneration
and mute signals there too.
@mute_signals(signals.pre_save, signals.post_save)
@register(_name="subscription_no_signals")
class SubscriptionNoSignalsFactory(SubscriptionFactory):
@classmethod
def _after_postgeneration(cls, *args, **kwargs):
with mute_signals(signals.pre_save, signals.post_save):
super()._after_postgeneration(*args, **kwargs)
Problem description
I have the following factory definition using the mute_signals decorator:
@register(_name="subscription_no_signals")
@mute_signals(signals.pre_save, signals.post_save)
class SubscriptionNoSignalsFactory(SubscriptionFactory):
@classmethod
def _create(cls, *args, **kwargs):
print("what the &$#*")
# super().create(*args, **kwargs)
from core.models import Subscription
print(f"{signals.post_save._live_receivers(Subscription)}")
print(f"{signals.pre_save._live_receivers(Subscription)}")
return super()._create(*args, **kwargs)
NOTE: I also attempted changing the order of the decorators.
And the following unit test:
@pytest.mark.parametrize("subscription_no_signals__complete", [False])
def test_subscription_too_old_to_mark_complete(subscription_no_signals):
subscription = subscription_no_signals
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert subscription.pk in email.subject
assert subscription.pk in email.body
assert "too old" in email.body
The test fails with an error as a result of signals being executed when they shouldn't be. The following output is captured from the print statements in _create
. The empty lists being printed show that at the time of _create
, no signals are connected. The "unique" message ("what the...") ensures this output is not coming from anywhere else.:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> captured stdout >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
what the &$#*
[]
[]
I also poked around in pdb and found that pytest-factoryboy is saving the model with signals connected.
###########################################
# Signals are connected now. Why? They should be disconnected.
###########################################
> /Users/me/dev/my-project/754-get-rid-of-hardcoded-usernames/core/signals.py(106)subscription_changed()
(Pdb) from django.db.models import signals
(Pdb) from core.models import Subscription
(Pdb) signals.post_save._live_receivers(Subscription)
[<bound method HistoricalRecords.post_save of <simple_history.models.HistoricalRecords object at 0x123047490>>, <function subscription_changed at 0x1314039d0>, <function redcap_subscription_changed at 0x1314099d0>]
> /Users/me/dev/my-project/754-get-rid-of-hardcoded-usernames/core/signals.py(106)subscription_changed()
-> instance.check_complete()
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/django/dispatch/dispatcher.py(174)<listcomp>()
-> (receiver, receiver(signal=self, sender=sender, **named))
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/django/dispatch/dispatcher.py(173)send()
-> return [
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/django/db/models/base.py(791)save_base()
-> post_save.send(
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/django/db/models/base.py(743)save()
-> self.save_base(using=using, force_insert=force_insert,
(Pdb) up
##############################
# NOTE: We are using the "NoSignals" factory
##############################
-> factory._after_postgeneration(obj, create=True, results=results)
(Pdb) factory
<class 'conftest.SubscriptionNoSignalsFactory'>
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/factory/django.py(192)_after_postgeneration()
-> instance.save()
(Pdb) up
###################################
# pytest-factoryboy seems to be the culprit
###################################
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/pytest_factoryboy/plugin.py(89)after_postgeneration()
-> factory._after_postgeneration(obj, create=True, results=results)
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/pytest_factoryboy/plugin.py(104)evaluate()
-> self.after_postgeneration(request)
(Pdb) up
> /opt/homebrew/Caskroom/miniconda/base/envs/my-project-dev/lib/python3.8/site-packages/pytest_factoryboy/fixture.py(320)model_fixture()
-> factoryboy_request.evaluate(request)
(Pdb) request
<SubRequest 'subscription_no_signals' for <Function test_subscription_too_old_to_mark_complete[study__end_date0-study__start_date0-subscription_no_signals__start_date0-subscription_no_signals__end_date0-False]>>
So it seems mute_signals is broken by pytest-factoryboy. Are there any workarounds?