venv added, updated

This commit is contained in:
Norbert
2024-09-13 09:46:28 +02:00
parent 577596d9f3
commit 82af8c809a
4812 changed files with 640223 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
from .connectableobservable import ConnectableObservable
from .groupedobservable import GroupedObservable
from .observable import Observable
__all__ = ["Observable", "ConnectableObservable", "GroupedObservable"]

View File

@@ -0,0 +1,31 @@
from typing import TypeVar
from reactivex import Observable, never
from reactivex import operators as _
_T = TypeVar("_T")
def amb_(*sources: Observable[_T]) -> Observable[_T]:
"""Propagates the observable sequence that reacts first.
Example:
>>> winner = amb(xs, ys, zs)
Returns:
An observable sequence that surfaces any of the given sequences,
whichever reacted first.
"""
acc: Observable[_T] = never()
def func(previous: Observable[_T], current: Observable[_T]) -> Observable[_T]:
return _.amb(previous)(current)
for source in sources:
acc = func(acc, source)
return acc
__all__ = ["amb_"]

View File

@@ -0,0 +1,35 @@
from asyncio import Future
from typing import Callable, Mapping, Optional, TypeVar, Union
from reactivex import Observable, abc, defer, empty, from_future
_Key = TypeVar("_Key")
_T = TypeVar("_T")
def case_(
mapper: Callable[[], _Key],
sources: Mapping[_Key, Observable[_T]],
default_source: Optional[Union[Observable[_T], "Future[_T]"]] = None,
) -> Observable[_T]:
default_source_: Union[Observable[_T], "Future[_T]"] = default_source or empty()
def factory(_: abc.SchedulerBase) -> Observable[_T]:
try:
result: Union[Observable[_T], "Future[_T]"] = sources[mapper()]
except KeyError:
result = default_source_
if isinstance(result, Future):
result_: Observable[_T] = from_future(result)
else:
result_ = result
return result_
return defer(factory)
__all__ = ["case_"]

View File

