diff --git a/changes.md b/changes.md index 739473d6..1d4ff92c 100644 --- a/changes.md +++ b/changes.md @@ -27,6 +27,12 @@ f-string upgrades, `object` base class removal) and removed it from the ruff exclude list. `tests/test_subject/` also removed from the ruff exclude list as it already passed all checks. +- Testing: Fixed pyright (standard-mode) type errors in `tests/test_scheduler/`: + corrected `@classmethod` overrides of `VirtualTimeScheduler.add`, + fixed `[None]`-typed list variables, updated optional-import suppression + comments (`# type: ignore[import-untyped]`), narrowed callback parameter + types, and aligned `ScheduledItemTestScheduler` method signatures with the + `Scheduler` base class. - CI: Standardised `actions/setup-python` to `@v5` across all workflow jobs. ## 2.0.0-alpha diff --git a/pyproject.toml b/pyproject.toml index 91845f1c..ac6fc561 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,8 +110,8 @@ asyncio_mode = "strict" include = ["reactivex", "tests"] exclude = [ # Stage 1 complete, Stage 2 partial (test_integration complete) - # test_subject and test_scheduler now pass ruff and pyright (run separately) - # keeping them excluded until strict-mode pyright issues are resolved + # test_subject and test_scheduler now pass ruff and pyright in standard mode + # keeping them excluded until type annotations are added to all callbacks (strict mode) "tests/test_subject", "tests/test_scheduler", "tests/test_observable", diff --git a/tests/test_scheduler/test_catchscheduler.py b/tests/test_scheduler/test_catchscheduler.py index b2023cb1..5d50064d 100644 --- a/tests/test_scheduler/test_catchscheduler.py +++ b/tests/test_scheduler/test_catchscheduler.py @@ -1,6 +1,7 @@ import unittest from datetime import timedelta +from reactivex import typing as rx_typing from reactivex.scheduler import CatchScheduler, VirtualTimeScheduler @@ -13,8 +14,11 @@ def __init__(self, initial_clock=0.0): super().__init__(initial_clock) self.exc = None - def add(self, absolute, relative): - return absolute + relative + @classmethod + def add( + cls, absolute: rx_typing.AbsoluteTime, relative: rx_typing.RelativeTime + ) -> rx_typing.AbsoluteTime: + return absolute + relative # type: ignore[operator] def _wrap(self, action): def _action(scheduler, state=None): diff --git a/tests/test_scheduler/test_currentthreadscheduler.py b/tests/test_scheduler/test_currentthreadscheduler.py index 6d8168eb..0421ac14 100644 --- a/tests/test_scheduler/test_currentthreadscheduler.py +++ b/tests/test_scheduler/test_currentthreadscheduler.py @@ -20,7 +20,7 @@ def test_currentthread_singleton(self): assert scheduler[1] is scheduler[2] gate = [threading.Semaphore(0), threading.Semaphore(0)] - scheduler = [None, None] + scheduler: list[CurrentThreadScheduler | None] = [None, None] def run(idx): scheduler[idx] = CurrentThreadScheduler.singleton() diff --git a/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py b/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py index bd49e29f..055b292c 100644 --- a/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py +++ b/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py @@ -6,6 +6,7 @@ import pytest +from reactivex import abc from reactivex.scheduler.eventloop import AsyncIOScheduler CI = os.getenv("CI") is not None @@ -49,7 +50,7 @@ async def go(): scheduler = AsyncIOScheduler(loop) ran = False - def action(scheduler: AsyncIOScheduler, state: Any): + def action(scheduler: abc.SchedulerBase, state: Any): nonlocal ran ran = True diff --git a/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py b/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py index 6f0f3ccb..68ac5470 100644 --- a/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py +++ b/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py @@ -7,7 +7,7 @@ from reactivex.scheduler.eventloop import IOLoopScheduler tornado = pytest.importorskip("tornado") -from tornado import ioloop # noqa: E402 +from tornado import ioloop # noqa: E402 # type: ignore[import-untyped] class TestIOLoopScheduler(unittest.TestCase): diff --git a/tests/test_scheduler/test_eventloop/test_twistedscheduler.py b/tests/test_scheduler/test_eventloop/test_twistedscheduler.py index 4c22a9c0..86d755d0 100644 --- a/tests/test_scheduler/test_eventloop/test_twistedscheduler.py +++ b/tests/test_scheduler/test_eventloop/test_twistedscheduler.py @@ -6,8 +6,11 @@ from reactivex.scheduler.eventloop import TwistedScheduler twisted = pytest.importorskip("twisted") -from twisted.internet import defer, reactor # noqa: E402 -from twisted.trial import unittest # noqa: E402 +from twisted.internet import ( # noqa: E402 # type: ignore[import-untyped] + defer, + reactor, +) +from twisted.trial import unittest # noqa: E402 # type: ignore[import-untyped] class TestTwistedScheduler(unittest.TestCase): diff --git a/tests/test_scheduler/test_eventloopscheduler.py b/tests/test_scheduler/test_eventloopscheduler.py index 6ffa95a0..aafa8473 100644 --- a/tests/test_scheduler/test_eventloopscheduler.py +++ b/tests/test_scheduler/test_eventloopscheduler.py @@ -1,7 +1,7 @@ import os import threading import unittest -from datetime import timedelta +from datetime import datetime, timedelta from time import sleep import pytest @@ -132,7 +132,7 @@ def test_event_loop_schedule_action_relative_due(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) starttime = default_now() - endtime = None + endtime: datetime | None = None def action(scheduler, state): nonlocal endtime @@ -144,6 +144,7 @@ def action(scheduler, state): # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) + assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) assert scheduler._has_thread() is False @@ -152,7 +153,7 @@ def test_event_loop_schedule_action_absolute_due(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) starttime = default_now() - endtime = None + endtime: datetime | None = None def action(scheduler, state): nonlocal endtime @@ -164,6 +165,7 @@ def action(scheduler, state): # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) + assert endtime is not None diff = endtime - starttime assert diff < timedelta(milliseconds=180) assert scheduler._has_thread() is False @@ -241,12 +243,12 @@ def test_eventloop_schedule_periodic_dispose_error(self): ran = False - def action(scheduler, state): + def action(state: None) -> None: nonlocal ran ran = True with pytest.raises(DisposedException): - scheduler.schedule_periodic(0.1, action) + scheduler.schedule_periodic(0.1, action) # type: ignore[arg-type] assert ran is False assert scheduler._has_thread() is False diff --git a/tests/test_scheduler/test_immediatescheduler.py b/tests/test_scheduler/test_immediatescheduler.py index ad58cf69..9d72af9e 100644 --- a/tests/test_scheduler/test_immediatescheduler.py +++ b/tests/test_scheduler/test_immediatescheduler.py @@ -21,7 +21,7 @@ def test_immediate_singleton(self): assert scheduler[0] is scheduler[1] gate = [threading.Semaphore(0), threading.Semaphore(0)] - scheduler = [None, None] + scheduler: list[ImmediateScheduler | None] = [None, None] def run(idx): scheduler[idx] = ImmediateScheduler() diff --git a/tests/test_scheduler/test_mainloop/test_gtkscheduler.py b/tests/test_scheduler/test_mainloop/test_gtkscheduler.py index 50b087a6..d37ce995 100644 --- a/tests/test_scheduler/test_mainloop/test_gtkscheduler.py +++ b/tests/test_scheduler/test_mainloop/test_gtkscheduler.py @@ -10,7 +10,7 @@ from reactivex.scheduler.mainloop import GtkScheduler gi = pytest.importorskip("gi") -from gi.repository import GLib, Gtk # noqa: E402, I001 +from gi.repository import GLib, Gtk # noqa: E402, I001 # type: ignore[import-untyped] gi.require_version("Gtk", "3.0") diff --git a/tests/test_scheduler/test_newthreadscheduler.py b/tests/test_scheduler/test_newthreadscheduler.py index 049606e4..033e542f 100644 --- a/tests/test_scheduler/test_newthreadscheduler.py +++ b/tests/test_scheduler/test_newthreadscheduler.py @@ -75,13 +75,14 @@ def test_new_thread_schedule_periodic(self): period = 0.05 counter = 3 - def action(state: int): + def action(state: int | None) -> int | None: nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: gate.release() + return None scheduler.schedule_periodic(period, action, counter) gate.acquire() @@ -92,11 +93,12 @@ def test_new_thread_schedule_periodic_cancel(self): period = 0.1 counter = 4 - def action(state: int): + def action(state: int | None) -> int | None: nonlocal counter if state: counter -= 1 return state - 1 + return None disp = scheduler.schedule_periodic(period, action, counter) sleep(0.4) diff --git a/tests/test_scheduler/test_scheduleditem.py b/tests/test_scheduler/test_scheduleditem.py index db18dd6d..28dfef95 100644 --- a/tests/test_scheduler/test_scheduleditem.py +++ b/tests/test_scheduler/test_scheduleditem.py @@ -1,6 +1,9 @@ import unittest from datetime import timedelta +from typing import Any +from reactivex import abc +from reactivex import typing as rx_typing from reactivex.disposable import Disposable from reactivex.internal.basic import default_now from reactivex.scheduler.scheduleditem import ScheduledItem @@ -10,24 +13,42 @@ class ScheduledItemTestScheduler(Scheduler): def __init__(self): super() - self.action = None - self.state = None - self.disposable = None - - def invoke_action(self, action, state): + self.action: Any = None + self.state: Any = None + self.disposable: abc.DisposableBase | None = None + + def invoke_action( + self, + action: abc.ScheduledAction[Any], + state: Any = None, + ) -> abc.DisposableBase: self.action = action self.state = state self.disposable = super().invoke_action(action, state) return self.disposable - def schedule(self, action, state): - pass - - def schedule_relative(self, duetime, action, state): - pass - - def schedule_absolute(self, duetime, action, state): - pass + def schedule( + self, + action: abc.ScheduledAction[Any], + state: Any = None, + ) -> abc.DisposableBase: + return Disposable() + + def schedule_relative( + self, + duetime: rx_typing.RelativeTime, + action: abc.ScheduledAction[Any], + state: Any = None, + ) -> abc.DisposableBase: + return Disposable() + + def schedule_absolute( + self, + duetime: rx_typing.AbsoluteTime, + action: abc.ScheduledAction[Any], + state: Any = None, + ) -> abc.DisposableBase: + return Disposable() class TestScheduledItem(unittest.TestCase): diff --git a/tests/test_scheduler/test_timeoutscheduler.py b/tests/test_scheduler/test_timeoutscheduler.py index 2a9e119a..516246f6 100644 --- a/tests/test_scheduler/test_timeoutscheduler.py +++ b/tests/test_scheduler/test_timeoutscheduler.py @@ -18,7 +18,7 @@ def test_timeout_singleton(self): assert scheduler[0] is scheduler[1] gate = [threading.Semaphore(0), threading.Semaphore(0)] - scheduler = [None, None] + scheduler: list[TimeoutScheduler | None] = [None, None] def run(idx): scheduler[idx] = TimeoutScheduler() diff --git a/tests/test_scheduler/test_virtualtimescheduler.py b/tests/test_scheduler/test_virtualtimescheduler.py index a1a03546..be606b5e 100644 --- a/tests/test_scheduler/test_virtualtimescheduler.py +++ b/tests/test_scheduler/test_virtualtimescheduler.py @@ -2,14 +2,18 @@ import pytest +from reactivex import typing as rx_typing from reactivex.internal import ArgumentOutOfRangeException from reactivex.internal.constants import DELTA_ZERO, UTC_ZERO from reactivex.scheduler import VirtualTimeScheduler class VirtualSchedulerTestScheduler(VirtualTimeScheduler): - def add(self, absolute, relative): - return absolute + relative + @classmethod + def add( + cls, absolute: rx_typing.AbsoluteTime, relative: rx_typing.RelativeTime + ) -> rx_typing.AbsoluteTime: + return absolute + relative # type: ignore[operator] class TestVirtualTimeScheduler(unittest.TestCase): @@ -24,7 +28,7 @@ def test_virtual_now_float(self): assert scheduler.now == UTC_ZERO def test_virtual_now_timedelta(self): - scheduler = VirtualSchedulerTestScheduler(DELTA_ZERO) + scheduler = VirtualSchedulerTestScheduler(DELTA_ZERO) # type: ignore[arg-type] assert scheduler.clock == DELTA_ZERO assert scheduler.now == UTC_ZERO @@ -69,4 +73,4 @@ def test_virtual_schedule_advance_clock_error(self): scheduler = VirtualSchedulerTestScheduler() with pytest.raises(ArgumentOutOfRangeException): - scheduler.advance_to(scheduler._clock - 1) + scheduler.advance_to(scheduler._clock - 1) # type: ignore[operator]