FactoryBoy / factory_boy

A test fixtures replacement for Python
https://factoryboy.readthedocs.io/
MIT License
3.48k stars 392 forks source link

Regression on 3.2.1 causes RecursionError with django-dirtyfields #895

Open roniemartinez opened 2 years ago

roniemartinez commented 2 years ago

Description

A regression done in https://github.com/FactoryBoy/factory_boy/pull/799 causes a RecursionError. After upgrading from 3.2.0 to 3.2.1 the error happened.

To verify that this was really the cause of the issue, I installed 3.2.1 in current dir using pip install factory-boy -t ., modified these lines back to what it is originally in 3.2.0 and it is working again.

-            signal.receivers += receivers
+            signal.receivers = receivers

To Reproduce

Model / Factory code

Here is the model. To add, there are also signal receivers but will not include the full details.

class Company(DirtyFieldsMixin, models.Model):
    ...

@receiver(post_save, sender=Company)
def my_receiver(instance, raw, **kwargs):
    ...

class CompanyFactory(DjangoModelFactory):  # standard
    class Meta:
        model = Company
The issue

You can find the error here that happens on instance.save()

.venv/lib/python3.9/site-packages/factory/django.py:173: in _after_postgeneration
    instance.save()
src/company/models/company.py:607: in save
    return super().save(*args, **kwargs)
.venv/lib/python3.9/site-packages/safedelete/models.py:96: in save
    super(SafeDeleteModel, self).save(**kwargs)
.venv/lib/python3.9/site-packages/django/db/models/base.py:726: in save
    self.save_base(using=using, force_insert=force_insert,
.venv/lib/python3.9/site-packages/django/db/models/base.py:774: in save_base
    post_save.send(
.venv/lib/python3.9/site-packages/django/dispatch/dispatcher.py:180: in send
    return [
.venv/lib/python3.9/site-packages/django/dispatch/dispatcher.py:181: in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
.venv/lib/python3.9/site-packages/dirtyfields/dirtyfields.py:155: in reset_state
    new_state = instance._as_dict(check_relationship=True)
.venv/lib/python3.9/site-packages/dirtyfields/dirtyfields.py:94: in _as_dict
    all_field[field.name] = deepcopy(field_value)
/usr/local/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
E   RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)

Notes

Add any notes you feel relevant here :)

LincolnPuzey commented 2 years ago

@roniemartinez Can you provide the entire block of code around instance.save() that causes the stacktrace and the details of the Model fields?

roniemartinez commented 2 years ago

@LincolnPuzey

The errors aren't that helpful for me and I currently cannot replicate/isolate the source of the issue (it's a huge project). Here's the complete info.

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.10/site-packages/factory/base.py:40: in __call__
    return cls.create(**kwargs)
.venv/lib/python3.10/site-packages/factory/base.py:528: in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
.venv/lib/python3.10/site-packages/factory/django.py:117: in _generate
    return super()._generate(strategy, params)
.venv/lib/python3.10/site-packages/factory/base.py:465: in _generate
    return step.build()
.venv/lib/python3.10/site-packages/factory/builder.py:258: in build
    step.resolve(pre)
.venv/lib/python3.10/site-packages/factory/builder.py:199: in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
.venv/lib/python3.10/site-packages/factory/builder.py:344: in __getattr__
    value = value.evaluate_pre(
.venv/lib/python3.10/site-packages/factory/declarations.py:48: in evaluate_pre
    return self.evaluate(instance, step, context)
.venv/lib/python3.10/site-packages/factory/declarations.py:395: in evaluate
    return step.recurse(subfactory, extra, force_sequence=force_sequence)
.venv/lib/python3.10/site-packages/factory/builder.py:216: in recurse
    return builder.build(parent_step=self, force_sequence=force_sequence)
.venv/lib/python3.10/site-packages/factory/builder.py:271: in build
    postgen_results[declaration_name] = declaration.declaration.evaluate_post(
.venv/lib/python3.10/site-packages/factory/declarations.py:592: in evaluate_post
    return self.call(instance, step, postgen_context)
.venv/lib/python3.10/site-packages/factory/declarations.py:673: in call
    return step.recurse(factory, passed_kwargs)
.venv/lib/python3.10/site-packages/factory/builder.py:216: in recurse
    return builder.build(parent_step=self, force_sequence=force_sequence)
.venv/lib/python3.10/site-packages/factory/builder.py:258: in build
    step.resolve(pre)
.venv/lib/python3.10/site-packages/factory/builder.py:199: in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
.venv/lib/python3.10/site-packages/factory/builder.py:344: in __getattr__
    value = value.evaluate_pre(
.venv/lib/python3.10/site-packages/factory/declarations.py:48: in evaluate_pre
    return self.evaluate(instance, step, context)
.venv/lib/python3.10/site-packages/factory/declarations.py:395: in evaluate
    return step.recurse(subfactory, extra, force_sequence=force_sequence)
.venv/lib/python3.10/site-packages/factory/builder.py:216: in recurse
    return builder.build(parent_step=self, force_sequence=force_sequence)
.venv/lib/python3.10/site-packages/factory/builder.py:276: in build
    self.factory_meta.use_postgeneration_results(
.venv/lib/python3.10/site-packages/factory/base.py: in use_postgeneration_results
    self.factory._after_postgeneration(
.venv/lib/python3.10/site-packages/factory/django.py:173: in _after_postgeneration
    instance.save()
src/company/models/company.py:634: in save
    super(Company, self).save(*args, **kwargs)
.venv/lib/python3.10/site-packages/safedelete/models.py:107: in save
    super(SafeDeleteModel, self).save(**kwargs)
.venv/lib/python3.10/site-packages/django/db/models/base.py:739: in save
    self.save_base(using=using, force_insert=force_insert,
.venv/lib/python3.10/site-packages/django/db/models/base.py:787: in save_base
    post_save.send(
.venv/lib/python3.10/site-packages/django/dispatch/dispatcher.py:180: in send
    return [
.venv/lib/python3.10/site-packages/django/dispatch/dispatcher.py:181: in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
.venv/lib/python3.10/site-packages/dirtyfields/dirtyfields.py:163: in reset_state
    new_state = instance._as_dict(check_relationship=True)
.venv/lib/python3.10/site-packages/dirtyfields/dirtyfields.py:99: in _as_dict
    all_field[field.name] = deepcopy(field_value)
/usr/local/lib/python3.10/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.10/copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.10/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.10/copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.10/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.10/copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.10/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.10/copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.10/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.10/copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/usr/local/lib/python3.10/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/usr/local/lib/python3.10/copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
/usr/local/lib/python3.10/copy.py:146: in deepcopy
    y = copier(x, memo)
/usr/local/lib/python3.10/copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
E   RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)
jackkinsella commented 2 months ago

This bug caused our test suite to become infinitely slow.

We addressed by removing signals from our application.