In my job, we often write functions that do some calculation and write the result to a database. Sometimes mistakes are made and wrong stuff ends up being saved, therefore we also want to supply an interface to clean up after the function. This is an attempt of solving that problem.
from functools import wraps
def revert(func):
if not hasattr(func,'_revert'):
raise NotImplementedError(func.__name__ + " does not have a revert registered")
@wraps(func._revert)
def wrapper(*args,**kwargs):
return func._revert(*args,**kwargs)
return wrapper
def register_revert(revert):
def revert_added(func):
func._revert = revert
@wraps(func)
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper
return revert_added
def _is_it_a_str(maybe_str):
if not isinstance(maybe_str,str):
raise ValueError("I need a string")
def _revert_stuff(a_str):
_is_it_a_str(a_str)
print('revert stuff with ' + a_str)
@register_revert(_revert_stuff)
def do_stuff(a_str):
_is_it_a_str(a_str)
print('doing stuff with ' + a_str)
do_stuff('something')
# Oops:
revert(do_stuff)('something')
The idea is to write your function doing your job - do_something in this case. And then when you are ready to take it to the next level, you write its revert, register it and you are done.
I like that
- the solution does not require changing anything in the main functionality.
- you can use the same identical interface for many functions
- the syntax reads relatively intuitively
I think it is inconvenient that
- I need to maintain two identical interfaces - the one on do_stuff and _revert_stuff needs to be identical (or at least _revert_stuff needs to work getting the one from do_stuff).
- sometimes I want to type-check the arguments which I then also have to repeat (exemplified here with _is_it_a_str). I thought about moving that to another decorator, but I'm afraid it gets too confusing.
Edit: We use the newest stable version of Python
p.s. My first question tried my best. Critique welcome!
do_stuff
by decorating thedo_stuff
function and checking its inputs, for example with pydantic. \$\endgroup\$