yunojuno / django-side-effects

Django app used to centralise and document external side effects
MIT License
18 stars 4 forks source link

Add function signature matching checks #14

Closed hugorodgerbrown closed 5 years ago

hugorodgerbrown commented 5 years ago

This PR adds two features designed to ensure that the functions plugged into a side-effect are compatible.

System Check

The first feature is a Django System Check that checks all of the receiving functions for a registered event ("label") in the registry, and raises a Warning message if the functions are not all identical.

$ python manage.py check side_effects

WARNINGS:
?: (side_effects.W001) Multiple function signatures for event: "add_to_basket"
    HINT: Ensure that all functions decorated `@is_side_effect_of("add_to_basket")` have identical signatures.

NB these checks use inspect.Signature equivalence, which means that it's not checking 'compatibility', but exact equivalence. For example, def foo(arg1, arg2) and def bar(*args) are compatible, but will fail this check.

These warnings can be ignored.

Error on run_side_effects

The second feature is a new Exception SignatureMismatch that is raised when a side-effect handler (@is_side_effect_of) has a signature that is incompatible with the origin function (@has_side_effects). Currently in this situation the receiver will raise a TypeError, but this will be handled like any other exception raised by the receiver, and may be swallowed. This is not the desired behaviour, as the receiver has never actually been called. This will now raise SignatureMismatch. Note that this check handles the case where the return_value is included, or not.

@has_side_effects("foo")
def do_foo(arg1, arg2):
    pass

# all of the following will be checked against
# (arg1, arg2) and (arg1, arg2, return_value)

@is_side_effect_of("foo")
def valid_side_effect(arg1, arg2):
    pass

@is_side_effect_of("foo")
def valid_side_effect_with_variable_args(*args):
    pass

@is_side_effect_of("foo")
def valid_side_effect_with_return_value(arg1, arg2, return_value=None):
    pass

@is_side_effect_of("foo")
def invalid_side_effect(arg1):
    # this will raise SignatureMismatch