WIP: #170 #172

Draft
edraft wants to merge 20 commits from #170 into master
4 changed files with 113 additions and 35 deletions
Showing only changes of commit e6ee543a1d - Show all commits

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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)