142 lines
4.6 KiB
Python
142 lines
4.6 KiB
Python
from datetime import datetime
|
|
from typing import Any, Callable, List, Optional, TypeVar
|
|
|
|
from reactivex import Notification, Observable, abc
|
|
from reactivex import operators as ops
|
|
from reactivex import typing
|
|
from reactivex.disposable import (
|
|
CompositeDisposable,
|
|
MultipleAssignmentDisposable,
|
|
SerialDisposable,
|
|
)
|
|
from reactivex.internal.constants import DELTA_ZERO
|
|
from reactivex.notification import OnError
|
|
from reactivex.scheduler import TimeoutScheduler
|
|
|
|
from ._timestamp import Timestamp
|
|
|
|
_T = TypeVar("_T")
|
|
|
|
|
|
def observable_delay_timespan(
|
|
source: Observable[_T],
|
|
duetime: typing.RelativeTime,
|
|
scheduler: Optional[abc.SchedulerBase] = None,
|
|
) -> Observable[_T]:
|
|
def subscribe(
|
|
observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None
|
|
):
|
|
nonlocal duetime
|
|
|
|
_scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton()
|
|
|
|
if isinstance(duetime, datetime):
|
|
duetime_ = _scheduler.to_datetime(duetime) - _scheduler.now
|
|
else:
|
|
duetime_ = _scheduler.to_timedelta(duetime)
|
|
|
|
cancelable = SerialDisposable()
|
|
exception: Optional[Exception] = None
|
|
active = [False]
|
|
running = [False]
|
|
queue: List[Timestamp[Notification[_T]]] = []
|
|
|
|
def on_next(notification: Timestamp[Notification[_T]]) -> None:
|
|
nonlocal exception
|
|
should_run = False
|
|
|
|
with source.lock:
|
|
if isinstance(notification.value, OnError):
|
|
del queue[:]
|
|
queue.append(notification)
|
|
exception = notification.value.exception
|
|
should_run = not running[0]
|
|
else:
|
|
queue.append(
|
|
Timestamp(
|
|
value=notification.value,
|
|
timestamp=notification.timestamp + duetime_,
|
|
)
|
|
)
|
|
should_run = not active[0]
|
|
active[0] = True
|
|
|
|
if should_run:
|
|
if exception:
|
|
observer.on_error(exception)
|
|
else:
|
|
mad = MultipleAssignmentDisposable()
|
|
cancelable.disposable = mad
|
|
|
|
def action(scheduler: abc.SchedulerBase, state: Any = None):
|
|
if exception:
|
|
return
|
|
|
|
with source.lock:
|
|
running[0] = True
|
|
while True:
|
|
result = None
|
|
if queue and queue[0].timestamp <= scheduler.now:
|
|
result = queue.pop(0).value
|
|
|
|
if result:
|
|
result.accept(observer)
|
|
|
|
if not result:
|
|
break
|
|
|
|
should_continue = False
|
|
recurse_duetime: typing.RelativeTime = 0
|
|
if queue:
|
|
should_continue = True
|
|
diff = queue[0].timestamp - scheduler.now
|
|
recurse_duetime = max(DELTA_ZERO, diff)
|
|
else:
|
|
active[0] = False
|
|
|
|
ex = exception
|
|
running[0] = False
|
|
|
|
if ex:
|
|
observer.on_error(ex)
|
|
elif should_continue:
|
|
mad.disposable = scheduler.schedule_relative(
|
|
recurse_duetime, action
|
|
)
|
|
|
|
mad.disposable = _scheduler.schedule_relative(duetime_, action)
|
|
|
|
subscription = source.pipe(
|
|
ops.materialize(),
|
|
ops.timestamp(),
|
|
).subscribe(on_next, scheduler=_scheduler)
|
|
|
|
return CompositeDisposable(subscription, cancelable)
|
|
|
|
return Observable(subscribe)
|
|
|
|
|
|
def delay_(
|
|
duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None
|
|
) -> Callable[[Observable[_T]], Observable[_T]]:
|
|
def delay(source: Observable[_T]) -> Observable[_T]:
|
|
"""Time shifts the observable sequence.
|
|
|
|
A partially applied delay operator function.
|
|
|
|
Examples:
|
|
>>> res = delay(source)
|
|
|
|
Args:
|
|
source: The observable sequence to delay.
|
|
|
|
Returns:
|
|
A time-shifted observable sequence.
|
|
"""
|
|
return observable_delay_timespan(source, duetime, scheduler)
|
|
|
|
return delay
|
|
|
|
|
|
__all__ = ["delay_"]
|