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,25 @@
from .catchscheduler import CatchScheduler
from .currentthreadscheduler import CurrentThreadScheduler
from .eventloopscheduler import EventLoopScheduler
from .historicalscheduler import HistoricalScheduler
from .immediatescheduler import ImmediateScheduler
from .newthreadscheduler import NewThreadScheduler
from .scheduleditem import ScheduledItem
from .threadpoolscheduler import ThreadPoolScheduler
from .timeoutscheduler import TimeoutScheduler
from .trampolinescheduler import TrampolineScheduler
from .virtualtimescheduler import VirtualTimeScheduler
__all__ = [
"CatchScheduler",
"CurrentThreadScheduler",
"EventLoopScheduler",
"HistoricalScheduler",
"ImmediateScheduler",
"NewThreadScheduler",
"ScheduledItem",
"ThreadPoolScheduler",
"TimeoutScheduler",
"TrampolineScheduler",
"VirtualTimeScheduler",
]

View File

@@ -0,0 +1,177 @@
from datetime import datetime
from typing import Callable, Optional, TypeVar, cast
from reactivex import abc, typing
from reactivex.abc.scheduler import SchedulerBase
from reactivex.disposable import Disposable, SingleAssignmentDisposable
from .periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
class CatchScheduler(PeriodicScheduler):
def __init__(
self, scheduler: abc.SchedulerBase, handler: Callable[[Exception], bool]
) -> None:
"""Wraps a scheduler, passed as constructor argument, adding exception
handling for scheduled actions. The handler should return True to
indicate it handled the exception successfully. Falsy return values will
be taken to indicate that the exception should be escalated (raised by
this scheduler).
Args:
scheduler: The scheduler to be wrapped.
handler: Callable to handle exceptions raised by wrapped scheduler.
"""
super().__init__()
self._scheduler: abc.SchedulerBase = scheduler
self._handler: Callable[[Exception], bool] = handler
self._recursive_original: Optional[abc.SchedulerBase] = None
self._recursive_wrapper: Optional["CatchScheduler"] = None
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self._scheduler.now
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
action = self._wrap(action)
return self._scheduler.schedule(action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
action = self._wrap(action)
return self._scheduler.schedule_relative(duetime, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
action = self._wrap(action)
return self._scheduler.schedule_absolute(duetime, action, state=state)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work.
Args:
period: Period in seconds or timedelta for running the
work periodically.
action: Action to be executed.
state: [Optional] Initial state passed to the action upon
the first iteration.
Returns:
The disposable object used to cancel the scheduled
recurring action (best effort).
"""
schedule_periodic = getattr(self._scheduler, "schedule_periodic")
if not callable(schedule_periodic):
raise NotImplementedError
disp: SingleAssignmentDisposable = SingleAssignmentDisposable()
failed: bool = False
def periodic(state: Optional[_TState] = None) -> Optional[_TState]:
nonlocal failed
if failed:
return None
try:
return action(state)
except Exception as ex:
failed = True
if not self._handler(ex):
raise
disp.dispose()
return None
scheduler = cast(PeriodicScheduler, self._scheduler)
disp.disposable = scheduler.schedule_periodic(period, periodic, state=state)
return disp
def _clone(self, scheduler: abc.SchedulerBase) -> "CatchScheduler":
return CatchScheduler(scheduler, self._handler)
def _wrap(
self, action: typing.ScheduledAction[_TState]
) -> typing.ScheduledAction[_TState]:
parent = self
def wrapped_action(
self: abc.SchedulerBase, state: Optional[_TState]
) -> Optional[abc.DisposableBase]:
try:
return action(parent._get_recursive_wrapper(self), state)
except Exception as ex:
if not parent._handler(ex):
raise
return Disposable()
return wrapped_action
def _get_recursive_wrapper(self, scheduler: SchedulerBase) -> "CatchScheduler":
if self._recursive_wrapper is None or self._recursive_original != scheduler:
self._recursive_original = scheduler
wrapper = self._clone(scheduler)
wrapper._recursive_original = scheduler
wrapper._recursive_wrapper = wrapper
self._recursive_wrapper = wrapper
return self._recursive_wrapper

View File

@@ -0,0 +1,81 @@
import logging
from threading import Thread, current_thread, local
from typing import MutableMapping
from weakref import WeakKeyDictionary
from .trampoline import Trampoline
from .trampolinescheduler import TrampolineScheduler
log = logging.getLogger("Rx")
class CurrentThreadScheduler(TrampolineScheduler):
"""Represents an object that schedules units of work on the current thread.
You should never schedule timeouts using the *CurrentThreadScheduler*, as
that will block the thread while waiting.
Each instance manages a number of trampolines (and queues), one for each
thread that calls a *schedule* method. These trampolines are automatically
garbage-collected when threads disappear, because they're stored in a weak
key dictionary.
"""
_global: MutableMapping[
type, MutableMapping[Thread, "CurrentThreadScheduler"]
] = WeakKeyDictionary()
@classmethod
def singleton(cls) -> "CurrentThreadScheduler":
"""
Obtain a singleton instance for the current thread. Please note, if you
pass this instance to another thread, it will effectively behave as
if it were created by that other thread (separate trampoline and queue).
Returns:
The singleton *CurrentThreadScheduler* instance.
"""
thread = current_thread()
class_map = CurrentThreadScheduler._global.get(cls)
if class_map is None:
class_map_: MutableMapping[
Thread, "CurrentThreadScheduler"
] = WeakKeyDictionary()
CurrentThreadScheduler._global[cls] = class_map_
else:
class_map_ = class_map
try:
self = class_map_[thread]
except KeyError:
self = CurrentThreadSchedulerSingleton()
class_map_[thread] = self
return self
# pylint: disable=super-init-not-called
def __init__(self) -> None:
self._tramps: MutableMapping[Thread, Trampoline] = WeakKeyDictionary()
def get_trampoline(self) -> Trampoline:
thread = current_thread()
tramp = self._tramps.get(thread)
if tramp is None:
tramp = Trampoline()
self._tramps[thread] = tramp
return tramp
class _Local(local):
def __init__(self) -> None:
super().__init__()
self.tramp = Trampoline()
class CurrentThreadSchedulerSingleton(CurrentThreadScheduler):
_local = _Local()
# pylint: disable=super-init-not-called
def __init__(self) -> None:
pass
def get_trampoline(self) -> Trampoline:
return CurrentThreadSchedulerSingleton._local.tramp

View File

@@ -0,0 +1,15 @@
from .asyncioscheduler import AsyncIOScheduler
from .asynciothreadsafescheduler import AsyncIOThreadSafeScheduler
from .eventletscheduler import EventletScheduler
from .geventscheduler import GEventScheduler
from .ioloopscheduler import IOLoopScheduler
from .twistedscheduler import TwistedScheduler
__all__ = [
"AsyncIOScheduler",
"AsyncIOThreadSafeScheduler",
"EventletScheduler",
"GEventScheduler",
"IOLoopScheduler",
"TwistedScheduler",
]

View File

@@ -0,0 +1,123 @@
import asyncio
import logging
from datetime import datetime
from typing import Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class AsyncIOScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the asyncio mainloop. This class
does not use the asyncio threadsafe methods, if you need those please use
the AsyncIOThreadSafeScheduler class."""
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
"""Create a new AsyncIOScheduler.
Args:
loop: Instance of asyncio event loop to use; typically, you would
get this by asyncio.get_event_loop()
"""
super().__init__()
self._loop: asyncio.AbstractEventLoop = loop
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
handle = self._loop.call_soon(interval)
def dispose() -> None:
handle.cancel()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0:
return self.schedule(action, state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
handle = self._loop.call_later(seconds, interval)
def dispose() -> None:
handle.cancel()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(self._loop.time())

View File

@@ -0,0 +1,157 @@
import asyncio
import logging
from concurrent.futures import Future
from typing import List, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from .asyncioscheduler import AsyncIOScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class AsyncIOThreadSafeScheduler(AsyncIOScheduler):
"""A scheduler that schedules work via the asyncio mainloop. This is a
subclass of AsyncIOScheduler which uses the threadsafe asyncio methods.
"""
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
handle = self._loop.call_soon_threadsafe(interval)
def dispose() -> None:
if self._on_self_loop_or_not_running():
handle.cancel()
return
future: "Future[int]" = Future()
def cancel_handle() -> None:
handle.cancel()
future.set_result(0)
self._loop.call_soon_threadsafe(cancel_handle)
future.result()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0:
return self.schedule(action, state=state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
# the operations on the list used here are atomic, so there is no
# need to protect its access with a lock
handle: List[asyncio.Handle] = []
def stage2() -> None:
handle.append(self._loop.call_later(seconds, interval))
handle.append(self._loop.call_soon_threadsafe(stage2))
def dispose() -> None:
def do_cancel_handles() -> None:
try:
handle.pop().cancel()
handle.pop().cancel()
except Exception:
pass
if self._on_self_loop_or_not_running():
do_cancel_handles()
return
future: "Future[int]" = Future()
def cancel_handle() -> None:
do_cancel_handles()
future.set_result(0)
self._loop.call_soon_threadsafe(cancel_handle)
future.result()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
def _on_self_loop_or_not_running(self) -> bool:
"""
Returns True if either self._loop is not running, or we're currently
executing on self._loop. In both cases, waiting for a future to be
resolved on the loop would result in a deadlock.
"""
if not self._loop.is_running():
return True
current_loop = None
try:
# In python 3.7 there asyncio.get_running_loop() is prefered.
current_loop = asyncio.get_event_loop()
except RuntimeError:
# If there is no loop in current thread at all, and it is not main
# thread, we get error like:
# RuntimeError: There is no current event loop in thread 'Thread-1'
pass
return self._loop == current_loop

View File

@@ -0,0 +1,126 @@
import logging
from datetime import datetime
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class EventletScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the eventlet event loop.
http://eventlet.net/
"""
def __init__(self, eventlet: Any) -> None:
"""Create a new EventletScheduler.
Args:
eventlet: The eventlet module to use; typically, you would get this
by import eventlet
"""
super().__init__()
self._eventlet = eventlet
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
timer = self._eventlet.spawn(interval)
def dispose() -> None:
timer.kill()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0.0:
return self.schedule(action, state=state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
timer = self._eventlet.spawn_after(seconds, interval)
def dispose() -> None:
timer.kill()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(self._eventlet.hubs.get_hub().clock())

View File

@@ -0,0 +1,127 @@
import logging
from datetime import datetime
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class GEventScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the GEvent event loop.
http://www.gevent.org/
"""
def __init__(self, gevent: Any) -> None:
"""Create a new GEventScheduler.
Args:
gevent: The gevent module to use; typically ,you would get this by
import gevent
"""
super().__init__()
self._gevent = gevent
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
timer = self._gevent.spawn(interval)
def dispose() -> None:
timer.kill(block=False)
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0.0:
return self.schedule(action, state=state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
log.debug("timeout: %s", seconds)
timer = self._gevent.spawn_later(seconds, interval)
def dispose() -> None:
timer.kill(block=False)
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(self._gevent.get_hub().loop.now())

View File

@@ -0,0 +1,132 @@
import logging
from datetime import datetime
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class IOLoopScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the Tornado I/O main event loop.
Note, as of Tornado 6, this is just a wrapper around the asyncio loop.
http://tornado.readthedocs.org/en/latest/ioloop.html"""
def __init__(self, loop: Any) -> None:
"""Create a new IOLoopScheduler.
Args:
loop: The ioloop to use; typically, you would get this by
tornado import ioloop; ioloop.IOLoop.current()
"""
super().__init__()
self._loop = loop
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
disposed = False
def interval() -> None:
if not disposed:
sad.disposable = self.invoke_action(action, state=state)
self._loop.add_callback(interval)
def dispose() -> None:
nonlocal disposed
disposed = True
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0.0:
return self.schedule(action, state=state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state=state)
log.debug("timeout: %s", seconds)
timer = self._loop.call_later(seconds, interval)
def dispose() -> None:
self._loop.remove_timeout(timer)
self._loop.remove_timeout(timer)
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(self._loop.time())

View File

@@ -0,0 +1,113 @@
import logging
from datetime import datetime
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class TwistedScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the Twisted reactor mainloop."""
def __init__(self, reactor: Any) -> None:
"""Create a new TwistedScheduler.
Args:
reactor: The reactor to use; typically, you would get this
by from twisted.internet import reactor
"""
super().__init__()
self._reactor = reactor
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_relative(0.0, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = max(0.0, self.to_seconds(duetime))
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = action(self, state)
log.debug("timeout: %s", seconds)
timer = self._reactor.callLater(seconds, interval)
def dispose() -> None:
if not timer.called:
timer.cancel()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(float(self._reactor.seconds()))

View File

@@ -0,0 +1,218 @@
import logging
import threading
from collections import deque
from typing import Deque, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import Disposable
from reactivex.internal.concurrency import default_thread_factory
from reactivex.internal.constants import DELTA_ZERO
from reactivex.internal.exceptions import DisposedException
from reactivex.internal.priorityqueue import PriorityQueue
from .periodicscheduler import PeriodicScheduler
from .scheduleditem import ScheduledItem
log = logging.getLogger("Rx")
_TState = TypeVar("_TState")
class EventLoopScheduler(PeriodicScheduler, abc.DisposableBase):
"""Creates an object that schedules units of work on a designated thread."""
def __init__(
self,
thread_factory: Optional[typing.StartableFactory] = None,
exit_if_empty: bool = False,
) -> None:
super().__init__()
self._is_disposed = False
self._thread_factory: typing.StartableFactory = (
thread_factory or default_thread_factory
)
self._thread: Optional[typing.Startable] = None
self._condition = threading.Condition(threading.Lock())
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
self._ready_list: Deque[ScheduledItem] = deque()
self._exit_if_empty = exit_if_empty
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_absolute(self.now, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
return self.schedule_absolute(self.now + duetime, action, state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
if self._is_disposed:
raise DisposedException()
dt = self.to_datetime(duetime)
si: ScheduledItem = ScheduledItem(self, state, action, dt)
with self._condition:
if dt <= self.now:
self._ready_list.append(si)
else:
self._queue.enqueue(si)
self._condition.notify() # signal that a new item is available
self._ensure_thread()
return Disposable(si.cancel)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work.
Args:
period: Period in seconds or timedelta for running the
work periodically.
action: Action to be executed.
state: [Optional] Initial state passed to the action upon
the first iteration.
Returns:
The disposable object used to cancel the scheduled
recurring action (best effort).
"""
if self._is_disposed:
raise DisposedException()
return super().schedule_periodic(period, action, state=state)
def _has_thread(self) -> bool:
"""Checks if there is an event loop thread running."""
with self._condition:
return not self._is_disposed and self._thread is not None
def _ensure_thread(self) -> None:
"""Ensures there is an event loop thread running. Should be
called under the gate."""
if not self._thread:
thread = self._thread_factory(self.run)
self._thread = thread
thread.start()
def run(self) -> None:
"""Event loop scheduled on the designated event loop thread.
The loop is suspended/resumed using the condition which gets notified
by calls to Schedule or calls to dispose."""
ready: Deque[ScheduledItem] = deque()
while True:
with self._condition:
# The notification could be because of a call to dispose. This
# takes precedence over everything else: We'll exit the loop
# immediately. Subsequent calls to Schedule won't ever create a
# new thread.
if self._is_disposed:
return
# Sort the ready_list (from recent calls for immediate schedule)
# and the due subset of previously queued items.
time = self.now
while self._queue:
due = self._queue.peek().duetime
while self._ready_list and due > self._ready_list[0].duetime:
ready.append(self._ready_list.popleft())
if due > time:
break
ready.append(self._queue.dequeue())
while self._ready_list:
ready.append(self._ready_list.popleft())
# Execute the gathered actions
while ready:
item = ready.popleft()
if not item.is_cancelled():
item.invoke()
# Wait for next cycle, or if we're done let's exit if so configured
with self._condition:
if self._ready_list:
continue
elif self._queue:
time = self.now
item = self._queue.peek()
seconds = (item.duetime - time).total_seconds()
if seconds > 0:
log.debug("timeout: %s", seconds)
self._condition.wait(seconds)
elif self._exit_if_empty:
self._thread = None
return
else:
self._condition.wait()
def dispose(self) -> None:
"""Ends the thread associated with this scheduler. All
remaining work in the scheduler queue is abandoned.
"""
with self._condition:
if not self._is_disposed:
self._is_disposed = True
self._condition.notify()

View File

@@ -0,0 +1,20 @@
from datetime import datetime
from typing import Optional
from .scheduler import UTC_ZERO
from .virtualtimescheduler import VirtualTimeScheduler
class HistoricalScheduler(VirtualTimeScheduler):
"""Provides a virtual time scheduler that uses datetime for absolute time
and timedelta for relative time."""
def __init__(self, initial_clock: Optional[datetime] = None) -> None:
"""Creates a new historical scheduler with the specified initial clock
value.
Args:
initial_clock: Initial value for the clock.
"""
super().__init__(initial_clock or UTC_ZERO)

View File

@@ -0,0 +1,96 @@
from threading import Lock
from typing import MutableMapping, Optional, TypeVar
from weakref import WeakKeyDictionary
from reactivex import abc, typing
from reactivex.internal.constants import DELTA_ZERO
from reactivex.internal.exceptions import WouldBlockException
from .scheduler import Scheduler
_TState = TypeVar("_TState")
class ImmediateScheduler(Scheduler):
"""Represents an object that schedules units of work to run immediately,
on the current thread. You're not allowed to schedule timeouts using the
ImmediateScheduler since that will block the current thread while waiting.
Attempts to do so will raise a :class:`WouldBlockException`.
"""
_lock = Lock()
_global: MutableMapping[type, "ImmediateScheduler"] = WeakKeyDictionary()
@classmethod
def singleton(cls) -> "ImmediateScheduler":
with ImmediateScheduler._lock:
try:
self = ImmediateScheduler._global[cls]
except KeyError:
self = super().__new__(cls)
ImmediateScheduler._global[cls] = self
return self
def __new__(cls) -> "ImmediateScheduler":
return cls.singleton()
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.invoke_action(action, state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_timedelta(duetime)
if duetime > DELTA_ZERO:
raise WouldBlockException()
return self.invoke_action(action, state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state)

View File

@@ -0,0 +1,13 @@
from .gtkscheduler import GtkScheduler
from .pygamescheduler import PyGameScheduler
from .qtscheduler import QtScheduler
from .tkinterscheduler import TkinterScheduler
from .wxscheduler import WxScheduler
__all__ = [
"GtkScheduler",
"PyGameScheduler",
"QtScheduler",
"TkinterScheduler",
"WxScheduler",
]

View File

@@ -0,0 +1,144 @@
from typing import Any, Optional, TypeVar, cast
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
class GtkScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the GLib main loop
used in GTK+ applications.
See https://wiki.gnome.org/Projects/PyGObject
"""
def __init__(self, glib: Any) -> None:
"""Create a new GtkScheduler.
Args:
glib: The GLib module to use; typically, you would get this by
>>> import gi
>>> gi.require_version('Gtk', '3.0')
>>> from gi.repository import GLib
"""
super().__init__()
self._glib = glib
def _gtk_schedule(
self,
time: typing.AbsoluteOrRelativeTime,
action: typing.ScheduledSingleOrPeriodicAction[_TState],
state: Optional[_TState] = None,
periodic: bool = False,
) -> abc.DisposableBase:
msecs = max(0, int(self.to_seconds(time) * 1000.0))
sad = SingleAssignmentDisposable()
stopped = False
def timer_handler(_: Any) -> bool:
if stopped:
return False
nonlocal state
if periodic:
state = cast(typing.ScheduledPeriodicAction[_TState], action)(state)
else:
sad.disposable = self.invoke_action(
cast(typing.ScheduledAction[_TState], action), state=state
)
return periodic
self._glib.timeout_add(msecs, timer_handler, None)
def dispose() -> None:
nonlocal stopped
stopped = True
return CompositeDisposable(sad, Disposable(dispose))
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self._gtk_schedule(0.0, action, state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self._gtk_schedule(duetime, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self._gtk_schedule(duetime - self.now, action, state=state)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work to be executed in the loop.
Args:
period: Period in seconds for running the work repeatedly.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self._gtk_schedule(period, action, state=state, periodic=True)

View File

@@ -0,0 +1,113 @@
import logging
import threading
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.internal import PriorityQueue
from reactivex.internal.constants import DELTA_ZERO
from ..periodicscheduler import PeriodicScheduler
from ..scheduleditem import ScheduledItem
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class PyGameScheduler(PeriodicScheduler):
"""A scheduler that schedules works for PyGame.
Note that this class expects the caller to invoke run() repeatedly.
http://www.pygame.org/docs/ref/time.html
http://www.pygame.org/docs/ref/event.html"""
def __init__(self, pygame: Any):
"""Create a new PyGameScheduler.
Args:
pygame: The PyGame module to use; typically, you would get this by
import pygame
"""
super().__init__()
self._pygame = pygame # TODO not used, refactor to actually use pygame?
self._lock = threading.Lock()
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
log.debug("PyGameScheduler.schedule(state=%s)", state)
return self.schedule_absolute(self.now, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
return self.schedule_absolute(self.now + duetime, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
si: ScheduledItem = ScheduledItem(self, state, action, duetime)
with self._lock:
self._queue.enqueue(si)
return si.disposable
def run(self) -> None:
while self._queue:
with self._lock:
item: ScheduledItem = self._queue.peek()
diff = item.duetime - self.now
if diff > DELTA_ZERO:
break
item = self._queue.dequeue()
if not item.is_cancelled():
item.invoke()

View File

@@ -0,0 +1,143 @@
import logging
from datetime import timedelta
from typing import Any, Optional, Set, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger(__name__)
class QtScheduler(PeriodicScheduler):
"""A scheduler for a PyQt5/PySide2 event loop."""
def __init__(self, qtcore: Any):
"""Create a new QtScheduler.
Args:
qtcore: The QtCore instance to use; typically you would get this by
either import PyQt5.QtCore or import PySide2.QtCore
"""
super().__init__()
self._qtcore = qtcore
self._periodic_timers: Set[Any] = set()
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_relative(0.0, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
msecs = max(0, int(self.to_seconds(duetime) * 1000.0))
sad = SingleAssignmentDisposable()
is_disposed = False
def invoke_action() -> None:
if not is_disposed:
sad.disposable = action(self, state)
log.debug("relative timeout: %sms", msecs)
# Use static method, let Qt C++ handle QTimer lifetime
self._qtcore.QTimer.singleShot(msecs, invoke_action)
def dispose() -> None:
nonlocal is_disposed
is_disposed = True
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
delta: timedelta = self.to_datetime(duetime) - self.now
return self.schedule_relative(delta, action, state=state)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work to be executed in the loop.
Args:
period: Period in seconds for running the work repeatedly.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
msecs = max(0, int(self.to_seconds(period) * 1000.0))
sad = SingleAssignmentDisposable()
def interval() -> None:
nonlocal state
state = action(state)
log.debug("periodic timeout: %sms", msecs)
timer = self._qtcore.QTimer()
timer.setSingleShot(not period)
timer.timeout.connect(interval)
timer.setInterval(msecs)
self._periodic_timers.add(timer)
timer.start()
def dispose() -> None:
timer.stop()
self._periodic_timers.remove(timer)
timer.deleteLater()
return CompositeDisposable(sad, Disposable(dispose))

View File

@@ -0,0 +1,98 @@
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
class TkinterScheduler(PeriodicScheduler):
"""A scheduler that schedules work via the Tkinter main event loop.
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html
http://effbot.org/tkinterbook/widget.htm"""
def __init__(self, root: Any) -> None:
"""Create a new TkinterScheduler.
Args:
root: The Tk instance to use; typically, you would get this by
import tkinter; tkinter.Tk()
"""
super().__init__()
self._root = root
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_relative(0.0, action, state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def invoke_action() -> None:
sad.disposable = self.invoke_action(action, state=state)
msecs = max(0, int(self.to_seconds(duetime) * 1000.0))
timer = self._root.after(msecs, invoke_action)
def dispose() -> None:
self._root.after_cancel(timer)
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state=state)

View File

@@ -0,0 +1,177 @@
import logging
from typing import Any, Optional, Set, TypeVar, cast
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from ..periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class WxScheduler(PeriodicScheduler):
"""A scheduler for a wxPython event loop."""
def __init__(self, wx: Any) -> None:
"""Create a new WxScheduler.
Args:
wx: The wx module to use; typically, you would get this by
import wx
"""
super().__init__()
self._wx = wx
timer_class: Any = self._wx.Timer
class Timer(timer_class):
def __init__(self, callback: typing.Action) -> None:
super().__init__() # type: ignore
self.callback = callback
def Notify(self) -> None:
self.callback()
self._timer_class = Timer
self._timers: Set[Timer] = set()
def cancel_all(self) -> None:
"""Cancel all scheduled actions.
Should be called when destroying wx controls to prevent
accessing dead wx objects in actions that might be pending.
"""
for timer in self._timers:
timer.Stop() # type: ignore
def _wxtimer_schedule(
self,
time: typing.AbsoluteOrRelativeTime,
action: typing.ScheduledSingleOrPeriodicAction[_TState],
state: Optional[_TState] = None,
periodic: bool = False,
) -> abc.DisposableBase:
scheduler = self
sad = SingleAssignmentDisposable()
def interval() -> None:
nonlocal state
if periodic:
state = cast(typing.ScheduledPeriodicAction[_TState], action)(state)
else:
sad.disposable = cast(typing.ScheduledAction[_TState], action)(
scheduler, state
)
msecs = max(1, int(self.to_seconds(time) * 1000.0)) # Must be non-zero
log.debug("timeout wx: %s", msecs)
timer = self._timer_class(interval)
# A timer can only be used from the main thread
if self._wx.IsMainThread():
timer.Start(msecs, oneShot=not periodic) # type: ignore
else:
self._wx.CallAfter(timer.Start, msecs, oneShot=not periodic) # type: ignore
self._timers.add(timer)
def dispose() -> None:
timer.Stop() # type: ignore
self._timers.remove(timer)
return CompositeDisposable(sad, Disposable(dispose))
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
is_disposed = False
def invoke_action() -> None:
if not is_disposed:
sad.disposable = action(self, state)
self._wx.CallAfter(invoke_action)
def dispose() -> None:
nonlocal is_disposed
is_disposed = True
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self._wxtimer_schedule(duetime, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self._wxtimer_schedule(duetime - self.now, action, state=state)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work to be executed in the loop.
Args:
period: Period in seconds for running the work repeatedly.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self._wxtimer_schedule(period, action, state=state, periodic=True)

View File

@@ -0,0 +1,136 @@
import logging
import threading
from datetime import datetime
from typing import Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import Disposable
from reactivex.internal.concurrency import default_thread_factory
from .eventloopscheduler import EventLoopScheduler
from .periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class NewThreadScheduler(PeriodicScheduler):
"""Creates an object that schedules each unit of work on a separate thread."""
def __init__(
self, thread_factory: Optional[typing.StartableFactory] = None
) -> None:
super().__init__()
self.thread_factory: typing.StartableFactory = (
thread_factory or default_thread_factory
)
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
scheduler = EventLoopScheduler(
thread_factory=self.thread_factory, exit_if_empty=True
)
return scheduler.schedule(action, state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
scheduler = EventLoopScheduler(
thread_factory=self.thread_factory, exit_if_empty=True
)
return scheduler.schedule_relative(duetime, action, state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
dt = self.to_datetime(duetime)
return self.schedule_relative(dt - self.now, action, state=state)
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work.
Args:
period: Period in seconds or timedelta for running the
work periodically.
action: Action to be executed.
state: [Optional] Initial state passed to the action upon
the first iteration.
Returns:
The disposable object used to cancel the scheduled
recurring action (best effort).
"""
seconds: float = self.to_seconds(period)
timeout: float = seconds
disposed: threading.Event = threading.Event()
def run() -> None:
nonlocal state, timeout
while True:
if timeout > 0.0:
disposed.wait(timeout)
if disposed.is_set():
return
time: datetime = self.now
state = action(state)
timeout = seconds - (self.now - time).total_seconds()
thread = self.thread_factory(run)
thread.start()
def dispose() -> None:
disposed.set()
return Disposable(dispose)

View File

@@ -0,0 +1,60 @@
from datetime import datetime
from typing import Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import Disposable, MultipleAssignmentDisposable
from .scheduler import Scheduler
_TState = TypeVar("_TState")
class PeriodicScheduler(Scheduler, abc.PeriodicSchedulerBase):
"""Base class for the various periodic scheduler implementations in this
package as well as the mainloop sub-package.
"""
def schedule_periodic(
self,
period: typing.RelativeTime,
action: typing.ScheduledPeriodicAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules a periodic piece of work.
Args:
period: Period in seconds or timedelta for running the
work periodically.
action: Action to be executed.
state: [Optional] Initial state passed to the action upon
the first iteration.
Returns:
The disposable object used to cancel the scheduled
recurring action (best effort).
"""
disp: MultipleAssignmentDisposable = MultipleAssignmentDisposable()
seconds: float = self.to_seconds(period)
def periodic(
scheduler: abc.SchedulerBase, state: Optional[_TState] = None
) -> Optional[Disposable]:
if disp.is_disposed:
return None
now: datetime = scheduler.now
try:
state = action(state)
except Exception:
disp.dispose()
raise
time = seconds - (scheduler.now - now).total_seconds()
disp.disposable = scheduler.schedule_relative(time, periodic, state=state)
return None
disp.disposable = self.schedule_relative(period, periodic, state=state)
return disp

View File

@@ -0,0 +1,49 @@
from datetime import datetime
from typing import Any, Optional, TypeVar
from reactivex import abc
from reactivex.disposable import SingleAssignmentDisposable
from .scheduler import Scheduler
_TState = TypeVar("_TState")
class ScheduledItem(object):
def __init__(
self,
scheduler: Scheduler,
state: Optional[_TState],
action: abc.ScheduledAction[_TState],
duetime: datetime,
) -> None:
self.scheduler: Scheduler = scheduler
self.state: Optional[Any] = state
self.action: abc.ScheduledAction[_TState] = action
self.duetime: datetime = duetime
self.disposable: SingleAssignmentDisposable = SingleAssignmentDisposable()
def invoke(self) -> None:
ret = self.scheduler.invoke_action(self.action, state=self.state)
self.disposable.disposable = ret
def cancel(self) -> None:
"""Cancels the work item by disposing the resource returned by
invoke_core as soon as possible."""
self.disposable.dispose()
def is_cancelled(self) -> bool:
return self.disposable.is_disposed
def __lt__(self, other: "ScheduledItem") -> bool:
return self.duetime < other.duetime
def __gt__(self, other: "ScheduledItem") -> bool:
return self.duetime > other.duetime
def __eq__(self, other: Any) -> bool:
try:
return self.duetime == other.duetime
except AttributeError:
return NotImplemented

View File

@@ -0,0 +1,172 @@
from abc import abstractmethod
from datetime import datetime, timedelta
from typing import Optional, TypeVar
from reactivex import abc, typing
from reactivex.disposable import Disposable
from reactivex.internal.basic import default_now
from reactivex.internal.constants import UTC_ZERO
_TState = TypeVar("_TState")
class Scheduler(abc.SchedulerBase):
"""Base class for the various scheduler implementations in this package as
well as the mainloop sub-package. This does not include an implementation
of schedule_periodic, refer to PeriodicScheduler.
"""
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return default_now()
@abstractmethod
def schedule(
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return NotImplemented
@abstractmethod
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return NotImplemented
@abstractmethod
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return NotImplemented
def invoke_action(
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Invoke the given given action. This is typically called by instances
of ScheduledItem.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object returned by the action, if any; or a new
(no-op) disposable otherwise.
"""
ret = action(self, state)
if isinstance(ret, abc.DisposableBase):
return ret
return Disposable()
@classmethod
def to_seconds(cls, value: typing.AbsoluteOrRelativeTime) -> float:
"""Converts time value to seconds. This method handles both absolute
(datetime) and relative (timedelta) values. If the argument is already
a float, it is simply returned unchanged.
Args:
value: the time value to convert to seconds.
Returns:
The value converted to seconds.
"""
if isinstance(value, datetime):
value = value - UTC_ZERO
if isinstance(value, timedelta):
value = value.total_seconds()
return value
@classmethod
def to_datetime(cls, value: typing.AbsoluteOrRelativeTime) -> datetime:
"""Converts time value to datetime. This method handles both absolute
(float) and relative (timedelta) values. If the argument is already
a datetime, it is simply returned unchanged.
Args:
value: the time value to convert to datetime.
Returns:
The value converted to datetime.
"""
if isinstance(value, timedelta):
value = UTC_ZERO + value
elif not isinstance(value, datetime):
value = datetime.utcfromtimestamp(value)
return value
@classmethod
def to_timedelta(cls, value: typing.AbsoluteOrRelativeTime) -> timedelta:
"""Converts time value to timedelta. This method handles both absolute
(datetime) and relative (float) values. If the argument is already
a timedelta, it is simply returned unchanged. If the argument is an
absolute time, the result value will be the timedelta since the epoch,
January 1st, 1970, 00:00:00.
Args:
value: the time value to convert to timedelta.
Returns:
The value converted to timedelta.
"""
if isinstance(value, datetime):
value = value - UTC_ZERO
elif not isinstance(value, timedelta):
value = timedelta(seconds=value)
return value

View File

@@ -0,0 +1,37 @@
from concurrent.futures import Future, ThreadPoolExecutor
from typing import Any, Optional
from reactivex import abc, typing
from .newthreadscheduler import NewThreadScheduler
class ThreadPoolScheduler(NewThreadScheduler):
"""A scheduler that schedules work via the thread pool."""
class ThreadPoolThread(abc.StartableBase):
"""Wraps a concurrent future as a thread."""
def __init__(
self, executor: ThreadPoolExecutor, target: typing.StartableTarget
):
self.executor: ThreadPoolExecutor = executor
self.target: typing.StartableTarget = target
self.future: Optional["Future[Any]"] = None
def start(self) -> None:
self.future = self.executor.submit(self.target)
def cancel(self) -> None:
if self.future:
self.future.cancel()
def __init__(self, max_workers: Optional[int] = None) -> None:
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=max_workers)
def thread_factory(
target: typing.StartableTarget,
) -> ThreadPoolScheduler.ThreadPoolThread:
return self.ThreadPoolThread(self.executor, target)
super().__init__(thread_factory)

View File

@@ -0,0 +1,122 @@
from threading import Lock, Timer
from typing import MutableMapping, Optional, TypeVar
from weakref import WeakKeyDictionary
from reactivex import abc, typing
from reactivex.disposable import (
CompositeDisposable,
Disposable,
SingleAssignmentDisposable,
)
from .periodicscheduler import PeriodicScheduler
_TState = TypeVar("_TState")
class TimeoutScheduler(PeriodicScheduler):
"""A scheduler that schedules work via a timed callback."""
_lock = Lock()
_global: MutableMapping[type, "TimeoutScheduler"] = WeakKeyDictionary()
@classmethod
def singleton(cls) -> "TimeoutScheduler":
with TimeoutScheduler._lock:
try:
self = TimeoutScheduler._global[cls]
except KeyError:
self = super().__new__(cls)
TimeoutScheduler._global[cls] = self
return self
def __new__(cls) -> "TimeoutScheduler":
return cls.singleton()
def schedule(
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state)
timer = Timer(0, interval)
timer.daemon = True
timer.start()
def dispose() -> None:
timer.cancel()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
seconds = self.to_seconds(duetime)
if seconds <= 0.0:
return self.schedule(action, state)
sad = SingleAssignmentDisposable()
def interval() -> None:
sad.disposable = self.invoke_action(action, state)
timer = Timer(seconds, interval)
timer.daemon = True
timer.start()
def dispose() -> None:
timer.cancel()
return CompositeDisposable(sad, Disposable(dispose))
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = self.to_datetime(duetime)
return self.schedule_relative(duetime - self.now, action, state)
__all__ = ["TimeoutScheduler"]

View File

@@ -0,0 +1,62 @@
from collections import deque
from threading import Condition, Lock
from typing import Deque
from reactivex.internal.priorityqueue import PriorityQueue
from .scheduleditem import ScheduledItem
class Trampoline:
def __init__(self) -> None:
self._idle: bool = True
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
self._lock: Lock = Lock()
self._condition: Condition = Condition(self._lock)
def idle(self) -> bool:
with self._lock:
return self._idle
def run(self, item: ScheduledItem) -> None:
with self._lock:
self._queue.enqueue(item)
if self._idle:
self._idle = False
else:
self._condition.notify()
return
try:
self._run()
finally:
with self._lock:
self._idle = True
self._queue.clear()
def _run(self) -> None:
ready: Deque[ScheduledItem] = deque()
while True:
with self._lock:
while len(self._queue) > 0:
item: ScheduledItem = self._queue.peek()
if item.duetime <= item.scheduler.now:
self._queue.dequeue()
ready.append(item)
else:
break
while len(ready) > 0:
item = ready.popleft()
if not item.is_cancelled():
item.invoke()
with self._lock:
if len(self._queue) == 0:
break
item = self._queue.peek()
seconds = (item.duetime - item.scheduler.now).total_seconds()
if seconds > 0.0:
self._condition.wait(seconds)
__all__ = ["Trampoline"]

View File

@@ -0,0 +1,117 @@
import logging
from typing import Optional, TypeVar
from reactivex import abc, typing
from reactivex.abc.disposable import DisposableBase
from reactivex.abc.scheduler import ScheduledAction
from reactivex.internal.constants import DELTA_ZERO
from .scheduleditem import ScheduledItem
from .scheduler import Scheduler
from .trampoline import Trampoline
_TState = TypeVar("_TState")
log = logging.getLogger("Rx")
class TrampolineScheduler(Scheduler):
"""Represents an object that schedules units of work on the trampoline.
You should never schedule timeouts using the *TrampolineScheduler*, as
it will block the thread while waiting.
Each instance has its own trampoline (and queue), and you can schedule work
on it from different threads. Beware though, that the first thread to call
a *schedule* method while the trampoline is idle will then remain occupied
until the queue is empty.
"""
def __init__(self) -> None:
self._tramp = Trampoline()
def get_trampoline(self) -> Trampoline:
return self._tramp
def schedule(
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_absolute(self.now, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
return self.schedule_absolute(self.now + duetime, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
dt = self.to_datetime(duetime)
if dt > self.now:
log.warning("Do not schedule blocking work!")
item: ScheduledItem = ScheduledItem(self, state, action, dt)
self.get_trampoline().run(item)
return item.disposable
def schedule_required(self) -> bool:
"""Test if scheduling is required.
Gets a value indicating whether the caller must call a
schedule method. If the trampoline is active, then it returns
False; otherwise, if the trampoline is not active, then it
returns True.
"""
return self.get_trampoline().idle()
def ensure_trampoline(
self, action: ScheduledAction[_TState]
) -> Optional[DisposableBase]:
"""Method for testing the TrampolineScheduler."""
if self.schedule_required():
return self.schedule(action)
return action(self, None)

View File

@@ -0,0 +1,251 @@
import logging
import threading
from datetime import datetime, timedelta
from typing import Any, Optional, TypeVar
from reactivex import abc, typing
from reactivex.abc.scheduler import AbsoluteTime
from reactivex.internal import ArgumentOutOfRangeException, PriorityQueue
from .periodicscheduler import PeriodicScheduler
from .scheduleditem import ScheduledItem
log = logging.getLogger("Rx")
MAX_SPINNING = 100
_TState = TypeVar("_TState")
class VirtualTimeScheduler(PeriodicScheduler):
"""Virtual Scheduler. This scheduler should work with either
datetime/timespan or ticks as int/int"""
def __init__(self, initial_clock: typing.AbsoluteTime = 0) -> None:
"""Creates a new virtual time scheduler with the specified
initial clock value.
Args:
initial_clock: Initial value for the clock.
"""
super().__init__()
self._clock = initial_clock
self._is_enabled = False
self._lock: threading.Lock = threading.Lock()
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
def _get_clock(self) -> AbsoluteTime:
with self._lock:
return self._clock
clock = property(fget=_get_clock)
@property
def now(self) -> datetime:
"""Represents a notion of time for this scheduler. Tasks being
scheduled on a scheduler will adhere to the time denoted by this
property.
Returns:
The scheduler's current time, as a datetime instance.
"""
return self.to_datetime(self._clock)
def schedule(
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
) -> abc.DisposableBase:
"""Schedules an action to be executed.
Args:
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
return self.schedule_absolute(self._clock, action, state=state)
def schedule_relative(
self,
duetime: typing.RelativeTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed after duetime.
Args:
duetime: Relative time after which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
time: typing.AbsoluteTime = self.add(self._clock, duetime)
return self.schedule_absolute(time, action, state=state)
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: abc.ScheduledAction[_TState],
state: Optional[_TState] = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at duetime.
Args:
duetime: Absolute time at which to execute the action.
action: Action to be executed.
state: [Optional] state to be given to the action function.
Returns:
The disposable object used to cancel the scheduled action
(best effort).
"""
dt = self.to_datetime(duetime)
si: ScheduledItem = ScheduledItem(self, state, action, dt)
with self._lock:
self._queue.enqueue(si)
return si.disposable
def start(self) -> Any:
"""Starts the virtual time scheduler."""
with self._lock:
if self._is_enabled:
return
self._is_enabled = True
spinning: int = 0
while True:
with self._lock:
if not self._is_enabled or not self._queue:
break
item: ScheduledItem = self._queue.dequeue()
if item.duetime > self.now:
if isinstance(self._clock, datetime):
self._clock = item.duetime
else:
self._clock = self.to_seconds(item.duetime)
spinning = 0
elif spinning > MAX_SPINNING:
if isinstance(self._clock, datetime):
self.clock += timedelta(microseconds=1000)
else:
self._clock += 1.0
spinning = 0
if not item.is_cancelled():
item.invoke()
spinning += 1
self.stop()
def stop(self) -> None:
"""Stops the virtual time scheduler."""
with self._lock:
self._is_enabled = False
def advance_to(self, time: typing.AbsoluteTime) -> None:
"""Advances the schedulers clock to the specified absolute time,
running all work til that point.
Args:
time: Absolute time to advance the schedulers clock to.
"""
dt: datetime = self.to_datetime(time)
with self._lock:
if self.now > dt:
raise ArgumentOutOfRangeException()
if self.now == dt or self._is_enabled:
return
self._is_enabled = True
while True:
with self._lock:
if not self._is_enabled or not self._queue:
break
item: ScheduledItem = self._queue.peek()
if item.duetime > dt:
break
if item.duetime > self.now:
if isinstance(self._clock, datetime):
self._clock = item.duetime
else:
self._clock = self.to_seconds(item.duetime)
self._queue.dequeue()
if not item.is_cancelled():
item.invoke()
with self._lock:
self._is_enabled = False
if isinstance(self._clock, datetime):
self._clock = dt
else:
self._clock = self.to_seconds(dt)
def advance_by(self, time: typing.RelativeTime) -> None:
"""Advances the schedulers clock by the specified relative time,
running all work scheduled for that timespan.
Args:
time: Relative time to advance the schedulers clock by.
"""
log.debug("VirtualTimeScheduler.advance_by(time=%s)", time)
self.advance_to(self.add(self.now, self.to_timedelta(time)))
def sleep(self, time: typing.RelativeTime) -> None:
"""Advances the schedulers clock by the specified relative time.
Args:
time: Relative time to advance the schedulers clock by.
"""
absolute = self.add(self.now, self.to_timedelta(time))
dt: datetime = self.to_datetime(absolute)
if self.now > dt:
raise ArgumentOutOfRangeException()
with self._lock:
if isinstance(self._clock, datetime):
self._clock = dt
else:
self._clock = self.to_seconds(dt)
@classmethod
def add(
cls, absolute: typing.AbsoluteTime, relative: typing.RelativeTime
) -> typing.AbsoluteTime:
"""Adds a relative time value to an absolute time value.
Args:
absolute: Absolute virtual time value.
relative: Relative virtual time value to add.
Returns:
The resulting absolute virtual time sum value.
"""
return cls.to_datetime(absolute) + cls.to_timedelta(relative)