From 023826ae82d3e821a8e08921d5c564c5385d9db6 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 27 Feb 2026 17:30:27 +0100 Subject: [PATCH 1/3] Bump pytest-asyncio to 1.3.0 --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index f80536689..a44265453 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit pytest-cov pytest-sugar pytest-timeout -pytest-asyncio<1.0 +pytest-asyncio>=1.3.0 pytest-xdist pytest python-slugify From 8d79b476c08c7514ad05bfa7a8aa4d5ebf7a6a78 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 27 Feb 2026 17:30:43 +0100 Subject: [PATCH 2/3] Change to `inspect.iscoroutinefunction` --- zha/async_.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zha/async_.py b/zha/async_.py index 99a72bc1a..3e1ed43a4 100644 --- a/zha/async_.py +++ b/zha/async_.py @@ -10,6 +10,7 @@ import enum import functools from functools import cached_property +import inspect import logging import time from typing import ( @@ -133,7 +134,7 @@ def get_zhajob_callable_job_type(target: Callable[..., Any]) -> ZHAJobType: while isinstance(target, functools.partial): target = target.func - if asyncio.iscoroutinefunction(target): + if inspect.iscoroutinefunction(target): return ZHAJobType.Coroutinefunction else: return ZHAJobType.Callback From 1f25f3d93c9c99f625f3de342fc616550994afa8 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 27 Feb 2026 17:31:01 +0100 Subject: [PATCH 3/3] Make new asyncio-pytest actually work --- tests/conftest.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 990d3e1ba..33a11c2e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ """Test configuration for the ZHA component.""" import asyncio -from collections.abc import Callable, Generator +from collections.abc import AsyncGenerator, Callable, Generator from contextlib import contextmanager import logging import os @@ -12,6 +12,7 @@ import looptime import pytest +import pytest_asyncio import zigpy from zigpy.application import ControllerApplication import zigpy.config @@ -151,18 +152,18 @@ def expected_lingering_timers() -> bool: return False -@pytest.fixture(autouse=True) -def verify_cleanup( - event_loop: asyncio.AbstractEventLoop, +@pytest_asyncio.fixture(autouse=True) +async def verify_cleanup( expected_lingering_tasks: bool, # pylint: disable=redefined-outer-name expected_lingering_timers: bool, # pylint: disable=redefined-outer-name -) -> Generator[None, None, None]: +) -> AsyncGenerator[None, None]: """Verify that the test has cleaned up resources correctly.""" + event_loop = asyncio.get_running_loop() threads_before = frozenset(threading.enumerate()) tasks_before = asyncio.all_tasks(event_loop) yield - event_loop.run_until_complete(event_loop.shutdown_default_executor()) + await event_loop.shutdown_default_executor() if len(INSTANCES) >= 2: count = len(INSTANCES) @@ -172,7 +173,12 @@ def verify_cleanup( # Warn and clean-up lingering tasks and timers # before moving on to the next test. - tasks = asyncio.all_tasks(event_loop) - tasks_before + current_task = asyncio.current_task() + tasks = { + task + for task in asyncio.all_tasks(event_loop) - tasks_before + if task is not current_task + } for task in tasks: if expected_lingering_tasks: _LOGGER.warning("Lingering task after test %r", task) @@ -180,7 +186,7 @@ def verify_cleanup( pytest.fail(f"Lingering task after test {task!r}") task.cancel() if tasks: - event_loop.run_until_complete(asyncio.wait(tasks)) + await asyncio.wait(tasks) for handle in event_loop._scheduled: if not handle.cancelled():