My software has a callback API, to call functions with one argument:
In [1]:
tone_detected_callbacks = []
def on_tone_detected(callback):
tone_detected_callbacks.append(callback)
def tone_detected(pitch):
for callback in tone_detected_callbacks:
callback(pitch)
Sara writes a plugin which provides a callback:
In [2]:
def tone_callback_a(pitch):
print("Tone detected at %f Hz" % pitch)
on_tone_detected(tone_callback_a)
And there was much rejoicing.
In [3]:
tone_detected(227.5)
The software becomes more complex, and it can provide more information to callbacks:
In [4]:
def tone_detected(pitch, duration):
for callback in tone_detected_callbacks:
callback(pitch, duration)
But Sara's plugin hasn't been updated yet, so it doesn't expect the extra parameter.
In [5]:
tone_detected(227.5, 3)
backcall
is a library to solve this problem, so you can extend callback APIs in a backwards compatible way.
In [6]:
from backcall import callback_prototype
# A callback prototype specifies what parameters we're going to pass
@callback_prototype
def tone_detected_cb(pitch, duration):
pass
tone_detected_callbacks = []
def on_tone_detected(callback):
# This inspects callback, and wraps it in a function that will discard extra arguments
adapted = tone_detected_cb.adapt(callback)
tone_detected_callbacks.append(adapted)
def tone_detected(pitch, duration):
for callback in tone_detected_callbacks:
callback(pitch, duration)
Registering the callback looks just the same as before - callback providers don't need to do anything special.
In [7]:
on_tone_detected(tone_callback_a)
Now the extra parameter is discarded, and Sara's plugin gets only the information it expects.
In [8]:
tone_detected(227.5, 3)
Plus you've got an introspectable reference for the expected callback signature:
In [9]:
help(tone_detected_cb)