From 3c2c8b83f2876e8044efda2ad6965d8befa9a4af Mon Sep 17 00:00:00 2001 From: spacemanspiff2007 <10754716+spacemanspiff2007@users.noreply.github.com> Date: Wed, 15 Nov 2023 07:17:57 +0100 Subject: [PATCH] Updated dependencies --- .pre-commit-config.yaml | 7 --- .ruff.toml | 62 +++++++++++++++++++ docs/conf.py | 17 +++-- docs/interface_habapp.rst | 2 +- requirements.txt | 3 +- requirements_setup.txt | 16 ++--- requirements_tests.txt | 4 +- setup.py | 1 + src/HABApp/__cmd_args__.py | 11 ++-- src/HABApp/core/const/loop.py | 1 - src/HABApp/core/events/filter/event.py | 3 +- src/HABApp/core/events/filter/groups.py | 10 +-- .../wrapped_function/wrapped_async.py | 1 + src/HABApp/core/items/item.py | 2 +- src/HABApp/mqtt/connection/handler.py | 4 -- src/HABApp/mqtt/connection/subscribe.py | 8 ++- src/HABApp/mqtt/items/mqtt_item.py | 4 +- src/HABApp/mqtt/items/mqtt_pair_item.py | 8 ++- src/HABApp/mqtt/mqtt_interface.py | 11 ++-- src/HABApp/openhab/connection/connection.py | 4 +- .../openhab/connection/handler/func_async.py | 21 ++++--- .../openhab/connection/handler/handler.py | 2 +- .../plugins/load_transformations.py | 2 +- .../plugins/overview_broken_links.py | 2 +- .../plugins/plugin_things/cfg_validator.py | 3 +- src/HABApp/openhab/definitions/rest/items.py | 2 +- src/HABApp/openhab/definitions/rest/root.py | 1 - src/HABApp/openhab/items/contact_item.py | 10 +-- src/HABApp/openhab/map_items.py | 3 +- src/HABApp/parameters/parameter.py | 4 ++ src/HABApp/rule/interfaces/rule_subprocess.py | 4 +- src/HABApp/rule/rule.py | 37 +++++++---- src/HABApp/rule_ctx/rule_ctx.py | 5 +- src/HABApp/util/multimode/item.py | 8 ++- src/HABApp/util/multimode/mode_switch.py | 3 +- 35 files changed, 186 insertions(+), 100 deletions(-) create mode 100644 .ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 67fcdb42..048861d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,13 +34,6 @@ repos: # - pep8-naming==0.13.3 - - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 - hooks: - - id: pyupgrade - args: ["--py38-plus"] - - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..9ae65c5e --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,62 @@ + +line-length = 120 +indent-width = 4 + +target-version = "py38" +src = ["src", "test"] + +# https://docs.astral.sh/ruff/settings/#ignore-init-module-imports +ignore-init-module-imports = true + +extend-exclude = ["__init__.py"] + +select = [ + "E", "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "I", # https://docs.astral.sh/ruff/rules/#isort-i + "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up + + "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + "ASYNC", # https://docs.astral.sh/ruff/rules/#flake8-async-async + "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "EM", # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + "INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + "RET", # https://docs.astral.sh/ruff/rules/#flake8-return-ret + "SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "SLOT", # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + "T10", # https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 + "TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + "TD", # https://docs.astral.sh/ruff/rules/#flake8-todos-td + + "TRY", # https://docs.astral.sh/ruff/rules/#tryceratops-try + "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly + "PERF", # https://docs.astral.sh/ruff/rules/#perflint-perf + "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + + # "PL", # https://docs.astral.sh/ruff/rules/#pylint-pl + # "FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb +] + +ignore = [ + "RET501", # https://docs.astral.sh/ruff/rules/unnecessary-return-none/#unnecessary-return-none-ret501 + "TRY400" # https://docs.astral.sh/ruff/rules/error-instead-of-exception/ +] + + +[format] +# Use single quotes for non-triple-quoted strings. +quote-style = "single" + + + +[lint.flake8-builtins] +builtins-ignorelist = ["id", "input"] + + +[lint.per-file-ignores] +"docs/conf.py" = ["INP001", "A001"] +"setup.py" = ["PTH123"] diff --git a/docs/conf.py b/docs/conf.py index a9847ee9..c456e0b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,15 +11,15 @@ import os import re import sys +from pathlib import Path import sphinx -from docutils.nodes import Text, Node +from docutils.nodes import Node, Text from sphinx.addnodes import desc_signature IS_RTD_BUILD = os.environ.get('READTHEDOCS', '-').lower() == 'true' IS_CI = os.environ.get('CI', '-') == 'true' - # https://www.sphinx-doc.org/en/master/extdev/logging.html sphinx_logger = sphinx.util.logging.getLogger('post') logger_lvl = logging.DEBUG if IS_RTD_BUILD or IS_CI else logging.INFO # set level to DEBUG for CI @@ -35,7 +35,11 @@ def log(msg: str): # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -sys.path.insert(0, os.path.join(os.path.abspath('..'), 'src')) +src_folder = Path(__file__).parent.with_name('src') +assert src_folder.is_dir() + +# required for autodoc +sys.path.insert(0, str(src_folder)) # -- Project information ----------------------------------------------------- @@ -49,6 +53,7 @@ def log(msg: str): release = 'beta' try: from HABApp import __version__ + version = __version__ print(f'Building docs for {version}') except Exception as e: @@ -229,7 +234,6 @@ def log(msg: str): (re.compile(r'py:class'), re.compile(r'(?:datetime|pendulum|aiohttp|pathlib)\..+')) ] - # -- Extension configuration ------------------------------------------------- exec_code_working_dir = '../src' exec_code_source_folders = ['../src', '../tests'] @@ -237,7 +241,6 @@ def log(msg: str): autodoc_member_order = 'bysource' autoclass_content = 'class' - # No config on member autodoc_pydantic_model_show_config_member = False autodoc_pydantic_model_show_config_summary = False @@ -255,9 +258,6 @@ def log(msg: str): autodoc_pydantic_field_list_validators = False autodoc_pydantic_field_swap_name_and_alias = True - - - # ---------------------------------------------------------------------------------------------------------------------- # Post processing of default value @@ -339,7 +339,6 @@ def setup(app): 'python': ('https://docs.python.org/3', None) } - # Don't show warnings for missing python references since these are created via intersphinx during the RTD build if not IS_RTD_BUILD: nitpick_ignore_regex.append( diff --git a/docs/interface_habapp.rst b/docs/interface_habapp.rst index ba308d96..27c00785 100644 --- a/docs/interface_habapp.rst +++ b/docs/interface_habapp.rst @@ -122,7 +122,7 @@ And since it is just like a normal item triggering on changes etc. is possible, my_agg.aggregation_period(2 * 3600) # Use max as an aggregation function - my_agg.aggregation_func = max + my_agg.aggregation_func(max) The value of ``my_agg`` in the example will now always be the maximum of ``MyInputItem`` in the last two hours. diff --git a/requirements.txt b/requirements.txt index 65b8fb17..e3a28bec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,8 @@ # ----------------------------------------------------------------------------- # Packages for source formatting # ----------------------------------------------------------------------------- -pre-commit >= 3.3, < 3.4 +pre-commit >= 3.5, < 3.6 +ruff >= 0.1.5, < 0.2 # ----------------------------------------------------------------------------- # Packages for other developement tasks diff --git a/requirements_setup.txt b/requirements_setup.txt index cd8247b8..10aeb19d 100644 --- a/requirements_setup.txt +++ b/requirements_setup.txt @@ -1,21 +1,21 @@ -aiohttp == 3.8.5 -pydantic == 2.3.0 -msgspec == 0.18.2 +aiohttp == 3.8.6 +pydantic == 2.5.0 +msgspec == 0.18.4 pendulum == 2.1.2 bidict == 0.22.1 watchdog == 3.0.0 ujson == 5.8.0 -aiomqtt == 1.2.0 +aiomqtt == 1.2.1 immutables == 0.20 eascheduler == 0.1.11 -easyconfig == 0.3.0 -stack_data == 0.6.2 +easyconfig == 0.3.1 +stack_data == 0.6.3 colorama == 0.4.6 -voluptuous == 0.13.1 +voluptuous == 0.14.0 -typing-extensions == 4.7.1 +typing-extensions == 4.8.0 aiohttp-sse-client == 0.2.1 diff --git a/requirements_tests.txt b/requirements_tests.txt index 04e181d7..34746f45 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -6,6 +6,6 @@ # ----------------------------------------------------------------------------- # Packages to run source tests # ----------------------------------------------------------------------------- -packaging == 23.1 -pytest == 7.4.2 +packaging == 23.2 +pytest == 7.4.3 pytest-asyncio == 0.21.1 diff --git a/setup.py b/setup.py index 08cfd28e..3b5f6501 100644 --- a/setup.py +++ b/setup.py @@ -63,6 +63,7 @@ def load_req() -> typing.List[str]: "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Topic :: Home Automation" ], diff --git a/src/HABApp/__cmd_args__.py b/src/HABApp/__cmd_args__.py index 7e668717..9a79db7a 100644 --- a/src/HABApp/__cmd_args__.py +++ b/src/HABApp/__cmd_args__.py @@ -23,7 +23,8 @@ def get_uptime() -> float: lib.GetTickCount64.restype = ctypes.c_uint64 return lib.GetTickCount64() / 1000 - raise NotImplementedError(f'Not supported on {sys.platform}') + msg = f'Not supported on {sys.platform}' + raise NotImplementedError(msg) def parse_args(passed_args=None) -> argparse.Namespace: @@ -79,10 +80,10 @@ def find_config_folder(arg_config_path: typing.Optional[Path]) -> Path: # Nothing is specified, we try to find the config automatically check_path = [] try: - working_dir = Path(os.getcwd()) - check_path.append( working_dir / 'HABApp') - check_path.append( working_dir.with_name('HABApp')) - check_path.append( working_dir.parent.with_name('HABApp')) + working_dir = Path.cwd() + check_path.append(working_dir / 'HABApp') + check_path.append(working_dir.with_name('HABApp')) + check_path.append(working_dir.parent.with_name('HABApp')) except ValueError: # the ValueError gets raised if the working_dir or its parent is empty (e.g. c:\ or /) pass diff --git a/src/HABApp/core/const/loop.py b/src/HABApp/core/const/loop.py index 97eaaed2..08d660eb 100644 --- a/src/HABApp/core/const/loop.py +++ b/src/HABApp/core/const/loop.py @@ -2,7 +2,6 @@ import os import sys - # we can have subprocesses (https://docs.python.org/3/library/asyncio-platforms.html#subprocess-support-on-windows) # or mqtt support (https://github.com/sbtinstruments/aiomqtt#note-for-windows-users) # but not both. For testing, it makes sense to use mqtt support as a default diff --git a/src/HABApp/core/events/filter/event.py b/src/HABApp/core/events/filter/event.py index b013ff58..60c9b379 100644 --- a/src/HABApp/core/events/filter/event.py +++ b/src/HABApp/core/events/filter/event.py @@ -29,7 +29,8 @@ def __init__(self, event_class: HINT_ANY_CLASS, **kwargs): continue if arg not in type_hints: - raise AttributeError(f'Filter attribute "{arg}" does not exist for "{event_class.__name__}"') + msg = f'Filter attribute "{arg}" does not exist for "{event_class.__name__}"' + raise AttributeError(msg) if self.attr_name1 is None: self.attr_name1 = arg diff --git a/src/HABApp/core/events/filter/groups.py b/src/HABApp/core/events/filter/groups.py index bc5f5b25..9e567489 100644 --- a/src/HABApp/core/events/filter/groups.py +++ b/src/HABApp/core/events/filter/groups.py @@ -18,10 +18,7 @@ class OrFilterGroup(EventFilterBaseGroup): """Only one child filter has to match""" def trigger(self, event: Any) -> bool: - for f in self.filters: - if f.trigger(event): - return True - return False + return any(f.trigger(event) for f in self.filters) def describe(self) -> str: objs = [f.describe() for f in self.filters] @@ -32,10 +29,7 @@ class AndFilterGroup(EventFilterBaseGroup): """All child filters have to match""" def trigger(self, event: Any) -> bool: - for f in self.filters: - if not f.trigger(event): - return False - return True + return all(f.trigger(event) for f in self.filters) def describe(self) -> str: objs = [f.describe() for f in self.filters] diff --git a/src/HABApp/core/internals/wrapped_function/wrapped_async.py b/src/HABApp/core/internals/wrapped_function/wrapped_async.py index 40438d08..e3b3916f 100644 --- a/src/HABApp/core/internals/wrapped_function/wrapped_async.py +++ b/src/HABApp/core/internals/wrapped_function/wrapped_async.py @@ -4,6 +4,7 @@ from HABApp.core.asyncio import async_context, create_task from HABApp.core.const.hints import HINT_FUNC_ASYNC from HABApp.core.internals import Context + from .base import WrappedFunctionBase diff --git a/src/HABApp/core/items/item.py b/src/HABApp/core/items/item.py index 6e9da4a2..12045b08 100644 --- a/src/HABApp/core/items/item.py +++ b/src/HABApp/core/items/item.py @@ -1,5 +1,5 @@ from HABApp.core.errors import ItemNotFoundException -from HABApp.core.internals import uses_item_registry, uses_get_item +from HABApp.core.internals import uses_get_item, uses_item_registry from HABApp.core.items import BaseValueItem get_item = uses_get_item() diff --git a/src/HABApp/mqtt/connection/handler.py b/src/HABApp/mqtt/connection/handler.py index 4259ac3a..ebb63b94 100644 --- a/src/HABApp/mqtt/connection/handler.py +++ b/src/HABApp/mqtt/connection/handler.py @@ -61,7 +61,6 @@ async def on_setup(self, connection: MqttConnection): # clean_session=False ) - # noinspection PyProtectedMember async def on_connecting(self, connection: MqttConnection, context: CONTEXT_TYPE): assert context is not None @@ -73,9 +72,6 @@ async def on_disconnected(self, connection: MqttConnection, context: CONTEXT_TYP assert context is not None connection.log.info('Disconnected') - # remove this check when https://github.com/sbtinstruments/aiomqtt/pull/249 gets merged - if not context._lock.locked(): - await context._lock.acquire() await context.__aexit__(None, None, None) diff --git a/src/HABApp/mqtt/connection/subscribe.py b/src/HABApp/mqtt/connection/subscribe.py index 338be40a..9b3c5e78 100644 --- a/src/HABApp/mqtt/connection/subscribe.py +++ b/src/HABApp/mqtt/connection/subscribe.py @@ -1,19 +1,21 @@ from __future__ import annotations -from typing import Any, Iterable +from typing import TYPE_CHECKING, Any, Iterable import HABApp from HABApp.config import CONFIG -from HABApp.config.models.mqtt import QOS from HABApp.core.asyncio import run_func_from_async from HABApp.core.errors import ItemNotFoundException -from HABApp.core.internals import uses_post_event, uses_get_item, uses_item_registry +from HABApp.core.internals import uses_get_item, uses_item_registry, uses_post_event from HABApp.core.lib import SingleTask from HABApp.core.wrapper import process_exception from HABApp.mqtt.connection.connection import MqttPlugin from HABApp.mqtt.events import MqttValueChangeEvent, MqttValueUpdateEvent from HABApp.mqtt.mqtt_payload import get_msg_payload +if TYPE_CHECKING: + from HABApp.config.models.mqtt import QOS + SUBSCRIBE_CFG = CONFIG.mqtt.subscribe diff --git a/src/HABApp/mqtt/items/mqtt_item.py b/src/HABApp/mqtt/items/mqtt_item.py index a38c7cd1..64ba6b4b 100644 --- a/src/HABApp/mqtt/items/mqtt_item.py +++ b/src/HABApp/mqtt/items/mqtt_item.py @@ -1,3 +1,5 @@ +from typing import Optional + from HABApp.core.errors import ItemNotFoundException from HABApp.core.internals import uses_get_item, uses_item_registry from HABApp.core.items import BaseValueItem @@ -33,7 +35,7 @@ def get_create_item(cls, name: str, initial_value=None) -> 'MqttItem': assert isinstance(item, cls), f'{cls} != {type(item)}' return item - def publish(self, payload, qos: int = None, retain: bool = None): + def publish(self, payload, qos: Optional[int] = None, retain: Optional[bool] = None): """ Publish the payload under the topic from the item. diff --git a/src/HABApp/mqtt/items/mqtt_pair_item.py b/src/HABApp/mqtt/items/mqtt_pair_item.py index b7de2760..9880d3fb 100644 --- a/src/HABApp/mqtt/items/mqtt_pair_item.py +++ b/src/HABApp/mqtt/items/mqtt_pair_item.py @@ -3,6 +3,7 @@ from HABApp.core.errors import ItemNotFoundException from HABApp.core.internals import uses_item_registry from HABApp.mqtt.interface_sync import publish + from . import MqttBaseItem Items = uses_item_registry() @@ -14,7 +15,8 @@ def build_write_topic(read_topic: str) -> Optional[str]: parts.insert(-1, 'set') return '/'.join(parts) - raise ValueError(f'Can not build write topic for "{read_topic}"') + msg = f'Can not build write topic for "{read_topic}"' + raise ValueError(msg) class MqttPairItem(MqttBaseItem): @@ -46,11 +48,11 @@ def get_create_item(cls, name: str, write_topic: Optional[str] = None, initial_v assert isinstance(item, cls), f'{cls} != {type(item)}' return item - def __init__(self, name: str, initial_value=None, write_topic: str = None): + def __init__(self, name: str, initial_value=None, write_topic: Optional[str] = None): super().__init__(name, initial_value) self.write_topic: str = write_topic - def publish(self, payload, qos: int = None, retain: bool = None): + def publish(self, payload, qos: Optional[int] = None, retain: Optional[bool] = None): """ Publish the payload under the write topic from the item. diff --git a/src/HABApp/mqtt/mqtt_interface.py b/src/HABApp/mqtt/mqtt_interface.py index c7a40100..9b287cf8 100644 --- a/src/HABApp/mqtt/mqtt_interface.py +++ b/src/HABApp/mqtt/mqtt_interface.py @@ -3,17 +3,20 @@ import paho.mqtt.client as mqtt import HABApp +from HABApp.core.const.json import dump_json from HABApp.mqtt.connection.mqtt_connection import STATUS, log -from ..core.const.json import dump_json def __is_connected() -> bool: if STATUS.connected: return True - raise ConnectionError('Mqtt client not connected') + msg = 'Mqtt client not connected' + raise ConnectionError(msg) -def publish(topic: str, payload: typing.Any, qos: int = None, retain: bool = None) -> int: + +def publish(topic: str, payload: typing.Any, + qos: typing.Optional[int] = None, retain: typing.Optional[bool] = None) -> int: """ Publish a value under a certain topic. If qos and/or retain is not set the value from the configuration file will be used. @@ -51,7 +54,7 @@ def publish(topic: str, payload: typing.Any, qos: int = None, retain: bool = Non return info -def subscribe(topic: str, qos: int = None) -> int: +def subscribe(topic: str, qos: typing.Optional[int] = None) -> int: """ Subscribe to a MQTT topic. Note that subscriptions made this way are volatile and will only remain until the next disconnect. diff --git a/src/HABApp/openhab/connection/connection.py b/src/HABApp/openhab/connection/connection.py index 1067fee5..00a3d825 100644 --- a/src/HABApp/openhab/connection/connection.py +++ b/src/HABApp/openhab/connection/connection.py @@ -1,12 +1,12 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Optional, Any, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional import aiohttp import HABApp -from HABApp.core.connections import BaseConnection, Connections, ConnectionStateToEventBusPlugin, AutoReconnectPlugin +from HABApp.core.connections import AutoReconnectPlugin, BaseConnection, Connections, ConnectionStateToEventBusPlugin from HABApp.core.const.const import PYTHON_310 from HABApp.core.items.base_valueitem import datetime diff --git a/src/HABApp/openhab/connection/handler/func_async.py b/src/HABApp/openhab/connection/handler/func_async.py index b178383c..6f0dbbc9 100644 --- a/src/HABApp/openhab/connection/handler/func_async.py +++ b/src/HABApp/openhab/connection/handler/func_async.py @@ -142,7 +142,7 @@ async def async_create_item(item_type: str, name: str, if ret.status == 404: raise ItemNotFoundError.from_name(name) - elif ret.status == 405: + if ret.status == 405: raise ItemNotEditableError.from_name(name) return ret.status < 300 @@ -228,10 +228,11 @@ async def async_set_thing_enabled(thing: str | ItemRegistryItem, enabled: bool): if ret.status == 404: raise ThingNotFoundError.from_uid(thing) - elif ret.status == 409: + if ret.status == 409: raise ThingNotEditableError.from_uid(thing) - elif ret.status >= 300: - raise ValueError('Something went wrong') + if ret.status >= 300: + msg = 'Something went wrong' + raise ValueError(msg) return ret.status @@ -289,7 +290,9 @@ async def async_get_link(item: str | ItemRegistryItem, channel: str) -> ItemChan if resp.status == 404: raise LinkNotFoundError.from_names(item, channel) - raise LinkRequestError('Unexpected error') + + msg = 'Unexpected error' + raise LinkRequestError(msg) async def async_create_link( @@ -314,7 +317,9 @@ async def async_create_link( if resp.status == 405: LinkNotEditableError.from_names(item, channel) - raise LinkRequestError('Unexpected error') + + msg = 'Unexpected error' + raise LinkRequestError(msg) async def async_remove_link(item: str | ItemRegistryItem, channel: str): @@ -328,7 +333,9 @@ async def async_remove_link(item: str | ItemRegistryItem, channel: str): raise LinkNotFoundError.from_names(item, channel) if resp.status == 405: LinkNotEditableError.from_names(item, channel) - raise LinkRequestError('Unexpected error') + + msg = 'Unexpected error' + raise LinkRequestError(msg) # ---------------------------------------------------------------------------------------------------------------------- diff --git a/src/HABApp/openhab/connection/handler/handler.py b/src/HABApp/openhab/connection/handler/handler.py index 569c2d23..2bba83e7 100644 --- a/src/HABApp/openhab/connection/handler/handler.py +++ b/src/HABApp/openhab/connection/handler/handler.py @@ -178,7 +178,7 @@ async def on_connecting(self, connection: OpenhabConnection): # during startup we get OpenhabCredentialsInvalidError even though credentials are correct except (OpenhabDisconnectedError, OpenhabCredentialsInvalidError): connection.set_error() - raise AlreadyHandledException() + raise AlreadyHandledException() from None HANDLER = ConnectionHandler() diff --git a/src/HABApp/openhab/connection/plugins/load_transformations.py b/src/HABApp/openhab/connection/plugins/load_transformations.py index 0b55eeb3..f1a69b28 100644 --- a/src/HABApp/openhab/connection/plugins/load_transformations.py +++ b/src/HABApp/openhab/connection/plugins/load_transformations.py @@ -6,7 +6,7 @@ from HABApp.openhab.connection.connection import OpenhabConnection, OpenhabContext from HABApp.openhab.connection.handler.func_async import async_get_transformations from HABApp.openhab.transformations._map import MAP_REGISTRY -from HABApp.openhab.transformations.base import log, TransformationRegistryBase +from HABApp.openhab.transformations.base import TransformationRegistryBase, log Items = uses_item_registry() diff --git a/src/HABApp/openhab/connection/plugins/overview_broken_links.py b/src/HABApp/openhab/connection/plugins/overview_broken_links.py index e2f68a3f..e2e00e97 100644 --- a/src/HABApp/openhab/connection/plugins/overview_broken_links.py +++ b/src/HABApp/openhab/connection/plugins/overview_broken_links.py @@ -8,7 +8,7 @@ from HABApp.core.internals import uses_item_registry from HABApp.core.logger import log_warning from HABApp.openhab.connection.connection import OpenhabConnection -from HABApp.openhab.connection.handler.func_async import async_get_things, async_get_links +from HABApp.openhab.connection.handler.func_async import async_get_links, async_get_things PING_CONFIG: Final = CONFIG.openhab.ping diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py b/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py index 66defc10..51a2a6df 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py @@ -82,7 +82,8 @@ def validate_item_type(cls, v): try: return {k.lower(): k for k in ITEM_TYPES}[v.lower()] except KeyError: - raise ValueError(f'Must be one of {", ".join(ITEM_TYPES)}') + msg = f'Must be one of {", ".join(ITEM_TYPES)}' + raise ValueError(msg) from None @field_validator('metadata', mode='before') def make_meta_cfg(cls, v): diff --git a/src/HABApp/openhab/definitions/rest/items.py b/src/HABApp/openhab/definitions/rest/items.py index b9218d58..8a7f3896 100644 --- a/src/HABApp/openhab/definitions/rest/items.py +++ b/src/HABApp/openhab/definitions/rest/items.py @@ -1,4 +1,4 @@ -from typing import Optional, Union, List, Any, Dict +from typing import Any, Dict, List, Optional, Union from msgspec import Struct, field diff --git a/src/HABApp/openhab/definitions/rest/root.py b/src/HABApp/openhab/definitions/rest/root.py index 72e8496b..f235f8dd 100644 --- a/src/HABApp/openhab/definitions/rest/root.py +++ b/src/HABApp/openhab/definitions/rest/root.py @@ -2,7 +2,6 @@ from msgspec import Struct - # https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/beans/RootBean.java diff --git a/src/HABApp/openhab/items/contact_item.py b/src/HABApp/openhab/items/contact_item.py index 29fd0dca..be01e1d5 100644 --- a/src/HABApp/openhab/items/contact_item.py +++ b/src/HABApp/openhab/items/contact_item.py @@ -53,8 +53,8 @@ def is_closed(self) -> bool: return self.value == CLOSED def oh_send_command(self, value: Any = MISSING): - raise SendCommandNotSupported(f'{self.__class__.__name__} does not support send command! ' - 'See openHAB documentation for details.') + msg = f'{self.__class__.__name__} does not support send command! See openHAB documentation for details.' + raise SendCommandNotSupported(msg) def open(self): """Post an update to the item with the open value""" @@ -70,9 +70,11 @@ def __str__(self): def __eq__(self, other): if isinstance(other, ContactItem): return self.value == other.value - elif isinstance(other, str): + + if isinstance(other, str): return self.value == other - elif isinstance(other, int): + + if isinstance(other, int): if other and self.is_open(): return True if not other and self.is_closed(): diff --git a/src/HABApp/openhab/map_items.py b/src/HABApp/openhab/map_items.py index 9a505811..d08ed681 100644 --- a/src/HABApp/openhab/map_items.py +++ b/src/HABApp/openhab/map_items.py @@ -62,7 +62,8 @@ def map_item(name: str, type: str, value: Optional[str], if cls is not None: return cls.from_oh(name, value, label=label, tags=tags, groups=groups, metadata=meta) - raise ValueError(f'Unknown openHAB type: {type} for {name}') + msg = f'Unknown openHAB type: {type} for {name}' + raise ValueError(msg) except Exception as e: process_exception('map_items', e, logger=log) diff --git a/src/HABApp/parameters/parameter.py b/src/HABApp/parameters/parameter.py index 91c762a5..5879b1a0 100644 --- a/src/HABApp/parameters/parameter.py +++ b/src/HABApp/parameters/parameter.py @@ -1,3 +1,7 @@ +# ruff: noqa: TRY003, EM101 +# EM101 Exception must not use a string literal, assign to variable first +# TRY003 Avoid specifying long messages outside the exception class + import typing from math import ceil, floor diff --git a/src/HABApp/rule/interfaces/rule_subprocess.py b/src/HABApp/rule/interfaces/rule_subprocess.py index 863bd0fc..aae902ac 100644 --- a/src/HABApp/rule/interfaces/rule_subprocess.py +++ b/src/HABApp/rule/interfaces/rule_subprocess.py @@ -95,8 +95,8 @@ def __repr__(self): def __eq__(self, other): if isinstance(other, FinishedProcessInfo): return self.returncode == other.returncode and self.stdout == other.stdout and self.stderr == other.stderr - else: - return NotImplementedError + + return NotImplementedError HINT_PROCESS_CB_FULL: TypeAlias = Callable[[FinishedProcessInfo], Any] diff --git a/src/HABApp/rule/rule.py b/src/HABApp/rule/rule.py index 9e9a39c5..0b6d6161 100644 --- a/src/HABApp/rule/rule.py +++ b/src/HABApp/rule/rule.py @@ -3,7 +3,7 @@ import sys import warnings from pathlib import Path -from typing import Iterable, Union, Any, Optional, Tuple, Pattern, List, overload, Literal, TypeVar, Callable, Final +from typing import Any, Callable, Final, Iterable, List, Literal, Optional, Pattern, Tuple, TypeVar, Union, overload import HABApp import HABApp.core @@ -13,15 +13,28 @@ from HABApp.core.asyncio import create_task from HABApp.core.const.const import PYTHON_310 from HABApp.core.const.hints import HINT_EVENT_CALLBACK -from HABApp.core.internals import HINT_EVENT_FILTER_OBJ, HINT_EVENT_BUS_LISTENER, ContextProvidingObj, \ - uses_post_event, EventFilterBase, uses_item_registry, ContextBoundEventBusListener -from HABApp.core.internals import wrap_func -from HABApp.core.items import BaseItem, HINT_ITEM_OBJ, HINT_TYPE_ITEM_OBJ, BaseValueItem +from HABApp.core.internals import ( + HINT_EVENT_BUS_LISTENER, + HINT_EVENT_FILTER_OBJ, + ContextBoundEventBusListener, + ContextProvidingObj, + EventFilterBase, + uses_item_registry, + uses_post_event, + wrap_func, +) +from HABApp.core.items import HINT_ITEM_OBJ, HINT_TYPE_ITEM_OBJ, BaseItem, BaseValueItem from HABApp.rule import interfaces from HABApp.rule.scheduler import HABAppSchedulerView as _HABAppSchedulerView + from .interfaces import async_subprocess_exec -from .interfaces.rule_subprocess import build_exec_params, HINT_PYTHON_PATH, HINT_EXEC_ARGS, HINT_PROCESS_CB_SIMPLE, \ - HINT_PROCESS_CB_FULL +from .interfaces.rule_subprocess import ( + HINT_EXEC_ARGS, + HINT_PROCESS_CB_FULL, + HINT_PROCESS_CB_SIMPLE, + HINT_PYTHON_PATH, + build_exec_params, +) from .rule_hook import get_rule_hook as _get_rule_hook if PYTHON_310: @@ -249,11 +262,11 @@ def get_rule(self, rule_name: str) -> 'Union[Rule, List[Rule]]': @staticmethod def get_items(type: Union[Tuple[HINT_TYPE_ITEM_OBJ, ...], HINT_TYPE_ITEM_OBJ] = None, - name: Union[str, Pattern[str]] = None, - tags: Union[str, Iterable[str]] = None, - groups: Union[str, Iterable[str]] = None, - metadata: Union[str, Pattern[str]] = None, - metadata_value: Union[str, Pattern[str]] = None, + name: Union[str, Pattern[str], None] = None, + tags: Union[str, Iterable[str], None] = None, + groups: Union[str, Iterable[str], None] = None, + metadata: Union[str, Pattern[str], None] = None, + metadata_value: Union[str, Pattern[str], None] = None, ) -> Union[List[HINT_ITEM_OBJ], List[BaseItem]]: """Search the HABApp item registry and return the found items. diff --git a/src/HABApp/rule_ctx/rule_ctx.py b/src/HABApp/rule_ctx/rule_ctx.py index 871c8cf4..1558c377 100644 --- a/src/HABApp/rule_ctx/rule_ctx.py +++ b/src/HABApp/rule_ctx/rule_ctx.py @@ -1,10 +1,9 @@ import logging -from typing import Optional, Callable +from typing import Callable, Optional import HABApp from HABApp.core.const.topics import ALL_TOPICS -from HABApp.core.internals import Context, uses_item_registry, HINT_EVENT_BUS_LISTENER -from HABApp.core.internals import uses_event_bus +from HABApp.core.internals import HINT_EVENT_BUS_LISTENER, Context, uses_event_bus, uses_item_registry from HABApp.core.internals.event_bus import EventBusBaseListener event_bus = uses_event_bus() diff --git a/src/HABApp/util/multimode/item.py b/src/HABApp/util/multimode/item.py index 17d0774b..16a98b61 100644 --- a/src/HABApp/util/multimode/item.py +++ b/src/HABApp/util/multimode/item.py @@ -1,8 +1,9 @@ from threading import Lock -from typing import Optional, Dict, Any, Tuple, List +from typing import Any, Dict, List, Optional, Tuple from HABApp.core.const import MISSING from HABApp.core.items import Item + from .mode_base import HINT_BASE_MODE, BaseMode LOCK = Lock() @@ -117,7 +118,8 @@ def get_mode(self, name: str) -> HINT_BASE_MODE: try: return self.__values_by_name[name.lower()] except KeyError: - raise KeyError(f'Unknown mode "{name}"! Available: {", ".join(self.__values_by_name.keys())}') from None + msg = f'Unknown mode "{name}"! Available: {", ".join(self.__values_by_name.keys())}' + raise KeyError(msg) from None def calculate_value(self) -> Any: """Recalculate the value. If the new value is not ``MISSING`` the calculated value will be set as the item @@ -128,7 +130,7 @@ def calculate_value(self) -> Any: # recalculate value new_value = MISSING - for priority, child in self.__values_by_prio.items(): + for child in self.__values_by_prio.values(): new_value = child.calculate_value(new_value) # if nothing is set try the default diff --git a/src/HABApp/util/multimode/mode_switch.py b/src/HABApp/util/multimode/mode_switch.py index 8eb29f68..7e96ee67 100644 --- a/src/HABApp/util/multimode/mode_switch.py +++ b/src/HABApp/util/multimode/mode_switch.py @@ -64,7 +64,8 @@ def __init__(self, name: str, # prevent direct calling def set_enabled(self, value: bool, only_on_change: bool = False): """""" # Empty docstring so this function doesn't show up in Sphinx - raise PermissionError('Enabled is controlled through the switch item!') + msg = 'Enabled is controlled through the switch item!' + raise PermissionError(msg) def __switch_changed(self, event): self.__set_enable(event.value == ('ON' if not self.__invert_switch else 'OFF'))