Fixed subjects
This commit is contained in:
parent
7001b23b31
commit
e5fd7df519
@ -1,11 +1,11 @@
|
|||||||
from abc import abstractmethod, ABC
|
from abc import abstractmethod
|
||||||
|
|
||||||
from cpl_core.type import T
|
from cpl_core.type import T
|
||||||
|
|
||||||
|
|
||||||
class Observer(ABC):
|
class Observer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ABC.__init__(self)
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def next(self, value: T):
|
def next(self, value: T):
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from typing import Callable, Union, Any, Optional
|
from typing import Callable, Any, Optional
|
||||||
|
|
||||||
from cpl_reactive_extensions.abc.operator import Operator
|
from cpl_reactive_extensions.abc.operator import Operator
|
||||||
from cpl_reactive_extensions.abc.subscribable import Subscribable
|
from cpl_reactive_extensions.abc.subscribable import Subscribable
|
||||||
from cpl_reactive_extensions.subscriber import Observer, Subscriber
|
from cpl_reactive_extensions.subscriber import Observer, Subscriber
|
||||||
from cpl_reactive_extensions.subscription import Subscription
|
from cpl_reactive_extensions.subscription import Subscription
|
||||||
|
from cpl_reactive_extensions.type import ObserverOrCallable
|
||||||
|
|
||||||
|
|
||||||
class Observable(Subscribable):
|
class Observable(Subscribable):
|
||||||
@ -49,8 +50,14 @@ class Observable(Subscribable):
|
|||||||
def _subscribe(self, subscriber: Subscriber) -> Subscription:
|
def _subscribe(self, subscriber: Subscriber) -> Subscription:
|
||||||
return self._source.subscribe(subscriber)
|
return self._source.subscribe(subscriber)
|
||||||
|
|
||||||
|
def _try_subscribe(self, subscriber: Subscriber) -> Subscription:
|
||||||
|
try:
|
||||||
|
return self._subscribe(subscriber)
|
||||||
|
except Exception as e:
|
||||||
|
subscriber.error(e)
|
||||||
|
|
||||||
def subscribe(
|
def subscribe(
|
||||||
self, observer_or_next: Union[Callable, Observer], on_error: Callable = None, on_complete: Callable = None
|
self, observer_or_next: ObserverOrCallable, on_error: Callable = None, on_complete: Callable = None
|
||||||
) -> Subscription:
|
) -> Subscription:
|
||||||
subscriber = (
|
subscriber = (
|
||||||
observer_or_next
|
observer_or_next
|
||||||
@ -68,12 +75,6 @@ class Observable(Subscribable):
|
|||||||
|
|
||||||
return subscriber
|
return subscriber
|
||||||
|
|
||||||
def _try_subscribe(self, subscriber: Subscriber):
|
|
||||||
try:
|
|
||||||
return self._subscribe(subscriber)
|
|
||||||
except Exception as e:
|
|
||||||
subscriber.error(e)
|
|
||||||
|
|
||||||
def _call(self, observer: Observer):
|
def _call(self, observer: Observer):
|
||||||
try:
|
try:
|
||||||
self._subscribe(observer)
|
self._subscribe(observer)
|
||||||
|
@ -1,9 +1,100 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
from cpl_core.type import T
|
from cpl_core.type import T
|
||||||
|
from cpl_reactive_extensions.abc.observer import Observer
|
||||||
from cpl_reactive_extensions.observable import Observable
|
from cpl_reactive_extensions.observable import Observable
|
||||||
|
from cpl_reactive_extensions.subscriber import Subscriber
|
||||||
|
from cpl_reactive_extensions.subscription import Subscription
|
||||||
|
|
||||||
|
|
||||||
class Subject(Observable):
|
class Subject(Observable, Observer):
|
||||||
def __init__(self, _t: type):
|
def __init__(self, _t: type):
|
||||||
Observable.__init__(self)
|
Observable.__init__(self)
|
||||||
|
|
||||||
|
self.is_closed = False
|
||||||
self._t = _t
|
self._t = _t
|
||||||
|
self._current_observers: Optional[list[Observer]] = None
|
||||||
|
|
||||||
|
self.closed = False
|
||||||
|
self.observers: list[Observer] = []
|
||||||
|
self.is_stopped = False
|
||||||
|
self.has_error = False
|
||||||
|
self.raised_error: Any = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def observed(self) -> bool:
|
||||||
|
return len(self.observers) > 0
|
||||||
|
|
||||||
|
def _raise_if_closed(self):
|
||||||
|
if not self.closed:
|
||||||
|
return
|
||||||
|
raise Exception("Subject is unsubscribed!")
|
||||||
|
|
||||||
|
def next(self, value: T):
|
||||||
|
self._raise_if_closed()
|
||||||
|
|
||||||
|
if not isinstance(value, self._t):
|
||||||
|
raise TypeError()
|
||||||
|
|
||||||
|
if self.is_stopped:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._current_observers is None:
|
||||||
|
self._current_observers = self.observers
|
||||||
|
|
||||||
|
for observer in self._current_observers:
|
||||||
|
observer.next(value)
|
||||||
|
|
||||||
|
def error(self, error: Exception):
|
||||||
|
self._raise_if_closed()
|
||||||
|
if self.is_stopped:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_stopped = True
|
||||||
|
self.has_error = self.is_stopped
|
||||||
|
for observer in self.observers:
|
||||||
|
observer.error(error)
|
||||||
|
|
||||||
|
def complete(self):
|
||||||
|
self._raise_if_closed()
|
||||||
|
|
||||||
|
if self.is_stopped:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_stopped = True
|
||||||
|
for observer in self.observers:
|
||||||
|
observer.complete()
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
self.is_stopped = True
|
||||||
|
self.is_closed = True
|
||||||
|
self._current_observers = None
|
||||||
|
self.observers = []
|
||||||
|
|
||||||
|
def _try_subscribe(self, subscriber: Subscriber):
|
||||||
|
self._raise_if_closed()
|
||||||
|
return super()._try_subscribe(subscriber)
|
||||||
|
|
||||||
|
def _subscribe(self, subscriber: Subscriber) -> Subscription:
|
||||||
|
self._raise_if_closed()
|
||||||
|
self._check_finalized_statuses(subscriber)
|
||||||
|
return self._inner_subscribe(subscriber)
|
||||||
|
|
||||||
|
def _check_finalized_statuses(self, subscriber: Subscriber):
|
||||||
|
if self.has_error:
|
||||||
|
subscriber.error(self.raised_error)
|
||||||
|
elif self.is_stopped:
|
||||||
|
subscriber.complete()
|
||||||
|
|
||||||
|
def _inner_subscribe(self, subscriber: Subscriber) -> Optional[Subscription]:
|
||||||
|
if self.has_error or self.is_stopped:
|
||||||
|
return Subscription.empty()
|
||||||
|
|
||||||
|
self._current_observers = None
|
||||||
|
self.observers.append(subscriber)
|
||||||
|
|
||||||
|
def _initial():
|
||||||
|
self._current_observers = None
|
||||||
|
self.observers.remove(subscriber)
|
||||||
|
|
||||||
|
return Subscription(_initial)
|
||||||
|
@ -3,12 +3,20 @@ from typing import Callable
|
|||||||
from cpl_core.type import T
|
from cpl_core.type import T
|
||||||
from cpl_reactive_extensions.abc.observer import Observer
|
from cpl_reactive_extensions.abc.observer import Observer
|
||||||
from cpl_reactive_extensions.subscription import Subscription
|
from cpl_reactive_extensions.subscription import Subscription
|
||||||
|
from cpl_reactive_extensions.type import ObserverOrCallable
|
||||||
|
|
||||||
|
|
||||||
class Subscriber(Subscription, Observer):
|
class Subscriber(Subscription, Observer):
|
||||||
def __init__(self, on_next: Callable, on_error: Callable = None, on_complete: Callable = None):
|
def __init__(
|
||||||
|
self, on_next_or_observer: ObserverOrCallable, on_error: Callable = None, on_complete: Callable = None
|
||||||
|
):
|
||||||
Subscription.__init__(self)
|
Subscription.__init__(self)
|
||||||
self._on_next = on_next
|
if isinstance(on_next_or_observer, Observer):
|
||||||
|
self._on_next = on_next_or_observer.next
|
||||||
|
self._on_error = on_next_or_observer.error
|
||||||
|
self._on_complete = on_next_or_observer.complete
|
||||||
|
else:
|
||||||
|
self._on_next = on_next_or_observer
|
||||||
self._on_error = on_error
|
self._on_error = on_error
|
||||||
self._on_complete = on_complete
|
self._on_complete = on_complete
|
||||||
|
|
||||||
@ -29,3 +37,9 @@ class Subscriber(Subscription, Observer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._on_complete()
|
self._on_complete()
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
if self._closed:
|
||||||
|
return
|
||||||
|
|
||||||
|
super().unsubscribe()
|
||||||
|
@ -6,13 +6,18 @@ from cpl_reactive_extensions.abc.unsubscribable import Unsubscribable
|
|||||||
|
|
||||||
|
|
||||||
class Subscription(Unsubscribable):
|
class Subscription(Unsubscribable):
|
||||||
|
@staticmethod
|
||||||
|
def empty():
|
||||||
|
empty = Subscription()
|
||||||
|
empty.closed = True
|
||||||
|
return empty
|
||||||
|
|
||||||
def __init__(self, initial_teardown: Optional[Callable] = None):
|
def __init__(self, initial_teardown: Optional[Callable] = None):
|
||||||
Unsubscribable.__init__(self)
|
Unsubscribable.__init__(self)
|
||||||
|
|
||||||
self._initial_teardown = initial_teardown
|
self._initial_teardown = initial_teardown
|
||||||
|
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self._subscriptions = []
|
|
||||||
|
|
||||||
self._parentage: list[Subscription] = []
|
self._parentage: list[Subscription] = []
|
||||||
self._finalizers: list[Subscription] = []
|
self._finalizers: list[Subscription] = []
|
||||||
@ -21,9 +26,20 @@ class Subscription(Unsubscribable):
|
|||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
return self._closed
|
return self._closed
|
||||||
|
|
||||||
|
@closed.setter
|
||||||
|
def closed(self, value: bool):
|
||||||
|
self._closed = value
|
||||||
|
|
||||||
def _add_parent(self, parent: Subscription):
|
def _add_parent(self, parent: Subscription):
|
||||||
self._parentage.append(parent)
|
self._parentage.append(parent)
|
||||||
|
|
||||||
|
def _remove_parent(self, parent: Subscription):
|
||||||
|
if self == parent:
|
||||||
|
self._parentage.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._parentage.remove(parent)
|
||||||
|
|
||||||
def _has_parent(self, parent: Subscription) -> bool:
|
def _has_parent(self, parent: Subscription) -> bool:
|
||||||
return parent in self._parentage
|
return parent in self._parentage
|
||||||
|
|
||||||
@ -52,7 +68,7 @@ class Subscription(Unsubscribable):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
self._subscriptions.clear()
|
self._finalizers = None
|
||||||
|
|
||||||
def add(self, tear_down: Union[Subscription, Unsubscribable]):
|
def add(self, tear_down: Union[Subscription, Unsubscribable]):
|
||||||
if tear_down is None or tear_down == self:
|
if tear_down is None or tear_down == self:
|
||||||
@ -68,7 +84,10 @@ class Subscription(Unsubscribable):
|
|||||||
|
|
||||||
tear_down._add_parent(self)
|
tear_down._add_parent(self)
|
||||||
|
|
||||||
self._subscriptions.append(tear_down)
|
self._finalizers.append(tear_down)
|
||||||
|
|
||||||
def remove(self, tear_down: Union[Subscription, Unsubscribable]):
|
def remove(self, tear_down: Union[Subscription, Unsubscribable]):
|
||||||
self._subscriptions.remove(tear_down)
|
self._finalizers.remove(tear_down)
|
||||||
|
|
||||||
|
if isinstance(tear_down, Subscription):
|
||||||
|
tear_down._remove_parent(self)
|
||||||
|
5
src/cpl_reactive_extensions/type.py
Normal file
5
src/cpl_reactive_extensions/type.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from typing import Callable, Union
|
||||||
|
|
||||||
|
from cpl_reactive_extensions.abc.observer import Observer
|
||||||
|
|
||||||
|
ObserverOrCallable = Union[Callable, Observer]
|
@ -100,16 +100,18 @@ class ReactiveTestCase(unittest.TestCase):
|
|||||||
def test_subject(self):
|
def test_subject(self):
|
||||||
expected_x = 1
|
expected_x = 1
|
||||||
|
|
||||||
def _next(x):
|
def _next(calc, x):
|
||||||
nonlocal expected_x
|
nonlocal expected_x
|
||||||
self.assertEqual(expected_x, x)
|
self.assertEqual(expected_x, x)
|
||||||
|
if not calc:
|
||||||
|
return
|
||||||
expected_x += 1
|
expected_x += 1
|
||||||
if expected_x == 4:
|
if expected_x == 4:
|
||||||
expected_x = 1
|
expected_x = 1
|
||||||
|
|
||||||
subject = Subject(int)
|
subject = Subject(int)
|
||||||
subject.subscribe(_next, self._on_error)
|
subject.subscribe(lambda x: _next(False, x), self._on_error)
|
||||||
subject.subscribe(_next, self._on_error)
|
subject.subscribe(lambda x: _next(True, x), self._on_error)
|
||||||
|
|
||||||
observable = Observable.from_list([1, 2, 3])
|
observable = Observable.from_list([1, 2, 3])
|
||||||
observable.subscribe(subject, self._on_error)
|
observable.subscribe(subject, self._on_error)
|
||||||
|
Loading…
Reference in New Issue
Block a user