175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
from typing import Any, Callable, List, Optional, TypeVar, cast
|
|
|
|
from reactivex import Observable, abc, typing
|
|
from reactivex.disposable import (
|
|
CompositeDisposable,
|
|
SerialDisposable,
|
|
SingleAssignmentDisposable,
|
|
)
|
|
from reactivex.scheduler import TimeoutScheduler
|
|
|
|
_T = TypeVar("_T")
|
|
|
|
|
|
def debounce_(
|
|
duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase]
|
|
) -> Callable[[Observable[_T]], Observable[_T]]:
|
|
def debounce(source: Observable[_T]) -> Observable[_T]:
|
|
"""Ignores values from an observable sequence which are followed by
|
|
another value before duetime.
|
|
|
|
Example:
|
|
>>> res = debounce(source)
|
|
|
|
Args:
|
|
source: Source observable to debounce.
|
|
|
|
Returns:
|
|
An operator function that takes the source observable and
|
|
returns the debounced observable sequence.
|
|
"""
|
|
|
|
def subscribe(
|
|
observer: abc.ObserverBase[_T],
|
|
scheduler_: Optional[abc.SchedulerBase] = None,
|
|
) -> abc.DisposableBase:
|
|
_scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton()
|
|
cancelable = SerialDisposable()
|
|
has_value = [False]
|
|
value: List[_T] = [cast(_T, None)]
|
|
_id: List[int] = [0]
|
|
|
|
def on_next(x: _T) -> None:
|
|
has_value[0] = True
|
|
value[0] = x
|
|
_id[0] += 1
|
|
current_id = _id[0]
|
|
d = SingleAssignmentDisposable()
|
|
cancelable.disposable = d
|
|
|
|
def action(scheduler: abc.SchedulerBase, state: Any = None) -> None:
|
|
if has_value[0] and _id[0] == current_id:
|
|
observer.on_next(value[0])
|
|
has_value[0] = False
|
|
|
|
d.disposable = _scheduler.schedule_relative(duetime, action)
|
|
|
|
def on_error(exception: Exception) -> None:
|
|
cancelable.dispose()
|
|
observer.on_error(exception)
|
|
has_value[0] = False
|
|
_id[0] += 1
|
|
|
|
def on_completed() -> None:
|
|
cancelable.dispose()
|
|
if has_value[0]:
|
|
observer.on_next(value[0])
|
|
|
|
observer.on_completed()
|
|
has_value[0] = False
|
|
_id[0] += 1
|
|
|
|
subscription = source.subscribe(
|
|
on_next, on_error, on_completed, scheduler=scheduler_
|
|
)
|
|
return CompositeDisposable(subscription, cancelable)
|
|
|
|
return Observable(subscribe)
|
|
|
|
return debounce
|
|
|
|
|
|
def throttle_with_mapper_(
|
|
throttle_duration_mapper: Callable[[Any], Observable[Any]]
|
|
) -> Callable[[Observable[_T]], Observable[_T]]:
|
|
def throttle_with_mapper(source: Observable[_T]) -> Observable[_T]:
|
|
"""Partially applied throttle_with_mapper operator.
|
|
|
|
Ignores values from an observable sequence which are followed by
|
|
another value within a computed throttle duration.
|
|
|
|
Example:
|
|
>>> obs = throttle_with_mapper(source)
|
|
|
|
Args:
|
|
source: The observable source to throttle.
|
|
|
|
Returns:
|
|
The throttled observable sequence.
|
|
"""
|
|
|
|
def subscribe(
|
|
observer: abc.ObserverBase[_T],
|
|
scheduler: Optional[abc.SchedulerBase] = None,
|
|
) -> abc.DisposableBase:
|
|
cancelable = SerialDisposable()
|
|
has_value: bool = False
|
|
value: _T = cast(_T, None)
|
|
_id = [0]
|
|
|
|
def on_next(x: _T) -> None:
|
|
nonlocal value, has_value
|
|
|
|
throttle = None
|
|
try:
|
|
throttle = throttle_duration_mapper(x)
|
|
except Exception as e: # pylint: disable=broad-except
|
|
observer.on_error(e)
|
|
return
|
|
|
|
has_value = True
|
|
value = x
|
|
_id[0] += 1
|
|
current_id = _id[0]
|
|
d = SingleAssignmentDisposable()
|
|
cancelable.disposable = d
|
|
|
|
def on_next(x: Any) -> None:
|
|
nonlocal has_value
|
|
if has_value and _id[0] == current_id:
|
|
observer.on_next(value)
|
|
|
|
has_value = False
|
|
d.dispose()
|
|
|
|
def on_completed() -> None:
|
|
nonlocal has_value
|
|
if has_value and _id[0] == current_id:
|
|
observer.on_next(value)
|
|
|
|
has_value = False
|
|
d.dispose()
|
|
|
|
d.disposable = throttle.subscribe(
|
|
on_next, observer.on_error, on_completed, scheduler=scheduler
|
|
)
|
|
|
|
def on_error(e: Exception) -> None:
|
|
nonlocal has_value
|
|
cancelable.dispose()
|
|
observer.on_error(e)
|
|
has_value = False
|
|
_id[0] += 1
|
|
|
|
def on_completed() -> None:
|
|
nonlocal has_value
|
|
cancelable.dispose()
|
|
if has_value:
|
|
observer.on_next(value)
|
|
|
|
observer.on_completed()
|
|
has_value = False
|
|
_id[0] += 1
|
|
|
|
subscription = source.subscribe(
|
|
on_next, on_error, on_completed, scheduler=scheduler
|
|
)
|
|
return CompositeDisposable(subscription, cancelable)
|
|
|
|
return Observable(subscribe)
|
|
|
|
return throttle_with_mapper
|
|
|
|
|
|
__all__ = ["debounce_", "throttle_with_mapper_"]
|