I have a list of functions whose parameters in the signature should be validated with the same criteria each time.
def sum_first_positive(a=1,b=1,c=1): assert a > 0 return a + b + c def sum_second_positive(a=1,b=1,c=1): assert b > 0 return a + b + c def sum_third_positive(a=1,b=1,c=1): assert c > 0 return a + b + c def sum_all_positives(a=1,b=1,c=1): assert a > 0 assert b > 0 assert c > 0 return a + b + c
Rather than copy pasting the same validator inside each function, I want to use a decorator instead.
Here is my (working) attempt:
def decorator(*decorator_arguments): def inner_decorator(function): def wrapper(**kwargs): for val in decorator_arguments: if kwargs[val] <= 0: raise ValueError(f"Value '{val}' should be strictly positive. You passed {kwargs[val]}") return function(**kwargs) return wrapper return inner_decorator @decorator("c") def sum_third_positive(a=0, b=0, c=1): return a + b + c @decorator("a", "b", "c") def sum_all_positives(a=0, b=0, c=0): return a + b + c # Fails sum_all_positives(a=-1,b=1,c=1) # OK sum_third_positive(a=-1,b=1,c=10) # Fails sum_third_positive(a=-1,b=1,c=-10)
The problem is, the user will have to pass all the arguments by name as I can not pass positional arguments to the wrapper.
I can define the function as:
@decorator("c") def sum_third_positive(*, a=0, b=0, c=1): return a + b + c
to not allow for positional arguments, but this is not ideal.
Is there a solution to pass positional arguments, by position to the decorator arguments and check on their values?