From 4191b1764598947de475bbe1292af29776005f10 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 26 Sep 2024 00:56:19 +0400 Subject: [PATCH] Python 3.8, 3.10, and 3.12 support (#404) support python 3.8, 3.10 and 3.12 support --- .github/workflows/ci.yml | 10 +++++----- docs_src/generate_docs.py | 2 +- mqtt_io/__main__.py | 2 +- mqtt_io/config/__init__.py | 2 +- mqtt_io/modules/gpio/__init__.py | 7 ++++--- mqtt_io/modules/gpio/gpiozero.py | 4 ++-- mqtt_io/modules/gpio/mock.py | 15 ++++++++------- mqtt_io/modules/gpio/raspberrypi.py | 4 ++-- mqtt_io/modules/sensor/mock.py | 2 +- mqtt_io/mqtt/__init__.py | 2 +- mqtt_io/mqtt/aiomqtt.py | 4 ++-- mqtt_io/server.py | 10 +++++----- mqtt_io/tests/features/steps/config_main.py | 2 +- mqtt_io/tests/features/steps/events.py | 6 +++--- mqtt_io/tests/features/steps/module_gpio.py | 4 ++-- mqtt_io/tests/features/steps/server.py | 6 +++--- pyproject.toml | 6 +++--- tox.ini | 2 +- 18 files changed, 46 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fbd2587..48e700f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,15 +5,15 @@ on: - release jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.8] + python-version: ['3.8', '3.10', '3.12'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -97,7 +97,7 @@ jobs: if: steps.branch-names.outputs.is_tag == 'false' run: | git checkout gh-pages - git checkout ${{ steps.branch-names.outputs.current_branch }} + git checkout ${{ steps.branch-names.outputs.current_branch }} - name: Setup Python 3.8 uses: actions/setup-python@v2 with: diff --git a/docs_src/generate_docs.py b/docs_src/generate_docs.py index 588ee3fc..96d95698 100644 --- a/docs_src/generate_docs.py +++ b/docs_src/generate_docs.py @@ -13,7 +13,7 @@ from typing import Any, Dict, Iterator, List, Optional, Set, Tuple import semver -import yaml +import yaml # type: ignore from ast_to_xml import module_source from git import Repo from jinja2 import Template diff --git a/mqtt_io/__main__.py b/mqtt_io/__main__.py index d34e3370..bf442e92 100644 --- a/mqtt_io/__main__.py +++ b/mqtt_io/__main__.py @@ -8,7 +8,7 @@ from hashlib import sha256 from typing import Any, Optional -import yaml +import yaml # type: ignore from confp import render # type: ignore diff --git a/mqtt_io/config/__init__.py b/mqtt_io/config/__init__.py index 876be1b6..e898af3a 100644 --- a/mqtt_io/config/__init__.py +++ b/mqtt_io/config/__init__.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, cast import cerberus # type: ignore -import yaml +import yaml # type: ignore from ..exceptions import ConfigValidationFailed from ..types import ConfigType diff --git a/mqtt_io/modules/gpio/__init__.py b/mqtt_io/modules/gpio/__init__.py index b6f4540c..db27a70d 100644 --- a/mqtt_io/modules/gpio/__init__.py +++ b/mqtt_io/modules/gpio/__init__.py @@ -12,7 +12,7 @@ import logging from concurrent.futures import ThreadPoolExecutor from enum import Enum, Flag, IntFlag, auto -from typing import Any, Callable, Dict, Iterable, List, Optional +from typing import Any, Callable, Dict, Iterable, List, Optional, cast from ...types import ConfigType, PinType @@ -173,7 +173,7 @@ def setup_interrupt_internal( pin: PinType, edge: InterruptEdge, in_conf: ConfigType, - callback: Optional[Callable[[List[Any], Dict[Any, Any]], None]] = None, + callback: Optional[Callable[..., None]] = None, ) -> None: """ Used internally to ensure that `self.interrupt_edges` is updated for this pin. @@ -213,7 +213,7 @@ def remote_interrupt_for(self, pin: PinType) -> List[str]: """ Return the list of pin names that this pin is a remote interrupt for. """ - return self.pin_configs[pin].get("interrupt_for", []) + return cast(List[str], self.pin_configs[pin].get("interrupt_for", [])) def get_int_pins(self) -> List[PinType]: """ @@ -274,6 +274,7 @@ def get_interrupt_value(self, pin: PinType, *args: Any, **kwargs: Any) -> bool: for the value, or looking it up in a register that captures the value at time of interrupt. """ + return False async def get_interrupt_values_remote( self, pins: List[PinType] diff --git a/mqtt_io/modules/gpio/gpiozero.py b/mqtt_io/modules/gpio/gpiozero.py index c5a1bdf2..a1ad88c1 100644 --- a/mqtt_io/modules/gpio/gpiozero.py +++ b/mqtt_io/modules/gpio/gpiozero.py @@ -3,7 +3,7 @@ """ import logging from functools import partial -from typing import Optional, Any, Callable, List, Dict, TYPE_CHECKING, cast +from typing import Optional, Any, Callable, Dict, TYPE_CHECKING, cast from mqtt_io.modules.gpio import ( GenericGPIO, @@ -89,7 +89,7 @@ def setup_interrupt_callback( pin: PinType, edge: InterruptEdge, in_conf: ConfigType, - callback: Callable[[List[Any], Dict[Any, Any]], None], + callback: Callable[..., None], ) -> None: _LOG.debug( "Added interrupt to gpiozero Pi pin '%s' with callback '%s'", pin, callback diff --git a/mqtt_io/modules/gpio/mock.py b/mqtt_io/modules/gpio/mock.py index 32449ca9..154deea2 100644 --- a/mqtt_io/modules/gpio/mock.py +++ b/mqtt_io/modules/gpio/mock.py @@ -2,7 +2,7 @@ Mock GPIO module for using with the tests. """ -from typing import Any, Callable, Dict, Iterable, List, Optional +from typing import Callable, Dict, Iterable, List, Optional from unittest.mock import Mock from ...types import ConfigType, PinType @@ -39,7 +39,7 @@ def __init__(self, config: ConfigType): super().__init__(config) self.interrupt_callbacks: Dict[ - PinType, Callable[[List[Any], Dict[Any, Any]], None] + PinType, Callable[..., None] ] = {} def setup_interrupt_callback( @@ -47,12 +47,12 @@ def setup_interrupt_callback( pin: PinType, edge: InterruptEdge, in_conf: ConfigType, - callback: Callable[[List[Any], Dict[Any, Any]], None], + callback: Callable[..., None], ) -> None: self.interrupt_callbacks[pin] = callback def setup_module(self) -> None: - return super().setup_module() + return super().setup_module() # type: ignore[safe-super] def setup_pin( self, @@ -62,7 +62,8 @@ def setup_pin( pin_config: ConfigType, initial: Optional[str] = None, ) -> None: - return super().setup_pin(pin, direction, pullup, pin_config, initial=initial) + return super().setup_pin( # type: ignore[safe-super] + pin, direction, pullup, pin_config, initial=initial) def setup_interrupt( self, pin: PinType, edge: InterruptEdge, in_conf: ConfigType @@ -70,10 +71,10 @@ def setup_interrupt( return super().setup_interrupt(pin, edge, in_conf) def set_pin(self, pin: PinType, value: bool) -> None: - return super().set_pin(pin, value) + return super().set_pin(pin, value) # type: ignore[safe-super] def get_pin(self, pin: PinType) -> bool: - return super().get_pin(pin) + return super().get_pin(pin) # type: ignore[safe-super] def get_int_pins(self) -> List[PinType]: return super().get_int_pins() diff --git a/mqtt_io/modules/gpio/raspberrypi.py b/mqtt_io/modules/gpio/raspberrypi.py index 2f0d1aed..819b90df 100644 --- a/mqtt_io/modules/gpio/raspberrypi.py +++ b/mqtt_io/modules/gpio/raspberrypi.py @@ -3,7 +3,7 @@ """ import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from ...types import ConfigType, PinType from . import GenericGPIO, InterruptEdge, InterruptSupport, PinDirection, PinPUD @@ -60,7 +60,7 @@ def setup_interrupt_callback( pin: PinType, edge: InterruptEdge, in_conf: ConfigType, - callback: Callable[[List[Any], Dict[Any, Any]], None], + callback: Callable[..., None], ) -> None: gpio_edge = self.interrupt_edge_map[edge] _LOG.debug( diff --git a/mqtt_io/modules/sensor/mock.py b/mqtt_io/modules/sensor/mock.py index fb2b8677..eab05670 100644 --- a/mqtt_io/modules/sensor/mock.py +++ b/mqtt_io/modules/sensor/mock.py @@ -30,4 +30,4 @@ def setup_sensor(self, sens_conf: ConfigType) -> None: return super().setup_sensor(sens_conf) def get_value(self, sens_conf: ConfigType) -> SensorValueType: - return super().get_value(sens_conf) + return super().get_value(sens_conf) # type: ignore[safe-super] diff --git a/mqtt_io/mqtt/__init__.py b/mqtt_io/mqtt/__init__.py index 9503f6cf..16bd1b4a 100644 --- a/mqtt_io/mqtt/__init__.py +++ b/mqtt_io/mqtt/__init__.py @@ -61,7 +61,7 @@ class MQTTTLSOptions: ca_certs: Optional[str] = None certfile: Optional[str] = None keyfile: Optional[str] = None - cert_reqs: int = ssl.CERT_REQUIRED + cert_reqs: ssl.VerifyMode = ssl.CERT_REQUIRED tls_version: int = ssl.PROTOCOL_TLS ciphers: Optional[str] = None diff --git a/mqtt_io/mqtt/aiomqtt.py b/mqtt_io/mqtt/aiomqtt.py index 852b4196..41a2d2e5 100644 --- a/mqtt_io/mqtt/aiomqtt.py +++ b/mqtt_io/mqtt/aiomqtt.py @@ -8,8 +8,8 @@ from functools import wraps from typing import Any, Callable, List, Optional, Tuple, TypeVar, cast -from aiomqtt.client import Client, MqttError, Will, ProtocolVersion # type: ignore -from paho.mqtt import client as paho # type: ignore +from aiomqtt import Client, MqttError, Will, ProtocolVersion +from paho.mqtt import client as paho from . import ( AbstractMQTTClient, diff --git a/mqtt_io/server.py b/mqtt_io/server.py index 259f398c..42d323cf 100644 --- a/mqtt_io/server.py +++ b/mqtt_io/server.py @@ -18,9 +18,9 @@ from hashlib import sha1 from importlib import import_module from typing import Any, Dict, List, Optional, Tuple, Type, Union, overload -from aiomqtt.exceptions import MqttCodeError # type: ignore +from aiomqtt import MqttCodeError -import backoff # type: ignore +import backoff from typing_extensions import Literal from .config import ( @@ -648,10 +648,10 @@ async def poll_sensor( None """ - @backoff.on_exception( # type: ignore + @backoff.on_exception( backoff.expo, Exception, max_time=sens_conf["interval"] ) - @backoff.on_predicate( # type: ignore + @backoff.on_predicate( backoff.expo, lambda x: x is None, max_time=sens_conf["interval"] ) async def get_sensor_value( @@ -808,7 +808,7 @@ async def _mqtt_publish(self, msg: MQTTMessageSend, wait: bool = True) -> None: ) else: _LOG.debug( - "Publishing MQTT message on topic %r: %r", msg.topic, payload + "Publishing MQTT message on topic %r: %r", msg.topic, msg.payload ) await self.mqtt.publish(msg) diff --git a/mqtt_io/tests/features/steps/config_main.py b/mqtt_io/tests/features/steps/config_main.py index 5593a40a..d0543d2d 100644 --- a/mqtt_io/tests/features/steps/config_main.py +++ b/mqtt_io/tests/features/steps/config_main.py @@ -1,5 +1,5 @@ from typing import Any -import yaml +import yaml # type: ignore from behave import given, then, when # type: ignore from mqtt_io.config import validate_and_normalise_main_config from mqtt_io.exceptions import ConfigValidationFailed diff --git a/mqtt_io/tests/features/steps/events.py b/mqtt_io/tests/features/steps/events.py index 88a5b538..ae8fe968 100644 --- a/mqtt_io/tests/features/steps/events.py +++ b/mqtt_io/tests/features/steps/events.py @@ -2,16 +2,16 @@ import concurrent.futures from typing import Any, Dict, Type -import yaml +import yaml # type: ignore from behave import given, then, when # type: ignore from behave.api.async_step import async_run_until_complete # type: ignore from mqtt_io import events from mqtt_io.server import MqttIo try: - from unittest.mock import AsyncMock # type: ignore[attr-defined] + from unittest.mock import AsyncMock # type: ignore except ImportError: - from mock import AsyncMock # type: ignore[attr-defined] + from mock import AsyncMock # type: ignore # pylint: disable=function-redefined,protected-access diff --git a/mqtt_io/tests/features/steps/module_gpio.py b/mqtt_io/tests/features/steps/module_gpio.py index 54a51856..5a74f173 100644 --- a/mqtt_io/tests/features/steps/module_gpio.py +++ b/mqtt_io/tests/features/steps/module_gpio.py @@ -102,7 +102,7 @@ def step(context: Any, is_isnt: str, pin_name: str): poller_task_pin_names = { get_coro(task).cr_frame.f_locals["in_conf"]["name"] - for task in asyncio.Task.all_tasks(loop=mqttio.loop) + for task in asyncio.all_tasks(loop=mqttio.loop) if get_coro(task).__name__ == "digital_input_poller" } if is_isnt == "is": @@ -137,7 +137,7 @@ def step(context: Any, is_isnt: str, module_name: str): task_modules = { get_coro(task).cr_frame.f_locals["module"] - for task in asyncio.Task.all_tasks(loop=mqttio.loop) + for task in asyncio.all_tasks(loop=mqttio.loop) if get_coro(task).__name__ == "digital_output_loop" } if is_isnt == "is": diff --git a/mqtt_io/tests/features/steps/server.py b/mqtt_io/tests/features/steps/server.py index d58952f9..74ce0265 100644 --- a/mqtt_io/tests/features/steps/server.py +++ b/mqtt_io/tests/features/steps/server.py @@ -3,7 +3,7 @@ from typing import Any, Union from unittest.mock import Mock -import yaml +import yaml # type: ignore from behave import given, then, when # type: ignore from behave.api.async_step import async_run_until_complete # type: ignore from mqtt_io.exceptions import ConfigValidationFailed @@ -11,9 +11,9 @@ from mqtt_io.server import MqttIo try: - from unittest.mock import AsyncMock # type: ignore[attr-defined] + from unittest.mock import AsyncMock # type: ignore except ImportError: - from mock import AsyncMock # type: ignore[attr-defined] + from mock import AsyncMock # type: ignore # pylint: disable=function-redefined diff --git a/pyproject.toml b/pyproject.toml index d2da79f4..84108a53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ Cerberus = "^1.3.2" typing-extensions = "^4.4.0" dataclasses = { version = "^0.8", python = ">=3.6,<3.7" } aiomqtt = "^2.1.0" -backoff = "^1.10.0" +backoff = "^2.2.1" confp = "^0.4.0" # Fix for poetry/docutils related bug docutils = "0.18.1" @@ -23,8 +23,8 @@ docutils = "0.18.1" mock = { version = "^4.0.3", python = ">=3.6,<3.8" } pytest = "^6.2.2" tox = "^3.22.0" -pylint = "^2.6.2" -mypy = "^0.812" +pylint = "^3.2.7" +mypy = "^1.11.2" behave = "^1.2.6" coverage = "^5.4" md-toc = "^8.0.0" diff --git a/tox.ini b/tox.ini index 70c17caa..16ecc605 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = true -envlist = py3{6,7,8} +envlist = py3{8,10,12} [testenv] whitelist_externals = poetry