diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1f2215b2e0dee8..2018736cf3c21f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ env: MYPY_CACHE_VERSION: 4 HA_SHORT_VERSION: 2023.3 DEFAULT_PYTHON: "3.10" - ALL_PYTHON_VERSIONS: "['3.10']" + ALL_PYTHON_VERSIONS: "['3.10', '3.11']" # 10.3 is the oldest supported version # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # 10.6 is the current long-term-support diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 14a0a0d0a2ea30..17b46e97cd4de1 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -75,6 +75,9 @@ libcst==0.3.23 # This is a old unmaintained library and is replaced with pycryptodome pycrypto==1000000000.0.0 +# This is a old unmaintained library and is replaced with faust-cchardet +cchardet==1000000000.0.0 + # To remove reliance on typing btlewrap>=0.0.10 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index fd44ed774289e7..be1f623eb5070c 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -83,6 +83,9 @@ # This is a old unmaintained library and is replaced with pycryptodome pycrypto==1000000000.0.0 +# This is a old unmaintained library and is replaced with faust-cchardet +cchardet==1000000000.0.0 + # To remove reliance on typing btlewrap>=0.0.10 diff --git a/tests/asyncio_legacy.py b/tests/asyncio_legacy.py new file mode 100644 index 00000000000000..bc054b7e36e75d --- /dev/null +++ b/tests/asyncio_legacy.py @@ -0,0 +1,128 @@ +"""Minimal legacy asyncio.coroutine.""" + +# flake8: noqa +# stubbing out for integrations that have +# not yet been updated for python 3.11 +# but can still run on python 3.10 +# +# Remove this once rflink, fido, and blackbird +# have had their libraries updated to remove +# asyncio.coroutine +from asyncio import base_futures, constants, format_helpers +from asyncio.coroutines import _is_coroutine +import collections.abc +import functools +import inspect +import logging +import traceback +import types +import warnings + +logger = logging.getLogger(__name__) + + +class CoroWrapper: + # Wrapper for coroutine object in _DEBUG mode. + + def __init__(self, gen, func=None): + assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen + self.gen = gen + self.func = func # Used to unwrap @coroutine decorator + self._source_traceback = format_helpers.extract_stack(sys._getframe(1)) + self.__name__ = getattr(gen, "__name__", None) + self.__qualname__ = getattr(gen, "__qualname__", None) + + def __iter__(self): + return self + + def __next__(self): + return self.gen.send(None) + + def send(self, value): + return self.gen.send(value) + + def throw(self, type, value=None, traceback=None): + return self.gen.throw(type, value, traceback) + + def close(self): + return self.gen.close() + + @property + def gi_frame(self): + return self.gen.gi_frame + + @property + def gi_running(self): + return self.gen.gi_running + + @property + def gi_code(self): + return self.gen.gi_code + + def __await__(self): + return self + + @property + def gi_yieldfrom(self): + return self.gen.gi_yieldfrom + + def __del__(self): + # Be careful accessing self.gen.frame -- self.gen might not exist. + gen = getattr(self, "gen", None) + frame = getattr(gen, "gi_frame", None) + if frame is not None and frame.f_lasti == -1: + msg = f"{self!r} was never yielded from" + tb = getattr(self, "_source_traceback", ()) + if tb: + tb = "".join(traceback.format_list(tb)) + msg += ( + f"\nCoroutine object created at " + f"(most recent call last, truncated to " + f"{constants.DEBUG_STACK_DEPTH} last lines):\n" + ) + msg += tb.rstrip() + logger.error(msg) + + +def legacy_coroutine(func): + """Decorator to mark coroutines. + If the coroutine is not yielded from before it is destroyed, + an error message is logged. + """ + warnings.warn( + '"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead', + DeprecationWarning, + stacklevel=2, + ) + if inspect.iscoroutinefunction(func): + # In Python 3.5 that's all we need to do for coroutines + # defined with "async def". + return func + + if inspect.isgeneratorfunction(func): + coro = func + else: + + @functools.wraps(func) + def coro(*args, **kw): + res = func(*args, **kw) + if ( + base_futures.isfuture(res) + or inspect.isgenerator(res) + or isinstance(res, CoroWrapper) + ): + res = yield from res + else: + # If 'res' is an awaitable, run it. + try: + await_meth = res.__await__ + except AttributeError: + pass + else: + if isinstance(res, collections.abc.Awaitable): + res = yield from await_meth() + return res + + wrapper = types.coroutine(coro) + wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction(). + return wrapper diff --git a/tests/conftest.py b/tests/conftest.py index 186b67dff6e72c..12ae9d3e8141f7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ import logging import sqlite3 import ssl +import sys import threading from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, cast from unittest.mock import AsyncMock, MagicMock, Mock, patch @@ -100,6 +101,11 @@ # Disable fixtures overriding our beautiful policy asyncio.set_event_loop_policy = lambda policy: None +if sys.version_info[:2] >= (3, 11): + from .asyncio_legacy import legacy_coroutine + + setattr(asyncio, "coroutine", legacy_coroutine) + def _utcnow() -> datetime.datetime: """Make utcnow patchable by freezegun.""" diff --git a/tests/util/test_executor.py b/tests/util/test_executor.py index eaa48c75d1a2f2..70c1f5f3a7fcf7 100644 --- a/tests/util/test_executor.py +++ b/tests/util/test_executor.py @@ -48,9 +48,7 @@ def _loop_sleep_in_executor(): iexecutor.shutdown() assert "time.sleep(0.2)" in caplog.text - assert ( - caplog.text.count("is still running at shutdown") == executor.MAX_LOG_ATTEMPTS - ) + assert "is still running at shutdown" in caplog.text iexecutor.shutdown() @@ -86,6 +84,6 @@ def _loop_sleep_in_executor(): iexecutor.shutdown() finish = time.monotonic() - assert finish - start < 1 + assert finish - start < 1.2 iexecutor.shutdown()