# Event Signal

This 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
    
## Use

There are 5 main functions to use the signals.

    * get - 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 functions
    
## Example - signaler
Javascript like events for functions and objects.
```python
from event_signal import signaler


class XTest(object):
    def __init__(self, x=0):
        self._x = x

    def get_x(self):
        return self._x

    @signaler
    def 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 1
t.set_x.on("change", lambda x: print("new signal"))
t.set_x(2)
# x is changing
# x changed 2
# new signal
t.set_x.off("before_change", t.x_changing)
t.set_x(3)
# x changed 3
# new signal

t.set_x.block()
t.set_x(4)

t.set_x.block(block=False)
t.set_x(5)
# x changed 3
# new signal

t.set_x.block('change', True)
t.set_x(6)
```


Change the value that is passed to the change callback functions.
```python
from event_signal import signaler

class XTest(object):
    def __init__(self, x=0):
        self._x = x

    def 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 = 0
        elif x > 100:
            x = 100
        self._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_x
t.set_x(-1)
# x is changing -1
# x changed 0

t.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_property
A property with signaler capabilities
```python
from event_signal import signaler_property


class XTest(object):
    def __init__(self, x=0):
        self._x = x

    @signaler_property  # or signaler.property
    def x(self):
        return self._x

    @x.setter
    def 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 1
XTest.x.on(t, "change", lambda x: print("new signal"))
t.x = 2
# x is changing
# x changed 2
# new signal
XTest.x.off(t, "before_change", t.x_changing)
t.x = 3
# x changed 3
# new signal

XTest.x.block(t, 'change')
t.x = 4

XTest.x.block(t, 'change', False)
t.x = 5
# x changed 
# new signal
```

## Example - MethodObserver
Inheritable class or metaclass that makes every function/method in a class a signaler.

```python
from event_signal import MethodObserver


class XTest(MethodObserver):
    def __init__(self, x=0):
        self._x = x

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x
        
    def 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 2
t.set_x.on("before_change", t.x_changing)
t.set_x(3)
# x is changing
# x changed 3
t.set_x.off("before_change", t.x_changing)
t.set_x(4)
# x changed 4
```

## Example - Signal
Qt 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 to 
update 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 use 
this Signal from a separate thread to udate a QWidget it may not work properly and throw errors or warnings.

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.
```python
from event_signal import Signal


class XTest(object):
    x_changed = Signal(int)

    def __init__(self, x=0):
        self._x = x

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x
        self.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 2
t.x_changed.emit(3)
# x changed 3
print(t.get_x())
# 2
t.x_changed.disconnect(t.notify_x_changed)  # or t.x_changed.disconnect()
t.set_x(4)
print(t.get_x())
# 4
```

## Example - bind
bind 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 a 
property is not found and a setter function is used it will change that setter function to a signaler if it is not 
already 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 display 
the value and let the user change value. I wanted the two items decoupled. Occasionally, I wanted to programmatically 
set the data object value and wanted the GUI Widget to display this change automatically. The signals became annoying 
to deal with since I do a lot of work with threading. After several overly complex solutions, I made this bind function 
to make the GUI and data objects match values.

```python
from 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 = x
        self._y = y

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x
    
    @property    
    def y(self):
        return self._y
    
    @y.setter
    def y(self, y):
        self._y = y
    
        
t = XTest()
t2 = XTest()
bind(t, "x", t2)
t.set_x(1)
print(t.get_x())
# 1
assert t.get_x() == t2.get_x()

bind(t, "y", t2, "y")
t2.y = 2
print(t2.y)
# 2
assert t.y == t2.y
```

You can manually bind the signalers as well.
```python
from 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 = x
        self._y = y

    def get_x(self):
        return self._x

    @signaler(getter=get_x)
    def set_x(self, x):
        self._x = x
        
t1 = 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
```python
from 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 = x

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x
        
t = XTest()
t2 = XTest()
bind(t, "x", t2)

t.set_x(1)
print(t.get_x())
# 1
assert t.get_x() == t2.get_x()

unbind(t, "x")
t.set_x(2)
print(t.get_x())
# 2
assert t.get_x() != t2.get_x()

t2.set_x(3)
print(t2.get_x())
# 3
assert t.get_x() == t2.get_x()


unbind(t2.set_x)
t2.set_x(4)
print(t2.get_x())
# 4
assert t.get_x() != t2.get_x()


bind(t, "x", t2)
t.set_x(1)
print(t.get_x())
# 1
assert t.get_x() == t2.get_x()

unbind(t, "x", t2)
t.set_x(2)
print(t.get_x())
# 2
assert t.get_x() != t2.get_x()

t2.set_x(3)
print(t2.get_x())
# 3
assert t.get_x() != t2.get_x()
```
