diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b065bcea..29bf49a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - python: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] redis: ['5'] include: - python: '3.11' diff --git a/arq/cron.py b/arq/cron.py index 53f053f7..49378efc 100644 --- a/arq/cron.py +++ b/arq/cron.py @@ -1,5 +1,5 @@ -import asyncio import dataclasses +import inspect from dataclasses import dataclass from datetime import datetime, timedelta from typing import Optional, Union @@ -175,7 +175,7 @@ def cron( else: coroutine_ = coroutine - if not asyncio.iscoroutinefunction(coroutine_): + if not inspect.iscoroutinefunction(coroutine_): raise RuntimeError(f'{coroutine_} is not a coroutine function') timeout = to_seconds(timeout) keep_result = to_seconds(keep_result) diff --git a/arq/utils.py b/arq/utils.py index e4305e21..36a04613 100644 --- a/arq/utils.py +++ b/arq/utils.py @@ -99,7 +99,7 @@ def to_seconds(td: Optional['SecondsTimedelta']) -> Optional[float]: async def poll(step: float = 0.5) -> AsyncGenerator[float, None]: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() start = loop.time() while True: before = loop.time() diff --git a/arq/worker.py b/arq/worker.py index de368fac..24c51ff8 100644 --- a/arq/worker.py +++ b/arq/worker.py @@ -86,7 +86,7 @@ def func( else: coroutine_ = coroutine - if not asyncio.iscoroutinefunction(coroutine_): + if not inspect.iscoroutinefunction(coroutine_): raise RuntimeError(f'{coroutine_} is not a coroutine function') timeout = to_seconds(timeout) keep_result = to_seconds(keep_result) @@ -267,7 +267,11 @@ def __init__( # self.job_tasks holds references the actual jobs running self.job_tasks: dict[str, asyncio.Task[Any]] = {} self.main_task: Optional[asyncio.Task[None]] = None - self.loop = asyncio.get_event_loop() + try: + self.loop = asyncio.get_event_loop() + except RuntimeError: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) self.ctx = ctx or {} max_timeout = max(f.timeout_s or self.job_timeout_s for f in self.functions.values()) self.in_progress_timeout_s = (max_timeout or 0) + 10 diff --git a/pyproject.toml b/pyproject.toml index 3ceed681..ee990eeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Topic :: Internet', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Clustering', @@ -90,7 +91,13 @@ required-version = '>=0.8.4' [tool.pytest.ini_options] testpaths = 'tests' -filterwarnings = ['error'] +filterwarnings = [ + 'error', + # Emitted by pytest-asyncio, necessary until we update it: + '''ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning''', + '''ignore:'asyncio.get_event_loop_policy' is deprecated:DeprecationWarning''', + '''ignore:'asyncio.set_event_loop_policy' is deprecated:DeprecationWarning''', +] asyncio_mode = 'auto' timeout = 10