Package: event-signal
# Event SignalThis library was created to help maintain when variables are changed and when functions are called.There are 5 main utilities provided* signaler - Function decorator to help observe functions* signaler_property - Custom property that helps observe when a property value is changed or deleted.* MethodObserver - class mixin to make all function observable* Signal - Similar to Qt's signal without requiring PyQT or PySide* bind - Make two object share the same value## UseThere are 5 main functions to use the signals.* get_signal - returns a list of connected function callbacks* on - connect a callback function to a signal* off - disconnect a callback funciton from a signal* fire - Call all callback functions that are associated with a signal* block - Temporarily block a signal from calling callback functionsTwo basic signals are provided `'before_change'` and `'change'`. The signaler_property also has delete signals.* 'before_change' - This signal automatically fires before a function is called.* 'change' - This signal automatically fires after a function is called.* 'before_delete' - signaler_property fired before `del obj.property` is called.* 'delete' - signaler_property fired after `del obj.property` is called.## BasicsA signaler is a custom class that decorates a function. It acts just like a function (it is callable). A signaler canhave other functions attached to it with a name. When the signaler fire is called it will call all of the attachedfunctions.```pythonfrom event_signal import signalerdef my_function(a, b, c):print(a, b, c)my_function = signaler(my_function)# Call the signaler like a normal functionmy_function(1, 2, 3)# print = 1, 2, 3print('=====\n')def print_signal(value1, value2):print('print_signal called', value1, value2)my_function.on('my_signal', print_signal)my_function.fire('my_signal', 1, 2)# print = print_signal called 1 2print('=====\n')def another_signal(value1, value2):print('another_signal called', value1, value2, value1 == value2)my_function.on('my_signal', another_signal)my_function.fire('my_signal', 2, 2)# print = print_signal called 2 2# print = another_signal called 2 2 Trueprint('=====\n')my_function.off('my_signal', print_signal)my_function.fire('my_signal', 3, 2)# print = another_signal called 3 2 Falseprint('=====\n')print(my_function.get_signal('my_signal'))# print = [<function ...another_signal at 0x056076F0>]print('=====\n')my_function.block('my_signal')my_function.fire('my_signal', 4, 5)# No print!my_function.block('my_signal', False)my_function.fire('my_signal', 5, 6)# print = another_signal called 5 6 Falseprint('=====\n')```## Example - signalerJavascript like events for functions and objects.The signaler automatically creates and fires signals `'before_change'` and `'change'`.```pythonfrom event_signal import signalerclass XTest(object):def __init__(self, x=0):self._x = xdef get_x(self):return self._x@signalerdef set_x(self, x):self._x = x@set_x.on("before_change")def x_changing(self, x):print("x is changing")@set_x.on("change")def x_changed(self, x):print("x changed", x)t = XTest()t.set_x(1)# x is changing# x changed 1t.set_x.on("change", lambda x: print("new signal"))t.set_x(2)# x is changing# x changed 2# new signalt.set_x.off("before_change", t.x_changing)t.set_x(3)# x changed 3# new signalt.set_x.block()t.set_x(4)t.set_x.block(block=False)t.set_x(5)# x changed 3# new signalt.set_x.block('change', True)t.set_x(6)```Change the value that is passed to the change callback functions.```pythonfrom event_signal import signalerclass XTest(object):def __init__(self, x=0):self._x = xdef get_x(self):return self._x@signaler(getter=get_x)def set_x(self, x):"""Set x and force the value to be between 1 and 100."""if x < 0:x = 0elif x > 100:x = 100self._x = x@set_x.on("before_change")def x_changing(self, x):print("x is changing", x)@set_x.on("change")def x_changed(self, x):print("x changed", x)t = XTest()t.set_x(1)# x is changing 1# x changed 1# Normally (without the getter) the change callback functions receive# the x value that was passed into set_xt.set_x(-1)# x is changing -1# x changed 0t.set_x(102)# x is changing 102# x changed 100# In this case the value passed into the change callback functions# is the value returned from the signaler getter (t.get_x)# which is how the signaler_property works.```## Example - signaler_propertyA property with signaler capabilities.The signaler_property automatically creates and fires signals `'before_change'`, `'change'`, `'before_delete'`, and `'delete'`.```pythonfrom event_signal import signaler_propertyclass XTest(object):def __init__(self, x=0):self._x = x@signaler_property  # or signaler.propertydef x(self):return self._x@x.setterdef x(self, x):self._x = x@x.on("before_change")def x_changing(self, x):print("x is changing")@x.on("change")def x_changed(self, x):print("x changed", x)t = XTest()t.x = 1# x is changing# x changed 1XTest.x.on(t, "change", lambda x: print("new signal"))t.x = 2# x is changing# x changed 2# new signalXTest.x.off(t, "before_change", t.x_changing)t.x = 3# x changed 3# new signalXTest.x.block(t, 'change')t.x = 4XTest.x.block(t, 'change', False)t.x = 5# x changed# new signal```## Example - MethodObserverInheritable class or metaclass that makes every function/method in a class a signaler.```pythonfrom event_signal import MethodObserverclass XTest(MethodObserver):def __init__(self, x=0):self._x = xdef get_x(self):return self._xdef set_x(self, x):self._x = xdef x_changing(self, x):print("x is changing")def x_changed(self, x):print("x changed", x)t = XTest()t.set_x(1)t.set_x.on("change", t.x_changed)t.set_x(2)# x changed 2t.set_x.on("before_change", t.x_changing)t.set_x(3)# x is changing# x changed 3t.set_x.off("before_change", t.x_changing)t.set_x(4)# x changed 4```## Example - SignalQt like signal.**Warning:**Qt's signals are thread safe (depending on how you connect them). They call the callback functions in the main thread.Many Qt widgets error and do not update when a value is set from a separate thread. So Qt's Signal is a good way toupdate a QWidget's value display, but can be slow.The event_signal.Signal works like a Qt Signal with a direct connection.The callback functions are called in the same thread that originally called the function. If you are using Qt and usethis Signal from a separate thread to udate a QWidget it may not work properly and throw errors or warnings.For more information on how to make a QWidget thread safe go to <https://tasks.justengel.com/project/justengel-event-signal/wiki/signal-qt-thread-safe>Also this signal does not do any kind of type checking. Passing types into the Signal constructor ```Signal(int, str)```is just for looks and maybe code readability.```pythonfrom event_signal import Signalclass XTest(object):x_changed = Signal(int)def __init__(self, x=0):self._x = xdef get_x(self):return self._xdef set_x(self, x):self._x = xself.x_changed.emit(self._x)def notify_x_changed(self, x):print("x changed", x)t = XTest()t.set_x(1)t.x_changed.connect(t.notify_x_changed)t.set_x(2)# x changed 2t.x_changed.emit(3)# x changed 3print(t.get_x())# 2t.x_changed.disconnect(t.notify_x_changed)  # or t.x_changed.disconnect()t.set_x(4)print(t.get_x())# 4```## Example - bindbind the value of two objects together. This will automatically use properties or find setter methods("set_" + property_name or "set" + property_name). The binder will change a property to a signaler_property or if aproperty is not found and a setter function is used it will change that setter function to a signaler if it is notalready a signaler.The main goal is to help two objects keep the same value for a variable.When using Qt I found this very annoying. I wanted a regular python object to store data and a GUI Widget to displaythe value and let the user change value. I wanted the two items decoupled. Occasionally, I wanted to programmaticallyset the data object value and wanted the GUI Widget to display this change automatically. The signals became annoyingto deal with since I do a lot of work with threading. After several overly complex solutions, I made this bind functionto make the GUI and data objects match values.```pythonfrom event_signal import bind, bind_signals  # bind_signals is only for directly giving signalers.class XTest(object):def __init__(self, x=0, y=0):self._x = xself._y = ydef get_x(self):return self._xdef set_x(self, x):self._x = x@propertydef y(self):return self._y@y.setterdef y(self, y):self._y = yt = XTest()t2 = XTest()bind(t, "x", t2)t.set_x(1)print(t.get_x())# 1assert t.get_x() == t2.get_x()bind(t, "y", t2, "y")t2.y = 2print(t2.y)# 2assert t.y == t2.y```You can manually bind the signalers as well.```pythonfrom event_signal import signaler, bind_signals  # bind_signals is only for directly giving signalers.class Test2(object):def __init__(self, x=0, y=0):self._x = xself._y = ydef get_x(self):return self._x@signaler(getter=get_x)def set_x(self, x):self._x = xt1 = Test2()t2 = Test2()bind_signals(t1.set_x, t2.set_x)t1.set_x(2)assert t1.get_x() == t2.get_x()t2.set_x(5)assert t1.get_x() == t2.get_x()```An unbind option is also available and works just like the examples above accept you can choose to unbind a single object at a time```pythonfrom event_signal import bind, unbind, unbind_signals  # unbind_signals is only for directly giving one or more signalers.class XTest(object):def __init__(self, x=0):self._x = xdef get_x(self):return self._xdef set_x(self, x):self._x = xt = XTest()t2 = XTest()bind(t, "x", t2)t.set_x(1)print(t.get_x())# 1assert t.get_x() == t2.get_x()unbind(t, "x")t.set_x(2)print(t.get_x())# 2assert t.get_x() != t2.get_x()t2.set_x(3)print(t2.get_x())# 3assert t.get_x() == t2.get_x()unbind(t2.set_x)t2.set_x(4)print(t2.get_x())# 4assert t.get_x() != t2.get_x()bind(t, "x", t2)t.set_x(1)print(t.get_x())# 1assert t.get_x() == t2.get_x()unbind(t, "x", t2)t.set_x(2)print(t.get_x())# 2assert t.get_x() != t2.get_x()t2.set_x(3)print(t2.get_x())# 3assert t.get_x() != t2.get_x()```### bind QtI now provide a `bind_qt` and `unbind_qt` functions.```pythonfrom event_signal import signaler, bind_qt, unbind_qtfrom qtpy import QtWidgetsclass MyData(object):def __init__(self, name='hello'):self._name = namedef get_name(self):return self._name@signaler(getter=get_name)def set_name(self, name):self._name = str(name)app = QtWidgets.QApplication([])widg = QtWidgets.QWidget()lay = QtWidgets.QVBoxLayout()widg.setLayout(lay)widg.show()data = MyData()data.set_name.on('change', lambda name: print('data name changed to', name))inp = QtWidgets.QLineEdit('Hello World!')lay.addWidget(inp)bind_qt(data, 'set_name', inp, 'setText')  # qt_signal='editingFinished'btn = QtWidgets.QPushButton('Set Hello')def set_hello():data.set_name('Hello')btn.clicked.connect(set_hello)lay.addWidget(btn)unbind_btn = QtWidgets.QPushButton('unbind')def unbind_call():unbind_qt(data, 'set_name', inp, 'setText')unbind_btn.clicked.connect(unbind_call)lay.addWidget(unbind_btn)app.exec_()```To edit what widget value is used.```pythonfrom event_signal import qt_binderdef get_widget_value(widget):if isinstance(widget, CustomWidget):return widget.my_value()return qt_binder.get_widget_value(widget)qt_binder.get_widget_value = get_widget_value()```One thing to watch out for is if functions are called multiple times or are infinitely recursive.