Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
with:
CODE_FOLDER: bellows
CACHE_VERSION: 2
PYTHON_VERSION_DEFAULT: 3.9.15
PYTHON_VERSION_DEFAULT: 3.11.0
PRE_COMMIT_CACHE_PATH: ~/.cache/pre-commit
MINIMUM_COVERAGE_PERCENTAGE: 99
secrets:
Expand Down
8 changes: 2 additions & 6 deletions bellows/ash.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import abc
import asyncio
from asyncio import timeout as asyncio_timeout
import binascii
from collections.abc import Coroutine
import contextlib
Expand All @@ -12,11 +13,6 @@
import time
import typing

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
else:
from asyncio import timeout as asyncio_timeout # pragma: no cover

from zigpy.types import BaseDataclassMixin

import bellows.types as t
Expand Down Expand Up @@ -675,7 +671,7 @@ async def _send_data_frame(self, frame: AshFrame) -> None:
"NCP has entered into a failed state, not retrying"
)
raise
except asyncio.TimeoutError:
except TimeoutError:
_LOGGER.debug(
"No ACK received in %0.2fs (attempt %d) for %r",
self._t_rx_ack,
Expand Down
15 changes: 5 additions & 10 deletions bellows/ezsp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,19 @@
from __future__ import annotations

import asyncio
from asyncio import timeout as asyncio_timeout
import collections
from collections.abc import Callable, Generator
import contextlib
import dataclasses
import functools
import logging
import sys
from typing import Any, Callable, Generator
from typing import Any
import urllib.parse

from bellows.ash import NcpFailure

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
else:
from asyncio import timeout as asyncio_timeout # pragma: no cover

import zigpy.config

from bellows.ash import NcpFailure
import bellows.config as conf
from bellows.exception import EzspError, InvalidCommandError, InvalidCommandPayload
from bellows.ezsp import xncp
Expand Down Expand Up @@ -119,7 +114,7 @@ async def startup_reset(self) -> None:
try:
async with asyncio_timeout(NETWORK_COORDINATOR_STARTUP_RESET_WAIT):
await self._gw.wait_for_startup_reset()
except asyncio.TimeoutError:
except TimeoutError:
pass
else:
LOGGER.debug("Received a reset on startup, not resetting again")
Expand Down
13 changes: 4 additions & 9 deletions bellows/ezsp/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@

import abc
import asyncio
from asyncio import timeout as asyncio_timeout
import binascii
from collections.abc import AsyncGenerator, Callable, Iterable
import functools
import logging
import sys
import time
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Iterable

import zigpy.state

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
else:
from asyncio import timeout as asyncio_timeout # pragma: no cover
from typing import TYPE_CHECKING, Any

from zigpy.datastructures import PriorityDynamicBoundedSemaphore
import zigpy.state

from bellows.config import CONF_EZSP_POLICIES
from bellows.exception import InvalidCommandError
Expand Down
2 changes: 1 addition & 1 deletion bellows/ezsp/v13/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
""""EZSP Protocol version 13 protocol handler."""
from __future__ import annotations

from collections.abc import AsyncGenerator, Iterable
import logging
from typing import AsyncGenerator, Iterable

import voluptuous as vol
from zigpy.exceptions import NetworkNotFormed
Expand Down
2 changes: 1 addition & 1 deletion bellows/ezsp/v14/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""""EZSP Protocol version 14 protocol handler."""
from __future__ import annotations

from typing import AsyncGenerator
from collections.abc import AsyncGenerator

import voluptuous as vol
from zigpy.exceptions import NetworkNotFormed
Expand Down
6 changes: 3 additions & 3 deletions bellows/ezsp/v4/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
""""EZSP Protocol version 4 command."""
from __future__ import annotations

from collections.abc import AsyncGenerator, Iterable
import logging
import random
from typing import AsyncGenerator, Iterable

import voluptuous as vol
import zigpy.state
Expand Down Expand Up @@ -189,11 +189,11 @@ async def set_source_route(self, nwk: t.NWK, relays: list[t.NWK]) -> t.sl_Status

async def read_counters(self) -> dict[t.EmberCounterType, t.uint16_t]:
(res,) = await self.readCounters()
return dict(zip(t.EmberCounterType, res))
return dict(zip(t.EmberCounterType, res, strict=False))

async def read_and_clear_counters(self) -> dict[t.EmberCounterType, t.uint16_t]:
(res,) = await self.readAndClearCounters()
return dict(zip(t.EmberCounterType, res))
return dict(zip(t.EmberCounterType, res, strict=False))

async def set_extended_timeout(
self, nwk: t.NWK, ieee: t.EUI64, extended_timeout: bool = True
Expand Down
2 changes: 1 addition & 1 deletion bellows/ezsp/v5/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
""""EZSP Protocol version 5 protocol handler."""
from __future__ import annotations

from collections.abc import AsyncGenerator
import logging
from typing import AsyncGenerator

import voluptuous as vol

Expand Down
2 changes: 1 addition & 1 deletion bellows/ezsp/v7/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
""""EZSP Protocol version 7 protocol handler."""
from __future__ import annotations

from collections.abc import AsyncGenerator
import logging
from typing import AsyncGenerator

import voluptuous

Expand Down
3 changes: 1 addition & 2 deletions bellows/ezsp/v8/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
""""EZSP Protocol version 8 protocol handler."""
import asyncio
import logging
from typing import Tuple

import voluptuous

Expand Down Expand Up @@ -30,7 +29,7 @@ def _ezsp_frame_tx(self, name: str) -> bytes:
hdr = [self._seq, 0x00, 0x01]
return bytes(hdr) + t.uint16_t(cmd_id).serialize()

def _ezsp_frame_rx(self, data: bytes) -> Tuple[int, int, bytes]:
def _ezsp_frame_rx(self, data: bytes) -> tuple[int, int, bytes]:
"""Handler for received data frame."""
seq, data = data[0], data[3:]
frame_id, data = t.uint16_t.deserialize(data)
Expand Down
2 changes: 1 addition & 1 deletion bellows/ezsp/xncp.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Custom EZSP commands."""
from __future__ import annotations

from collections.abc import Callable
import dataclasses
import logging
from typing import Callable

import zigpy.types as t

Expand Down
6 changes: 1 addition & 5 deletions bellows/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from concurrent.futures import ThreadPoolExecutor
import functools
import logging
import sys

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -35,10 +34,7 @@
if self.loop is not None and not self.loop.is_closed():
return

executor_opts = {"max_workers": 1}
if sys.version_info[:2] >= (3, 6):
executor_opts["thread_name_prefix"] = __name__
executor = ThreadPoolExecutor(**executor_opts)
executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix=__name__)

thread_started_future = current_loop.create_future()

Expand Down Expand Up @@ -102,7 +98,7 @@
# Disconnected
LOGGER.warning("Attempted to use a closed event loop")
return
if asyncio.iscoroutinefunction(func):

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Check warning on line 101 in bellows/thread.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.14.0

'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead
future = asyncio.run_coroutine_threadsafe(call(), loop)
return asyncio.wrap_future(future, loop=curr_loop)
else:
Expand Down
2 changes: 1 addition & 1 deletion bellows/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def deserialize_dict(data, schema):

def serialize_dict(args, kwargs, schema):
params = {
**dict(zip(schema.keys(), args)),
**dict(zip(schema.keys(), args, strict=False)),
**kwargs,
}

Expand Down
7 changes: 1 addition & 6 deletions bellows/uart.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import asyncio
from asyncio import timeout as asyncio_timeout
import logging
import sys

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
else:
from asyncio import timeout as asyncio_timeout # pragma: no cover

import zigpy.config
import zigpy.serial
Expand Down
18 changes: 6 additions & 12 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
from __future__ import annotations

import asyncio
from datetime import datetime, timezone
from asyncio import timeout as asyncio_timeout
from collections.abc import AsyncGenerator
from datetime import UTC, datetime
import importlib.metadata
import logging
import os
import statistics
import sys
from typing import AsyncGenerator

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
else:
from asyncio import timeout as asyncio_timeout # pragma: no cover

import importlib.metadata

import zigpy.application
import zigpy.config
Expand Down Expand Up @@ -843,7 +837,7 @@ async def _packet_capture(self, channel: int):
with self._ezsp.callback_for_commands(
{"mfglibRxHandler"},
callback=lambda _, response: queue.put_nowait(
(datetime.now(timezone.utc), response)
(datetime.now(UTC), response)
),
):
while True:
Expand Down Expand Up @@ -1096,7 +1090,7 @@ async def _watchdog_feed(self):
cnt._last_reset_value = 0

LOGGER.debug("%s", counters)
except (asyncio.TimeoutError, EzspError) as exc:
except (TimeoutError, EzspError) as exc:
# TODO: converted Silvercrest gateways break without this
LOGGER.warning("Watchdog heartbeat timeout: %s", repr(exc))
self._watchdog_failures += 1
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ authors = [
]
readme = "README.md"
license = {text = "GPL-3.0"}
requires-python = ">=3.8"
requires-python = ">=3.11"
dependencies = [
"click",
"click-log>=0.2.1",
"voluptuous",
"zigpy>=0.83.0",
'async-timeout; python_version<"3.11"',
]

[tool.setuptools.packages.find]
Expand Down
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
target-version = "py38"
target-version = "py311"

select = [
"B007", # Loop control variable {name} not used within loop body
Expand Down
14 changes: 7 additions & 7 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def test_frame_handler_ignored(app, aps_frame):
0xFF,
),
)
def test_send_failure(app, aps, ieee, msg_type):
async def test_send_failure(app, aps, ieee, msg_type):
fut = app._pending_requests[(0xBEED, 254)] = asyncio.Future()
app.ezsp_callback_handler(
"messageSentHandler", [msg_type, 0xBEED, aps, 254, t.EmberStatus.SUCCESS, b""]
Expand Down Expand Up @@ -583,7 +583,7 @@ def test_send_failure_unexpected(app, aps, ieee):
)


def test_send_success(app, aps, ieee):
async def test_send_success(app, aps, ieee):
fut = app._pending_requests[(0xBEED, 253)] = asyncio.Future()
app.ezsp_callback_handler(
"messageSentHandler",
Expand Down Expand Up @@ -1267,7 +1267,7 @@ async def nop_mock():
raise EzspError
else:
return ([0] * 10,)
raise asyncio.TimeoutError
raise TimeoutError

app._ezsp._protocol.getValue.return_value = [t.EmberStatus.SUCCESS, b"\xFE"]
app._ezsp._protocol.nop.side_effect = nop_mock
Expand All @@ -1286,7 +1286,7 @@ async def nop_mock():
await app._watchdog_feed()

# The last time will throw a real error
with pytest.raises(asyncio.TimeoutError):
with pytest.raises(TimeoutError):
await app._watchdog_feed()

if ezsp_version == 4:
Expand All @@ -1309,7 +1309,7 @@ async def counters_mock():
raise EzspError
else:
return ([0, 1, 2, 3],)
raise asyncio.TimeoutError
raise TimeoutError

app._ezsp._protocol.getValue = AsyncMock(
return_value=[t.EmberStatus.SUCCESS, b"\xFE"]
Expand Down Expand Up @@ -1346,7 +1346,7 @@ async def counters_mock():
raise EzspError
else:
return {t.EmberCounterType(i): v for i, v in enumerate([0, 1, 2, 3])}
raise asyncio.TimeoutError
raise TimeoutError

app._ezsp.read_counters = AsyncMock(side_effect=counters_mock)
app._ezsp.nop = AsyncMock(side_effect=EzspError)
Expand Down Expand Up @@ -1846,7 +1846,7 @@ async def test_startup_concurrency_setting(
)
async def test_energy_scanning(app, scan_results):
app._ezsp.startScan = AsyncMock(
return_value=list(zip(range(11, 26 + 1), scan_results))
return_value=list(zip(range(11, 26 + 1), scan_results, strict=True))
)

results = await app.energy_scan(
Expand Down
6 changes: 3 additions & 3 deletions tests/test_ash.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def rst_frame_received(self, frame: ash.RstFrame) -> None:
async def _send_data_frame(self, frame: ash.AshFrame) -> None:
try:
return await super()._send_data_frame(frame)
except asyncio.TimeoutError:
except TimeoutError:
self._enter_ncp_error_state(
t.NcpResetCode.ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT
)
Expand Down Expand Up @@ -549,7 +549,7 @@ async def test_ash_end_to_end(transport_cls: type[FakeTransport]) -> None:
send_task = asyncio.create_task(host.send_data(b"host failure"))
await asyncio.sleep(host._t_rx_ack * 15)

with pytest.raises(asyncio.TimeoutError):
with pytest.raises(TimeoutError):
await send_task

ncp_ezsp.data_received.reset_mock()
Expand All @@ -575,7 +575,7 @@ async def test_ash_end_to_end(transport_cls: type[FakeTransport]) -> None:
send_task = asyncio.create_task(ncp.send_data(b"ncp failure"))
await asyncio.sleep(ncp._t_rx_ack * 15)

with pytest.raises(asyncio.TimeoutError):
with pytest.raises(TimeoutError):
await send_task

assert (
Expand Down
Loading
Loading