@@ -0,0 +1,84 @@
from typing import Any, Iterable, Optional, TypeVar
from reactivex import Observable, abc
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SerialDisposable,
SingleAssignmentDisposable,
)
from reactivex.scheduler import CurrentThreadScheduler
_T = TypeVar("_T")
def catch_with_iterable_(sources: Iterable[Observable[_T]]) -> Observable[_T]:
"""Continues an observable sequence that is terminated by an
exception with the next observable sequence.
Examples:
>>> res = catch([xs, ys, zs])
>>> res = reactivex.catch(src for src in [xs, ys, zs])
Args:
sources: an Iterable of observables. Thus a generator is accepted.
Returns:
An observable sequence containing elements from consecutive
source sequences until a source sequence terminates
successfully.
"""
sources_ = iter(sources)
def subscribe(
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler_ or CurrentThreadScheduler.singleton()
subscription = SerialDisposable()
cancelable = SerialDisposable()
last_exception = None
is_disposed = False
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
def on_error(exn: Exception) -> None:
nonlocal last_exception
last_exception = exn
cancelable.disposable = _scheduler.schedule(action)
if is_disposed:
return
try:
current = next(sources_)
except StopIteration:
if last_exception:
observer.on_error(last_exception)
else:
observer.on_completed()
except Exception as ex: # pylint: disable=broad-except
observer.on_error(ex)
else:
d = SingleAssignmentDisposable()
subscription.disposable = d
d.disposable = current.subscribe(
observer.on_next,
on_error,
observer.on_completed,
scheduler=scheduler_,
)
cancelable.disposable = _scheduler.schedule(action)
def dispose() -> None:
nonlocal is_disposed
is_disposed = True
return CompositeDisposable(subscription, cancelable, Disposable(dispose))
return Observable(subscribe)
__all__ = ["catch_with_iterable_"]

View File

@@ -0,0 +1,76 @@
from typing import Any, List, Optional, Tuple
from reactivex import Observable, abc
from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable
def combine_latest_(*sources: Observable[Any]) -> Observable[Tuple[Any, ...]]:
"""Merges the specified observable sequences into one observable
sequence by creating a tuple whenever any of the
observable sequences produces an element.
Examples:
>>> obs = combine_latest(obs1, obs2, obs3)
Returns:
An observable sequence containing the result of combining
elements of the sources into a tuple.
"""
parent = sources[0]
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> CompositeDisposable:
n = len(sources)
has_value = [False] * n
has_value_all = [False]
is_done = [False] * n
values = [None] * n
def _next(i: Any) -> None:
has_value[i] = True
if has_value_all[0] or all(has_value):
res = tuple(values)
observer.on_next(res)
elif all([x for j, x in enumerate(is_done) if j != i]):
observer.on_completed()
has_value_all[0] = all(has_value)
def done(i: Any) -> None:
is_done[i] = True
if all(is_done):
observer.on_completed()
subscriptions: List[Optional[SingleAssignmentDisposable]] = [None] * n
def func(i: int) -> None:
subscriptions[i] = SingleAssignmentDisposable()
def on_next(x: Any) -> None:
with parent.lock:
values[i] = x
_next(i)
def on_completed() -> None:
with parent.lock:
done(i)
subscription = subscriptions[i]
assert subscription
subscription.disposable = sources[i].subscribe(
on_next, observer.on_error, on_completed, scheduler=scheduler
)
for idx in range(n):
func(idx)
return CompositeDisposable(subscriptions)
return Observable(subscribe)
__all__ = ["combine_latest_"]

View File

@@ -0,0 +1,62 @@
from typing import Any, Iterable, Optional, TypeVar
from reactivex import Observable, abc
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SerialDisposable,
SingleAssignmentDisposable,
)
from reactivex.scheduler import CurrentThreadScheduler
_T = TypeVar("_T")
def concat_with_iterable_(sources: Iterable[Observable[_T]]) -> Observable[_T]:
def subscribe(
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler_ or CurrentThreadScheduler.singleton()
sources_ = iter(sources)
subscription = SerialDisposable()
cancelable = SerialDisposable()
is_disposed = False
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
nonlocal is_disposed
if is_disposed:
return
def on_completed() -> None:
cancelable.disposable = _scheduler.schedule(action)
try:
current = next(sources_)
except StopIteration:
observer.on_completed()
except Exception as ex: # pylint: disable=broad-except
observer.on_error(ex)
else:
d = SingleAssignmentDisposable()
subscription.disposable = d
d.disposable = current.subscribe(
observer.on_next,
observer.on_error,
on_completed,
scheduler=scheduler_,
)
cancelable.disposable = _scheduler.schedule(action)
def dispose() -> None:
nonlocal is_disposed
is_disposed = True
return CompositeDisposable(subscription, cancelable, Disposable(dispose))
return Observable(subscribe)
__all__ = ["concat_with_iterable_"]

View File

@@ -0,0 +1,82 @@
from typing import List, Optional, TypeVar
from reactivex import abc
from reactivex.disposable import CompositeDisposable, Disposable
from .observable import Observable
_T = TypeVar("_T")
class ConnectableObservable(Observable[_T]):
"""Represents an observable that can be connected and
disconnected."""
def __init__(self, source: abc.ObservableBase[_T], subject: abc.SubjectBase[_T]):
self.subject = subject
self.has_subscription = False
self.subscription: Optional[abc.DisposableBase] = None
self.source = source
super().__init__()
def _subscribe_core(
self,
observer: abc.ObserverBase[_T],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
return self.subject.subscribe(observer, scheduler=scheduler)
def connect(
self, scheduler: Optional[abc.SchedulerBase] = None
) -> Optional[abc.DisposableBase]:
"""Connects the observable."""
if not self.has_subscription:
self.has_subscription = True
def dispose() -> None:
self.has_subscription = False
subscription = self.source.subscribe(self.subject, scheduler=scheduler)
self.subscription = CompositeDisposable(subscription, Disposable(dispose))
return self.subscription
def auto_connect(self, subscriber_count: int = 1) -> Observable[_T]:
"""Returns an observable sequence that stays connected to the
source indefinitely to the observable sequence.
Providing a subscriber_count will cause it to connect() after
that many subscriptions occur. A subscriber_count of 0 will
result in emissions firing immediately without waiting for
subscribers.
"""
connectable_subscription: List[Optional[abc.DisposableBase]] = [None]
count = [0]
source = self
is_connected = [False]
if subscriber_count == 0:
connectable_subscription[0] = source.connect()
is_connected[0] = True
def subscribe(
observer: abc.ObserverBase[_T],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
count[0] += 1
should_connect = count[0] == subscriber_count and not is_connected[0]
subscription = source.subscribe(observer)
if should_connect:
connectable_subscription[0] = source.connect(scheduler)
is_connected[0] = True
def dispose() -> None:
subscription.dispose()
count[0] -= 1
is_connected[0] = False
return Disposable(dispose)
return Observable(subscribe)

View File

@@ -0,0 +1,43 @@
from asyncio import Future
from typing import Callable, Optional, TypeVar, Union
from reactivex import Observable, abc, from_future, throw
from reactivex.scheduler import ImmediateScheduler
_T = TypeVar("_T")
def defer_(
factory: Callable[[abc.SchedulerBase], Union[Observable[_T], "Future[_T]"]]
) -> Observable[_T]:
"""Returns an observable sequence that invokes the specified factory
function whenever a new observer subscribes.
Example:
>>> res = defer(lambda scheduler: of(1, 2, 3))
Args:
observable_factory: Observable factory function to invoke for
each observer that subscribes to the resulting sequence. The
factory takes a single argument, the scheduler used.
Returns:
An observable sequence whose observers trigger an invocation
of the given observable factory function.
"""
def subscribe(
observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
try:
result = factory(scheduler or ImmediateScheduler.singleton())
except Exception as ex: # By design. pylint: disable=W0703
return throw(ex).subscribe(observer)
result = from_future(result) if isinstance(result, Future) else result
return result.subscribe(observer, scheduler=scheduler)
return Observable(subscribe)
__all__ = ["defer_"]

View File

@@ -0,0 +1,22 @@
from typing import Any, Optional
from reactivex import Observable, abc
from reactivex.scheduler import ImmediateScheduler
def empty_(scheduler: Optional[abc.SchedulerBase] = None) -> Observable[Any]:
def subscribe(
observer: abc.ObserverBase[Any], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or ImmediateScheduler.singleton()
def action(_: abc.SchedulerBase, __: Any) -> None:
observer.on_completed()
return _scheduler.schedule(action)
return Observable(subscribe)
__all__ = ["empty_"]

View File

@@ -0,0 +1,72 @@
from typing import Any, List, Optional, Tuple, cast
from reactivex import Observable, abc
from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable
def fork_join_(*sources: Observable[Any]) -> Observable[Tuple[Any, ...]]:
"""Wait for observables to complete and then combine last values
they emitted into a tuple. Whenever any of that observables completes
without emitting any value, result sequence will complete at that moment as well.
Examples:
>>> obs = reactivex.fork_join(obs1, obs2, obs3)
Returns:
An observable sequence containing the result of combining last element from
each source in given sequence.
"""
parent = sources[0]
def subscribe(
observer: abc.ObserverBase[Tuple[Any, ...]],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
n = len(sources)
values = [None] * n
is_done = [False] * n
has_value = [False] * n
def done(i: int) -> None:
is_done[i] = True
if not has_value[i]:
observer.on_completed()
return
if all(is_done):
if all(has_value):
observer.on_next(tuple(values))
observer.on_completed()
else:
observer.on_completed()
subscriptions: List[SingleAssignmentDisposable] = [
cast(SingleAssignmentDisposable, None)
] * n
def _subscribe(i: int) -> None:
subscriptions[i] = SingleAssignmentDisposable()
def on_next(value: Any) -> None:
with parent.lock:
values[i] = value
has_value[i] = True
def on_completed() -> None:
with parent.lock:
done(i)
subscriptions[i].disposable = sources[i].subscribe(
on_next, observer.on_error, on_completed, scheduler=scheduler
)
for i in range(n):
_subscribe(i)
return CompositeDisposable(subscriptions)
return Observable(subscribe)
__all__ = ["fork_join_"]

View File

@@ -0,0 +1,59 @@
from typing import Any, Callable, Optional
from reactivex import Observable, abc, typing
from reactivex.disposable import Disposable
def from_callback_(
func: Callable[..., Callable[..., None]],
mapper: Optional[typing.Mapper[Any, Any]] = None,
) -> Callable[[], Observable[Any]]:
"""Converts a callback function to an observable sequence.
Args:
func: Function with a callback as the last argument to
convert to an Observable sequence.
mapper: [Optional] A mapper which takes the arguments
from the callback to produce a single item to yield on next.
Returns:
A function, when executed with the required arguments minus
the callback, produces an Observable sequence with a single value of
the arguments to the callback as a list.
"""
def function(*args: Any) -> Observable[Any]:
arguments = list(args)
def subscribe(
observer: abc.ObserverBase[Any],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
def handler(*args: Any) -> None:
results = list(args)
if mapper:
try:
results = mapper(args)
except Exception as err: # pylint: disable=broad-except
observer.on_error(err)
return
observer.on_next(results)
else:
if len(results) <= 1:
observer.on_next(*results)
else:
observer.on_next(results)
observer.on_completed()
arguments.append(handler)
func(*arguments)
return Disposable()
return Observable(subscribe)
return function
__all__ = ["from_callback_"]

View File

@@ -0,0 +1,49 @@
import asyncio
from asyncio import Future
from typing import Any, Optional, TypeVar, cast
from reactivex import Observable, abc
from reactivex.disposable import Disposable
_T = TypeVar("_T")
def from_future_(future: "Future[_T]") -> Observable[_T]:
"""Converts a Future to an Observable sequence
Args:
future -- A Python 3 compatible future.
https://docs.python.org/3/library/asyncio-task.html#future
Returns:
An Observable sequence which wraps the existing future success
and failure.
"""
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
def done(future: "Future[_T]") -> None:
try:
value: Any = future.result()
except Exception as ex:
observer.on_error(ex)
except asyncio.CancelledError as ex: # pylint: disable=broad-except
# asyncio.CancelledError is a BaseException, so need to cast
observer.on_error(cast(Exception, ex))
else:
observer.on_next(value)
observer.on_completed()
future.add_done_callback(done)
def dispose() -> None:
if future:
future.cancel()
return Disposable(dispose)
return Observable(subscribe)
__all__ = ["from_future_"]

View File

@@ -0,0 +1,56 @@
from typing import Any, Iterable, Optional, TypeVar
from reactivex import Observable, abc
from reactivex.disposable import CompositeDisposable, Disposable
from reactivex.scheduler import CurrentThreadScheduler
_T = TypeVar("_T")
def from_iterable_(
iterable: Iterable[_T], scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[_T]:
"""Converts an iterable to an observable sequence.
Example:
>>> from_iterable([1,2,3])
Args:
iterable: A Python iterable
scheduler: An optional scheduler to schedule the values on.
Returns:
The observable sequence whose elements are pulled from the
given iterable sequence.
"""
def subscribe(
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton()
iterator = iter(iterable)
disposed = False
def action(_: abc.SchedulerBase, __: Any = None) -> None:
nonlocal disposed
try:
while not disposed:
value = next(iterator)
observer.on_next(value)
except StopIteration:
observer.on_completed()
except Exception as error: # pylint: disable=broad-except
observer.on_error(error)
def dispose() -> None:
nonlocal disposed
disposed = True
disp = Disposable(dispose)
return CompositeDisposable(_scheduler.schedule(action), disp)
return Observable(subscribe)
__all__ = ["from_iterable_"]

View File

@@ -0,0 +1,57 @@
from typing import Any, Optional, TypeVar, cast
from reactivex import Observable, abc, typing
from reactivex.disposable import MultipleAssignmentDisposable
from reactivex.scheduler import CurrentThreadScheduler
_TState = TypeVar("_TState")
def generate_(
initial_state: _TState,
condition: typing.Predicate[_TState],
iterate: typing.Mapper[_TState, _TState],
) -> Observable[_TState]:
def subscribe(
observer: abc.ObserverBase[_TState],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
scheduler = scheduler or CurrentThreadScheduler.singleton()
first = True
state = initial_state
mad = MultipleAssignmentDisposable()
def action(scheduler: abc.SchedulerBase, state1: Any = None) -> None:
nonlocal first
nonlocal state
has_result = False
result: _TState = cast(_TState, None)
try:
if first:
first = False
else:
state = iterate(state)
has_result = condition(state)
if has_result:
result = state
except Exception as exception: # pylint: disable=broad-except
observer.on_error(exception)
return
if has_result:
observer.on_next(result)
mad.disposable = scheduler.schedule(action)
else:
observer.on_completed()
mad.disposable = scheduler.schedule(action)
return mad
return Observable(subscribe)
__all__ = ["generate_"]

View File

@@ -0,0 +1,89 @@
from typing import Any, Callable, Optional, TypeVar, cast
from reactivex import Observable, abc
from reactivex.disposable import MultipleAssignmentDisposable
from reactivex.scheduler import TimeoutScheduler
from reactivex.typing import Mapper, Predicate, RelativeTime
_TState = TypeVar("_TState")
def generate_with_relative_time_(
initial_state: _TState,
condition: Predicate[_TState],
iterate: Mapper[_TState, _TState],
time_mapper: Callable[[_TState], RelativeTime],
) -> Observable[_TState]:
"""Generates an observable sequence by iterating a state from an
initial state until the condition fails.
Example:
res = source.generate_with_relative_time(
0, lambda x: True, lambda x: x + 1, lambda x: 0.5
)
Args:
initial_state: Initial state.
condition: Condition to terminate generation (upon returning
false).
iterate: Iteration step function.
time_mapper: Time mapper function to control the speed of
values being produced each iteration, returning relative
times, i.e. either floats denoting seconds or instances of
timedelta.
Returns:
The generated sequence.
"""
def subscribe(
observer: abc.ObserverBase[_TState],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
scheduler = scheduler or TimeoutScheduler.singleton()
mad = MultipleAssignmentDisposable()
state = initial_state
has_result = False
result: _TState = cast(_TState, None)
first = True
time: Optional[RelativeTime] = None
def action(scheduler: abc.SchedulerBase, _: Any) -> None:
nonlocal state
nonlocal has_result
nonlocal result
nonlocal first
nonlocal time
if has_result:
observer.on_next(result)
try:
if first:
first = False
else:
state = iterate(state)
has_result = condition(state)
if has_result:
result = state
time = time_mapper(state)
except Exception as e: # pylint: disable=broad-except
observer.on_error(e)
return
if has_result:
assert time
mad.disposable = scheduler.schedule_relative(time, action)
else:
observer.on_completed()
mad.disposable = scheduler.schedule_relative(0, action)
return mad
return Observable(subscribe)
__all__ = ["generate_with_relative_time_"]

View File

@@ -0,0 +1,40 @@
from typing import Generic, Optional, TypeVar
from reactivex import abc
from reactivex.disposable import CompositeDisposable, Disposable, RefCountDisposable
from .observable import Observable
_T = TypeVar("_T")
_TKey = TypeVar("_TKey")
class GroupedObservable(Generic[_TKey, _T], Observable[_T]):
def __init__(
self,
key: _TKey,
underlying_observable: Observable[_T],
merged_disposable: Optional[RefCountDisposable] = None,
):
super().__init__()
self.key = key
def subscribe(
observer: abc.ObserverBase[_T],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
return CompositeDisposable(
merged_disposable.disposable if merged_disposable else Disposable(),
underlying_observable.subscribe(observer, scheduler=scheduler),
)
self.underlying_observable = (
underlying_observable if not merged_disposable else Observable(subscribe)
)
def _subscribe_core(
self,
observer: abc.ObserverBase[_T],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
return self.underlying_observable.subscribe(observer, scheduler=scheduler)

View File

@@ -0,0 +1,55 @@
from asyncio import Future
from typing import Callable, TypeVar, Union
import reactivex
from reactivex import Observable, abc
_T = TypeVar("_T")
def if_then_(
condition: Callable[[], bool],
then_source: Union[Observable[_T], "Future[_T]"],
else_source: Union[None, Observable[_T], "Future[_T]"] = None,
) -> Observable[_T]:
"""Determines whether an observable collection contains values.
Example:
1 - res = reactivex.if_then(condition, obs1)
2 - res = reactivex.if_then(condition, obs1, obs2)
Args:
condition: The condition which determines if the then_source or
else_source will be run.
then_source: The observable sequence or Promise that
will be run if the condition function returns true.
else_source: [Optional] The observable sequence or
Promise that will be run if the condition function returns
False. If this is not provided, it defaults to
reactivex.empty
Returns:
An observable sequence which is either the then_source or
else_source.
"""
else_source_: Union[Observable[_T], "Future[_T]"] = else_source or reactivex.empty()
then_source = (
reactivex.from_future(then_source)
if isinstance(then_source, Future)
else then_source
)
else_source_ = (
reactivex.from_future(else_source_)
if isinstance(else_source_, Future)
else else_source_
)
def factory(_: abc.SchedulerBase) -> Union[Observable[_T], "Future[_T]"]:
return then_source if condition() else else_source_
return reactivex.defer(factory)
__all__ = ["if_then_"]

View File

@@ -0,0 +1,13 @@
from typing import Optional
from reactivex import Observable, abc, timer, typing
def interval_(
period: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[int]:
return timer(period, period, scheduler)
__all__ = ["interval_"]

View File

@@ -0,0 +1,273 @@
import re
import threading
from datetime import datetime, timedelta
from typing import Any, List, Mapping, Optional, Tuple, Union
from reactivex import Notification, Observable, abc, notification, typing
from reactivex.disposable import CompositeDisposable, Disposable
from reactivex.scheduler import NewThreadScheduler
new_thread_scheduler = NewThreadScheduler()
# tokens will be searched in the order below using pipe
# group of elements: match any characters surrounded by ()
pattern_group = r"(\(.*?\))"
# timespan: match one or multiple hyphens
pattern_ticks = r"(-+)"
# comma err: match any comma which is not in a group
pattern_comma_error = r"(,)"
# element: match | or # or one or more characters which are not - | # ( ) ,
pattern_element = r"(#|\||[^-,()#\|]+)"
pattern = r"|".join(
[
pattern_group,
pattern_ticks,
pattern_comma_error,
pattern_element,
]
)
tokens = re.compile(pattern)
def hot(
string: str,
timespan: typing.RelativeTime = 0.1,
duetime: typing.AbsoluteOrRelativeTime = 0.0,
lookup: Optional[Mapping[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[Any]:
_scheduler = scheduler or new_thread_scheduler
if isinstance(duetime, datetime):
duetime = duetime - _scheduler.now
messages = parse(
string,
timespan=timespan,
time_shift=duetime,
lookup=lookup,
error=error,
raise_stopped=True,
)
lock = threading.RLock()
is_stopped = False
observers: List[abc.ObserverBase[Any]] = []
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
# should a hot observable already completed or on error
# re-push on_completed/on_error at subscription time?
if not is_stopped:
with lock:
observers.append(observer)
def dispose() -> None:
with lock:
try:
observers.remove(observer)
except ValueError:
pass
return Disposable(dispose)
def create_action(notification: Notification[Any]) -> typing.ScheduledAction[Any]:
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
nonlocal is_stopped
with lock:
for observer in observers:
notification.accept(observer)
if notification.kind in ("C", "E"):
is_stopped = True
return action
for message in messages:
timespan, notification = message
action = create_action(notification)
# Don't make closures within a loop
_scheduler.schedule_relative(timespan, action)
return Observable(subscribe)
def from_marbles(
string: str,
timespan: typing.RelativeTime = 0.1,
lookup: Optional[Mapping[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[Any]:
messages = parse(
string, timespan=timespan, lookup=lookup, error=error, raise_stopped=True
)
def subscribe(
observer: abc.ObserverBase[Any], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or new_thread_scheduler
disp = CompositeDisposable()
def schedule_msg(
message: Tuple[typing.RelativeTime, Notification[Any]]
) -> None:
duetime, notification = message
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
notification.accept(observer)
disp.add(_scheduler.schedule_relative(duetime, action))
for message in messages:
# Don't make closures within a loop
schedule_msg(message)
return disp
return Observable(subscribe)
def parse(
string: str,
timespan: typing.RelativeTime = 1.0,
time_shift: typing.RelativeTime = 0.0,
lookup: Optional[Mapping[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
raise_stopped: bool = False,
) -> List[Tuple[typing.RelativeTime, notification.Notification[Any]]]:
"""Convert a marble diagram string to a list of messages.
Each character in the string will advance time by timespan
(exept for space). Characters that are not special (see the table below)
will be interpreted as a value to be emitted. numbers will be cast
to int or float.
Special characters:
+--------+--------------------------------------------------------+
| `-` | advance time by timespan |
+--------+--------------------------------------------------------+
| `#` | on_error() |
+--------+--------------------------------------------------------+
| `|` | on_completed() |
+--------+--------------------------------------------------------+
| `(` | open a group of elements sharing the same timestamp |
+--------+--------------------------------------------------------+
| `)` | close a group of elements |
+--------+--------------------------------------------------------+
| `,` | separate elements in a group |
+--------+--------------------------------------------------------+
| space | used to align multiple diagrams, does not advance time |
+--------+--------------------------------------------------------+
In a group of elements, the position of the initial `(` determines the
timestamp at which grouped elements will be emitted. E.g. `--(12,3,4)--`
will emit 12, 3, 4 at 2 * timespan and then advance virtual time
by 8 * timespan.
Examples:
>>> parse("--1--(2,3)-4--|")
>>> parse("a--b--c-", lookup={'a': 1, 'b': 2, 'c': 3})
>>> parse("a--b---#", error=ValueError("foo"))
Args:
string: String with marble diagram
timespan: [Optional] duration of each character in second.
If not specified, defaults to 0.1s.
lookup: [Optional] dict used to convert an element into a specified
value. If not specified, defaults to {}.
time_shift: [Optional] time used to delay every elements.
If not specified, defaults to 0.0s.
error: [Optional] exception that will be use in place of the # symbol.
If not specified, defaults to Exception('error').
raise_finished: [optional] raise ValueError if elements are
declared after on_completed or on_error symbol.
Returns:
A list of messages defined as a tuple of (timespan, notification).
"""
error_ = error or Exception("error")
lookup_ = lookup or {}
if isinstance(timespan, timedelta):
timespan = timespan.total_seconds()
if isinstance(time_shift, timedelta):
time_shift = time_shift.total_seconds()
string = string.replace(" ", "")
# try to cast a string to an int, then to a float
def try_number(element: str) -> Union[float, str]:
try:
return int(element)
except ValueError:
try:
return float(element)
except ValueError:
return element
def map_element(
time: typing.RelativeTime, element: str
) -> Tuple[typing.RelativeTime, Notification[Any]]:
if element == "|":
return (time, notification.OnCompleted())
elif element == "#":
return (time, notification.OnError(error_))
else:
value = try_number(element)
value = lookup_.get(value, value)
return (time, notification.OnNext(value))
is_stopped = False
def check_stopped(element: str) -> None:
nonlocal is_stopped
if raise_stopped:
if is_stopped:
raise ValueError("Elements cannot be declared after a # or | symbol.")
if element in ("#", "|"):
is_stopped = True
iframe = 0
messages: List[Tuple[typing.RelativeTime, Notification[Any]]] = []
for results in tokens.findall(string):
timestamp = iframe * timespan + time_shift
group, ticks, comma_error, element = results
if group:
elements = group[1:-1].split(",")
for elm in elements:
check_stopped(elm)
grp_messages = [
map_element(timestamp, elm) for elm in elements if elm != ""
]
messages.extend(grp_messages)
iframe += len(group)
if ticks:
iframe += len(ticks)
if comma_error:
raise ValueError("Comma is only allowed in group of elements.")
if element:
check_stopped(element)
message = map_element(timestamp, element)
messages.append(message)
iframe += len(element)
return messages

View File

@@ -0,0 +1,14 @@
from typing import TypeVar
import reactivex
from reactivex import Observable
from reactivex import operators as ops
_T = TypeVar("_T")
def merge_(*sources: Observable[_T]) -> Observable[_T]:
return reactivex.from_iterable(sources).pipe(ops.merge_all())
__all__ = ["merge_"]

View File

@@ -0,0 +1,23 @@
from typing import Any, Optional
from reactivex import Observable, abc
from reactivex.disposable import Disposable
def never_() -> Observable[Any]:
"""Returns a non-terminating observable sequence, which can be used
to denote an infinite duration (e.g. when using reactive joins).
Returns:
An observable sequence whose observers will never get called.
"""
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
return Disposable()
return Observable(subscribe)
__all__ = ["never_"]

View File

@@ -0,0 +1,357 @@
# By design, pylint: disable=C0302
from __future__ import annotations
import asyncio
import threading
from typing import Any, Callable, Generator, Optional, TypeVar, Union, cast, overload
from reactivex import abc
from reactivex.disposable import Disposable
from reactivex.scheduler import CurrentThreadScheduler
from reactivex.scheduler.eventloop import AsyncIOScheduler
from ..observer import AutoDetachObserver
_A = TypeVar("_A")
_B = TypeVar("_B")
_C = TypeVar("_C")
_D = TypeVar("_D")
_E = TypeVar("_E")
_F = TypeVar("_F")
_G = TypeVar("_G")
_T_out = TypeVar("_T_out", covariant=True)
class Observable(abc.ObservableBase[_T_out]):
"""Observable base class.
Represents a push-style collection, which you can :func:`pipe <pipe>` into
:mod:`operators <reactivex.operators>`."""
def __init__(self, subscribe: Optional[abc.Subscription[_T_out]] = None) -> None:
"""Creates an observable sequence object from the specified
subscription function.
Args:
subscribe: [Optional] Subscription function
"""
super().__init__()
self.lock = threading.RLock()
self._subscribe = subscribe
def _subscribe_core(
self,
observer: abc.ObserverBase[_T_out],
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
return self._subscribe(observer, scheduler) if self._subscribe else Disposable()
def subscribe(
self,
on_next: Optional[
Union[abc.ObserverBase[_T_out], abc.OnNext[_T_out], None]
] = None,
on_error: Optional[abc.OnError] = None,
on_completed: Optional[abc.OnCompleted] = None,
*,
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
"""Subscribe an observer to the observable sequence.
You may subscribe using an observer or callbacks, not both; if the first
argument is an instance of :class:`Observer <..abc.ObserverBase>` or if
it has a (callable) attribute named :code:`on_next`, then any callback
arguments will be ignored.
Examples:
>>> source.subscribe()
>>> source.subscribe(observer)
>>> source.subscribe(observer, scheduler=scheduler)
>>> source.subscribe(on_next)
>>> source.subscribe(on_next, on_error)
>>> source.subscribe(on_next, on_error, on_completed)
>>> source.subscribe(on_next, on_error, on_completed, scheduler=scheduler)
Args:
observer: [Optional] The object that is to receive
notifications.
on_error: [Optional] Action to invoke upon exceptional termination
of the observable sequence.
on_completed: [Optional] Action to invoke upon graceful termination
of the observable sequence.
on_next: [Optional] Action to invoke for each element in the
observable sequence.
scheduler: [Optional] The default scheduler to use for this
subscription.
Returns:
Disposable object representing an observer's subscription to
the observable sequence.
"""
if (
isinstance(on_next, abc.ObserverBase)
or hasattr(on_next, "on_next")
and callable(getattr(on_next, "on_next"))
):
obv = cast(abc.ObserverBase[_T_out], on_next)
on_next = obv.on_next
on_error = obv.on_error
on_completed = obv.on_completed
auto_detach_observer: AutoDetachObserver[_T_out] = AutoDetachObserver(
on_next, on_error, on_completed
)
def fix_subscriber(
subscriber: Union[abc.DisposableBase, Callable[[], None]]
) -> abc.DisposableBase:
"""Fixes subscriber to make sure it returns a Disposable instead
of None or a dispose function"""
if isinstance(subscriber, abc.DisposableBase) or hasattr(
subscriber, "dispose"
):
# Note: cast can be avoided using Protocols (Python 3.9)
return cast(abc.DisposableBase, subscriber)
return Disposable(subscriber)
def set_disposable(
_: Optional[abc.SchedulerBase] = None, __: Any = None
) -> None:
try:
subscriber = self._subscribe_core(auto_detach_observer, scheduler)
except Exception as ex: # By design. pylint: disable=W0703
if not auto_detach_observer.fail(ex):
raise
else:
auto_detach_observer.subscription = fix_subscriber(subscriber)
# Subscribe needs to set up the trampoline before for subscribing.
# Actually, the first call to Subscribe creates the trampoline so
# that it may assign its disposable before any observer executes
# OnNext over the CurrentThreadScheduler. This enables single-
# threaded cancellation
# https://social.msdn.microsoft.com/Forums/en-US/eb82f593-9684-4e27-
# 97b9-8b8886da5c33/whats-the-rationale-behind-how-currentthreadsche
# dulerschedulerequired-behaves?forum=rx
current_thread_scheduler = CurrentThreadScheduler.singleton()
if current_thread_scheduler.schedule_required():
current_thread_scheduler.schedule(set_disposable)
else:
set_disposable()
# Hide the identity of the auto detach observer
return Disposable(auto_detach_observer.dispose)
@overload
def pipe(self, __op1: Callable[[Observable[_T_out]], _A]) -> _A:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
) -> _B:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
__op3: Callable[[_B], _C],
) -> _C:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
__op3: Callable[[_B], _C],
__op4: Callable[[_C], _D],
) -> _D:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
__op3: Callable[[_B], _C],
__op4: Callable[[_C], _D],
__op5: Callable[[_D], _E],
) -> _E:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
__op3: Callable[[_B], _C],
__op4: Callable[[_C], _D],
__op5: Callable[[_D], _E],
__op6: Callable[[_E], _F],
) -> _F:
...
@overload
def pipe(
self,
__op1: Callable[[Observable[_T_out]], _A],
__op2: Callable[[_A], _B],
__op3: Callable[[_B], _C],
__op4: Callable[[_C], _D],
__op5: Callable[[_D], _E],
__op6: Callable[[_E], _F],
__op7: Callable[[_F], _G],
) -> _G:
...
def pipe(self, *operators: Callable[[Any], Any]) -> Any:
"""Compose multiple operators left to right.
Composes zero or more operators into a functional composition.
The operators are composed from left to right. A composition of zero
operators gives back the original source.
Examples:
>>> source.pipe() == source
>>> source.pipe(f) == f(source)
>>> source.pipe(g, f) == f(g(source))
>>> source.pipe(h, g, f) == f(g(h(source)))
Args:
operators: Sequence of operators.
Returns:
The composed observable.
"""
from ..pipe import pipe as pipe_
return pipe_(self, *operators)
def run(self) -> Any:
"""Run source synchronously.
Subscribes to the observable source. Then blocks and waits for the
observable source to either complete or error. Returns the
last value emitted, or throws exception if any error occurred.
Examples:
>>> result = run(source)
Raises:
SequenceContainsNoElementsError: if observable completes
(on_completed) without any values being emitted.
Exception: raises exception if any error (on_error) occurred.
Returns:
The last element emitted from the observable.
"""
from ..run import run
return run(self)
def __await__(self) -> Generator[Any, None, _T_out]:
"""Awaits the given observable.
Returns:
The last item of the observable sequence.
"""
from ..operators._tofuture import to_future_
loop = asyncio.get_event_loop()
future: asyncio.Future[_T_out] = self.pipe(
to_future_(scheduler=AsyncIOScheduler(loop=loop))
)
return future.__await__()
def __add__(self, other: Observable[_T_out]) -> Observable[_T_out]:
"""Pythonic version of :func:`concat <reactivex.concat>`.
Example:
>>> zs = xs + ys
Args:
other: The second observable sequence in the concatenation.
Returns:
Concatenated observable sequence.
"""
from reactivex import concat
return concat(self, other)
def __iadd__(self, other: Observable[_T_out]) -> "Observable[_T_out]":
"""Pythonic use of :func:`concat <reactivex.concat>`.
Example:
>>> xs += ys
Args:
other: The second observable sequence in the concatenation.
Returns:
Concatenated observable sequence.
"""
from reactivex import concat
return concat(self, other)
def __getitem__(self, key: Union[slice, int]) -> Observable[_T_out]:
"""
Pythonic version of :func:`slice <reactivex.operators.slice>`.
Slices the given observable using Python slice notation. The arguments
to slice are `start`, `stop` and `step` given within brackets `[]` and
separated by the colons `:`.
It is basically a wrapper around the operators
:func:`skip <reactivex.operators.skip>`,
:func:`skip_last <reactivex.operators.skip_last>`,
:func:`take <reactivex.operators.take>`,
:func:`take_last <reactivex.operators.take_last>` and
:func:`filter <reactivex.operators.filter>`.
The following diagram helps you remember how slices works with streams.
Positive numbers are relative to the start of the events, while negative
numbers are relative to the end (close) of the stream.
.. code::
r---e---a---c---t---i---v---e---!
0 1 2 3 4 5 6 7 8
-8 -7 -6 -5 -4 -3 -2 -1 0
Examples:
>>> result = source[1:10]
>>> result = source[1:-2]
>>> result = source[1:-1:2]
Args:
key: Slice object
Returns:
Sliced observable sequence.
Raises:
TypeError: If key is not of type :code:`int` or :code:`slice`
"""
if isinstance(key, slice):
start, stop, step = key.start, key.stop, key.step
else:
start, stop, step = key, key + 1, 1
from ..operators._slice import slice_
return slice_(start, stop, step)(self)
__all__ = ["Observable"]

View File

@@ -0,0 +1,73 @@
from asyncio import Future
from typing import Callable, Optional, TypeVar, Union
import reactivex
from reactivex import Observable, abc
from reactivex.disposable import (
CompositeDisposable,
SerialDisposable,
SingleAssignmentDisposable,
)
from reactivex.scheduler import CurrentThreadScheduler
_T = TypeVar("_T")
def on_error_resume_next_(
*sources: Union[
Observable[_T], "Future[_T]", Callable[[Optional[Exception]], Observable[_T]]
]
) -> Observable[_T]:
"""Continues an observable sequence that is terminated normally or
by an exception with the next observable sequence.
Examples:
>>> res = reactivex.on_error_resume_next(xs, ys, zs)
Returns:
An observable sequence that concatenates the source sequences,
even if a sequence terminates exceptionally.
"""
sources_ = iter(sources)
def subscribe(
observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
scheduler = scheduler or CurrentThreadScheduler.singleton()
subscription = SerialDisposable()
cancelable = SerialDisposable()
def action(
scheduler: abc.SchedulerBase, state: Optional[Exception] = None
) -> None:
try:
source = next(sources_)
except StopIteration:
observer.on_completed()
return
# Allow source to be a factory method taking an error
source = source(state) if callable(source) else source
current = (
reactivex.from_future(source) if isinstance(source, Future) else source
)
d = SingleAssignmentDisposable()
subscription.disposable = d
def on_resume(state: Optional[Exception] = None) -> None:
scheduler.schedule(action, state)
d.disposable = current.subscribe(
observer.on_next, on_resume, on_resume, scheduler=scheduler
)
cancelable.disposable = scheduler.schedule(action)
return CompositeDisposable(subscription, cancelable)
return Observable(subscribe)
__all__ = ["on_error_resume_next_"]

View File

@@ -0,0 +1,70 @@
from sys import maxsize
from typing import Iterator, Optional
from reactivex import Observable, abc
from reactivex.disposable import MultipleAssignmentDisposable
from reactivex.scheduler import CurrentThreadScheduler
def range_(
start: int,
stop: Optional[int] = None,
step: Optional[int] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[int]:
"""Generates an observable sequence of integral numbers within a
specified range, using the specified scheduler to send out observer
messages.
Examples:
>>> res = range(10)
>>> res = range(0, 10)
>>> res = range(0, 10, 1)
Args:
start: The value of the first integer in the sequence.
stop: [Optional] Generate number up to (exclusive) the stop
value. Default is `sys.maxsize`.
step: [Optional] The step to be used (default is 1).
scheduler: The scheduler to schedule the values on.
Returns:
An observable sequence that contains a range of sequential
integral numbers.
"""
_stop: int = maxsize if stop is None else stop
_step: int = 1 if step is None else step
if step is None and stop is None:
range_t = range(start)
elif step is None:
range_t = range(start, _stop)
else:
range_t = range(start, _stop, _step)
def subscribe(
observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
nonlocal range_t
_scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton()
sd = MultipleAssignmentDisposable()
def action(
scheduler: abc.SchedulerBase, iterator: Optional[Iterator[int]]
) -> None:
try:
assert iterator
observer.on_next(next(iterator))
sd.disposable = _scheduler.schedule(action, state=iterator)
except StopIteration:
observer.on_completed()
sd.disposable = _scheduler.schedule(action, iter(range_t))
return sd
return Observable(subscribe)
__all__ = ["range_"]

View File

@@ -0,0 +1,35 @@
from typing import Optional, TypeVar
import reactivex
from reactivex import Observable
from reactivex import operators as ops
_T = TypeVar("_T")
def repeat_value_(value: _T, repeat_count: Optional[int] = None) -> Observable[_T]:
"""Generates an observable sequence that repeats the given element
the specified number of times.
Examples:
1 - res = repeat_value(42)
2 - res = repeat_value(42, 4)
Args:
value: Element to repeat.
repeat_count: [Optional] Number of times to repeat the element.
If not specified, repeats indefinitely.
Returns:
An observable sequence that repeats the given element the
specified number of times.
"""
if repeat_count == -1:
repeat_count = None
xs = reactivex.return_value(value)
return xs.pipe(ops.repeat(repeat_count))
__all__ = ["repeat_value_"]

View File

@@ -0,0 +1,64 @@
from typing import Any, Callable, Optional, TypeVar
from reactivex import Observable, abc
from reactivex.scheduler import CurrentThreadScheduler
_T = TypeVar("_T")
def return_value_(
value: _T, scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[_T]:
"""Returns an observable sequence that contains a single element,
using the specified scheduler to send out observer messages.
There is an alias called 'just'.
Examples:
>>> res = return(42)
>>> res = return(42, rx.Scheduler.timeout)
Args:
value: Single element in the resulting observable sequence.
Returns:
An observable sequence containing the single specified
element.
"""
def subscribe(
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton()
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
observer.on_next(value)
observer.on_completed()
return _scheduler.schedule(action)
return Observable(subscribe)
def from_callable_(
supplier: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[_T]:
def subscribe(
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton()
def action(_: abc.SchedulerBase, __: Any = None) -> None:
nonlocal observer
try:
observer.on_next(supplier())
observer.on_completed()
except Exception as e: # pylint: disable=broad-except
observer.on_error(e)
return _scheduler.schedule(action)
return Observable(subscribe)
__all__ = ["return_value_", "from_callable_"]

View File

@@ -0,0 +1,36 @@
from typing import Callable, Optional, TypeVar
from reactivex import Observable, abc, to_async
_T = TypeVar("_T")
def start_(
func: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[_T]:
"""Invokes the specified function asynchronously on the specified
scheduler, surfacing the result through an observable sequence.
Example:
>>> res = reactivex.start(lambda: pprint('hello'))
>>> res = reactivex.start(lambda: pprint('hello'), rx.Scheduler.timeout)
Args:
func: Function to run asynchronously.
scheduler: [Optional] Scheduler to run the function on. If
not specified, defaults to Scheduler.timeout.
Remarks:
The function is called immediately, not during the subscription
of the resulting sequence. Multiple subscriptions to the
resulting sequence can observe the function's result.
Returns:
An observable sequence exposing the function's result value,
or an exception.
"""
return to_async(func, scheduler)()
__all__ = ["start_"]

View File

@@ -0,0 +1,18 @@
from asyncio import Future
from typing import Callable, TypeVar
from reactivex import Observable, from_future, throw
_T = TypeVar("_T")
def start_async_(function_async: Callable[[], "Future[_T]"]) -> Observable[_T]:
try:
future = function_async()
except Exception as ex: # pylint: disable=broad-except
return throw(ex)
return from_future(future)
__all__ = ["start_async_"]

View File

@@ -0,0 +1,25 @@
from typing import Any, Optional, Union
from reactivex import Observable, abc
from reactivex.scheduler import ImmediateScheduler
def throw_(
exception: Union[str, Exception], scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[Any]:
exception_ = exception if isinstance(exception, Exception) else Exception(exception)
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or ImmediateScheduler.singleton()
def action(scheduler: abc.SchedulerBase, state: Any) -> None:
observer.on_error(exception_)
return _scheduler.schedule(action)
return Observable(subscribe)
__all__ = ["throw_"]

View File

@@ -0,0 +1,134 @@
from datetime import datetime
from typing import Any, Optional
from reactivex import Observable, abc, typing
from reactivex.disposable import MultipleAssignmentDisposable
from reactivex.scheduler import TimeoutScheduler
from reactivex.scheduler.periodicscheduler import PeriodicScheduler
def observable_timer_date(
duetime: typing.AbsoluteTime, scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[int]:
def subscribe(
observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler: abc.SchedulerBase = (
scheduler or scheduler_ or TimeoutScheduler.singleton()
)
def action(scheduler: abc.SchedulerBase, state: Any) -> None:
observer.on_next(0)
observer.on_completed()
return _scheduler.schedule_absolute(duetime, action)
return Observable(subscribe)
def observable_timer_duetime_and_period(
duetime: typing.AbsoluteOrRelativeTime,
period: typing.AbsoluteOrRelativeTime,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[int]:
def subscribe(
observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton()
nonlocal duetime
if not isinstance(duetime, datetime):
duetime = _scheduler.now + _scheduler.to_timedelta(duetime)
p = max(0.0, _scheduler.to_seconds(period))
mad = MultipleAssignmentDisposable()
dt = duetime
count = 0
def action(scheduler: abc.SchedulerBase, state: Any) -> None:
nonlocal dt
nonlocal count
if p > 0.0:
now = scheduler.now
dt = dt + scheduler.to_timedelta(p)
if dt <= now:
dt = now + scheduler.to_timedelta(p)
observer.on_next(count)
count += 1
mad.disposable = scheduler.schedule_absolute(dt, action)
mad.disposable = _scheduler.schedule_absolute(dt, action)
return mad
return Observable(subscribe)
def observable_timer_timespan(
duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None
) -> Observable[int]:
def subscribe(
observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
_scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton()
d = _scheduler.to_seconds(duetime)
def action(scheduler: abc.SchedulerBase, state: Any) -> None:
observer.on_next(0)
observer.on_completed()
if d <= 0.0:
return _scheduler.schedule(action)
return _scheduler.schedule_relative(d, action)
return Observable(subscribe)
def observable_timer_timespan_and_period(
duetime: typing.RelativeTime,
period: typing.RelativeTime,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[int]:
if duetime == period:
def subscribe(
observer: abc.ObserverBase[int],
scheduler_: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
_scheduler: abc.SchedulerBase = (
scheduler or scheduler_ or TimeoutScheduler.singleton()
)
def action(count: Optional[int] = None) -> Optional[int]:
if count is not None:
observer.on_next(count)
return count + 1
return None
if not isinstance(_scheduler, PeriodicScheduler):
raise ValueError("Sceduler must be PeriodicScheduler")
return _scheduler.schedule_periodic(period, action, state=0)
return Observable(subscribe)
return observable_timer_duetime_and_period(duetime, period, scheduler)
def timer_(
duetime: typing.AbsoluteOrRelativeTime,
period: Optional[typing.RelativeTime] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> Observable[int]:
if isinstance(duetime, datetime):
if period is None:
return observable_timer_date(duetime, scheduler)
else:
return observable_timer_duetime_and_period(duetime, period, scheduler)
if period is None:
return observable_timer_timespan(duetime, scheduler)
return observable_timer_timespan_and_period(duetime, period, scheduler)
__all__ = ["timer_"]

View File

@@ -0,0 +1,54 @@
from typing import Any, Callable, Optional, TypeVar
from reactivex import Observable, abc
from reactivex import operators as ops
from reactivex.scheduler import TimeoutScheduler
from reactivex.subject import AsyncSubject
_T = TypeVar("_T")
def to_async_(
func: Callable[..., _T], scheduler: Optional[abc.SchedulerBase] = None
) -> Callable[..., Observable[_T]]:
"""Converts the function into an asynchronous function. Each
invocation of the resulting asynchronous function causes an
invocation of the original synchronous function on the specified
scheduler.
Examples:
res = reactivex.to_async(lambda x, y: x + y)(4, 3)
res = reactivex.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3)
res = reactivex.to_async(lambda x: log.debug(x), Scheduler.timeout)('hello')
Args:
func: Function to convert to an asynchronous function.
scheduler: [Optional] Scheduler to run the function on. If not
specified, defaults to Scheduler.timeout.
Returns:
Aynchronous function.
"""
_scheduler = scheduler or TimeoutScheduler.singleton()
def wrapper(*args: Any) -> Observable[_T]:
subject: AsyncSubject[_T] = AsyncSubject()
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
try:
result = func(*args)
except Exception as ex: # pylint: disable=broad-except
subject.on_error(ex)
return
subject.on_next(result)
subject.on_completed()
_scheduler.schedule(action)
return subject.pipe(ops.as_observable())
return wrapper
__all__ = ["to_async_"]

View File

@@ -0,0 +1,53 @@
from typing import Callable, Optional, TypeVar
import reactivex
from reactivex import Observable, abc
from reactivex.disposable import CompositeDisposable, Disposable
_T = TypeVar("_T")
def using_(
resource_factory: Callable[[], abc.DisposableBase],
observable_factory: Callable[[abc.DisposableBase], Observable[_T]],
) -> Observable[_T]:
"""Constructs an observable sequence that depends on a resource
object, whose lifetime is tied to the resulting observable
sequence's lifetime.
Example:
>>> res = reactivex.using(lambda: AsyncSubject(), lambda: s: s)
Args:
resource_factory: Factory function to obtain a resource object.
observable_factory: Factory function to obtain an observable
sequence that depends on the obtained resource.
Returns:
An observable sequence whose lifetime controls the lifetime
of the dependent resource object.
"""
def subscribe(
observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
disp: abc.DisposableBase = Disposable()
try:
resource = resource_factory()
if resource is not None:
disp = resource
source = observable_factory(resource)
except Exception as exception: # pylint: disable=broad-except
d = reactivex.throw(exception).subscribe(observer, scheduler=scheduler)
return CompositeDisposable(d, disp)
return CompositeDisposable(
source.subscribe(observer, scheduler=scheduler), disp
)
return Observable(subscribe)
__all__ = ["using_"]

View File

@@ -0,0 +1,59 @@
from typing import Any, List, Optional, Tuple
from reactivex import Observable, abc
from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable
from reactivex.internal.utils import NotSet
def with_latest_from_(
parent: Observable[Any], *sources: Observable[Any]
) -> Observable[Tuple[Any, ...]]:
NO_VALUE = NotSet()
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> abc.DisposableBase:
def subscribeall(
parent: Observable[Any], *children: Observable[Any]
) -> List[SingleAssignmentDisposable]:
values = [NO_VALUE for _ in children]
def subscribechild(
i: int, child: Observable[Any]
) -> SingleAssignmentDisposable:
subscription = SingleAssignmentDisposable()
def on_next(value: Any) -> None:
with parent.lock:
values[i] = value
subscription.disposable = child.subscribe(
on_next, observer.on_error, scheduler=scheduler
)
return subscription
parent_subscription = SingleAssignmentDisposable()
def on_next(value: Any) -> None:
with parent.lock:
if NO_VALUE not in values:
result = (value,) + tuple(values)
observer.on_next(result)
children_subscription = [
subscribechild(i, child) for i, child in enumerate(children)
]
disp = parent.subscribe(
on_next, observer.on_error, observer.on_completed, scheduler=scheduler
)
parent_subscription.disposable = disp
return [parent_subscription] + children_subscription
return CompositeDisposable(subscribeall(parent, *sources))
return Observable(subscribe)
__all__ = ["with_latest_from_"]

View File

@@ -0,0 +1,90 @@
from asyncio import Future
from threading import RLock
from typing import Any, List, Optional, Tuple
from reactivex import Observable, abc, from_future
from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable
from reactivex.internal import synchronized
def zip_(*args: Observable[Any]) -> Observable[Tuple[Any, ...]]:
"""Merges the specified observable sequences into one observable
sequence by creating a tuple whenever all of the
observable sequences have produced an element at a corresponding
index.
Example:
>>> res = zip(obs1, obs2)
Args:
args: Observable sources to zip.
Returns:
An observable sequence containing the result of combining
elements of the sources as tuple.
"""
sources = list(args)
def subscribe(
observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None
) -> CompositeDisposable:
n = len(sources)
queues: List[List[Any]] = [[] for _ in range(n)]
lock = RLock()
is_completed = [False] * n
@synchronized(lock)
def next_(i: int) -> None:
if all(len(q) for q in queues):
try:
queued_values = [x.pop(0) for x in queues]
res = tuple(queued_values)
except Exception as ex: # pylint: disable=broad-except
observer.on_error(ex)
return
observer.on_next(res)
# after sending the zipped values, complete the observer if at least one
# upstream observable is completed and its queue has length zero
if any(
(
done
for queue, done in zip(queues, is_completed)
if len(queue) == 0
)
):
observer.on_completed()
def completed(i: int) -> None:
is_completed[i] = True
if len(queues[i]) == 0:
observer.on_completed()
subscriptions: List[Optional[abc.DisposableBase]] = [None] * n
def func(i: int) -> None:
source: Observable[Any] = sources[i]
if isinstance(source, Future):
source = from_future(source)
sad = SingleAssignmentDisposable()
def on_next(x: Any) -> None:
queues[i].append(x)
next_(i)
sad.disposable = source.subscribe(
on_next, observer.on_error, lambda: completed(i), scheduler=scheduler
)
subscriptions[i] = sad
for idx in range(n):
func(idx)
return CompositeDisposable(subscriptions)
return Observable(subscribe)
__all__ = ["zip_"]