From 32bef9809995f44142b0c1947822a8b8f2f5b9d2 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 2 Jun 2026 13:13:26 -0500 Subject: [PATCH 1/6] upgrade pyupgrade to start using 3.10+ --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ebff87..487247f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,7 @@ repos: rev: 'v3.19.1' hooks: - id: pyupgrade - args: ['--py38-plus'] + args: ['--py310-plus'] - repo: https://github.com/PyCQA/flake8 rev: '7.2.0' hooks: From 20a523f879c02618af7a0e06383a555748ecb85a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:17:15 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiojobs/_scheduler.py | 38 +++++++++++++++++++------------------- aiojobs/aiohttp.py | 6 +++--- tests/conftest.py | 5 +++-- tests/test_aiohttp.py | 2 +- tests/test_scheduler.py | 5 +++-- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/aiojobs/_scheduler.py b/aiojobs/_scheduler.py index 9a483c5..924e6c2 100644 --- a/aiojobs/_scheduler.py +++ b/aiojobs/_scheduler.py @@ -5,7 +5,6 @@ from types import TracebackType from typing import ( Any, - Callable, Dict, Optional, Set, @@ -13,6 +12,7 @@ TypeVar, Union, ) +from collections.abc import Callable from ._job import Job @@ -26,7 +26,7 @@ _T = TypeVar("_T") _FutureLike = Union["asyncio.Future[_T]", Awaitable[_T]] -ExceptionHandler = Callable[["Scheduler", Dict[str, Any]], None] +ExceptionHandler = Callable[["Scheduler", dict[str, Any]], None] class Scheduler(Collection[Job[object]]): @@ -47,27 +47,27 @@ class Scheduler(Collection[Job[object]]): def __init__( self, *, - close_timeout: Optional[float] = 0.1, - wait_timeout: Optional[float] = 60, - limit: Optional[int] = 100, + close_timeout: float | None = 0.1, + wait_timeout: float | None = 60, + limit: int | None = 100, pending_limit: int = 10000, - exception_handler: Optional[ExceptionHandler] = None, + exception_handler: ExceptionHandler | None = None, ): if exception_handler is not None and not callable(exception_handler): raise TypeError( f"A callable object or None is expected, got {exception_handler!r}" ) - self._jobs: Set[Job[object]] = set() - self._shields: Set[asyncio.Task[object]] = set() + self._jobs: set[Job[object]] = set() + self._shields: set[asyncio.Task[object]] = set() self._close_timeout = close_timeout self._wait_timeout = wait_timeout self._limit = limit self._exception_handler = exception_handler - self._failed_tasks: asyncio.Queue[Optional[asyncio.Task[object]]] = ( + self._failed_tasks: asyncio.Queue[asyncio.Task[object] | None] = ( asyncio.Queue() ) - self._failed_task: Optional[asyncio.Task[None]] = None + self._failed_task: asyncio.Task[None] | None = None if sys.version_info < (3, 10): self._failed_task = asyncio.create_task(self._wait_failed()) self._pending: asyncio.Queue[Job[object]] = asyncio.Queue(maxsize=pending_limit) @@ -96,14 +96,14 @@ async def __aenter__(self: Self) -> Self: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: await self.wait_and_close() @property - def limit(self) -> Optional[int]: + def limit(self) -> int | None: return self._limit @property @@ -111,7 +111,7 @@ def pending_limit(self) -> int: return self._pending.maxsize @property - def close_timeout(self) -> Optional[float]: + def close_timeout(self) -> float | None: return self._close_timeout @property @@ -127,7 +127,7 @@ def closed(self) -> bool: return self._closed async def spawn( - self, coro: Coroutine[object, object, _T], name: Optional[str] = None + self, coro: Coroutine[object, object, _T], name: str | None = None ) -> Job[_T]: if self._closed: raise RuntimeError("Scheduling a new job after closing") @@ -186,7 +186,7 @@ def _outer_done_callback(outer: "asyncio.Future[object]") -> None: outer.add_done_callback(_outer_done_callback) return outer - async def wait_and_close(self, timeout: Optional[float] = None) -> None: + async def wait_and_close(self, timeout: float | None = None) -> None: if timeout is None: timeout = self._wait_timeout with suppress(asyncio.TimeoutError): @@ -225,14 +225,14 @@ async def close(self) -> None: self._failed_tasks.put_nowait(None) await self._failed_task - def call_exception_handler(self, context: Dict[str, Any]) -> None: + def call_exception_handler(self, context: dict[str, Any]) -> None: if self._exception_handler is None: asyncio.get_running_loop().call_exception_handler(context) else: self._exception_handler(self, context) @property - def exception_handler(self) -> Optional[ExceptionHandler]: + def exception_handler(self) -> ExceptionHandler | None: return self._exception_handler def _done(self, job: Job[object]) -> None: diff --git a/aiojobs/aiohttp.py b/aiojobs/aiohttp.py index ef61eb7..1a0ed9b 100644 --- a/aiojobs/aiohttp.py +++ b/aiojobs/aiohttp.py @@ -3,11 +3,11 @@ from functools import wraps from typing import ( Any, - Callable, Optional, TypeVar, Union, ) +from collections.abc import Callable from aiohttp import web @@ -31,11 +31,11 @@ def get_scheduler(request: web.Request) -> Scheduler: return scheduler -def get_scheduler_from_app(app: web.Application) -> Optional[Scheduler]: +def get_scheduler_from_app(app: web.Application) -> Scheduler | None: return app.get(AIOJOBS_SCHEDULER) -def get_scheduler_from_request(request: web.Request) -> Optional[Scheduler]: +def get_scheduler_from_request(request: web.Request) -> Scheduler | None: return request.config_dict.get(AIOJOBS_SCHEDULER) diff --git a/tests/conftest.py b/tests/conftest.py index 451eb1a..08ed7f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,13 @@ import asyncio from collections.abc import AsyncIterator, Awaitable -from typing import Any, Callable, Dict +from typing import Any, Dict +from collections.abc import Callable import pytest from aiojobs import Scheduler -PARAMS: Dict[str, Any] = { +PARAMS: dict[str, Any] = { "close_timeout": 1.0, "limit": 100, "pending_limit": 0, diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py index 73a8c6e..401a219 100644 --- a/tests/test_aiohttp.py +++ b/tests/test_aiohttp.py @@ -1,6 +1,6 @@ import asyncio from collections.abc import Awaitable -from typing import Callable +from collections.abc import Callable import pytest from aiohttp import ClientSession, web diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index ef64073..d6dc2c1 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -1,7 +1,8 @@ import asyncio import sys from collections.abc import Awaitable -from typing import Callable, List, NoReturn +from typing import List, NoReturn +from collections.abc import Callable from unittest import mock import pytest @@ -366,7 +367,7 @@ async def test_scheduler_concurrency_pending_limit( async def coro(fut: "asyncio.Future[object]") -> None: await fut - futures: List[asyncio.Future[object]] = [asyncio.Future() for _ in range(3)] + futures: list[asyncio.Future[object]] = [asyncio.Future() for _ in range(3)] jobs = [] async def spawn() -> None: From b01b3669c005b6efaaf2937ca74991dfd959f95c Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 2 Jun 2026 13:25:54 -0500 Subject: [PATCH 3/6] apply fixes --- aiojobs/__init__.py | 9 +++++---- aiojobs/_job.py | 24 +++++++++++++----------- aiojobs/_scheduler.py | 22 ++++++++-------------- aiojobs/aiohttp.py | 13 ++++++------- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/aiojobs/__init__.py b/aiojobs/__init__.py index cef5daa..a1e370b 100644 --- a/aiojobs/__init__.py +++ b/aiojobs/__init__.py @@ -5,8 +5,9 @@ """ +from __future__ import annotations + import warnings -from typing import Optional from ._job import Job from ._scheduler import ExceptionHandler, Scheduler @@ -16,10 +17,10 @@ async def create_scheduler( *, - close_timeout: Optional[float] = 0.1, - limit: Optional[int] = 100, + close_timeout: float | None = 0.1, + limit: int | None = 100, pending_limit: int = 10000, - exception_handler: Optional[ExceptionHandler] = None, + exception_handler: ExceptionHandler | None = None, ) -> Scheduler: warnings.warn("Scheduler can now be instantiated directly.", DeprecationWarning) diff --git a/aiojobs/_job.py b/aiojobs/_job.py index d4acdd3..2ea47c7 100644 --- a/aiojobs/_job.py +++ b/aiojobs/_job.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import asyncio import sys import traceback from collections.abc import Coroutine -from typing import TYPE_CHECKING, Generic, Optional, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar if sys.version_info >= (3, 11): from asyncio import timeout as asyncio_timeout @@ -33,17 +35,17 @@ def __init__( self, coro: Coroutine[object, object, _T], scheduler: Scheduler, - name: Optional[str] = None, + name: str | None = None, ): self._coro = coro - self._scheduler: Optional[Scheduler] = scheduler + self._scheduler: Scheduler | None = scheduler self._name = name loop = asyncio.get_running_loop() self._started = loop.create_future() self._closed = False self._explicit = False - self._task: Optional[asyncio.Task[_T]] = None + self._task: asyncio.Task[_T] | None = None tb = traceback.extract_stack(sys._getframe(2)) if loop.get_debug() else None self._source_traceback = tb @@ -71,7 +73,7 @@ def pending(self) -> bool: def closed(self) -> bool: return self._closed - def get_name(self) -> Optional[str]: + def get_name(self) -> str | None: """Get the task name. See https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.get_name. @@ -84,14 +86,14 @@ def set_name(self, name: str) -> None: if self._task is not None: self._task.set_name(name) - async def _do_wait(self, timeout: Optional[float]) -> _T: + async def _do_wait(self, timeout: float | None) -> _T: async with asyncio_timeout(timeout): # TODO: add a test for waiting for a pending coro await self._started assert self._task is not None # Task should have been created before this. return await self._task - async def _wait(self, *, timeout: Optional[float] = None) -> _T: + async def _wait(self, *, timeout: float | None = None) -> _T: assert self._scheduler is not None # Only removed when not _closed. scheduler = self._scheduler try: @@ -103,14 +105,14 @@ async def _wait(self, *, timeout: Optional[float] = None) -> _T: await self._close(scheduler.close_timeout) raise - async def wait(self, *, timeout: Optional[float] = None) -> _T: + async def wait(self, *, timeout: float | None = None) -> _T: if self._closed: assert self._task is not None # Task must have been created if closed. return await self._task self._explicit = True return await self._wait(timeout=timeout) - async def close(self, *, timeout: Optional[float] = None) -> None: + async def close(self, *, timeout: float | None = None) -> None: if self._closed: return self._explicit = True @@ -119,7 +121,7 @@ async def close(self, *, timeout: Optional[float] = None) -> None: timeout = self._scheduler.close_timeout await self._close(timeout) - async def _close(self, timeout: Optional[float]) -> None: + async def _close(self, timeout: float | None) -> None: self._closed = True if self._task is None: # the task is closed immediately without actual execution @@ -159,7 +161,7 @@ def _start(self) -> None: self._task.add_done_callback(self._done_callback) self._started.set_result(None) - def _done_callback(self, task: "asyncio.Task[_T]") -> None: + def _done_callback(self, task: asyncio.Task[_T]) -> None: assert self._scheduler is not None scheduler = self._scheduler scheduler._done(self) diff --git a/aiojobs/_scheduler.py b/aiojobs/_scheduler.py index 924e6c2..afbe44e 100644 --- a/aiojobs/_scheduler.py +++ b/aiojobs/_scheduler.py @@ -1,18 +1,14 @@ +from __future__ import annotations + import asyncio import sys -from collections.abc import Awaitable, Collection, Coroutine, Iterator +from collections.abc import Awaitable, Callable, Collection, Coroutine, Iterator from contextlib import suppress from types import TracebackType from typing import ( Any, - Dict, - Optional, - Set, - Type, TypeVar, - Union, ) -from collections.abc import Callable from ._job import Job @@ -25,7 +21,7 @@ Self = TypeVar("Self", bound="Scheduler") _T = TypeVar("_T") -_FutureLike = Union["asyncio.Future[_T]", Awaitable[_T]] +_FutureLike = asyncio.Future[_T] | Awaitable[_T] ExceptionHandler = Callable[["Scheduler", dict[str, Any]], None] @@ -64,9 +60,7 @@ def __init__( self._wait_timeout = wait_timeout self._limit = limit self._exception_handler = exception_handler - self._failed_tasks: asyncio.Queue[asyncio.Task[object] | None] = ( - asyncio.Queue() - ) + self._failed_tasks: asyncio.Queue[asyncio.Task[object] | None] = asyncio.Queue() self._failed_task: asyncio.Task[None] | None = None if sys.version_info < (3, 10): self._failed_task = asyncio.create_task(self._wait_failed()) @@ -150,7 +144,7 @@ async def spawn( self._jobs.add(job) return job - def shield(self, arg: _FutureLike[_T]) -> "asyncio.Future[_T]": + def shield(self, arg: _FutureLike[_T]) -> asyncio.Future[_T]: inner = asyncio.ensure_future(arg) if inner.done(): return inner @@ -163,7 +157,7 @@ def shield(self, arg: _FutureLike[_T]) -> "asyncio.Future[_T]": loop = inner.get_loop() outer = loop.create_future() - def _inner_done_callback(inner: "asyncio.Task[object]") -> None: + def _inner_done_callback(inner: asyncio.Task[object]) -> None: if outer.cancelled(): if not inner.cancelled(): inner.exception() @@ -178,7 +172,7 @@ def _inner_done_callback(inner: "asyncio.Task[object]") -> None: else: outer.set_result(inner.result()) - def _outer_done_callback(outer: "asyncio.Future[object]") -> None: + def _outer_done_callback(outer: asyncio.Future[object]) -> None: if not inner.done(): inner.remove_done_callback(_inner_done_callback) diff --git a/aiojobs/aiohttp.py b/aiojobs/aiohttp.py index 1a0ed9b..64453be 100644 --- a/aiojobs/aiohttp.py +++ b/aiojobs/aiohttp.py @@ -1,13 +1,12 @@ +from __future__ import annotations + import asyncio -from collections.abc import AsyncIterator, Awaitable, Coroutine +from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine from functools import wraps from typing import ( Any, - Optional, TypeVar, - Union, ) -from collections.abc import Callable from aiohttp import web @@ -17,8 +16,8 @@ __all__ = ("setup", "spawn", "get_scheduler", "get_scheduler_from_app", "atomic") _T = TypeVar("_T") -_FutureLike = Union["asyncio.Future[_T]", Awaitable[_T]] -_RequestView = TypeVar("_RequestView", bound=Union[web.Request, web.View]) +_FutureLike = asyncio.Future[_T] | Awaitable[_T] +_RequestView = TypeVar("_RequestView", web.Request, web.View) AIOJOBS_SCHEDULER = web.AppKey("AIOJOBS_SCHEDULER", Scheduler) @@ -43,7 +42,7 @@ async def spawn(request: web.Request, coro: Coroutine[object, object, _T]) -> Jo return await get_scheduler(request).spawn(coro) -def shield(request: web.Request, arg: _FutureLike[_T]) -> "asyncio.Future[_T]": +def shield(request: web.Request, arg: _FutureLike[_T]) -> asyncio.Future[_T]: return get_scheduler(request).shield(arg) From 44f01aa8c9ec418a3ddb0be5a0d1f6c3db50ee02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:28:28 +0000 Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/conftest.py | 3 +-- tests/test_aiohttp.py | 3 +-- tests/test_scheduler.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 08ed7f4..5e94ab4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,6 @@ import asyncio -from collections.abc import AsyncIterator, Awaitable +from collections.abc import AsyncIterator, Awaitable, Callable from typing import Any, Dict -from collections.abc import Callable import pytest diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py index 401a219..7a901dc 100644 --- a/tests/test_aiohttp.py +++ b/tests/test_aiohttp.py @@ -1,6 +1,5 @@ import asyncio -from collections.abc import Awaitable -from collections.abc import Callable +from collections.abc import Awaitable, Callable import pytest from aiohttp import ClientSession, web diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index d6dc2c1..dd5dcb1 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -1,8 +1,7 @@ import asyncio import sys -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from typing import List, NoReturn -from collections.abc import Callable from unittest import mock import pytest From b92b970cde5780a5c5ceb6211f1d63dd7ecf33cd Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 2 Jun 2026 13:28:42 -0500 Subject: [PATCH 5/6] add | to bound in _RequestView --- aiojobs/aiohttp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiojobs/aiohttp.py b/aiojobs/aiohttp.py index 64453be..28179f8 100644 --- a/aiojobs/aiohttp.py +++ b/aiojobs/aiohttp.py @@ -17,7 +17,7 @@ _T = TypeVar("_T") _FutureLike = asyncio.Future[_T] | Awaitable[_T] -_RequestView = TypeVar("_RequestView", web.Request, web.View) +_RequestView = TypeVar("_RequestView", bound=web.Request | web.View) AIOJOBS_SCHEDULER = web.AppKey("AIOJOBS_SCHEDULER", Scheduler) From 405bba018b5d3c29d09881cedaa6db54d10fad9a Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 2 Jun 2026 13:31:01 -0500 Subject: [PATCH 6/6] fix up tests to remove unused things. --- tests/conftest.py | 2 +- tests/test_scheduler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5e94ab4..a934a1a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import asyncio from collections.abc import AsyncIterator, Awaitable, Callable -from typing import Any, Dict +from typing import Any import pytest diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index dd5dcb1..4d13daf 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -1,7 +1,7 @@ import asyncio import sys from collections.abc import Awaitable, Callable -from typing import List, NoReturn +from typing import NoReturn from unittest import mock import pytest