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,14 @@
from .mockdisposable import MockDisposable
from .reactivetest import OnErrorPredicate, OnNextPredicate, ReactiveTest, is_prime
from .recorded import Recorded
from .testscheduler import TestScheduler
__all__ = [
"MockDisposable",
"OnErrorPredicate",
"OnNextPredicate",
"ReactiveTest",
"Recorded",
"TestScheduler",
"is_prime",
]

View File

@@ -0,0 +1,57 @@
from typing import Any, List, Optional, TypeVar
from reactivex import Notification, Observable, abc
from reactivex.disposable import CompositeDisposable, Disposable
from reactivex.scheduler import VirtualTimeScheduler
from .recorded import Recorded
from .subscription import Subscription
_T = TypeVar("_T")
class ColdObservable(Observable[_T]):
def __init__(
self, scheduler: VirtualTimeScheduler, messages: List[Recorded[_T]]
) -> None:
super().__init__()
self.scheduler = scheduler
self.messages = messages
self.subscriptions: List[Subscription] = []
def _subscribe_core(
self,
observer: Optional[abc.ObserverBase[_T]] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
self.subscriptions.append(Subscription(self.scheduler.clock))
index = len(self.subscriptions) - 1
disp = CompositeDisposable()
def get_action(notification: Notification[_T]) -> abc.ScheduledAction[_T]:
def action(
scheduler: abc.SchedulerBase, state: Any = None
) -> abc.DisposableBase:
if observer:
notification.accept(observer)
return Disposable()
return action
for message in self.messages:
notification = message.value
if not isinstance(notification, Notification):
raise ValueError("Must be notification")
# Don't make closures within a loop
action = get_action(notification)
disp.add(self.scheduler.schedule_relative(message.time, action))
def dispose() -> None:
start = self.subscriptions[index].subscribe
end = self.scheduler.to_seconds(self.scheduler.now)
self.subscriptions[index] = Subscription(start, int(end))
disp.dispose()
return Disposable(dispose)

View File

@@ -0,0 +1,61 @@
from typing import Any, List, Optional, TypeVar
from reactivex import Observable, abc
from reactivex.disposable import Disposable
from reactivex.notification import Notification
from reactivex.scheduler import VirtualTimeScheduler
from .recorded import Recorded
from .subscription import Subscription
_T = TypeVar("_T")
class HotObservable(Observable[_T]):
def __init__(
self, scheduler: VirtualTimeScheduler, messages: List[Recorded[_T]]
) -> None:
super().__init__()
self.scheduler = scheduler
self.messages = messages
self.subscriptions: List[Subscription] = []
self.observers: List[abc.ObserverBase[_T]] = []
observable = self
def get_action(notification: Notification[_T]) -> abc.ScheduledAction[_T]:
def action(scheduler: abc.SchedulerBase, state: Any) -> abc.DisposableBase:
for observer in observable.observers[:]:
notification.accept(observer)
return Disposable()
return action
for message in self.messages:
notification = message.value
if not isinstance(notification, Notification):
raise ValueError("Must be notification")
# Warning: Don't make closures within a loop
action = get_action(notification)
scheduler.schedule_absolute(message.time, action)
def _subscribe_core(
self,
observer: Optional[abc.ObserverBase[_T]] = None,
scheduler: Optional[abc.SchedulerBase] = None,
) -> abc.DisposableBase:
if observer:
self.observers.append(observer)
self.subscriptions.append(Subscription(self.scheduler.clock))
index = len(self.subscriptions) - 1
def dispose_action() -> None:
if observer:
self.observers.remove(observer)
start = self.subscriptions[index].subscribe
end = self.scheduler.clock
self.subscriptions[index] = Subscription(start, end)
return Disposable(dispose_action)

View File

@@ -0,0 +1,197 @@
from contextlib import contextmanager
from typing import Any, Dict, Generator, List, NamedTuple, Optional, Tuple, Union
from warnings import warn
import reactivex
from reactivex import Observable, typing
from reactivex.notification import Notification, OnError, OnNext
from reactivex.observable.marbles import parse
from reactivex.scheduler import NewThreadScheduler
from reactivex.typing import Callable, RelativeTime
from .reactivetest import ReactiveTest
from .recorded import Recorded
from .testscheduler import TestScheduler
new_thread_scheduler = NewThreadScheduler()
class MarblesContext(NamedTuple):
start: Callable[
[Union[Observable[Any], Callable[[], Observable[Any]]]], List[Recorded[Any]]
]
cold: Callable[
[str, Optional[Dict[Union[str, float], Any]], Optional[Exception]],
Observable[Any],
]
hot: Callable[
[str, Optional[Dict[Union[str, float], Any]], Optional[Exception]],
Observable[Any],
]
exp: Callable[
[str, Optional[Dict[Union[str, float], Any]], Optional[Exception]],
List[Recorded[Any]],
]
@contextmanager
def marbles_testing(
timespan: RelativeTime = 1.0,
) -> Generator[MarblesContext, None, None]:
"""
Initialize a :class:`rx.testing.TestScheduler` and return a namedtuple
containing the following functions that wrap its methods.
:func:`cold()`:
Parse a marbles string and return a cold observable
:func:`hot()`:
Parse a marbles string and return a hot observable
:func:`start()`:
Start the test scheduler, invoke the create function,
subscribe to the resulting sequence, dispose the subscription and
return the resulting records
:func:`exp()`:
Parse a marbles string and return a list of records
Examples:
>>> with marbles_testing() as (start, cold, hot, exp):
... obs = hot("-a-----b---c-|")
... ex = exp( "-a-----b---c-|")
... results = start(obs)
... assert results == ex
The underlying test scheduler is initialized with the following
parameters:
- created time = 100.0s
- subscribed = 200.0s
- disposed = 1000.0s
**IMPORTANT**: regarding :func:`hot()`, a marble declared as the
first character will be skipped by the test scheduler.
E.g. hot("a--b--") will only emit b.
"""
scheduler = TestScheduler()
created = 100.0
disposed = 1000.0
subscribed = 200.0
start_called = False
outside_of_context = False
def check() -> None:
if outside_of_context:
warn(
"context functions should not be called outside of " "with statement.",
UserWarning,
stacklevel=3,
)
if start_called:
warn(
"start() should only be called one time inside " "a with statement.",
UserWarning,
stacklevel=3,
)
def test_start(
create: Union[Observable[Any], Callable[[], Observable[Any]]]
) -> List[Recorded[Any]]:
nonlocal start_called
check()
if isinstance(create, Observable):
create_ = create
def default_create() -> Observable[Any]:
return create_
create_function = default_create
else:
create_function = create
mock_observer = scheduler.start(
create=create_function,
created=created,
subscribed=subscribed,
disposed=disposed,
)
start_called = True
return mock_observer.messages
def test_expected(
string: str,
lookup: Optional[Dict[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
) -> List[Recorded[Any]]:
messages = parse(
string,
timespan=timespan,
time_shift=subscribed,
lookup=lookup,
error=error,
)
return messages_to_records(messages)
def test_cold(
string: str,
lookup: Optional[Dict[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
) -> Observable[Any]:
check()
return reactivex.from_marbles(
string,
timespan=timespan,
lookup=lookup,
error=error,
)
def test_hot(
string: str,
lookup: Optional[Dict[Union[str, float], Any]] = None,
error: Optional[Exception] = None,
) -> Observable[Any]:
check()
hot_obs: Observable[Any] = reactivex.hot(
string,
timespan=timespan,
duetime=subscribed,
lookup=lookup,
error=error,
scheduler=scheduler,
)
return hot_obs
try:
yield MarblesContext(test_start, test_cold, test_hot, test_expected)
finally:
outside_of_context = True
def messages_to_records(
messages: List[Tuple[typing.RelativeTime, Notification[Any]]]
) -> List[Recorded[Any]]:
"""
Helper function to convert messages returned by parse() to a list of
Recorded.
"""
records: List[Recorded[Any]] = []
for message in messages:
time, notification = message
if isinstance(time, float):
time_ = int(time)
else:
time_ = time.microseconds // 1000
if isinstance(notification, OnNext):
record = ReactiveTest.on_next(time_, notification.value)
elif isinstance(notification, OnError):
record = ReactiveTest.on_error(time_, notification.exception)
else:
record = ReactiveTest.on_completed(time_)
records.append(record)
return records

View File

@@ -0,0 +1,14 @@
from typing import List
from reactivex import abc, typing
from reactivex.scheduler import VirtualTimeScheduler
class MockDisposable(abc.DisposableBase):
def __init__(self, scheduler: VirtualTimeScheduler):
self.scheduler = scheduler
self.disposes: List[typing.AbsoluteTime] = []
self.disposes.append(self.scheduler.clock)
def dispose(self) -> None:
self.disposes.append(self.scheduler.clock)

View File

@@ -0,0 +1,24 @@
from typing import List, TypeVar
from reactivex import abc
from reactivex.notification import OnCompleted, OnError, OnNext
from reactivex.scheduler import VirtualTimeScheduler
from .recorded import Recorded
_T = TypeVar("_T")
class MockObserver(abc.ObserverBase[_T]):
def __init__(self, scheduler: VirtualTimeScheduler) -> None:
self.scheduler = scheduler
self.messages: List[Recorded[_T]] = []
def on_next(self, value: _T) -> None:
self.messages.append(Recorded(self.scheduler.clock, OnNext(value)))
def on_error(self, error: Exception) -> None:
self.messages.append(Recorded(self.scheduler.clock, OnError(error)))
def on_completed(self) -> None:
self.messages.append(Recorded(self.scheduler.clock, OnCompleted()))

View File

@@ -0,0 +1,82 @@
import math
import types
from typing import Any, Generic, TypeVar, Union
from reactivex import typing
from reactivex.notification import OnCompleted, OnError, OnNext
from .recorded import Recorded
from .subscription import Subscription
_T = TypeVar("_T")
def is_prime(i: int) -> bool:
"""Tests if number is prime or not"""
if i <= 1:
return False
_max = int(math.floor(math.sqrt(i)))
for j in range(2, _max + 1):
if not i % j:
return False
return True
# New predicate tests
class OnNextPredicate(Generic[_T]):
def __init__(self, predicate: typing.Predicate[_T]) -> None:
self.predicate = predicate
def __eq__(self, other: Any) -> bool:
if other == self:
return True
if other is None:
return False
if other.kind != "N":
return False
return self.predicate(other.value)
class OnErrorPredicate(Generic[_T]):
def __init__(self, predicate: typing.Predicate[_T]):
self.predicate = predicate
def __eq__(self, other: Any) -> bool:
if other == self:
return True
if other is None:
return False
if other.kind != "E":
return False
return self.predicate(other.exception)
class ReactiveTest:
created = 100
subscribed = 200
disposed = 1000
@staticmethod
def on_next(ticks: int, value: _T) -> Recorded[_T]:
if isinstance(value, types.FunctionType):
return Recorded(ticks, OnNextPredicate(value))
return Recorded(ticks, OnNext(value))
@staticmethod
def on_error(ticks: int, error: Union[Exception, str]) -> Recorded[Any]:
if isinstance(error, types.FunctionType):
return Recorded(ticks, OnErrorPredicate(error))
return Recorded(ticks, OnError(error))
@staticmethod
def on_completed(ticks: int) -> Recorded[Any]:
return Recorded(ticks, OnCompleted())
@staticmethod
def subscribe(start: int, end: int) -> Subscription:
return Subscription(start, end)

View File

@@ -0,0 +1,41 @@
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast
from reactivex import Notification
if TYPE_CHECKING:
from .reactivetest import OnErrorPredicate, OnNextPredicate
_T = TypeVar("_T")
class Recorded(Generic[_T]):
def __init__(
self,
time: int,
value: Union[Notification[_T], "OnNextPredicate[_T]", "OnErrorPredicate[_T]"],
# comparer: Optional[typing.Comparer[_T]] = None,
):
self.time = time
self.value = value
# self.comparer = comparer or default_comparer
def __eq__(self, other: Any) -> bool:
"""Returns true if a recorded value matches another recorded value"""
if isinstance(other, Recorded):
other_ = cast(Recorded[_T], other)
time_match = self.time == other_.time
if not time_match:
return False
return self.value == other_.value
return False
equals = __eq__
def __repr__(self) -> str:
return str(self)
def __str__(self) -> str:
return "%s@%s" % (self.value, self.time)

View File

@@ -0,0 +1,25 @@
import sys
from typing import Any, Optional
class Subscription:
def __init__(self, start: int, end: Optional[int] = None):
self.subscribe = start
self.unsubscribe = end or sys.maxsize
def equals(self, other: Any) -> bool:
return (
self.subscribe == other.subscribe and self.unsubscribe == other.unsubscribe
)
def __eq__(self, other: Any) -> bool:
return self.equals(other)
def __repr__(self) -> str:
return str(self)
def __str__(self) -> str:
unsubscribe = (
"Infinite" if self.unsubscribe == sys.maxsize else self.unsubscribe
)
return "(%s, %s)" % (self.subscribe, unsubscribe)

View File

@@ -0,0 +1,165 @@
from typing import Any, Callable, List, Optional, TypeVar, Union, cast
import reactivex
from reactivex import Observable, abc, typing
from reactivex.disposable import Disposable
from reactivex.scheduler import VirtualTimeScheduler
from reactivex.testing.recorded import Recorded
from .coldobservable import ColdObservable
from .hotobservable import HotObservable
from .mockobserver import MockObserver
from .reactivetest import ReactiveTest
_T = TypeVar("_T")
_TState = TypeVar("_TState")
class TestScheduler(VirtualTimeScheduler):
"""Test time scheduler used for testing applications and libraries
built using Reactive Extensions. All time, both absolute and relative is
specified as integer ticks"""
__test__ = False
def schedule_absolute(
self,
duetime: typing.AbsoluteTime,
action: typing.ScheduledAction[_TState],
state: _TState = None,
) -> abc.DisposableBase:
"""Schedules an action to be executed at the specified virtual
time.
Args:
duetime: Absolute virtual time at which to execute the
action.
action: Action to be executed.
state: State passed to the action to be executed.
Returns:
Disposable object used to cancel the scheduled action
(best effort).
"""
duetime = duetime if isinstance(duetime, float) else self.to_seconds(duetime)
return super().schedule_absolute(duetime, action, state)
def start(
self,
create: Optional[Callable[[], Observable[_T]]] = None,
created: Optional[float] = None,
subscribed: Optional[float] = None,
disposed: Optional[float] = None,
) -> MockObserver[_T]:
"""Starts the test scheduler and uses the specified virtual
times to invoke the factory function, subscribe to the
resulting sequence, and dispose the subscription.
Args:
create: Factory method to create an observable sequence.
created: Virtual time at which to invoke the factory to
create an observable sequence.
subscribed: Virtual time at which to subscribe to the
created observable sequence.
disposed: Virtual time at which to dispose the
subscription.
Returns:
Observer with timestamped recordings of notification
messages that were received during the virtual time window
when the subscription to the source sequence was active.
"""
# Defaults
created = created or ReactiveTest.created
subscribed = subscribed or ReactiveTest.subscribed
disposed = disposed or ReactiveTest.disposed
observer = self.create_observer()
subscription: Optional[abc.DisposableBase] = None
source: Optional[abc.ObservableBase[_T]] = None
def action_create(
scheduler: abc.SchedulerBase, state: Any = None
) -> abc.DisposableBase:
"""Called at create time. Defaults to 100"""
nonlocal source
source = create() if create is not None else reactivex.never()
return Disposable()
self.schedule_absolute(created, action_create)
def action_subscribe(
scheduler: abc.SchedulerBase, state: Any = None
) -> abc.DisposableBase:
"""Called at subscribe time. Defaults to 200"""
nonlocal subscription
if source:
subscription = source.subscribe(observer, scheduler=scheduler)
return Disposable()
self.schedule_absolute(subscribed, action_subscribe)
def action_dispose(
scheduler: abc.SchedulerBase, state: Any = None
) -> abc.DisposableBase:
"""Called at dispose time. Defaults to 1000"""
if subscription:
subscription.dispose()
return Disposable()
self.schedule_absolute(disposed, action_dispose)
super().start()
return observer
def create_hot_observable(
self, *args: Union[Recorded[_T], List[Recorded[_T]]]
) -> HotObservable[_T]:
"""Creates a hot observable using the specified timestamped
notification messages either as a list or by multiple arguments.
Args:
messages: Notifications to surface through the created sequence at
their specified absolute virtual times.
Returns hot observable sequence that can be used to assert the timing
of subscriptions and notifications.
"""
if args and isinstance(args[0], List):
messages = args[0]
else:
messages = cast(List[Recorded[_T]], list(args))
return HotObservable(self, messages)
def create_cold_observable(
self, *args: Union[Recorded[_T], List[Recorded[_T]]]
) -> ColdObservable[_T]:
"""Creates a cold observable using the specified timestamped
notification messages either as an array or arguments.
Args:
args: Notifications to surface through the created sequence
at their specified virtual time offsets from the
sequence subscription time.
Returns:
Cold observable sequence that can be used to assert the
timing of subscriptions and notifications.
"""
if args and isinstance(args[0], list):
messages = args[0]
else:
messages = cast(List[Recorded[_T]], list(args))
return ColdObservable(self, messages)
def create_observer(self) -> MockObserver[Any]:
"""Creates an observer that records received notification messages and
timestamps those. Return an Observer that can be used to assert the
timing of received notifications.
"""
return MockObserver(self)