FactoryBoy / factory_boy

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

Providing a `django.mute_signals` like mechanism for SQLAlchemy #825

Open moumoutte opened 3 years ago

moumoutte commented 3 years ago

The problem

SQLAlchemy provides a listener mechanism similar to django signals in order to execute code based on events handler like pre_save or post_save. Some models can be plugged in with this concept and code are executed before/after insert/update/delete events. Sometime, those pieces of code are not relevant to run into the factory scope and we want something able to unplug it on the fly.

Proposed solution

In the flavour of django.mute_signals provides a context_manager/decorator able to mute SQLAlchemy listeners.

based on this kind of declaration


class ModelWithListener(Model):
     id = ...

@listen_for(ModelWithListener, 'after_insert'):
def unwanted_function(...):
    ...

with mute_listeners([ModelWithListener, 'after_insert', unwanted_function]):
    ModelWithListenerFactory()

or


@mute_listeners([(ModelWithListener, 'after_insert', unwanted_function)])
def test():
     ModelWithListenerFactory()

We could easily imagine an option attribute already declared into the Meta of the factory


    class ModelWithListenerFactory(Factory):
        class Meta:
               model = ModelWithListener
               sqlalchemy_mute_listeners = [
                    ('after_insert', unwanted_function)
               ]
rbarrois commented 3 years ago

Nice idea!

Regarding the API, it looks like we would be disabling the behaviour of a specific signal on a specific model + event. Since we're already working on a factory (which is often tied to a single model), can't we just pass the unwanted_function and have the code figure things out?

What I mean is that, even if I have a signal tied to 5 models, when I mute it within UserFactory, I don't really need to say "Mute it only for the User model — it's already scoped to that model (implicitly).

This would provide a simpler API, but might prevent some actual use cases. Although I'm not sure whether it's easy to do in SQLAlchemy :D