WIP: #170 #172
@ -1,18 +1,56 @@
|
|||||||
from typing import Callable
|
from typing import Callable, Union
|
||||||
|
|
||||||
from cpl_reactive_extensions.observer import Observer
|
from cpl_reactive_extensions.observer import Observer
|
||||||
|
|
||||||
|
|
||||||
class Observable:
|
class Observable:
|
||||||
def __init__(self, callback: Callable):
|
def __init__(self, callback: Callable = None):
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
self._subscriptions: list[Callable] = []
|
|
||||||
|
|
||||||
def _run_subscriptions(self):
|
self._observers: list[Observer] = []
|
||||||
for callback in self._subscriptions:
|
|
||||||
callback()
|
|
||||||
|
|
||||||
def subscribe(self, observer: Observer):
|
@staticmethod
|
||||||
|
def from_list(values: list):
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
def callback(x: Observer):
|
||||||
|
nonlocal i
|
||||||
|
if i == len(values):
|
||||||
|
i = 0
|
||||||
|
x.complete()
|
||||||
|
else:
|
||||||
|
x.next(values[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if not x.closed:
|
||||||
|
callback(x)
|
||||||
|
|
||||||
|
observable = Observable(callback)
|
||||||
|
return observable
|
||||||
|
|
||||||
|
def subscribe(
|
||||||
|
self, observer_or_next: Union[Callable, Observer], on_error: Callable = None, on_complete: Callable = None
|
||||||
|
) -> Observer:
|
||||||
|
if isinstance(observer_or_next, Callable):
|
||||||
|
observer = Observer(observer_or_next, on_error, on_complete)
|
||||||
|
elif isinstance(observer_or_next, Observable):
|
||||||
|
observer = observer_or_next
|
||||||
|
else:
|
||||||
|
observer = observer_or_next
|
||||||
|
|
||||||
|
if self._callback is None:
|
||||||
|
self._observers.append(observer)
|
||||||
|
return observer
|
||||||
|
|
||||||
|
if len(observer._observers) > 0:
|
||||||
|
for observer in observer._observers:
|
||||||
|
self._call(observer)
|
||||||
|
else:
|
||||||
|
self._call(observer)
|
||||||
|
|
||||||
|
return observer
|
||||||
|
|
||||||
|
def _call(self, observer: Observer):
|
||||||
try:
|
try:
|
||||||
self._callback(observer)
|
self._callback(observer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -6,8 +6,14 @@ from cpl_core.type import T
|
|||||||
class Observer:
|
class Observer:
|
||||||
def __init__(self, on_next: Callable, on_error: Callable = None, on_complete: Callable = None):
|
def __init__(self, on_next: Callable, on_error: Callable = None, on_complete: Callable = None):
|
||||||
self._on_next = on_next
|
self._on_next = on_next
|
||||||
self._on_error = on_error if on_error is not None else lambda err: err
|
self._on_error = on_error
|
||||||
self._on_complete = on_complete if on_complete is not None else lambda x: x
|
self._on_complete = on_complete
|
||||||
|
|
||||||
|
self._closed = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed(self) -> bool:
|
||||||
|
return self._closed
|
||||||
|
|
||||||
def next(self, value: T):
|
def next(self, value: T):
|
||||||
self._on_next(value)
|
self._on_next(value)
|
||||||
|
@ -3,15 +3,18 @@ from cpl_reactive_extensions.observable import Observable
|
|||||||
|
|
||||||
|
|
||||||
class Subject(Observable):
|
class Subject(Observable):
|
||||||
def __init__(self):
|
def __init__(self, _t: type):
|
||||||
Observable.__init__(self)
|
Observable.__init__(self)
|
||||||
|
|
||||||
|
self._t = _t
|
||||||
self._value: T = None
|
self._value: T = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> T:
|
def value(self) -> T:
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
def emit(self, value: T):
|
def next(self, value: T):
|
||||||
|
if not isinstance(value, self._t):
|
||||||
|
raise TypeError(f"Expected {self._t.__name__} not {type(value).__name__}")
|
||||||
|
|
||||||
self._value = value
|
self._value = value
|
||||||
self._subscriptions()
|
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
|
import traceback
|
||||||
import unittest
|
import unittest
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
|
|
||||||
|
from cpl_core.console import Console
|
||||||
from cpl_reactive_extensions.observable import Observable
|
from cpl_reactive_extensions.observable import Observable
|
||||||
from cpl_reactive_extensions.observer import Observer
|
from cpl_reactive_extensions.observer import Observer
|
||||||
|
from cpl_reactive_extensions.subject import Subject
|
||||||
|
|
||||||
|
|
||||||
class ReactiveTestCase(unittest.TestCase):
|
class ReactiveTestCase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
self._error = False
|
||||||
|
self._completed = False
|
||||||
|
|
||||||
|
def _on_error(self, ex: Exception):
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
Console.error(f"Somthing went wrong: {ex}", tb)
|
||||||
|
self._error = True
|
||||||
|
|
||||||
|
def _on_complete(self):
|
||||||
|
self._completed = True
|
||||||
|
|
||||||
def test_observer(self):
|
def test_observer(self):
|
||||||
called = 0
|
called = 0
|
||||||
has_error = False
|
|
||||||
completed = False
|
|
||||||
test_x = 1
|
test_x = 1
|
||||||
|
|
||||||
def callback(observer: Observer):
|
def callback(observer: Observer):
|
||||||
@ -38,34 +48,55 @@ class ReactiveTestCase(unittest.TestCase):
|
|||||||
called += 1
|
called += 1
|
||||||
self.assertEqual(test_x, x)
|
self.assertEqual(test_x, x)
|
||||||
|
|
||||||
def on_err():
|
|
||||||
nonlocal has_error
|
|
||||||
has_error = True
|
|
||||||
|
|
||||||
def on_complete():
|
|
||||||
nonlocal completed
|
|
||||||
completed = True
|
|
||||||
|
|
||||||
self.assertEqual(called, 0)
|
self.assertEqual(called, 0)
|
||||||
self.assertFalse(has_error)
|
self.assertFalse(self._error)
|
||||||
self.assertFalse(completed)
|
self.assertFalse(self._completed)
|
||||||
observable.subscribe(
|
observable.subscribe(
|
||||||
Observer(
|
on_next,
|
||||||
on_next,
|
self._on_error,
|
||||||
on_err,
|
self._on_complete,
|
||||||
on_complete,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
self.assertEqual(called, 3)
|
self.assertEqual(called, 3)
|
||||||
self.assertFalse(has_error)
|
self.assertFalse(self._error)
|
||||||
self.assertFalse(completed)
|
self.assertFalse(self._completed)
|
||||||
|
|
||||||
def complete():
|
def complete():
|
||||||
self.assertEqual(called, 4)
|
self.assertEqual(called, 4)
|
||||||
self.assertFalse(has_error)
|
self.assertFalse(self._error)
|
||||||
self.assertTrue(completed)
|
self.assertTrue(self._completed)
|
||||||
|
|
||||||
Timer(1.0, complete).start()
|
Timer(1.0, complete).start()
|
||||||
|
|
||||||
|
def test_observable_from(self):
|
||||||
|
expected_x = 1
|
||||||
|
|
||||||
|
def _next(x):
|
||||||
|
nonlocal expected_x
|
||||||
|
self.assertEqual(expected_x, x)
|
||||||
|
expected_x += 1
|
||||||
|
|
||||||
|
observable = Observable.from_list([1, 2, 3, 4])
|
||||||
|
observable.subscribe(
|
||||||
|
_next,
|
||||||
|
self._on_error,
|
||||||
|
)
|
||||||
|
self.assertFalse(self._error)
|
||||||
|
|
||||||
def test_subject(self):
|
def test_subject(self):
|
||||||
pass
|
expected_x = 1
|
||||||
|
|
||||||
|
def _next(x):
|
||||||
|
nonlocal expected_x
|
||||||
|
self.assertEqual(expected_x, x)
|
||||||
|
expected_x += 1
|
||||||
|
if expected_x == 4:
|
||||||
|
expected_x = 1
|
||||||
|
|
||||||
|
subject = Subject(int)
|
||||||
|
subject.subscribe(_next, self._on_error, self._on_complete)
|
||||||
|
subject.subscribe(_next, self._on_error, self._on_complete)
|
||||||
|
|
||||||
|
observable = Observable.from_list([1, 2, 3])
|
||||||
|
observable.subscribe(subject, self._on_error, self._on_complete)
|
||||||
|
|
||||||
|
self.assertFalse(self._error)
|
||||||
|
Loading…
Reference in New Issue
Block a user