From dae93fd0a5253b9c2fe7ef81c7d552c0c15f3f57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Jul 2023 21:30:58 -0500 Subject: [PATCH 1/4] Replace asyncio.wait_for with asyncio.timeout Fallback to using async_timeout on older python asyncio.wait_for has some underlying problems that are only fixed in cpython 3.12. See https://github.com/python/cpython/pull/98518 --- kasa/protocol.py | 16 ++++++++++++---- poetry.lock | 14 +++++++++++++- pyproject.toml | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/kasa/protocol.py b/kasa/protocol.py index cd9066c6f..58dbd0b80 100755 --- a/kasa/protocol.py +++ b/kasa/protocol.py @@ -14,6 +14,7 @@ import errno import logging import struct +import sys from pprint import pformat as pf from typing import Dict, Generator, Optional, Union @@ -21,6 +22,12 @@ from .json import dumps as json_dumps from .json import loads as json_loads +if sys.version_info[:2] < (3, 11): + from async_timeout import timeout as asyncio_timeout +else: + from asyncio import timeout as asyncio_timeout + + _LOGGER = logging.getLogger(__name__) _NO_RETRY_ERRORS = {errno.EHOSTDOWN, errno.EHOSTUNREACH, errno.ECONNREFUSED} @@ -79,8 +86,10 @@ async def _connect(self, timeout: int) -> None: if self.writer: return self.reader = self.writer = None + task = asyncio.open_connection(self.host, self.port) - self.reader, self.writer = await asyncio.wait_for(task, timeout=timeout) + async with asyncio_timeout(timeout): + self.reader, self.writer = await task async def _execute_query(self, request: str) -> Dict: """Execute a query on the device and wait for the response.""" @@ -155,9 +164,8 @@ async def _query(self, request: str, retry_count: int, timeout: int) -> Dict: try: assert self.reader is not None assert self.writer is not None - return await asyncio.wait_for( - self._execute_query(request), timeout=timeout - ) + async with asyncio_timeout(timeout): + await self._execute_query(request) except Exception as ex: await self.close() if retry >= retry_count: diff --git a/poetry.lock b/poetry.lock index c846dc1a8..23e4b3638 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,6 +34,18 @@ doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "s test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + [[package]] name = "asyncclick" version = "8.1.3.4" @@ -1404,4 +1416,4 @@ speedups = ["orjson", "kasa-crypt"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b85f55f0ca928b1f3510da37196c21f40eb07cd4d07b3a9c3dd29215ba9777fe" +content-hash = "fcb657fbabe28548021f5f6a1fcb7b60aa82d60f3de015b4b0c7b37260a6a29f" diff --git a/pyproject.toml b/pyproject.toml index 68a2159d3..c905cf915 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ sphinx_rtd_theme = { version = "^0", optional = true } sphinxcontrib-programoutput = { version = "^0", optional = true } myst-parser = { version = "*", optional = true } docutils = { version = ">=0.17", optional = true } +async-timeout = ">=3.0.0" [tool.poetry.dev-dependencies] pytest = "*" From fe6a6f52d5b0f756ac021f50dde826e8383e1e68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Jul 2023 21:36:19 -0500 Subject: [PATCH 2/4] Replace asyncio.wait_for with asyncio.timeout Fallback to using async_timeout on older python asyncio.wait_for has some underlying problems that are only fixed in cpython 3.12. See https://github.com/python/cpython/pull/98518 --- kasa/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kasa/protocol.py b/kasa/protocol.py index 58dbd0b80..b88e1fb1e 100755 --- a/kasa/protocol.py +++ b/kasa/protocol.py @@ -165,7 +165,7 @@ async def _query(self, request: str, retry_count: int, timeout: int) -> Dict: assert self.reader is not None assert self.writer is not None async with asyncio_timeout(timeout): - await self._execute_query(request) + return await self._execute_query(request) except Exception as ex: await self.close() if retry >= retry_count: From 16c971106a9243de0e771b661ff67def5150d4bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Jul 2023 03:56:05 -0500 Subject: [PATCH 3/4] drop asyncio.timeout until cpython 3.12 is the min supported --- kasa/protocol.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/kasa/protocol.py b/kasa/protocol.py index b88e1fb1e..4d14d922d 100755 --- a/kasa/protocol.py +++ b/kasa/protocol.py @@ -14,20 +14,17 @@ import errno import logging import struct -import sys from pprint import pformat as pf from typing import Dict, Generator, Optional, Union +# When support for cpython older than 3.12 is dropped +# async_timeout can be replaced with asyncio.timeout +from async_timeout import timeout as asyncio_timeout + from .exceptions import SmartDeviceException from .json import dumps as json_dumps from .json import loads as json_loads -if sys.version_info[:2] < (3, 11): - from async_timeout import timeout as asyncio_timeout -else: - from asyncio import timeout as asyncio_timeout - - _LOGGER = logging.getLogger(__name__) _NO_RETRY_ERRORS = {errno.EHOSTDOWN, errno.EHOSTUNREACH, errno.ECONNREFUSED} From 061f4625bef14ad1c52b39394d9893e51a21a458 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Jul 2023 03:59:30 -0500 Subject: [PATCH 4/4] drop asyncio.timeout until cpython 3.11 is the min supported --- kasa/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kasa/protocol.py b/kasa/protocol.py index 4d14d922d..461dd85ad 100755 --- a/kasa/protocol.py +++ b/kasa/protocol.py @@ -17,7 +17,7 @@ from pprint import pformat as pf from typing import Dict, Generator, Optional, Union -# When support for cpython older than 3.12 is dropped +# When support for cpython older than 3.11 is dropped # async_timeout can be replaced with asyncio.timeout from async_timeout import timeout as asyncio_timeout