From b24f93c5dfb9aa8efe788b8431a506b9c91b2c24 Mon Sep 17 00:00:00 2001 From: spacemanspiff2007 <10754716+spacemanspiff2007@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:05:33 +0200 Subject: [PATCH] 24.08.0 (#450) - Fixed an issue with thing re-sync - Updated number parsing logic - ``ItemTimeSeriesEvent`` gets ignored - Removed verbose error messages when openHAB server disconnects - Updated dependencies - Reformatted some files --- .github/workflows/publish-dockerhub.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/run-tox.yml | 2 +- .pre-commit-config.yaml | 27 ++-- .ruff.toml | 8 +- docs/conf.py | 2 +- docs/requirements.txt | 6 +- readme.md | 8 + requirements.txt | 2 +- requirements_setup.txt | 16 +- requirements_tests.txt | 6 +- .../lib/HABAppTests/event_waiter.py | 2 +- .../lib/HABAppTests/item_waiter.py | 2 +- .../lib/HABAppTests/openhab_tmp_item.py | 6 +- run/conf_testing/lib/HABAppTests/test_data.py | 3 +- .../lib/HABAppTests/test_rule/_rule_ids.py | 2 + .../test_rule/test_case/test_result.py | 1 + .../lib/HABAppTests/test_rule/test_rule.py | 9 +- run/conf_testing/lib/HABAppTests/utils.py | 2 +- .../rules/habapp/test_event_listener.py | 6 +- .../rules/habapp/test_group_listener.py | 6 +- run/conf_testing/rules/habapp/test_habapp.py | 12 +- .../rules/habapp/test_parameter_files.py | 4 +- .../rules/habapp/test_rule_context.py | 3 +- .../rules/habapp/test_scheduler.py | 4 +- .../rules/habapp/test_util_fade.py | 3 +- run/conf_testing/rules/habapp/test_utils.py | 4 +- .../rules/openhab/test_event_types.py | 13 +- run/conf_testing/rules/openhab/test_groups.py | 7 +- .../rules/openhab/test_habapp_internals.py | 10 +- .../rules/openhab/test_interface.py | 13 +- .../rules/openhab/test_interface_links.py | 39 ++--- .../rules/openhab/test_item_change.py | 5 +- .../rules/openhab/test_item_funcs.py | 19 ++- run/conf_testing/rules/openhab/test_items.py | 4 +- run/conf_testing/rules/openhab/test_links.py | 3 +- .../rules/openhab/test_max_sse_msg_size.py | 4 +- run/conf_testing/rules/openhab/test_things.py | 3 +- .../rules/openhab/test_transformations.py | 4 +- run/conf_testing/rules/openhab_bugs.py | 14 +- setup.py | 36 ++--- src/HABApp/__check_dependency_packages__.py | 1 + src/HABApp/__main__.py | 6 +- src/HABApp/__setup_packages__.py | 3 +- src/HABApp/__splash_screen__.py | 10 +- src/HABApp/__version__.py | 2 +- src/HABApp/config/config.py | 2 + src/HABApp/config/loader.py | 8 +- src/HABApp/config/logging/config.py | 6 +- src/HABApp/config/logging/default_logfile.py | 8 +- src/HABApp/config/logging/handler.py | 2 +- src/HABApp/config/logging/queue_handler.py | 8 +- src/HABApp/config/models/application.py | 7 +- src/HABApp/config/models/directories.py | 1 + src/HABApp/config/models/habapp.py | 10 +- src/HABApp/config/models/location.py | 2 +- src/HABApp/core/asyncio.py | 2 +- src/HABApp/core/connections/_definitions.py | 1 + .../core/connections/base_connection.py | 11 +- src/HABApp/core/connections/base_plugin.py | 5 +- .../core/connections/connection_task.py | 2 +- .../core/connections/plugin_callback.py | 5 +- .../connections/plugins/auto_reconnect.py | 2 +- src/HABApp/core/const/json.py | 1 + src/HABApp/core/const/log.py | 1 + src/HABApp/core/const/loop.py | 5 +- src/HABApp/core/const/yml.py | 1 + src/HABApp/core/events/events.py | 2 +- src/HABApp/core/events/filter/groups.py | 2 +- .../core/events/filter/habapp_events.py | 2 +- src/HABApp/core/files/file/file.py | 6 +- src/HABApp/core/files/file/file_types.py | 5 +- src/HABApp/core/files/file/properties.py | 2 +- src/HABApp/core/files/folders/folders.py | 9 +- src/HABApp/core/files/manager/files.py | 3 +- .../core/files/manager/listen_events.py | 5 +- src/HABApp/core/files/manager/worker.py | 2 + src/HABApp/core/files/watcher/base_watcher.py | 2 +- src/HABApp/core/files/watcher/file_watcher.py | 9 +- .../core/files/watcher/folder_watcher.py | 3 +- src/HABApp/core/internals/context/context.py | 3 +- .../core/internals/context/get_context.py | 7 +- .../core/internals/event_bus/event_bus.py | 7 +- .../core/internals/event_bus_listener.py | 3 +- src/HABApp/core/internals/event_filter.py | 2 +- src/HABApp/core/internals/proxy/proxies.py | 4 +- src/HABApp/core/internals/proxy/proxy_obj.py | 5 +- .../core/internals/wrapped_function/base.py | 3 +- .../wrapped_function/wrapped_sync.py | 3 +- .../wrapped_function/wrapped_thread.py | 10 +- .../internals/wrapped_function/wrapper.py | 17 ++- src/HABApp/core/items/base_item_times.py | 6 +- src/HABApp/core/items/base_valueitem.py | 1 + src/HABApp/core/items/item.py | 1 + src/HABApp/core/items/item_aggregation.py | 14 +- src/HABApp/core/items/item_color.py | 1 + src/HABApp/core/items/tmp_data.py | 2 + src/HABApp/core/lib/exceptions/format.py | 5 +- .../core/lib/exceptions/format_frame.py | 5 +- .../core/lib/exceptions/format_frame_vars.py | 14 +- src/HABApp/core/lib/funcs.py | 5 +- .../core/lib/parameters/positive_time_diff.py | 1 + src/HABApp/core/lib/pending_future.py | 4 +- src/HABApp/core/lib/priority_list.py | 2 +- src/HABApp/core/lib/single_task.py | 5 +- src/HABApp/core/logger.py | 4 +- src/HABApp/core/wrapper.py | 4 +- src/HABApp/mqtt/connection/connection.py | 5 +- src/HABApp/mqtt/connection/handler.py | 7 +- src/HABApp/mqtt/connection/subscribe.py | 1 + src/HABApp/mqtt/items/mqtt_item.py | 1 + src/HABApp/mqtt/items/mqtt_pair_item.py | 1 + src/HABApp/mqtt/mqtt_payload.py | 5 +- src/HABApp/openhab/connection/connection.py | 21 ++- .../openhab/connection/handler/func_async.py | 38 +++-- .../openhab/connection/handler/func_sync.py | 24 ++- .../openhab/connection/handler/handler.py | 4 +- .../openhab/connection/plugins/events_sse.py | 4 +- .../openhab/connection/plugins/load_items.py | 17 ++- .../plugins/overview_broken_links.py | 1 + .../connection/plugins/overview_things.py | 1 + src/HABApp/openhab/connection/plugins/ping.py | 3 +- .../connection/plugins/plugin_things/_log.py | 1 + .../plugins/plugin_things/cfg_validator.py | 8 +- .../plugin_things/file_writer/formatter.py | 3 +- .../file_writer/formatter_builder.py | 3 +- .../plugin_things/file_writer/writer.py | 13 +- .../plugins/plugin_things/filters.py | 4 +- .../plugins/plugin_things/item_worker.py | 17 ++- .../plugins/plugin_things/plugin_things.py | 15 +- .../plugins/plugin_things/str_builder.py | 4 +- .../plugins/plugin_things/thing_config.py | 1 + .../plugins/plugin_things/thing_worker.py | 1 + .../openhab/definitions/helpers/log_table.py | 2 +- .../definitions/helpers/persistence_data.py | 16 +- .../openhab/definitions/rest/habapp_data.py | 2 +- .../openhab/definitions/rest/persistence.py | 2 +- src/HABApp/openhab/definitions/rest/root.py | 1 + .../openhab/definitions/rest/systeminfo.py | 2 +- src/HABApp/openhab/definitions/rest/things.py | 5 +- src/HABApp/openhab/definitions/things.py | 3 +- src/HABApp/openhab/definitions/values.py | 17 +-- src/HABApp/openhab/events/event_filters.py | 5 +- src/HABApp/openhab/events/item_events.py | 13 +- src/HABApp/openhab/events/thing_events.py | 6 +- src/HABApp/openhab/interface_async.py | 25 ++- src/HABApp/openhab/interface_sync.py | 26 ++-- src/HABApp/openhab/item_to_reg.py | 12 +- src/HABApp/openhab/items/color_item.py | 2 + src/HABApp/openhab/items/commands.py | 17 ++- src/HABApp/openhab/items/contact_item.py | 16 +- src/HABApp/openhab/items/datetime_item.py | 5 +- src/HABApp/openhab/items/dimmer_item.py | 11 +- src/HABApp/openhab/items/image_item.py | 6 +- src/HABApp/openhab/items/number_item.py | 7 +- .../openhab/items/rollershutter_item.py | 11 +- src/HABApp/openhab/items/string_item.py | 5 +- src/HABApp/openhab/items/switch_item.py | 6 +- src/HABApp/openhab/items/thing_item.py | 4 +- src/HABApp/openhab/items/tuple_items.py | 5 +- src/HABApp/openhab/map_events.py | 31 +++- src/HABApp/openhab/map_items.py | 15 +- src/HABApp/openhab/map_values.py | 28 ++-- .../openhab/transformations/_map/registry.py | 4 +- src/HABApp/openhab/transformations/base.py | 2 +- src/HABApp/parameters/parameter_files.py | 2 + src/HABApp/parameters/parameters.py | 3 +- src/HABApp/rule/interfaces/_http.py | 2 +- src/HABApp/rule/interfaces/http_interface.py | 2 +- src/HABApp/rule/rule.py | 1 + src/HABApp/rule/rule_hook.py | 2 + src/HABApp/rule/scheduler/executor.py | 3 +- .../rule/scheduler/habappschedulerview.py | 18 +-- src/HABApp/rule/scheduler/scheduler.py | 5 +- src/HABApp/rule_ctx/rule_ctx.py | 3 +- .../rule_manager/benchmark/bench_file.py | 1 + .../rule_manager/benchmark/bench_habapp.py | 3 +- .../rule_manager/benchmark/bench_mqtt.py | 5 +- src/HABApp/rule_manager/benchmark/bench_oh.py | 2 + .../rule_manager/benchmark/bench_times.py | 2 +- src/HABApp/rule_manager/rule_file.py | 4 +- src/HABApp/runtime/runtime.py | 8 +- src/HABApp/runtime/shutdown.py | 2 +- src/HABApp/util/fade/fade.py | 6 +- src/HABApp/util/multimode/mode_base.py | 2 +- src/HABApp/util/multimode/mode_switch.py | 1 + src/HABApp/util/rate_limiter/limiter.py | 10 +- src/HABApp/util/rate_limiter/parser.py | 5 +- tests/conftest.py | 10 +- tests/helpers/event_bus.py | 2 +- tests/helpers/habapp_config.py | 3 +- tests/helpers/inspect/classes.py | 2 +- tests/helpers/inspect/docstr.py | 5 +- tests/helpers/inspect/habapp.py | 2 +- tests/helpers/inspect/module.py | 2 +- tests/helpers/log/log_collector.py | 10 +- tests/helpers/log/log_matcher.py | 4 +- tests/helpers/log/log_utils.py | 2 +- tests/helpers/parameters.py | 2 +- tests/helpers/parent_rule.py | 4 +- tests/helpers/sync_worker.py | 7 +- tests/helpers/traceback.py | 10 +- tests/rule_runner/rule_runner.py | 2 +- tests/rule_runner/test_rule_runner.py | 5 +- tests/test_all/test_items.py | 2 +- tests/test_config/test_platform.py | 3 +- tests/test_core/test_connections.py | 4 +- tests/test_core/test_context.py | 2 +- tests/test_core/test_event_bus.py | 2 +- .../test_events/test_core_filters.py | 12 +- .../test_files/test_file_dependencies.py | 2 +- .../test_files/test_file_properties.py | 28 ++-- tests/test_core/test_files/test_rel_name.py | 4 +- tests/test_core/test_item_registry.py | 2 +- tests/test_core/test_item_watch.py | 2 +- tests/test_core/test_items/test_item.py | 1 + tests/test_core/test_items/test_item_color.py | 6 +- .../test_items/test_item_interface.py | 2 +- tests/test_core/test_items/test_item_times.py | 12 +- .../test_lib/test_format_traceback.py | 73 ++++----- tests/test_core/test_lib/test_single_task.py | 2 +- tests/test_core/test_logger.py | 2 +- tests/test_core/test_wrapped_func.py | 6 +- tests/test_core/test_wrapper.py | 9 +- tests/test_docs.py | 2 +- tests/test_mqtt/test_interface.py | 2 +- tests/test_mqtt/test_mqtt_filters.py | 10 +- .../test_events/test_from_dict.py | 60 +++++--- .../test_events/test_oh_filters.py | 13 +- tests/test_openhab/test_helpers/test_table.py | 2 +- tests/test_openhab/test_interface_sync.py | 26 +++- tests/test_openhab/test_items/test_all.py | 23 ++- .../test_openhab/test_items/test_commands.py | 8 +- tests/test_openhab/test_items/test_image.py | 2 +- tests/test_openhab/test_items/test_mapping.py | 6 +- tests/test_openhab/test_items/test_thing.py | 8 +- tests/test_openhab/test_openhab_datatypes.py | 2 +- .../test_plugins/test_broken_links.py | 4 +- .../test_plugins/test_load_items.py | 70 ++++----- .../test_plugins/test_thing/test_errors.py | 26 ++-- .../test_thing/test_file_format.py | 4 +- .../test_file_writer/test_builder.py | 10 +- .../test_plugins/test_thing/test_thing_cfg.py | 16 +- tests/test_openhab/test_rest/test_grp_func.py | 10 +- tests/test_openhab/test_rest/test_items.py | 144 +++++++++--------- tests/test_openhab/test_rest/test_links.py | 15 +- tests/test_openhab/test_rest/test_things.py | 124 +++++++-------- .../test_transformations/test_map.py | 2 + tests/test_openhab/test_values.py | 15 +- tests/test_packages.py | 2 + tests/test_parameters/test_base.py | 6 +- tests/test_parameters/test_dict_parameter.py | 6 +- tests/test_parameters/test_parameter.py | 6 +- tests/test_rule/__exec_python_file.py | 1 + tests/test_rule/test_item_search.py | 2 +- tests/test_rule/test_process.py | 32 ++-- tests/test_rule/test_rule_factory.py | 4 +- tests/test_rule/test_rule_funcs.py | 9 +- tests/test_utils/test_functions.py | 4 +- tests/test_utils/test_multivalue.py | 5 +- tests/test_utils/test_rate_limiter.py | 2 +- tests/test_utils/test_threshold.py | 22 +-- 262 files changed, 1292 insertions(+), 894 deletions(-) diff --git a/.github/workflows/publish-dockerhub.yml b/.github/workflows/publish-dockerhub.yml index 01d70400..0d4e7d06 100644 --- a/.github/workflows/publish-dockerhub.yml +++ b/.github/workflows/publish-dockerhub.yml @@ -9,7 +9,7 @@ on: jobs: buildx: runs-on: ubuntu-latest - environment: release + environment: dockerhub steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 503f142a..b5eef206 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -7,7 +7,7 @@ jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI runs-on: ubuntu-latest - environment: release + environment: pypi permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write diff --git a/.github/workflows/run-tox.yml b/.github/workflows/run-tox.yml index 43fba253..888dc09c 100644 --- a/.github/workflows/run-tox.yml +++ b/.github/workflows/run-tox.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.11' - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 tests: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb3b23b6..290169f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-ast - id: check-builtin-literals @@ -14,24 +14,15 @@ repos: - id: trailing-whitespace -# - repo: https://github.com/pycqa/isort -# rev: 5.11.4 -# hooks: -# - id: isort -# name: isort (python) - - - - repo: https://github.com/PyCQA/flake8 - rev: '7.0.0' + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 hooks: - - id: flake8 - # additional_dependencies: - # - flake8-bugbear==23.1.14 - # - flake8-comprehensions==3.10.1 - # - flake8-pytest-style==1.6 - # - flake8-unused-arguments==0.0.12 - # - flake8-noqa==1.3 - # - pep8-naming==0.13.3 + - id: ruff + # I001 [*] Import block is un-sorted or un-formatted + # UP035 [*] Import from {target} instead: {names} + # Q000 [*] Double quote found but single quotes preferred + # Q001 [*] Double quote multiline found but single quotes preferred + args: [ "--select", "I001,UP035,Q000,Q001", "--fix"] - repo: https://github.com/pre-commit/pygrep-hooks diff --git a/.ruff.toml b/.ruff.toml index e44eb57d..37712318 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -5,14 +5,13 @@ 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", "src/__test_*.py" ] +[lint] select = [ "E", "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w "I", # https://docs.astral.sh/ruff/rules/#isort-i @@ -57,6 +56,11 @@ ignore = [ quote-style = "single" +# https://docs.astral.sh/ruff/settings/#lintflake8-quotes +[lint.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "single" + [lint.flake8-builtins] builtins-ignorelist = ["id", "input"] diff --git a/docs/conf.py b/docs/conf.py index 365ecc03..a9acc2fe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -264,7 +264,7 @@ def log(msg: str): # Post processing of default value regex_path = re.compile(r"^\w+Path\('([^']+)'\)") -assert regex_path.search('WindowsPath(\'lib\')').group(1) == 'lib' +assert regex_path.search("WindowsPath('lib')").group(1) == 'lib' regex_item = re.compile(r'(class \w+Item)\(.+\)') diff --git a/docs/requirements.txt b/docs/requirements.txt index ee2ceeab..9e8994d9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ # Packages required to build the documentation -sphinx == 7.2.6 -sphinx-autodoc-typehints == 2.0.0 +sphinx == 7.4.7 +sphinx-autodoc-typehints == 2.2.3 sphinx_rtd_theme == 2.0.0 sphinx-exec-code == 0.12 -autodoc_pydantic == 2.0.1 +autodoc_pydantic == 2.2.0 sphinx-copybutton == 0.5.2 diff --git a/readme.md b/readme.md index c73e6ac1..8337c0f6 100644 --- a/readme.md +++ b/readme.md @@ -127,6 +127,14 @@ MyOpenhabRule() ``` # Changelog +#### 24.08.0 (2024-08-01) +- Fixed an issue with thing re-sync +- Updated number parsing logic +- ``ItemTimeSeriesEvent`` gets ignored +- Removed verbose error messages when openHAB server disconnects +- Updated dependencies +- Reformatted some files + #### 24.02.0 (2024-02-14) - For openHAB >= 4.1 it's possible to wait for a minimum openHAB uptime before connecting (defaults to 60s) - Renamed config entry mqtt.connection.client_id to identifier (backwards compatible) diff --git a/requirements.txt b/requirements.txt index 1b803801..a1ae591c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ # Packages for source formatting # ----------------------------------------------------------------------------- pre-commit == 3.5.0 # 3.6.0 requires python >= 3.10 -ruff == 0.2.1 +ruff == 0.5.0 # ----------------------------------------------------------------------------- # Packages for other developement tasks diff --git a/requirements_setup.txt b/requirements_setup.txt index e610aeeb..21a20135 100644 --- a/requirements_setup.txt +++ b/requirements_setup.txt @@ -1,10 +1,10 @@ -aiohttp == 3.9.3 -pydantic == 2.6.1 +aiohttp == 3.10.0 +pydantic == 2.8.2 msgspec == 0.18.6 -bidict == 0.22.1 -watchdog == 4.0.0 -ujson == 5.9.0 -aiomqtt == 2.0.0 +bidict == 0.23.1 +watchdog == 4.0.1 +ujson == 5.10.0 +aiomqtt == 2.2.0 immutables == 0.20 eascheduler == 0.1.11 @@ -12,10 +12,12 @@ easyconfig == 0.3.2 pendulum == 2.1.2 stack_data == 0.6.3 colorama == 0.4.6 +fastnumbers == 5.1.0 + voluptuous == 0.14.2 -typing-extensions == 4.9.0 +typing-extensions == 4.12.2 aiohttp-sse-client == 0.2.1 diff --git a/requirements_tests.txt b/requirements_tests.txt index 3d25d953..e4ae1c6e 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -6,6 +6,6 @@ # ----------------------------------------------------------------------------- # Packages to run source tests # ----------------------------------------------------------------------------- -packaging == 23.2 -pytest == 7.4.4 -pytest-asyncio == 0.23.4 +packaging == 24.1 +pytest == 8.3.2 +pytest-asyncio == 0.23.8 diff --git a/run/conf_testing/lib/HABAppTests/event_waiter.py b/run/conf_testing/lib/HABAppTests/event_waiter.py index e4e4fe54..a5f2568e 100644 --- a/run/conf_testing/lib/HABAppTests/event_waiter.py +++ b/run/conf_testing/lib/HABAppTests/event_waiter.py @@ -57,7 +57,7 @@ def wait_for_event(self, **kwargs) -> EVENT_TYPE: time.sleep(0.02) if time.time() > start + self.timeout: - expected_values = "with " + ", ".join([f"{__k}={__v}" for __k, __v in kwargs.items()]) if kwargs else "" + expected_values = 'with ' + ', '.join([f'{__k}={__v}' for __k, __v in kwargs.items()]) if kwargs else '' msg = f'Timeout while waiting for {self.event_filter.describe()} for {self.name} {expected_values}' raise TestCaseFailed(msg) diff --git a/run/conf_testing/lib/HABAppTests/item_waiter.py b/run/conf_testing/lib/HABAppTests/item_waiter.py index d2ccbe7f..7228bfb1 100644 --- a/run/conf_testing/lib/HABAppTests/item_waiter.py +++ b/run/conf_testing/lib/HABAppTests/item_waiter.py @@ -35,7 +35,7 @@ def wait_for_attribs(self, **kwargs): f'{name:>{indent:d}s}: {get_equal_text(getattr(self.item, name), target)}' for name, target in kwargs.items() ] - failed_msg = "\n".join(failed) + failed_msg = '\n'.join(failed) msg = f'Timeout waiting for {self.item.name}!\n{failed_msg}' raise TestCaseFailed(msg) diff --git a/run/conf_testing/lib/HABAppTests/openhab_tmp_item.py b/run/conf_testing/lib/HABAppTests/openhab_tmp_item.py index ef7e3613..040a3120 100644 --- a/run/conf_testing/lib/HABAppTests/openhab_tmp_item.py +++ b/run/conf_testing/lib/HABAppTests/openhab_tmp_item.py @@ -50,7 +50,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def remove(self): HABApp.openhab.interface_sync.remove_item(self.name) - def _create(self, label="", category="", tags: List[str] = [], groups: List[str] = [], + def _create(self, label='', category='', tags: List[str] = [], groups: List[str] = [], group_type: str = '', group_function: str = '', group_function_params: List[str] = []): interface = HABApp.openhab.interface_sync @@ -58,7 +58,7 @@ def _create(self, label="", category="", tags: List[str] = [], groups: List[str] tags=tags, groups=groups, group_type=group_type, group_function=group_function, group_function_params=group_function_params) - def create_item(self, label="", category="", tags: List[str] = [], groups: List[str] = [], + def create_item(self, label='', category='', tags: List[str] = [], groups: List[str] = [], group_type: str = '', group_function: str = '', group_function_params: List[str] = []) -> HABApp.openhab.items.OpenhabItem: @@ -75,7 +75,7 @@ def create_item(self, label="", category="", tags: List[str] = [], groups: List[ return HABApp.openhab.items.OpenhabItem.get_item(self.name) - def modify(self, label="", category="", tags: List[str] = [], groups: List[str] = [], + def modify(self, label='', category='', tags: List[str] = [], groups: List[str] = [], group_type: str = '', group_function: str = '', group_function_params: List[str] = []): with EventWaiter(TOPIC_ITEMS, HABApp.core.events.EventFilter(HABApp.openhab.events.ItemUpdatedEvent)) as w: diff --git a/run/conf_testing/lib/HABAppTests/test_data.py b/run/conf_testing/lib/HABAppTests/test_data.py index 8d5ccc5d..bf64be86 100644 --- a/run/conf_testing/lib/HABAppTests/test_data.py +++ b/run/conf_testing/lib/HABAppTests/test_data.py @@ -2,6 +2,7 @@ import datetime import itertools + # we only support milliseconds on openHAB side now = datetime.datetime.now() now = now.replace(microsecond=now.microsecond // 1000 * 1000) @@ -17,7 +18,7 @@ 'Dimmer': [0, 100, 55.5], 'Location': [(1, 2, 3), (-1.1, 2.2, 3.3), (5, 6)], 'Number': [-111, 222, -13.13, 55.55], - 'Player': ["PLAY", "PAUSE", "REWIND", "FASTFORWARD"], + 'Player': ['PLAY', 'PAUSE', 'REWIND', 'FASTFORWARD'], 'Rollershutter': [0, 100, 30.5], 'String': ['A', 'B', 'C', '', 'öäüß'], 'Switch': ['ON', 'OFF'], diff --git a/run/conf_testing/lib/HABAppTests/test_rule/_rule_ids.py b/run/conf_testing/lib/HABAppTests/test_rule/_rule_ids.py index a1dbabbe..237b598a 100644 --- a/run/conf_testing/lib/HABAppTests/test_rule/_rule_ids.py +++ b/run/conf_testing/lib/HABAppTests/test_rule/_rule_ids.py @@ -2,8 +2,10 @@ import typing import HABAppTests + from ._rule_status import TestRuleStatus + LOCK = threading.Lock() diff --git a/run/conf_testing/lib/HABAppTests/test_rule/test_case/test_result.py b/run/conf_testing/lib/HABAppTests/test_rule/test_case/test_result.py index 3bb8793e..8e5f3eee 100644 --- a/run/conf_testing/lib/HABAppTests/test_rule/test_case/test_result.py +++ b/run/conf_testing/lib/HABAppTests/test_rule/test_case/test_result.py @@ -5,6 +5,7 @@ import HABApp from HABAppTests.errors import TestCaseFailed, TestCaseWarning + log = logging.getLogger('HABApp.Tests') diff --git a/run/conf_testing/lib/HABAppTests/test_rule/test_rule.py b/run/conf_testing/lib/HABAppTests/test_rule/test_rule.py index 4b124c80..171cd67d 100644 --- a/run/conf_testing/lib/HABAppTests/test_rule/test_rule.py +++ b/run/conf_testing/lib/HABAppTests/test_rule/test_rule.py @@ -1,13 +1,14 @@ import logging from pathlib import Path -from typing import Dict, Callable -from typing import List +from typing import Callable, Dict, List import HABApp -from HABAppTests.test_rule.test_case import TestResult, TestResultStatus, TestCase -from ._rule_ids import get_test_rules, get_next_id, test_rules_running +from HABAppTests.test_rule.test_case import TestCase, TestResult, TestResultStatus + +from ._rule_ids import get_next_id, get_test_rules, test_rules_running from ._rule_status import TestRuleStatus + log = logging.getLogger('HABApp.Tests') diff --git a/run/conf_testing/lib/HABAppTests/utils.py b/run/conf_testing/lib/HABAppTests/utils.py index c47d253e..04b81de9 100644 --- a/run/conf_testing/lib/HABAppTests/utils.py +++ b/run/conf_testing/lib/HABAppTests/utils.py @@ -43,7 +43,7 @@ def run_coro(coro: typing.Coroutine): def find_astro_sun_thing() -> str: items = HABApp.core.Items.get_items() for item in items: - if isinstance(item, Thing) and item.name.startswith("astro:sun"): + if isinstance(item, Thing) and item.name.startswith('astro:sun'): return item.name raise ValueError('No astro thing found!') diff --git a/run/conf_testing/rules/habapp/test_event_listener.py b/run/conf_testing/rules/habapp/test_event_listener.py index bc902954..8d357b93 100644 --- a/run/conf_testing/rules/habapp/test_event_listener.py +++ b/run/conf_testing/rules/habapp/test_event_listener.py @@ -1,10 +1,12 @@ import logging +from HABAppTests import TestBaseRule, get_random_name + from HABApp.core.events import ValueChangeEventFilter from HABApp.core.items import Item -from HABApp.util import EventListenerGroup -from HABAppTests import TestBaseRule, get_random_name from HABApp.rule_ctx import HABAppRuleContext +from HABApp.util import EventListenerGroup + log = logging.getLogger('HABApp.Tests.MultiMode') diff --git a/run/conf_testing/rules/habapp/test_group_listener.py b/run/conf_testing/rules/habapp/test_group_listener.py index 78cb433d..ffbf4486 100644 --- a/run/conf_testing/rules/habapp/test_group_listener.py +++ b/run/conf_testing/rules/habapp/test_group_listener.py @@ -1,11 +1,13 @@ import logging import time +from HABAppTests import TestBaseRule +from HABAppTests.errors import TestCaseFailed + from HABApp.core.events import ValueUpdateEventFilter from HABApp.core.items import Item from HABApp.util import EventListenerGroup -from HABAppTests import TestBaseRule -from HABAppTests.errors import TestCaseFailed + log = logging.getLogger('HABApp.Tests.MultiMode') diff --git a/run/conf_testing/rules/habapp/test_habapp.py b/run/conf_testing/rules/habapp/test_habapp.py index d07815c6..98ff6bab 100644 --- a/run/conf_testing/rules/habapp/test_habapp.py +++ b/run/conf_testing/rules/habapp/test_habapp.py @@ -1,10 +1,16 @@ import time +from HABAppTests import EventWaiter, TestBaseRule, get_random_name + import HABApp -from HABApp.core.events import ItemNoUpdateEvent, ItemNoChangeEvent, ValueUpdateEvent, EventFilter, \ - ValueUpdateEventFilter +from HABApp.core.events import ( + EventFilter, + ItemNoChangeEvent, + ItemNoUpdateEvent, + ValueUpdateEvent, + ValueUpdateEventFilter, +) from HABApp.core.items import Item -from HABAppTests import TestBaseRule, EventWaiter, get_random_name class TestItemEvents(TestBaseRule): diff --git a/run/conf_testing/rules/habapp/test_parameter_files.py b/run/conf_testing/rules/habapp/test_parameter_files.py index af97a05d..7beb0052 100644 --- a/run/conf_testing/rules/habapp/test_parameter_files.py +++ b/run/conf_testing/rules/habapp/test_parameter_files.py @@ -1,8 +1,10 @@ import logging -import HABApp from HABAppTests import TestBaseRule +import HABApp + + log = logging.getLogger('HABApp.TestParameterFiles') # User Parameter files to create rules dynamically diff --git a/run/conf_testing/rules/habapp/test_rule_context.py b/run/conf_testing/rules/habapp/test_rule_context.py index 795a7e2f..e552f825 100644 --- a/run/conf_testing/rules/habapp/test_rule_context.py +++ b/run/conf_testing/rules/habapp/test_rule_context.py @@ -1,8 +1,9 @@ from time import sleep +from HABAppTests import TestBaseRule + from HABApp import Rule from HABApp.core.internals import get_current_context -from HABAppTests import TestBaseRule class OtherRule(Rule): diff --git a/run/conf_testing/rules/habapp/test_scheduler.py b/run/conf_testing/rules/habapp/test_scheduler.py index 2230fdb2..922a5038 100644 --- a/run/conf_testing/rules/habapp/test_scheduler.py +++ b/run/conf_testing/rules/habapp/test_scheduler.py @@ -1,9 +1,11 @@ import logging import time +from HABAppTests import TestBaseRule, get_random_name + from HABApp.core.events import ValueUpdateEventFilter from HABApp.core.items import Item -from HABAppTests import TestBaseRule, get_random_name + log = logging.getLogger('HABApp.TestParameterFiles') diff --git a/run/conf_testing/rules/habapp/test_util_fade.py b/run/conf_testing/rules/habapp/test_util_fade.py index 454f9855..82da0833 100644 --- a/run/conf_testing/rules/habapp/test_util_fade.py +++ b/run/conf_testing/rules/habapp/test_util_fade.py @@ -1,8 +1,9 @@ import time +from HABAppTests import ItemWaiter, TestBaseRule + from HABApp.core.items import Item from HABApp.util.fade.fade import Fade -from HABAppTests import ItemWaiter, TestBaseRule class TestFadeRun(TestBaseRule): diff --git a/run/conf_testing/rules/habapp/test_utils.py b/run/conf_testing/rules/habapp/test_utils.py index 3231653c..43ca0011 100644 --- a/run/conf_testing/rules/habapp/test_utils.py +++ b/run/conf_testing/rules/habapp/test_utils.py @@ -1,10 +1,12 @@ +import logging + from HABAppTests import ItemWaiter, OpenhabTmpItem, TestBaseRule, get_random_name import HABApp -import logging from HABApp.openhab.items import OpenhabItem from HABApp.util.multimode import MultiModeItem, SwitchItemValueMode + log = logging.getLogger('HABApp.Tests.MultiMode') diff --git a/run/conf_testing/rules/openhab/test_event_types.py b/run/conf_testing/rules/openhab/test_event_types.py index 97ce43f3..f579ff6b 100644 --- a/run/conf_testing/rules/openhab/test_event_types.py +++ b/run/conf_testing/rules/openhab/test_event_types.py @@ -1,7 +1,14 @@ -from HABApp.core.events import ValueUpdateEventFilter +from HABAppTests import ( + EventWaiter, + ItemWaiter, + OpenhabTmpItem, + TestBaseRule, + get_openhab_test_events, + get_openhab_test_states, + get_openhab_test_types, +) -from HABAppTests import TestBaseRule, EventWaiter, OpenhabTmpItem, get_openhab_test_events, \ - get_openhab_test_types, get_openhab_test_states, ItemWaiter +from HABApp.core.events import ValueUpdateEventFilter class TestOpenhabEventTypes(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_groups.py b/run/conf_testing/rules/openhab/test_groups.py index 7f425981..39f15883 100644 --- a/run/conf_testing/rules/openhab/test_groups.py +++ b/run/conf_testing/rules/openhab/test_groups.py @@ -1,8 +1,9 @@ -from HABApp.openhab.items import SwitchItem, GroupItem -from HABAppTests import ItemWaiter, TestBaseRule, OpenhabTmpItem, EventWaiter -from HABApp.openhab.events import ItemStateUpdatedEventFilter +from HABAppTests import EventWaiter, ItemWaiter, OpenhabTmpItem, TestBaseRule from HABAppTests.errors import TestCaseFailed +from HABApp.openhab.events import ItemStateUpdatedEventFilter +from HABApp.openhab.items import GroupItem, SwitchItem + class TestOpenhabGroupFunction(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_habapp_internals.py b/run/conf_testing/rules/openhab/test_habapp_internals.py index 72022e98..d60cece3 100644 --- a/run/conf_testing/rules/openhab/test_habapp_internals.py +++ b/run/conf_testing/rules/openhab/test_habapp_internals.py @@ -1,7 +1,11 @@ -from HABApp.openhab.connection.handler.func_async import async_get_item_with_habapp_meta, async_set_habapp_metadata, \ - async_remove_habapp_metadata +from HABAppTests import OpenhabTmpItem, TestBaseRule, run_coro + +from HABApp.openhab.connection.handler.func_async import ( + async_get_item_with_habapp_meta, + async_remove_habapp_metadata, + async_set_habapp_metadata, +) from HABApp.openhab.definitions.rest.habapp_data import HABAppThingPluginData -from HABAppTests import TestBaseRule, OpenhabTmpItem, run_coro class OpenhabMetaData(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_interface.py b/run/conf_testing/rules/openhab/test_interface.py index ac9564ea..d1da0bc0 100644 --- a/run/conf_testing/rules/openhab/test_interface.py +++ b/run/conf_testing/rules/openhab/test_interface.py @@ -11,9 +11,16 @@ # ---------------------------------------------------------------------------------------------------------------------- import time +from HABAppTests import ( + ItemWaiter, + OpenhabTmpItem, + TestBaseRule, + get_openhab_test_states, + get_openhab_test_types, + get_random_name, +) + import HABApp -from HABAppTests import ItemWaiter, OpenhabTmpItem, TestBaseRule, get_openhab_test_states, get_openhab_test_types, \ - get_random_name class TestOpenhabInterface(TestBaseRule): @@ -135,7 +142,7 @@ def test_metadata(self): def test_async_oder(self): with OpenhabTmpItem('String', 'AsyncOrderTest') as item, ItemWaiter(item) as waiter: for _ in range(10): - for i in range(0, 5): + for i in range(5): item.oh_post_update(i) waiter.wait_for_state('4') diff --git a/run/conf_testing/rules/openhab/test_interface_links.py b/run/conf_testing/rules/openhab/test_interface_links.py index 4cd046c2..e5329a9a 100644 --- a/run/conf_testing/rules/openhab/test_interface_links.py +++ b/run/conf_testing/rules/openhab/test_interface_links.py @@ -1,6 +1,7 @@ +from HABAppTests import TestBaseRule, find_astro_sun_thing + from HABApp.openhab.errors import LinkNotFoundError from HABApp.openhab.items import Thing -from HABAppTests import TestBaseRule, find_astro_sun_thing class TestOpenhabInterfaceLinks(TestBaseRule): @@ -9,27 +10,27 @@ def __init__(self): self.config.skip_on_failure = True - self.item_name: str = "" - self.astro_sun_thing: str = "" - self.channel_uid: str = "" + self.item_name: str = '' + self.astro_sun_thing: str = '' + self.channel_uid: str = '' - self.add_test("link creation", self.test_create_link) - self.add_test("created link is gettable and equal", self.test_get_link) - self.add_test("link existence", self.test_link_existence) - self.add_test("link removal", self.test_remove_link) - self.add_test("link update", self.test_update_link) + self.add_test('link creation', self.test_create_link) + self.add_test('created link is gettable and equal', self.test_get_link) + self.add_test('link existence', self.test_link_existence) + self.add_test('link removal', self.test_remove_link) + self.add_test('link update', self.test_update_link) def __create_test_item(self): - self.openhab.create_item("Number", self.item_name) + self.openhab.create_item('Number', self.item_name) def set_up(self): - self.item_name: str = "TestOpenhabInterfaceLinksItem" + self.item_name: str = 'TestOpenhabInterfaceLinksItem' self.astro_sun_thing: str = find_astro_sun_thing() - self.channel_uid: str = f"{self.astro_sun_thing}:rise#start" + self.channel_uid: str = f'{self.astro_sun_thing}:rise#start' self.__create_test_item() if not self.openhab.item_exists(self.item_name): - raise Exception("item could not be created") + raise Exception('item could not be created') def tear_down(self): try: @@ -41,14 +42,14 @@ def tear_down(self): self.openhab.remove_item(self.item_name) def __find_astro_sun_thing(self) -> str: - found_uid: str = "" + found_uid: str = '' for item in self.get_items(Thing, name='^astro:sun'): found_uid = item.name return found_uid def test_update_link(self): - assert self.oh.create_link(self.item_name, self.channel_uid, {"profile": "system:default"}) + assert self.oh.create_link(self.item_name, self.channel_uid, {'profile': 'system:default'}) assert self.oh.get_link(self.item_name, self.channel_uid) new_cfg = {'profile': 'system:offset', 'offset': 7.0} @@ -58,7 +59,7 @@ def test_update_link(self): assert channel_link.configuration == new_cfg def test_get_link(self): - target = {"profile": "system:default"} + target = {'profile': 'system:default'} assert self.oh.create_link(self.item_name, self.channel_uid, target) link = self.oh.get_link(self.item_name, self.channel_uid) @@ -67,7 +68,7 @@ def test_get_link(self): assert link.configuration == target def test_remove_link(self): - assert self.oh.create_link(self.item_name, self.channel_uid, {"profile": "system:default"}) + assert self.oh.create_link(self.item_name, self.channel_uid, {'profile': 'system:default'}) self.oh.remove_link(self.item_name, self.channel_uid) try: self.oh.get_link(self.item_name, self.channel_uid) @@ -76,7 +77,7 @@ def test_remove_link(self): pass def test_link_existence(self): - assert self.oh.create_link(self.item_name, self.channel_uid, {"profile": "system:default"}) + assert self.oh.create_link(self.item_name, self.channel_uid, {'profile': 'system:default'}) assert self.oh.get_link(self.item_name, self.channel_uid) self.oh.remove_link(self.item_name, self.channel_uid) @@ -87,7 +88,7 @@ def test_link_existence(self): pass def test_create_link(self): - assert self.oh.create_link(self.item_name, self.channel_uid, {"profile": "system:default"}) + assert self.oh.create_link(self.item_name, self.channel_uid, {'profile': 'system:default'}) TestOpenhabInterfaceLinks() diff --git a/run/conf_testing/rules/openhab/test_item_change.py b/run/conf_testing/rules/openhab/test_item_change.py index f00f802c..58ee1640 100644 --- a/run/conf_testing/rules/openhab/test_item_change.py +++ b/run/conf_testing/rules/openhab/test_item_change.py @@ -1,9 +1,10 @@ +from HABAppTests import EventWaiter, OpenhabTmpItem, TestBaseRule + from HABApp.core.events import EventFilter from HABApp.openhab.definitions.topics import TOPIC_ITEMS from HABApp.openhab.events import ItemUpdatedEvent from HABApp.openhab.interface_sync import create_item -from HABApp.openhab.items import StringItem, NumberItem, DatetimeItem -from HABAppTests import TestBaseRule, OpenhabTmpItem, EventWaiter +from HABApp.openhab.items import DatetimeItem, NumberItem, StringItem class ChangeItemType(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_item_funcs.py b/run/conf_testing/rules/openhab/test_item_funcs.py index 4167ed4a..63140949 100644 --- a/run/conf_testing/rules/openhab/test_item_funcs.py +++ b/run/conf_testing/rules/openhab/test_item_funcs.py @@ -2,9 +2,20 @@ import logging import typing -from HABApp.openhab.items import OpenhabItem, NumberItem, ContactItem, LocationItem -from HABApp.openhab.items import SwitchItem, RollershutterItem, DimmerItem, ColorItem, ImageItem -from HABAppTests import TestBaseRule, ItemWaiter, OpenhabTmpItem, get_openhab_test_states, get_openhab_test_types +from HABAppTests import ItemWaiter, OpenhabTmpItem, TestBaseRule, get_openhab_test_states, get_openhab_test_types + +from HABApp.openhab.items import ( + ColorItem, + ContactItem, + DimmerItem, + ImageItem, + LocationItem, + NumberItem, + OpenhabItem, + RollershutterItem, + SwitchItem, +) + log = logging.getLogger('HABApp.Tests') @@ -58,7 +69,7 @@ def __init__(self): self.add_func_test( LocationItem, { TestParam( - 'oh_post_update', func_params="52.5185537,13.3758636,43", result=(52.5185537, 13.3758636, 43) + 'oh_post_update', func_params='52.5185537,13.3758636,43', result=(52.5185537, 13.3758636, 43) ) } ) diff --git a/run/conf_testing/rules/openhab/test_items.py b/run/conf_testing/rules/openhab/test_items.py index a152d811..6ca730cc 100644 --- a/run/conf_testing/rules/openhab/test_items.py +++ b/run/conf_testing/rules/openhab/test_items.py @@ -1,13 +1,13 @@ import asyncio +from HABAppTests import EventWaiter, ItemWaiter, OpenhabTmpItem, TestBaseRule from immutables import Map from HABApp.core.const import loop from HABApp.core.events import ValueUpdateEventFilter from HABApp.core.types import HSB, RGB from HABApp.openhab.interface_async import async_get_items -from HABApp.openhab.items import GroupItem, StringItem, ColorItem, NumberItem -from HABAppTests import OpenhabTmpItem, TestBaseRule, ItemWaiter, EventWaiter +from HABApp.openhab.items import ColorItem, GroupItem, NumberItem, StringItem class OpenhabItems(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_links.py b/run/conf_testing/rules/openhab/test_links.py index 87d3821c..339453f4 100644 --- a/run/conf_testing/rules/openhab/test_links.py +++ b/run/conf_testing/rules/openhab/test_links.py @@ -1,7 +1,8 @@ -from HABApp.openhab.connection.handler.func_async import async_get_links, async_get_link from HABAppTests import TestBaseRule from HABAppTests.utils import find_astro_sun_thing, run_coro +from HABApp.openhab.connection.handler.func_async import async_get_link, async_get_links + class OpenhabLinkApi(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_max_sse_msg_size.py b/run/conf_testing/rules/openhab/test_max_sse_msg_size.py index 8828ea2f..e0adf221 100644 --- a/run/conf_testing/rules/openhab/test_max_sse_msg_size.py +++ b/run/conf_testing/rules/openhab/test_max_sse_msg_size.py @@ -1,8 +1,10 @@ import logging +from HABAppTests import EventWaiter, ItemWaiter, OpenhabTmpItem, TestBaseRule + from HABApp.openhab.events import ItemStateChangedEventFilter from HABApp.openhab.items import OpenhabItem -from HABAppTests import EventWaiter, ItemWaiter, OpenhabTmpItem, TestBaseRule + log = logging.getLogger('HABApp.Tests') diff --git a/run/conf_testing/rules/openhab/test_things.py b/run/conf_testing/rules/openhab/test_things.py index 3dc7af3e..8c410d0a 100644 --- a/run/conf_testing/rules/openhab/test_things.py +++ b/run/conf_testing/rules/openhab/test_things.py @@ -1,8 +1,9 @@ +from time import sleep + from HABAppTests import TestBaseRule from HABAppTests.utils import find_astro_sun_thing from HABApp.openhab.items import Thing -from time import sleep class OpenhabThings(TestBaseRule): diff --git a/run/conf_testing/rules/openhab/test_transformations.py b/run/conf_testing/rules/openhab/test_transformations.py index 437ec77a..b7f67adb 100644 --- a/run/conf_testing/rules/openhab/test_transformations.py +++ b/run/conf_testing/rules/openhab/test_transformations.py @@ -1,6 +1,8 @@ -from HABApp.openhab import transformations from HABAppTests import TestBaseRule +from HABApp.openhab import transformations + + obj = transformations.map['de.map'] diff --git a/run/conf_testing/rules/openhab_bugs.py b/run/conf_testing/rules/openhab_bugs.py index 116c50a1..6a12be3c 100644 --- a/run/conf_testing/rules/openhab_bugs.py +++ b/run/conf_testing/rules/openhab_bugs.py @@ -3,9 +3,15 @@ import time -from HABApp.openhab.connection_handler.func_async import async_create_item, async_get_item, ItemNotFoundError, \ - async_create_channel_link, async_remove_item -from HABAppTests import TestBaseRule, get_random_name, run_coro, find_astro_sun_thing +from HABAppTests import TestBaseRule, find_astro_sun_thing, get_random_name, run_coro + +from HABApp.openhab.connection_handler.func_async import ( + ItemNotFoundError, + async_create_channel_link, + async_create_item, + async_get_item, + async_remove_item, +) class BugLinks(TestBaseRule): @@ -16,7 +22,7 @@ def __init__(self): def create_meta(self): astro_thing = find_astro_sun_thing() - astro_channel = f"{astro_thing}:rise#start" + astro_channel = f'{astro_thing}:rise#start' name = get_random_name('DateTime') # create item and link diff --git a/setup.py b/setup.py index 3b5f6501..4ce56476 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ # Load version number without importing HABApp def load_version() -> str: version: typing.Dict[str, str] = {} - with open("src/HABApp/__version__.py") as fp: + with open('src/HABApp/__version__.py') as fp: exec(fp.read(), version) assert version['__version__'], version return version['__version__'] @@ -27,15 +27,15 @@ def load_req() -> typing.List[str]: readme = Path(__file__).with_name('readme.md') long_description = '' if readme.is_file(): - with readme.open("r", encoding='utf-8') as fh: + with readme.open('r', encoding='utf-8') as fh: long_description = fh.read() setup( - name="HABApp", + name='HABApp', version=__version__, - author="spaceman_spiff", + author='spaceman_spiff', # author_email="", - description="Easy automation with MQTT and/or openHAB. Create home automation rules in python.", + description='Easy automation with MQTT and/or openHAB. Create home automation rules in python.', keywords=[ 'mqtt', 'openhab', @@ -43,8 +43,8 @@ def load_req() -> typing.List[str]: 'home automation' ], long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/spacemanspiff2007/HABApp", + long_description_content_type='text/markdown', + url='https://github.com/spacemanspiff2007/HABApp', project_urls={ 'Documentation': 'https://habapp.readthedocs.io/', 'GitHub': 'https://github.com/spacemanspiff2007/HABApp', @@ -55,17 +55,17 @@ def load_req() -> typing.List[str]: install_requires=load_req(), python_requires='>=3.8', classifiers=[ - "Development Status :: 4 - Beta", - "Framework :: AsyncIO", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", - "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" + 'Development Status :: 4 - Beta', + 'Framework :: AsyncIO', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.8', + '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' ], entry_points={ 'console_scripts': [ diff --git a/src/HABApp/__check_dependency_packages__.py b/src/HABApp/__check_dependency_packages__.py index dad27f87..72b0fbd5 100644 --- a/src/HABApp/__check_dependency_packages__.py +++ b/src/HABApp/__check_dependency_packages__.py @@ -15,6 +15,7 @@ def get_dependencies() -> list[str]: 'colorama', 'eascheduler', 'easyconfig', + 'fastnumbers', 'pydantic', 'stack_data', 'voluptuous', diff --git a/src/HABApp/__main__.py b/src/HABApp/__main__.py index eacc5331..4fe7d3c9 100644 --- a/src/HABApp/__main__.py +++ b/src/HABApp/__main__.py @@ -4,7 +4,7 @@ import typing import HABApp -from HABApp.__cmd_args__ import parse_args, find_config_folder +from HABApp.__cmd_args__ import find_config_folder, parse_args from HABApp.__debug_info__ import print_debug_info from HABApp.__splash_screen__ import show_screen @@ -27,7 +27,7 @@ def main() -> typing.Union[int, str]: # see if we have user code (e.g. for additional logging configuration or additional setup) try: - import HABAppUser # noqa: F401 + import HABAppUser except ModuleNotFoundError: pass @@ -52,5 +52,5 @@ def main() -> typing.Union[int, str]: return 0 -if __name__ == "__main__": +if __name__ == '__main__': sys.exit(main()) diff --git a/src/HABApp/__setup_packages__.py b/src/HABApp/__setup_packages__.py index 998726c3..9bef3b05 100644 --- a/src/HABApp/__setup_packages__.py +++ b/src/HABApp/__setup_packages__.py @@ -1,10 +1,11 @@ from HABApp.__check_dependency_packages__ import check_dependency_packages + # ----------------------------------------------------------------------------- # if installed we use uvloop because it seems to be much faster (untested) # ----------------------------------------------------------------------------- try: - import uvloop # type: ignore + import uvloop # type: ignore uvloop.install() print('Using uvloop') diff --git a/src/HABApp/__splash_screen__.py b/src/HABApp/__splash_screen__.py index b785e029..709f25cc 100644 --- a/src/HABApp/__splash_screen__.py +++ b/src/HABApp/__splash_screen__.py @@ -1,26 +1,26 @@ -from colorama import just_fix_windows_console, deinit, Fore +from colorama import Fore, deinit, just_fix_windows_console from HABApp.__version__ import __version__ def show_screen(): - text = r""" + text = r''' _ _ _ ____ _ | | | | / \ | __ ) / \ _ __ _ __ | |_| | / _ \ | _ \ / _ \ | '_ \| '_ \ | _ |/ ___ \| |_) / ___ \| |_) | |_) | |_| |_/_/ \_|____/_/ \_| .__/| .__/ |_| |_| -""" +''' - red = r""" + red = r''' _ / \ _ __ _ __ / _ \ | '_ \| '_ \ / ___ \| |_) | |_) | /_/ \_| .__/| .__/ |_| |_| -""" +''' just_fix_windows_console() diff --git a/src/HABApp/__version__.py b/src/HABApp/__version__.py index 40d7fdd1..dd7e031b 100644 --- a/src/HABApp/__version__.py +++ b/src/HABApp/__version__.py @@ -10,4 +10,4 @@ # Development versions contain the DEV-COUNTER postfix: # - 24.01.0.DEV-1 -__version__ = '24.02.0' +__version__ = '24.08.0' diff --git a/src/HABApp/config/config.py b/src/HABApp/config/config.py index daf64960..d4dfc914 100644 --- a/src/HABApp/config/config.py +++ b/src/HABApp/config/config.py @@ -1,4 +1,6 @@ from easyconfig import create_app_config + from .models import ApplicationConfig + CONFIG: ApplicationConfig = create_app_config(ApplicationConfig()) diff --git a/src/HABApp/config/loader.py b/src/HABApp/config/loader.py index b0d3ec9a..26a1019c 100644 --- a/src/HABApp/config/loader.py +++ b/src/HABApp/config/loader.py @@ -3,17 +3,19 @@ from pathlib import Path from typing import List +import eascheduler import pydantic import HABApp -import eascheduler from HABApp import __version__ from HABApp.config.config import CONFIG from HABApp.config.logging import HABAppQueueHandler -from .errors import InvalidConfigError, AbsolutePathExpected -from .logging import create_default_logfile, get_logging_dict, rotate_files, inject_log_buffer + +from .errors import AbsolutePathExpected, InvalidConfigError +from .logging import create_default_logfile, get_logging_dict, inject_log_buffer, rotate_files from .logging.buffered_logger import BufferedLogger + log = logging.getLogger('HABApp.Config') diff --git a/src/HABApp/config/logging/config.py b/src/HABApp/config/logging/config.py index db3148b2..da9ca9cc 100644 --- a/src/HABApp/config/logging/config.py +++ b/src/HABApp/config/logging/config.py @@ -4,10 +4,12 @@ from pathlib import Path from typing import Any, Dict, List, Optional +from easyconfig.yaml import yaml_safe as _yaml_safe + import HABApp from HABApp.config.config import CONFIG from HABApp.config.errors import AbsolutePathExpected -from easyconfig.yaml import yaml_safe as _yaml_safe + from .buffered_logger import BufferedLogger from .queue_handler import HABAppQueueHandler, SimpleQueue @@ -87,7 +89,7 @@ def get_logging_dict(path: Path, log: BufferedLogger) -> Optional[dict]: if not isinstance(level, int): level = logging._nameToLevel[level] logging.addLevelName(level, str(alias)) - log.debug(f'Added custom Level "{str(alias)}" ({level})') + log.debug(f'Added custom Level "{alias!s}" ({level})') return cfg diff --git a/src/HABApp/config/logging/default_logfile.py b/src/HABApp/config/logging/default_logfile.py index 3326232a..eed8022b 100644 --- a/src/HABApp/config/logging/default_logfile.py +++ b/src/HABApp/config/logging/default_logfile.py @@ -6,7 +6,7 @@ def get_default_logfile() -> str: - template = Template(""" + template = Template(''' ${LOG_LEVELS}formatters: HABApp_format: format: '[%(asctime)s] [%(name)25s] %(levelname)8s | %(message)s' @@ -55,7 +55,7 @@ def get_default_logfile() -> str: - EventFile propagate: False -""") +''') # Default values are relative subs = { @@ -77,8 +77,8 @@ def get_default_logfile() -> str: # With openhabian we typically use frontail if is_openhabian(): - subs['FRONTAIL_FORMAT'] = '\n'\ - ' Frontail_format:\n' \ + subs['FRONTAIL_FORMAT'] = "\n"\ + " Frontail_format:\n" \ " format: '%(asctime)s.%(msecs)03d [%(levelname)-5s] [%(name)-36s] - %(message)s'\n" \ " datefmt: '%Y-%m-%d %H:%M:%S'" diff --git a/src/HABApp/config/logging/handler.py b/src/HABApp/config/logging/handler.py index ad920d22..b1a90cea 100644 --- a/src/HABApp/config/logging/handler.py +++ b/src/HABApp/config/logging/handler.py @@ -33,7 +33,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def compressed_namer(self, default_name: str) -> str: - return default_name + ".gz" + return default_name + '.gz' def compressed_rotator(self, source: str, dest: str): src = Path(source) diff --git a/src/HABApp/config/logging/queue_handler.py b/src/HABApp/config/logging/queue_handler.py index eb85ed6f..d0e9737b 100644 --- a/src/HABApp/config/logging/queue_handler.py +++ b/src/HABApp/config/logging/queue_handler.py @@ -1,12 +1,14 @@ import logging -from queue import SimpleQueue, Empty -from threading import Thread, Lock +from queue import Empty, SimpleQueue +from threading import Lock, Thread from time import sleep -from typing import Optional, Final +from typing import Final, Optional import HABApp + from .config import CONFIG + log = logging.getLogger('HABApp.logging') diff --git a/src/HABApp/config/models/application.py b/src/HABApp/config/models/application.py index 41adecbf..682c7b42 100644 --- a/src/HABApp/config/models/application.py +++ b/src/HABApp/config/models/application.py @@ -1,10 +1,11 @@ from easyconfig import AppBaseModel +from pydantic import Field + +from HABApp.config.models.directories import DirectoriesConfig +from HABApp.config.models.habapp import HABAppConfig from HABApp.config.models.location import LocationConfig from HABApp.config.models.mqtt import MqttConfig from HABApp.config.models.openhab import OpenhabConfig -from HABApp.config.models.habapp import HABAppConfig -from HABApp.config.models.directories import DirectoriesConfig -from pydantic import Field class ApplicationConfig(AppBaseModel): diff --git a/src/HABApp/config/models/directories.py b/src/HABApp/config/models/directories.py index 5a6ed986..a6226fa3 100644 --- a/src/HABApp/config/models/directories.py +++ b/src/HABApp/config/models/directories.py @@ -8,6 +8,7 @@ from HABApp.config.platform_defaults import get_log_folder + log = logging.getLogger('HABApp.Config') diff --git a/src/HABApp/config/models/habapp.py b/src/HABApp/config/models/habapp.py index df50819d..25f05834 100644 --- a/src/HABApp/config/models/habapp.py +++ b/src/HABApp/config/models/habapp.py @@ -4,20 +4,20 @@ class ThreadPoolConfig(BaseModel): enabled: bool = True - """When the thread pool is disabled HABApp will become an asyncio application. + '''When the thread pool is disabled HABApp will become an asyncio application. Use only if you have experience developing asyncio applications! - If the thread pool is disabled using blocking calls in functions can and will break HABApp""" + If the thread pool is disabled using blocking calls in functions can and will break HABApp''' threads: conint(ge=1, le=16) = 10 - """Amount of threads to use for the executor""" + '''Amount of threads to use for the executor''' class LoggingConfig(BaseModel): use_buffer: bool = Field(True, alias='use buffer') - """Automatically inject a buffer for the event log""" + '''Automatically inject a buffer for the event log''' flush_every: float = Field(0.5, alias='flush every', ge=0.1) - """Wait time in seconds before the buffer gets flushed again when it was empty""" + '''Wait time in seconds before the buffer gets flushed again when it was empty''' class HABAppConfig(BaseModel): diff --git a/src/HABApp/config/models/location.py b/src/HABApp/config/models/location.py index 00d713a5..a89376af 100644 --- a/src/HABApp/config/models/location.py +++ b/src/HABApp/config/models/location.py @@ -1,8 +1,8 @@ import logging.config +from easyconfig import BaseModel from pydantic import Field -from easyconfig import BaseModel log = logging.getLogger('HABApp.Config') diff --git a/src/HABApp/core/asyncio.py b/src/HABApp/core/asyncio.py index 78ad13ee..3086250c 100644 --- a/src/HABApp/core/asyncio.py +++ b/src/HABApp/core/asyncio.py @@ -101,7 +101,7 @@ def run_func_from_async(func: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwa future = _run_coroutine_threadsafe(_run_func_from_async_helper(func, *args, **kwargs), loop) return future # Doc build fails if we enable this - # Todo: Fix the Rule Runner + # TODO: Fix the Rule Runner # return future.result() diff --git a/src/HABApp/core/connections/_definitions.py b/src/HABApp/core/connections/_definitions.py index f286bf8a..288daa13 100644 --- a/src/HABApp/core/connections/_definitions.py +++ b/src/HABApp/core/connections/_definitions.py @@ -5,6 +5,7 @@ from HABApp.core.const.const import StrEnum + connection_log = logging.getLogger('HABApp.connection') diff --git a/src/HABApp/core/connections/base_connection.py b/src/HABApp/core/connections/base_connection.py index d906ee20..2c40bba9 100644 --- a/src/HABApp/core/connections/base_connection.py +++ b/src/HABApp/core/connections/base_connection.py @@ -1,14 +1,16 @@ from __future__ import annotations from asyncio import CancelledError -from typing import Final, TYPE_CHECKING, Callable, Literal +from typing import TYPE_CHECKING, Callable, Final, Literal import HABApp from HABApp.core.connections._definitions import ConnectionStatus, connection_log from HABApp.core.connections.status_transitions import StatusTransitions -from HABApp.core.lib import SingleTask, PriorityList +from HABApp.core.lib import PriorityList, SingleTask + from ..wrapper import process_exception + if TYPE_CHECKING: from .base_plugin import BaseConnectionPlugin from .plugin_callback import PluginCallbackHandler @@ -96,6 +98,8 @@ def process_exception(self, e: Exception, func: Callable | str | None): name = func if isinstance(func, str) else func.__qualname__ self.log.debug(f'Error in {name:s}: {e} ({e.__class__.__name__})') else: + if func is None: + func = f'{self.name} connection' process_exception(func, e, self.log) def register_plugin(self, obj: BaseConnectionPlugin, priority: int | Literal['first', 'last'] | None = None): @@ -110,7 +114,8 @@ def register_plugin(self, obj: BaseConnectionPlugin, priority: int | Literal['fi for p in self.plugins: if p.plugin_name == obj.plugin_name: - raise ValueError(f'Plugin with the same name already registered: {p}') + msg = f'Plugin with the same name already registered: {p}' + raise ValueError(msg) for status, handler in get_plugin_callbacks(obj): if priority is None: diff --git a/src/HABApp/core/connections/base_plugin.py b/src/HABApp/core/connections/base_plugin.py index 5e777646..e33f681b 100644 --- a/src/HABApp/core/connections/base_plugin.py +++ b/src/HABApp/core/connections/base_plugin.py @@ -1,12 +1,13 @@ from __future__ import annotations -from typing import Final, TYPE_CHECKING, Generic, TypeVar, Callable, Awaitable, Any +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Final, Generic, TypeVar from HABApp.core.lib import SingleTask + if TYPE_CHECKING: - from .plugin_callback import PluginCallbackHandler from .base_connection import BaseConnection + from .plugin_callback import PluginCallbackHandler T = TypeVar('T', bound='BaseConnection') diff --git a/src/HABApp/core/connections/connection_task.py b/src/HABApp/core/connections/connection_task.py index 5d5db590..54f46d7b 100644 --- a/src/HABApp/core/connections/connection_task.py +++ b/src/HABApp/core/connections/connection_task.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Final, Callable, Awaitable, Any +from typing import Any, Awaitable, Callable, Final from HABApp.core.lib import SingleTask diff --git a/src/HABApp/core/connections/plugin_callback.py b/src/HABApp/core/connections/plugin_callback.py index 70c9c5f0..285b884d 100644 --- a/src/HABApp/core/connections/plugin_callback.py +++ b/src/HABApp/core/connections/plugin_callback.py @@ -2,11 +2,12 @@ import re from dataclasses import dataclass -from inspect import signature, iscoroutinefunction, getmembers -from typing import Awaitable, Callable, Any, TYPE_CHECKING +from inspect import getmembers, iscoroutinefunction, signature +from typing import TYPE_CHECKING, Any, Awaitable, Callable from ._definitions import ConnectionStatus + if TYPE_CHECKING: from .base_connection import BaseConnection from .base_plugin import BaseConnectionPlugin diff --git a/src/HABApp/core/connections/plugins/auto_reconnect.py b/src/HABApp/core/connections/plugins/auto_reconnect.py index da7f0936..9c00de01 100644 --- a/src/HABApp/core/connections/plugins/auto_reconnect.py +++ b/src/HABApp/core/connections/plugins/auto_reconnect.py @@ -1,6 +1,6 @@ from __future__ import annotations -from asyncio import sleep, Task, create_task, CancelledError +from asyncio import CancelledError, Task, create_task, sleep from HABApp.core.connections import BaseConnection, BaseConnectionPlugin diff --git a/src/HABApp/core/const/json.py b/src/HABApp/core/const/json.py index e31c9431..0d09e3d2 100644 --- a/src/HABApp/core/const/json.py +++ b/src/HABApp/core/const/json.py @@ -2,6 +2,7 @@ import msgspec + try: import ujson load_json: Callable[[str], Any] = ujson.loads diff --git a/src/HABApp/core/const/log.py b/src/HABApp/core/const/log.py index fe5cdb4f..580f3ecf 100644 --- a/src/HABApp/core/const/log.py +++ b/src/HABApp/core/const/log.py @@ -1,3 +1,4 @@ from typing import Final + TOPIC_EVENTS: Final = 'HABApp.EventBus' diff --git a/src/HABApp/core/const/loop.py b/src/HABApp/core/const/loop.py index 08d660eb..733e2693 100644 --- a/src/HABApp/core/const/loop.py +++ b/src/HABApp/core/const/loop.py @@ -2,11 +2,12 @@ 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 -if (sys.platform.lower() == "win32" or os.name.lower() == "nt") and os.environ.get('HABAPP_NO_MQTT') is None: - from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy +if (sys.platform.lower() == 'win32' or os.name.lower() == 'nt') and os.environ.get('HABAPP_NO_MQTT') is None: + from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy set_event_loop_policy(WindowsSelectorEventLoopPolicy()) loop = asyncio.new_event_loop() diff --git a/src/HABApp/core/const/yml.py b/src/HABApp/core/const/yml.py index 21dee11a..7a70ae37 100644 --- a/src/HABApp/core/const/yml.py +++ b/src/HABApp/core/const/yml.py @@ -1,5 +1,6 @@ import ruamel.yaml + yml = ruamel.yaml.YAML() yml.default_flow_style = False yml.default_style = False # type: ignore diff --git a/src/HABApp/core/events/events.py b/src/HABApp/core/events/events.py index 75fe81a7..2260304a 100644 --- a/src/HABApp/core/events/events.py +++ b/src/HABApp/core/events/events.py @@ -1,4 +1,4 @@ -from typing import Any, Union, Final +from typing import Any, Final, Union class ComplexEventValue: diff --git a/src/HABApp/core/events/filter/groups.py b/src/HABApp/core/events/filter/groups.py index 9e567489..44ebe8f4 100644 --- a/src/HABApp/core/events/filter/groups.py +++ b/src/HABApp/core/events/filter/groups.py @@ -1,6 +1,6 @@ from typing import Any, Tuple -from HABApp.core.internals import EventFilterBase, HINT_EVENT_FILTER_OBJ +from HABApp.core.internals import HINT_EVENT_FILTER_OBJ, EventFilterBase class EventFilterBaseGroup(EventFilterBase): diff --git a/src/HABApp/core/events/filter/habapp_events.py b/src/HABApp/core/events/filter/habapp_events.py index 2ddf5f7a..482dbeb4 100644 --- a/src/HABApp/core/events/filter/habapp_events.py +++ b/src/HABApp/core/events/filter/habapp_events.py @@ -1,7 +1,7 @@ from typing import Any from HABApp.core.const import MISSING -from HABApp.core.events import ValueUpdateEvent, ValueChangeEvent +from HABApp.core.events import ValueChangeEvent, ValueUpdateEvent from HABApp.core.events.filter.event import TypeBoundEventFilter diff --git a/src/HABApp/core/files/file/file.py b/src/HABApp/core/files/file/file.py index f45f8a83..02835b69 100644 --- a/src/HABApp/core/files/file/file.py +++ b/src/HABApp/core/files/file/file.py @@ -2,14 +2,16 @@ import logging from pathlib import Path -from typing import Callable, Awaitable, Any +from typing import Any, Awaitable, Callable -from HABApp.core.files.errors import CircularReferenceError, DependencyDoesNotExistError, AlreadyHandledFileError +from HABApp.core.files.errors import AlreadyHandledFileError, CircularReferenceError, DependencyDoesNotExistError from HABApp.core.files.file.properties import FileProperties from HABApp.core.files.manager.files import FILES, file_state_changed from HABApp.core.wrapper import process_exception + from . import FileState + log = logging.getLogger('HABApp.files') diff --git a/src/HABApp/core/files/file/file_types.py b/src/HABApp/core/files/file/file_types.py index a507abbe..011a4c18 100644 --- a/src/HABApp/core/files/file/file_types.py +++ b/src/HABApp/core/files/file/file_types.py @@ -5,10 +5,11 @@ from pydantic import ValidationError -from HABApp.core.files.file import HABAppFile, FileState -from HABApp.core.files.file.properties import get_properties, FileProperties +from HABApp.core.files.file import FileState, HABAppFile +from HABApp.core.files.file.properties import FileProperties, get_properties from HABApp.core.logger import HABAppError + FILE_TYPES: dict[str, type[HABAppFile]] = {} diff --git a/src/HABApp/core/files/file/properties.py b/src/HABApp/core/files/file/properties.py index 56189207..ef9c2ee0 100644 --- a/src/HABApp/core/files/file/properties.py +++ b/src/HABApp/core/files/file/properties.py @@ -1,7 +1,7 @@ import re from typing import List -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from HABApp.core.const import yml diff --git a/src/HABApp/core/files/folders/folders.py b/src/HABApp/core/files/folders/folders.py index 8700853d..c00c755e 100644 --- a/src/HABApp/core/files/folders/folders.py +++ b/src/HABApp/core/files/folders/folders.py @@ -1,13 +1,14 @@ from pathlib import Path -from typing import Dict -from typing import List, Type +from typing import Dict, List, Type import HABApp from HABApp.core.const.topics import TOPIC_FILES as T_FILES -from HABApp.core.events.habapp_events import RequestFileUnloadEvent, RequestFileLoadEvent -from ..watcher import AggregatingAsyncEventHandler +from HABApp.core.events.habapp_events import RequestFileLoadEvent, RequestFileUnloadEvent from HABApp.core.internals import uses_post_event +from ..watcher import AggregatingAsyncEventHandler + + FOLDERS: Dict[str, 'ConfiguredFolder'] = {} post_event = uses_post_event() diff --git a/src/HABApp/core/files/manager/files.py b/src/HABApp/core/files/manager/files.py index 70c9c35d..62900b21 100644 --- a/src/HABApp/core/files/manager/files.py +++ b/src/HABApp/core/files/manager/files.py @@ -1,4 +1,5 @@ -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING, Dict + if TYPE_CHECKING: import HABApp diff --git a/src/HABApp/core/files/manager/listen_events.py b/src/HABApp/core/files/manager/listen_events.py index 8c6b7944..e5f063e7 100644 --- a/src/HABApp/core/files/manager/listen_events.py +++ b/src/HABApp/core/files/manager/listen_events.py @@ -3,9 +3,10 @@ import HABApp from HABApp.core.const.topics import TOPIC_FILES as T_FILES -from HABApp.core.events.habapp_events import RequestFileUnloadEvent, RequestFileLoadEvent from HABApp.core.events import EventFilter -from HABApp.core.internals import wrap_func, EventBusListener, uses_event_bus +from HABApp.core.events.habapp_events import RequestFileLoadEvent, RequestFileUnloadEvent +from HABApp.core.internals import EventBusListener, uses_event_bus, wrap_func + log = logging.getLogger('HABApp.Files') event_bus = uses_event_bus() diff --git a/src/HABApp/core/files/manager/worker.py b/src/HABApp/core/files/manager/worker.py index cd9732f6..54fa1a3d 100644 --- a/src/HABApp/core/files/manager/worker.py +++ b/src/HABApp/core/files/manager/worker.py @@ -7,8 +7,10 @@ import HABApp from HABApp.core.files.file import FileState from HABApp.core.files.folders import get_prefixes + from . import FILES + log = logging.getLogger('HABApp.files') diff --git a/src/HABApp/core/files/watcher/base_watcher.py b/src/HABApp/core/files/watcher/base_watcher.py index 3555c172..c4da3be8 100644 --- a/src/HABApp/core/files/watcher/base_watcher.py +++ b/src/HABApp/core/files/watcher/base_watcher.py @@ -1,9 +1,9 @@ import logging from pathlib import Path -from watchdog.events import FileSystemEvent from watchdog.events import EVENT_TYPE_CLOSED as WD_EVENT_TYPE_CLOSED from watchdog.events import EVENT_TYPE_OPENED as WD_EVENT_TYPE_OPENED +from watchdog.events import FileSystemEvent log = logging.getLogger('HABApp.file.events') diff --git a/src/HABApp/core/files/watcher/file_watcher.py b/src/HABApp/core/files/watcher/file_watcher.py index ba5438ee..63e5673f 100644 --- a/src/HABApp/core/files/watcher/file_watcher.py +++ b/src/HABApp/core/files/watcher/file_watcher.py @@ -1,13 +1,14 @@ from asyncio import run_coroutine_threadsafe, sleep from pathlib import Path from time import monotonic -from typing import Any, List, Set, Awaitable, Callable +from typing import Any, Awaitable, Callable, List, Set import HABApp -from HABApp.core.wrapper import ignore_exception -from .base_watcher import EventFilterBase -from .base_watcher import FileSystemEventHandler from HABApp.core.asyncio import AsyncContext +from HABApp.core.wrapper import ignore_exception + +from .base_watcher import EventFilterBase, FileSystemEventHandler + DEBOUNCE_TIME: float = 0.6 diff --git a/src/HABApp/core/files/watcher/folder_watcher.py b/src/HABApp/core/files/watcher/folder_watcher.py index 053ecaf7..17b14c64 100644 --- a/src/HABApp/core/files/watcher/folder_watcher.py +++ b/src/HABApp/core/files/watcher/folder_watcher.py @@ -1,13 +1,14 @@ import logging from pathlib import Path from threading import Lock -from typing import Optional, Dict +from typing import Dict, Optional from watchdog.observers import Observer from watchdog.observers.api import ObservedWatch from .base_watcher import FileSystemEventHandler + log = logging.getLogger('HABApp.files.watcher') LOCK = Lock() diff --git a/src/HABApp/core/internals/context/context.py b/src/HABApp/core/internals/context/context.py index 34951192..896eefab 100644 --- a/src/HABApp/core/internals/context/context.py +++ b/src/HABApp/core/internals/context/context.py @@ -1,5 +1,4 @@ -from typing import Set, Optional, Callable -from typing import TypeVar +from typing import Callable, Optional, Set, TypeVar from HABApp.core.errors import ContextBoundObjectIsAlreadyLinkedError, ContextBoundObjectIsAlreadyUnlinkedError diff --git a/src/HABApp/core/internals/context/get_context.py b/src/HABApp/core/internals/context/get_context.py index 897a6f4a..5489b07a 100644 --- a/src/HABApp/core/internals/context/get_context.py +++ b/src/HABApp/core/internals/context/get_context.py @@ -1,10 +1,11 @@ # noinspection PyProtectedMember from sys import _getframe as sys_get_frame from types import FrameType -from typing import Optional, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Union + +from HABApp.core.errors import ContextNotFoundError, ContextNotSetError +from HABApp.core.internals.context import Context, ContextBoundObj, ContextProvidingObj -from HABApp.core.errors import ContextNotSetError, ContextNotFoundError -from HABApp.core.internals.context import ContextProvidingObj, ContextBoundObj, Context if TYPE_CHECKING: import HABApp diff --git a/src/HABApp/core/internals/event_bus/event_bus.py b/src/HABApp/core/internals/event_bus/event_bus.py index 06828840..89821db8 100644 --- a/src/HABApp/core/internals/event_bus/event_bus.py +++ b/src/HABApp/core/internals/event_bus/event_bus.py @@ -1,11 +1,12 @@ import logging import threading -from typing import Any, TypeVar -from typing import Dict, List +from typing import Any, Dict, List, TypeVar +from HABApp.core.const.log import TOPIC_EVENTS from HABApp.core.events import ComplexEventValue, ValueChangeEvent + from .base_listener import EventBusBaseListener -from HABApp.core.const.log import TOPIC_EVENTS + event_log = logging.getLogger(TOPIC_EVENTS) habapp_log = logging.getLogger('HABApp') diff --git a/src/HABApp/core/internals/event_bus_listener.py b/src/HABApp/core/internals/event_bus_listener.py index 085bb306..aea04f7b 100644 --- a/src/HABApp/core/internals/event_bus_listener.py +++ b/src/HABApp/core/internals/event_bus_listener.py @@ -1,9 +1,8 @@ from typing import Optional, TypeVar +from HABApp.core.internals import HINT_EVENT_FILTER_OBJ, AutoContextBoundObj, Context, uses_event_bus from HABApp.core.internals.event_bus import EventBusBaseListener from HABApp.core.internals.wrapped_function import TYPE_WRAPPED_FUNC_OBJ, WrappedFunctionBase -from HABApp.core.internals import uses_event_bus, Context -from HABApp.core.internals import HINT_EVENT_FILTER_OBJ, AutoContextBoundObj event_bus = uses_event_bus() diff --git a/src/HABApp/core/internals/event_filter.py b/src/HABApp/core/internals/event_filter.py index 2083a839..3c7bf152 100644 --- a/src/HABApp/core/internals/event_filter.py +++ b/src/HABApp/core/internals/event_filter.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Any +from typing import Any, TypeVar class EventFilterBase: diff --git a/src/HABApp/core/internals/proxy/proxies.py b/src/HABApp/core/internals/proxy/proxies.py index f3608a73..e1d1113c 100644 --- a/src/HABApp/core/internals/proxy/proxies.py +++ b/src/HABApp/core/internals/proxy/proxies.py @@ -1,5 +1,7 @@ +from typing import TYPE_CHECKING, Any, Callable + from HABApp.core.internals.proxy.proxy_obj import create_proxy, replace_proxies -from typing import Callable, TYPE_CHECKING, Any + if TYPE_CHECKING: import HABApp diff --git a/src/HABApp/core/internals/proxy/proxy_obj.py b/src/HABApp/core/internals/proxy/proxy_obj.py index ceafb79c..59fa7a0a 100644 --- a/src/HABApp/core/internals/proxy/proxy_obj.py +++ b/src/HABApp/core/internals/proxy/proxy_obj.py @@ -1,9 +1,10 @@ # noinspection PyProtectedMember from sys import _getframe as sys_get_frame -from typing import Dict, List, Optional, Final, Callable +from typing import Callable, Dict, Final, List, Optional from HABApp.core.errors import ProxyObjHasNotBeenReplacedError + PROXIES: List['StartUpProxyObj'] = [] @@ -40,7 +41,7 @@ def __init__(self, to_replace: Callable, globals: dict): @property def to_replace_name(self) -> str: - return str(getattr(self.to_replace, "__name__", self.to_replace)) + return str(getattr(self.to_replace, '__name__', self.to_replace)) def replace(self, replacements: Dict[object, object], final: bool): assert self.globals is not None diff --git a/src/HABApp/core/internals/wrapped_function/base.py b/src/HABApp/core/internals/wrapped_function/base.py index df1923fc..1548e822 100644 --- a/src/HABApp/core/internals/wrapped_function/base.py +++ b/src/HABApp/core/internals/wrapped_function/base.py @@ -1,11 +1,12 @@ import logging -from typing import Optional, TypeVar, Callable +from typing import Callable, Optional, TypeVar from HABApp.core.const.topics import TOPIC_ERRORS as TOPIC_ERRORS from HABApp.core.events.habapp_events import HABAppException from HABApp.core.internals import Context, ContextProvidingObj, uses_event_bus from HABApp.core.lib import format_exception + default_logger = logging.getLogger('HABApp.Worker') event_bus = uses_event_bus() diff --git a/src/HABApp/core/internals/wrapped_function/wrapped_sync.py b/src/HABApp/core/internals/wrapped_function/wrapped_sync.py index 87f85daa..452ebd29 100644 --- a/src/HABApp/core/internals/wrapped_function/wrapped_sync.py +++ b/src/HABApp/core/internals/wrapped_function/wrapped_sync.py @@ -1,8 +1,9 @@ import logging -from typing import Optional, Callable +from typing import Callable, Optional from HABApp.core.asyncio import async_context, create_task from HABApp.core.internals import Context + from .base import WrappedFunctionBase diff --git a/src/HABApp/core/internals/wrapped_function/wrapped_thread.py b/src/HABApp/core/internals/wrapped_function/wrapped_thread.py index c133da11..c2543cb5 100644 --- a/src/HABApp/core/internals/wrapped_function/wrapped_thread.py +++ b/src/HABApp/core/internals/wrapped_function/wrapped_thread.py @@ -1,18 +1,18 @@ import io import logging -from cProfile import Profile from concurrent.futures import ThreadPoolExecutor -from pstats import SortKey -from pstats import Stats +from cProfile import Profile +from pstats import SortKey, Stats from threading import Lock from time import monotonic -from typing import Callable, Any, Set, Final, Dict, Tuple -from typing import Optional +from typing import Any, Callable, Dict, Final, Optional, Set, Tuple from HABApp.core.const import loop from HABApp.core.internals import Context, ContextProvidingObj + from .base import WrappedFunctionBase, default_logger + POOL: Optional[ThreadPoolExecutor] = None POOL_THREADS: int = 0 POOL_INFO: Set['PoolFunc'] = set() diff --git a/src/HABApp/core/internals/wrapped_function/wrapper.py b/src/HABApp/core/internals/wrapped_function/wrapper.py index 60edb762..6a758cea 100644 --- a/src/HABApp/core/internals/wrapped_function/wrapper.py +++ b/src/HABApp/core/internals/wrapped_function/wrapper.py @@ -1,14 +1,19 @@ import logging from asyncio import iscoroutinefunction -from typing import Union, Optional, Callable, Type +from typing import Callable, Optional, Type, Union from HABApp.config import CONFIG from HABApp.core.internals import Context from HABApp.core.internals.wrapped_function.base import TYPE_WRAPPED_FUNC_OBJ -from HABApp.core.internals.wrapped_function.wrapped_async import WrappedAsyncFunction, TYPE_FUNC_ASYNC +from HABApp.core.internals.wrapped_function.wrapped_async import TYPE_FUNC_ASYNC, WrappedAsyncFunction from HABApp.core.internals.wrapped_function.wrapped_sync import WrappedSyncFunction -from HABApp.core.internals.wrapped_function.wrapped_thread import HINT_FUNC_SYNC, WrappedThreadFunction, \ - create_thread_pool, stop_thread_pool, run_in_thread_pool +from HABApp.core.internals.wrapped_function.wrapped_thread import ( + HINT_FUNC_SYNC, + WrappedThreadFunction, + create_thread_pool, + run_in_thread_pool, + stop_thread_pool, +) def wrap_func(func: Union[HINT_FUNC_SYNC, TYPE_FUNC_ASYNC], @@ -25,7 +30,9 @@ def wrap_func(func: Union[HINT_FUNC_SYNC, TYPE_FUNC_ASYNC], type_name: str = func.__class__.__name__ except Exception: type_name = type(func) - raise ValueError(f'Callable or coroutine function expected! Got "{func}" (type {type_name:s})') + + msg = f'Callable or coroutine function expected! Got "{func}" (type {type_name:s})' + raise TypeError(msg) if iscoroutinefunction(func): return WrappedAsyncFunction(func, name=name, logger=logger, context=context) diff --git a/src/HABApp/core/items/base_item_times.py b/src/HABApp/core/items/base_item_times.py index 679b7225..64f63ea6 100644 --- a/src/HABApp/core/items/base_item_times.py +++ b/src/HABApp/core/items/base_item_times.py @@ -1,14 +1,16 @@ import logging -from typing import Generic, TypeVar, List, Union, Type +from typing import Generic, List, Type, TypeVar, Union from pendulum import DateTime from HABApp.core.asyncio import run_func_from_async + from .base_item_watch import BaseWatch, ItemNoChangeWatch, ItemNoUpdateWatch + log = logging.getLogger('HABApp') -WATCH_OBJ = TypeVar("WATCH_OBJ", bound=BaseWatch) +WATCH_OBJ = TypeVar('WATCH_OBJ', bound=BaseWatch) class ItemTimes(Generic[WATCH_OBJ]): diff --git a/src/HABApp/core/items/base_valueitem.py b/src/HABApp/core/items/base_valueitem.py index a17a4db5..1b18507d 100644 --- a/src/HABApp/core/items/base_valueitem.py +++ b/src/HABApp/core/items/base_valueitem.py @@ -12,6 +12,7 @@ from HABApp.core.items.base_item import BaseItem from HABApp.core.lib.funcs import compare as _compare + if typing.TYPE_CHECKING: datetime = datetime diff --git a/src/HABApp/core/items/item.py b/src/HABApp/core/items/item.py index 12045b08..defa0d02 100644 --- a/src/HABApp/core/items/item.py +++ b/src/HABApp/core/items/item.py @@ -2,6 +2,7 @@ from HABApp.core.internals import uses_get_item, uses_item_registry from HABApp.core.items import BaseValueItem + get_item = uses_get_item() item_registry = uses_item_registry() diff --git a/src/HABApp/core/items/item_aggregation.py b/src/HABApp/core/items/item_aggregation.py index 1b68109d..2497f996 100644 --- a/src/HABApp/core/items/item_aggregation.py +++ b/src/HABApp/core/items/item_aggregation.py @@ -6,11 +6,17 @@ import HABApp from HABApp.core.errors import ItemNotFoundException -from HABApp.core.internals import HINT_EVENT_BUS_LISTENER, wrap_func -from HABApp.core.wrapper import process_exception -from HABApp.core.internals import uses_item_registry, uses_get_item, uses_event_bus, EventBusListener -from HABApp.core.items import BaseValueItem from HABApp.core.events import EventFilter, ValueChangeEvent, ValueUpdateEvent +from HABApp.core.internals import ( + HINT_EVENT_BUS_LISTENER, + EventBusListener, + uses_event_bus, + uses_get_item, + uses_item_registry, + wrap_func, +) +from HABApp.core.items import BaseValueItem +from HABApp.core.wrapper import process_exception get_item = uses_get_item() diff --git a/src/HABApp/core/items/item_color.py b/src/HABApp/core/items/item_color.py index 5f01e432..352dd183 100644 --- a/src/HABApp/core/items/item_color.py +++ b/src/HABApp/core/items/item_color.py @@ -6,6 +6,7 @@ from HABApp.core.items import BaseValueItem from HABApp.core.lib import hsb_to_rgb, rgb_to_hsb + HUE_FACTOR = 360 PERCENT_FACTOR = 100 diff --git a/src/HABApp/core/items/tmp_data.py b/src/HABApp/core/items/tmp_data.py index d5e55540..ccd05f77 100644 --- a/src/HABApp/core/items/tmp_data.py +++ b/src/HABApp/core/items/tmp_data.py @@ -4,8 +4,10 @@ import HABApp from HABApp.core.lib import PendingFuture + from .base_item_watch import BaseWatch + if typing.TYPE_CHECKING: from .base_item import BaseItem diff --git a/src/HABApp/core/lib/exceptions/format.py b/src/HABApp/core/lib/exceptions/format.py index 965c0a50..b4dea245 100644 --- a/src/HABApp/core/lib/exceptions/format.py +++ b/src/HABApp/core/lib/exceptions/format.py @@ -1,9 +1,10 @@ from traceback import format_exception as _format_exception from types import TracebackType -from typing import Tuple, Union, Any, List, Type +from typing import Any, List, Tuple, Type, Union from HABApp.core.const.const import PYTHON_310 + if PYTHON_310: from typing import TypeAlias else: @@ -59,7 +60,7 @@ def format_exception(e: HINT_EXCEPTION) -> List[str]: else: # repeated frames in case of recursion if added: - tb.append(f"... {frame_info.description} ...\n") + tb.append(f'... {frame_info.description} ...\n') # add a short traceback tb.append(SEPARATOR_NEW_FRAME) diff --git a/src/HABApp/core/lib/exceptions/format_frame.py b/src/HABApp/core/lib/exceptions/format_frame.py index 0d57c682..7e8836c3 100644 --- a/src/HABApp/core/lib/exceptions/format_frame.py +++ b/src/HABApp/core/lib/exceptions/format_frame.py @@ -1,11 +1,12 @@ import re from typing import List -from stack_data import FrameInfo, LINE_GAP +from stack_data import LINE_GAP, FrameInfo -from .const import SEPARATOR_NEW_FRAME, PRE_INDENT +from .const import PRE_INDENT, SEPARATOR_NEW_FRAME from .format_frame_vars import format_frame_variables + SUPPRESSED_HABAPP_PATHS = ( # This exception formatter re.compile(r'[/\\]HABApp[/\\]core[/\\]lib[/\\]exceptions[/\\]'), diff --git a/src/HABApp/core/lib/exceptions/format_frame_vars.py b/src/HABApp/core/lib/exceptions/format_frame_vars.py index 3efb2bde..8cc7259b 100644 --- a/src/HABApp/core/lib/exceptions/format_frame_vars.py +++ b/src/HABApp/core/lib/exceptions/format_frame_vars.py @@ -1,15 +1,17 @@ import ast import datetime -from inspect import ismodule, isclass +from inspect import isclass, ismodule from pathlib import Path -from typing import List, Tuple, Callable, Any, Set +from typing import Any, Callable, List, Set, Tuple +from easyconfig.config_objs import ConfigObj from immutables import Map from stack_data import Variable -from HABApp.core.const.json import load_json, dump_json -from easyconfig.config_objs import ConfigObj -from .const import SEPARATOR_VARIABLES, PRE_INDENT +from HABApp.core.const.json import dump_json, load_json + +from .const import PRE_INDENT, SEPARATOR_VARIABLES + # don't show these types in the traceback SKIPPED_TYPES = ( @@ -117,6 +119,6 @@ def format_frame_variables(tb: List[str], stack_variables: List[Variable]): tb.append(SEPARATOR_VARIABLES) for name, value in variables.items(): - tb.append(f'{" " * (PRE_INDENT + 1)}{name} = {repr(value)}') + tb.append(f'{" " * (PRE_INDENT + 1)}{name} = {value!r}') tb.append(SEPARATOR_VARIABLES) diff --git a/src/HABApp/core/lib/funcs.py b/src/HABApp/core/lib/funcs.py index 2f59f87c..3d0e3721 100644 --- a/src/HABApp/core/lib/funcs.py +++ b/src/HABApp/core/lib/funcs.py @@ -1,9 +1,10 @@ -from pathlib import Path -from typing import List, Iterable, TYPE_CHECKING import operator as _operator +from pathlib import Path +from typing import TYPE_CHECKING, Iterable, List from HABApp.core.const import MISSING + if TYPE_CHECKING: import HABApp diff --git a/src/HABApp/core/lib/parameters/positive_time_diff.py b/src/HABApp/core/lib/parameters/positive_time_diff.py index 6c7737c2..a6bcdd4a 100644 --- a/src/HABApp/core/lib/parameters/positive_time_diff.py +++ b/src/HABApp/core/lib/parameters/positive_time_diff.py @@ -1,6 +1,7 @@ from datetime import timedelta from typing import Union + TH_POSITIVE_TIME_DIFF = Union[int, float, timedelta] diff --git a/src/HABApp/core/lib/pending_future.py b/src/HABApp/core/lib/pending_future.py index e9dc2271..22109d0d 100644 --- a/src/HABApp/core/lib/pending_future.py +++ b/src/HABApp/core/lib/pending_future.py @@ -1,12 +1,12 @@ import asyncio import typing -from asyncio import Task, sleep, run_coroutine_threadsafe, create_task +from asyncio import Task, create_task, run_coroutine_threadsafe, sleep from typing import Any, Awaitable, Callable, Optional from HABApp.core.const import loop -# todo: switch to time.monotonic for measurements instead of fixed sleep time +# TODO: switch to time.monotonic for measurements instead of fixed sleep time class PendingFuture: def __init__(self, future: Callable[[], Awaitable[Any]], secs: typing.Union[int, float]): diff --git a/src/HABApp/core/lib/priority_list.py b/src/HABApp/core/lib/priority_list.py index af3e0421..b8477a65 100644 --- a/src/HABApp/core/lib/priority_list.py +++ b/src/HABApp/core/lib/priority_list.py @@ -24,7 +24,7 @@ def sort_func(obj: T_ENTRY): return prio.get(key, 1), key -# Todo: Move this to the connection +# TODO: Move this to the connection class PriorityList(Generic[T]): def __init__(self): self._objs: list[T_ENTRY] = [] diff --git a/src/HABApp/core/lib/single_task.py b/src/HABApp/core/lib/single_task.py index 532b4221..b7393912 100644 --- a/src/HABApp/core/lib/single_task.py +++ b/src/HABApp/core/lib/single_task.py @@ -1,8 +1,9 @@ -from asyncio import Task, current_task, CancelledError -from typing import Callable, Awaitable, Any, Final, Optional +from asyncio import CancelledError, Task, current_task +from typing import Any, Awaitable, Callable, Final, Optional from HABApp.core.const import loop + _TASK_REFS = set() diff --git a/src/HABApp/core/logger.py b/src/HABApp/core/logger.py index b7aaf7b7..bc68bc9d 100644 --- a/src/HABApp/core/logger.py +++ b/src/HABApp/core/logger.py @@ -1,11 +1,11 @@ import logging import typing -from HABApp.core.internals import uses_post_event -from HABApp.core.lib import format_exception from HABApp.core.const.topics import TOPIC_ERRORS as _T_ERRORS from HABApp.core.const.topics import TOPIC_INFOS as _T_INFOS from HABApp.core.const.topics import TOPIC_WARNINGS as _T_WARNINGS +from HABApp.core.internals import uses_post_event +from HABApp.core.lib import format_exception post_event = uses_post_event() diff --git a/src/HABApp/core/wrapper.py b/src/HABApp/core/wrapper.py index d9e1b85e..c3d0043d 100644 --- a/src/HABApp/core/wrapper.py +++ b/src/HABApp/core/wrapper.py @@ -3,9 +3,10 @@ import logging import typing from logging import Logger + # noinspection PyProtectedMember from sys import _getframe as sys_get_frame -from typing import Union, Callable +from typing import Callable, Union from HABApp.core.const.topics import TOPIC_ERRORS as TOPIC_ERRORS from HABApp.core.const.topics import TOPIC_WARNINGS as TOPIC_WARNINGS @@ -13,6 +14,7 @@ from HABApp.core.internals import uses_post_event from HABApp.core.lib import format_exception + log = logging.getLogger('HABApp') post_event = uses_post_event() diff --git a/src/HABApp/mqtt/connection/connection.py b/src/HABApp/mqtt/connection/connection.py index f159770f..4c2ba818 100644 --- a/src/HABApp/mqtt/connection/connection.py +++ b/src/HABApp/mqtt/connection/connection.py @@ -7,11 +7,12 @@ import HABApp from HABApp.core.asyncio import AsyncContext -from HABApp.core.connections import BaseConnection, Connections, ConnectionStateToEventBusPlugin, AutoReconnectPlugin +from HABApp.core.connections import AutoReconnectPlugin, BaseConnection, Connections, ConnectionStateToEventBusPlugin from HABApp.core.connections.base_connection import AlreadyHandledException from HABApp.core.connections.base_plugin import BaseConnectionPluginConnectedTask from HABApp.core.const.const import PYTHON_310 + log = logging.getLogger('HABApp.mqtt.connection') if PYTHON_310: @@ -26,8 +27,8 @@ def setup(): config = HABApp.config.CONFIG.mqtt from HABApp.mqtt.connection.handler import CONNECTION_HANDLER - from HABApp.mqtt.connection.subscribe import SUBSCRIPTION_HANDLER from HABApp.mqtt.connection.publish import PUBLISH_HANDLER + from HABApp.mqtt.connection.subscribe import SUBSCRIPTION_HANDLER connection = Connections.add(MqttConnection()) diff --git a/src/HABApp/mqtt/connection/handler.py b/src/HABApp/mqtt/connection/handler.py index 92c692aa..569c6939 100644 --- a/src/HABApp/mqtt/connection/handler.py +++ b/src/HABApp/mqtt/connection/handler.py @@ -30,7 +30,7 @@ async def on_setup(self, connection: MqttConnection): tls_insecure: bool | None = None tls_ca_cert: str | None = None if tls_enabled := config.tls.enabled: - log.debug("TLS enabled") + log.debug('TLS enabled') # add option to specify tls certificate ca_cert = config.tls.ca_cert @@ -41,7 +41,7 @@ async def on_setup(self, connection: MqttConnection): connection.set_error() return None - log.debug(f"CA cert path: {ca_cert}") + log.debug(f'CA cert path: {ca_cert}') tls_ca_cert = str(ca_cert) # we can only set tls_insecure if we have a tls connection @@ -67,9 +67,6 @@ async def on_connecting(self, connection: MqttConnection, context: CONTEXT_TYPE) connection.log.info(f'Connecting to {context._hostname}:{context._port}') await context.__aenter__() - # TODO: remove once https://github.com/sbtinstruments/aiomqtt/issues/268 has been fixed - context.messages = context._messages() - connection.log.info('Connection successful') async def on_disconnected(self, connection: MqttConnection, context: CONTEXT_TYPE): diff --git a/src/HABApp/mqtt/connection/subscribe.py b/src/HABApp/mqtt/connection/subscribe.py index 84f05f70..d8b7e83a 100644 --- a/src/HABApp/mqtt/connection/subscribe.py +++ b/src/HABApp/mqtt/connection/subscribe.py @@ -13,6 +13,7 @@ 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 diff --git a/src/HABApp/mqtt/items/mqtt_item.py b/src/HABApp/mqtt/items/mqtt_item.py index 64ba6b4b..8bc17431 100644 --- a/src/HABApp/mqtt/items/mqtt_item.py +++ b/src/HABApp/mqtt/items/mqtt_item.py @@ -5,6 +5,7 @@ from HABApp.core.items import BaseValueItem from HABApp.mqtt.interface_sync import publish + get_item = uses_get_item() item_registry = uses_item_registry() diff --git a/src/HABApp/mqtt/items/mqtt_pair_item.py b/src/HABApp/mqtt/items/mqtt_pair_item.py index 9880d3fb..23ad41be 100644 --- a/src/HABApp/mqtt/items/mqtt_pair_item.py +++ b/src/HABApp/mqtt/items/mqtt_pair_item.py @@ -6,6 +6,7 @@ from . import MqttBaseItem + Items = uses_item_registry() diff --git a/src/HABApp/mqtt/mqtt_payload.py b/src/HABApp/mqtt/mqtt_payload.py index 6c6d724d..83065160 100644 --- a/src/HABApp/mqtt/mqtt_payload.py +++ b/src/HABApp/mqtt/mqtt_payload.py @@ -1,5 +1,5 @@ import logging -from typing import Tuple, Any, Optional +from typing import Any, Optional, Tuple from aiomqtt import Message @@ -7,6 +7,7 @@ from HABApp.core.const.log import TOPIC_EVENTS from HABApp.core.wrapper import process_exception + log = logging.getLogger(f'{TOPIC_EVENTS}.mqtt') @@ -16,7 +17,7 @@ def get_msg_payload(msg: Message) -> Tuple[Optional[str], Any]: raw = msg.payload try: - val = raw.decode("utf-8") + val = raw.decode('utf-8') except UnicodeDecodeError: # Payload ist a byte stream if log.isEnabledFor(logging.DEBUG): diff --git a/src/HABApp/openhab/connection/connection.py b/src/HABApp/openhab/connection/connection.py index 22d62672..a44bafb7 100644 --- a/src/HABApp/openhab/connection/connection.py +++ b/src/HABApp/openhab/connection/connection.py @@ -10,6 +10,7 @@ from HABApp.core.const.const import PYTHON_310 from HABApp.core.items.base_valueitem import datetime + if PYTHON_310: from typing import TypeAlias else: @@ -52,10 +53,18 @@ def setup(): config = HABApp.config.CONFIG.openhab from HABApp.openhab.connection.handler import HANDLER as CONNECTION_HANDLER - from HABApp.openhab.connection.plugins import (WaitForStartlevelPlugin, LoadOpenhabItemsPlugin, - SseEventListenerPlugin, OUTGOING_PLUGIN, LoadTransformationsPlugin, - WaitForPersistenceRestore, PingPlugin, ThingOverviewPlugin, - TextualThingConfigPlugin, BrokenLinksPlugin) + from HABApp.openhab.connection.plugins import ( + OUTGOING_PLUGIN, + BrokenLinksPlugin, + LoadOpenhabItemsPlugin, + LoadTransformationsPlugin, + PingPlugin, + SseEventListenerPlugin, + TextualThingConfigPlugin, + ThingOverviewPlugin, + WaitForPersistenceRestore, + WaitForStartlevelPlugin, + ) connection = Connections.add(OpenhabConnection()) connection.register_plugin(CONNECTION_HANDLER) @@ -86,8 +95,8 @@ def __init__(self): def is_silent_exception(self, e: Exception): return isinstance(e, ( - # aiohttp Exceptions - aiohttp.ClientPayloadError, aiohttp.ClientConnectorError, aiohttp.ClientOSError, + # https://docs.aiohttp.org/en/stable/client_reference.html#client-exceptions + aiohttp.ClientError, # aiohttp_sse_client Exceptions ConnectionRefusedError, ConnectionError, ConnectionAbortedError) diff --git a/src/HABApp/openhab/connection/handler/func_async.py b/src/HABApp/openhab/connection/handler/func_async.py index 6f0dbbc9..ccdc1c58 100644 --- a/src/HABApp/openhab/connection/handler/func_async.py +++ b/src/HABApp/openhab/connection/handler/func_async.py @@ -7,17 +7,33 @@ from HABApp.core.const.json import decode_struct from HABApp.core.internals import ItemRegistryItem -from HABApp.openhab.definitions.rest import PersistenceServiceResp -from HABApp.openhab.definitions.rest import RootResp, SystemInfoRootResp, ItemResp, ShortItemResp, ItemHistoryResp, \ - ItemChannelLinkResp -from HABApp.openhab.definitions.rest import ThingResp -from HABApp.openhab.definitions.rest import TransformationResp -from HABApp.openhab.errors import (ThingNotFoundError, ItemNotFoundError, ItemNotEditableError, - MetadataNotEditableError, ThingNotEditableError, TransformationsRequestError, - PersistenceRequestError, LinkRequestError, LinkNotFoundError, LinkNotEditableError) -from . import convert_to_oh_type -from .handler import get, put, delete, post +from HABApp.openhab.definitions.rest import ( + ItemChannelLinkResp, + ItemHistoryResp, + ItemResp, + PersistenceServiceResp, + RootResp, + ShortItemResp, + SystemInfoRootResp, + ThingResp, + TransformationResp, +) +from HABApp.openhab.errors import ( + ItemNotEditableError, + ItemNotFoundError, + LinkNotEditableError, + LinkNotFoundError, + LinkRequestError, + MetadataNotEditableError, + PersistenceRequestError, + ThingNotEditableError, + ThingNotFoundError, + TransformationsRequestError, +) + from ...definitions.rest.habapp_data import get_api_vals, load_habapp_meta +from . import convert_to_oh_type +from .handler import delete, get, post, put # ---------------------------------------------------------------------------------------------------------------------- @@ -278,7 +294,7 @@ def __get_item_link_url(item: str | ItemRegistryItem, channel: str) -> str: # rest/links/ endpoint needs the channel to be url encoded # (AAAA:BBBB:CCCC:0#NAME -> AAAA%3ABBBB%3ACCCC%3A0%23NAME) # otherwise the REST-api returns HTTP-Status 500 InternalServerError - return '/rest/links/' + quote_url(f"{item:s}/{channel:s}") + return '/rest/links/' + quote_url(f'{item:s}/{channel:s}') async def async_get_link(item: str | ItemRegistryItem, channel: str) -> ItemChannelLinkResp: diff --git a/src/HABApp/openhab/connection/handler/func_sync.py b/src/HABApp/openhab/connection/handler/func_sync.py index 8ae1b0ee..fcc1ce7b 100644 --- a/src/HABApp/openhab/connection/handler/func_sync.py +++ b/src/HABApp/openhab/connection/handler/func_sync.py @@ -5,14 +5,26 @@ from HABApp.core.asyncio import run_coro_from_thread from HABApp.core.internals import ItemRegistryItem -from .func_async import async_create_item, async_get_item, \ - async_get_thing, async_set_thing_enabled, \ - async_set_metadata, async_remove_metadata, \ - async_remove_item, async_item_exists, async_get_persistence_data, async_set_persistence_data, \ - async_get_persistence_services, async_get_link, async_create_link, async_remove_link from HABApp.openhab import definitions from HABApp.openhab.definitions.helpers import OpenhabPersistenceData -from HABApp.openhab.definitions.rest import ItemResp, ItemChannelLinkResp +from HABApp.openhab.definitions.rest import ItemChannelLinkResp, ItemResp + +from .func_async import ( + async_create_item, + async_create_link, + async_get_item, + async_get_link, + async_get_persistence_data, + async_get_persistence_services, + async_get_thing, + async_item_exists, + async_remove_item, + async_remove_link, + async_remove_metadata, + async_set_metadata, + async_set_persistence_data, + async_set_thing_enabled, +) # ---------------------------------------------------------------------------------------------------------------------- diff --git a/src/HABApp/openhab/connection/handler/handler.py b/src/HABApp/openhab/connection/handler/handler.py index 4fe2b67e..85bea717 100644 --- a/src/HABApp/openhab/connection/handler/handler.py +++ b/src/HABApp/openhab/connection/handler/handler.py @@ -4,7 +4,7 @@ import aiohttp from aiohttp.client import ClientResponse, _RequestContextManager -from aiohttp.hdrs import METH_GET, METH_POST, METH_PUT, METH_DELETE +from aiohttp.hdrs import METH_DELETE, METH_GET, METH_POST, METH_PUT from HABApp.config import CONFIG from HABApp.core.connections import BaseConnectionPlugin @@ -12,7 +12,7 @@ from HABApp.core.connections.base_connection import AlreadyHandledException from HABApp.core.const.json import dump_json from HABApp.openhab.connection.connection import OpenhabConnection, OpenhabContext -from HABApp.openhab.errors import OpenhabDisconnectedError, OpenhabCredentialsInvalidError +from HABApp.openhab.errors import OpenhabCredentialsInvalidError, OpenhabDisconnectedError # noinspection PyProtectedMember diff --git a/src/HABApp/openhab/connection/plugins/events_sse.py b/src/HABApp/openhab/connection/plugins/events_sse.py index 9835350b..df302437 100644 --- a/src/HABApp/openhab/connection/plugins/events_sse.py +++ b/src/HABApp/openhab/connection/plugins/events_sse.py @@ -17,6 +17,7 @@ from HABApp.openhab.connection.connection import OpenhabConnection from HABApp.openhab.process_events import on_sse_event + Items = uses_item_registry() @@ -72,9 +73,10 @@ async def sse_task(self): continue # https://github.com/spacemanspiff2007/HABApp/issues/437 + # https://github.com/spacemanspiff2007/HABApp/issues/449 # openHAB will automatically restore the future states of the item # which means we can safely ignore these events because we will see the ItemStateUpdatedEvent - if e_type == 'ItemTimeSeriesUpdatedEvent': + if e_type in ('ItemTimeSeriesUpdatedEvent', 'ItemTimeSeriesEvent'): continue # process diff --git a/src/HABApp/openhab/connection/plugins/load_items.py b/src/HABApp/openhab/connection/plugins/load_items.py index 261bf4b5..04a6a7cb 100644 --- a/src/HABApp/openhab/connection/plugins/load_items.py +++ b/src/HABApp/openhab/connection/plugins/load_items.py @@ -11,12 +11,17 @@ from HABApp.core.internals import uses_item_registry from HABApp.openhab.connection.connection import OpenhabConnection, OpenhabContext from HABApp.openhab.connection.handler import map_null_str -from HABApp.openhab.connection.handler.func_async import async_get_items, \ - async_get_all_items_state, async_get_things +from HABApp.openhab.connection.handler.func_async import async_get_all_items_state, async_get_items, async_get_things from HABApp.openhab.definitions import QuantityValue from HABApp.openhab.definitions.rest import ThingResp -from HABApp.openhab.item_to_reg import fresh_item_sync, add_to_registry, remove_from_registry, add_thing_to_registry, \ - remove_thing_from_registry +from HABApp.openhab.item_to_reg import ( + add_thing_to_registry, + add_to_registry, + fresh_item_sync, + remove_from_registry, + remove_thing_from_registry, +) + log = logging.getLogger('HABApp.openhab.items') Items = uses_item_registry() @@ -120,7 +125,7 @@ async def load_things(self, context: OpenhabContext): t = add_thing_to_registry(thing) created_things[t.name] = (t, t.last_update) - context.created_items.update(created_things) + context.created_things.update(created_things) # remove things which were deleted ist = set(Items.get_item_names()) @@ -158,6 +163,6 @@ def thing_changed(old: HABApp.openhab.items.Thing, new: ThingResp) -> bool: old.status_detail != new.status.detail or \ old.status_description != ('' if not new.status.description else new.status.description) or \ old.label != new.label or \ - old.location != new.configuration or \ + old.location != new.location or \ old.configuration != new.configuration or \ old.properties != new.properties diff --git a/src/HABApp/openhab/connection/plugins/overview_broken_links.py b/src/HABApp/openhab/connection/plugins/overview_broken_links.py index e2e00e97..3a0dffb8 100644 --- a/src/HABApp/openhab/connection/plugins/overview_broken_links.py +++ b/src/HABApp/openhab/connection/plugins/overview_broken_links.py @@ -10,6 +10,7 @@ from HABApp.openhab.connection.connection import OpenhabConnection from HABApp.openhab.connection.handler.func_async import async_get_links, async_get_things + PING_CONFIG: Final = CONFIG.openhab.ping Items = uses_item_registry() diff --git a/src/HABApp/openhab/connection/plugins/overview_things.py b/src/HABApp/openhab/connection/plugins/overview_things.py index f24c5704..cb014ce9 100644 --- a/src/HABApp/openhab/connection/plugins/overview_things.py +++ b/src/HABApp/openhab/connection/plugins/overview_things.py @@ -11,6 +11,7 @@ from HABApp.openhab.connection.connection import OpenhabConnection from HABApp.openhab.definitions.helpers.log_table import Table + PING_CONFIG: Final = CONFIG.openhab.ping Items = uses_item_registry() diff --git a/src/HABApp/openhab/connection/plugins/ping.py b/src/HABApp/openhab/connection/plugins/ping.py index 1e5113f6..2fb2cd3b 100644 --- a/src/HABApp/openhab/connection/plugins/ping.py +++ b/src/HABApp/openhab/connection/plugins/ping.py @@ -8,10 +8,11 @@ import HABApp.openhab.events from HABApp.config import CONFIG from HABApp.core.connections import BaseConnectionPlugin -from HABApp.core.internals import uses_item_registry, uses_event_bus +from HABApp.core.internals import uses_event_bus, uses_item_registry from HABApp.core.lib import SingleTask from HABApp.openhab.connection.connection import OpenhabConnection + PING_CONFIG: Final = CONFIG.openhab.ping log = logging.getLogger('HABApp.openhab.items') diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/_log.py b/src/HABApp/openhab/connection/plugins/plugin_things/_log.py index 253a753a..bf86b71d 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/_log.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/_log.py @@ -1,6 +1,7 @@ import logging from typing import Final as _Final + log: _Final = logging.getLogger('HABApp.openhab.thing') log_cfg: _Final = log.getChild('cfg') log_item: _Final = log.getChild('item') 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 51a2a6df..ae6df480 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/cfg_validator.py @@ -1,18 +1,20 @@ import re import typing from dataclasses import dataclass -from typing import Iterator, Optional, Union, Dict, List +from typing import Dict, Iterator, List, Optional, Union -from pydantic import BaseModel as _BaseModel, Field, ValidationError, field_validator, \ - ConfigDict, TypeAdapter, AfterValidator +from pydantic import AfterValidator, ConfigDict, Field, TypeAdapter, ValidationError, field_validator +from pydantic import BaseModel as _BaseModel from HABApp.core.const.const import PYTHON_310 from HABApp.core.logger import HABAppError from HABApp.openhab.connection.plugins.plugin_things.filters import ChannelFilter, ThingFilter from HABApp.openhab.connection.plugins.plugin_things.str_builder import StrBuilder from HABApp.openhab.definitions import ITEM_TYPES + from ._log import log + if PYTHON_310: from typing import Annotated else: diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter.py b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter.py index 0926a931..31c75f47 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter.py @@ -1,7 +1,8 @@ -from typing import Final, Iterable, List, Dict, TypeVar +from typing import Dict, Final, Iterable, List, TypeVar from HABApp.core.const.const import PYTHON_311 + if not PYTHON_311: from typing_extensions import Self else: diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter_builder.py b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter_builder.py index d0018a82..20c30c22 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter_builder.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/formatter_builder.py @@ -1,6 +1,7 @@ -from typing import Final, Optional, Callable, Any +from typing import Any, Callable, Final, Optional from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import UserItem + from .formatter import TYPE_FORMATTER, EmptyFormatter, ValueFormatter diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/writer.py b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/writer.py index e6ff7ac1..cc795fff 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/writer.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/file_writer/writer.py @@ -1,12 +1,19 @@ import re from pathlib import Path -from typing import Iterable, Optional, List, Dict +from typing import Dict, Iterable, List, Optional from HABApp.core.const.const import PYTHON_311 from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import UserItem + from .formatter import FormatterScope -from .formatter_builder import ValueFormatterBuilder, MultipleValueFormatterBuilder, ConstValueFormatterBuilder, \ - MetadataFormatter, LinkFormatter +from .formatter_builder import ( + ConstValueFormatterBuilder, + LinkFormatter, + MetadataFormatter, + MultipleValueFormatterBuilder, + ValueFormatterBuilder, +) + if not PYTHON_311: from typing_extensions import Self diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/filters.py b/src/HABApp/openhab/connection/plugins/plugin_things/filters.py index b4ef21b3..6b522633 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/filters.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/filters.py @@ -1,10 +1,12 @@ import logging import re -from typing import Dict, Iterator, List, Any +from typing import Any, Dict, Iterator, List from HABApp.openhab.definitions.helpers.log_table import Table + from ._log import log + THING_ALIAS: Dict[str, str] = { 'thing_uid': 'UID', 'thing_type': 'thingTypeUID', diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/item_worker.py b/src/HABApp/openhab/connection/plugins/plugin_things/item_worker.py index faf32650..9b8b8e28 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/item_worker.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/item_worker.py @@ -1,11 +1,20 @@ -from typing import Set, Dict +from typing import Dict, Set import HABApp -from HABApp.openhab.connection.handler.func_async import async_set_habapp_metadata, async_create_item, \ - async_remove_item, async_create_link, async_get_items, \ - async_remove_link, async_remove_metadata, async_set_metadata, async_get_item_with_habapp_meta +from HABApp.openhab.connection.handler.func_async import ( + async_create_item, + async_create_link, + async_get_item_with_habapp_meta, + async_get_items, + async_remove_item, + async_remove_link, + async_remove_metadata, + async_set_habapp_metadata, + async_set_metadata, +) from HABApp.openhab.definitions.rest import ItemResp from HABApp.openhab.definitions.rest.habapp_data import HABAppThingPluginData, load_habapp_meta + from ._log import log_item as log from .cfg_validator import UserItem diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/plugin_things.py b/src/HABApp/openhab/connection/plugins/plugin_things/plugin_things.py index 111167b2..80208885 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/plugin_things.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/plugin_things.py @@ -13,14 +13,19 @@ from HABApp.core.files.folders import add_folder as add_habapp_folder from HABApp.core.files.watcher import AggregatingAsyncEventHandler from HABApp.core.lib import PendingFuture -from HABApp.core.logger import log_warning, HABAppError +from HABApp.core.logger import HABAppError, log_warning from HABApp.openhab.connection.connection import OpenhabConnection -from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import validate_cfg, InvalidItemNameError -from HABApp.openhab.connection.plugins.plugin_things.filters import THING_ALIAS, CHANNEL_ALIAS -from HABApp.openhab.connection.plugins.plugin_things.filters import apply_filters, log_overview +from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import InvalidItemNameError, validate_cfg +from HABApp.openhab.connection.plugins.plugin_things.filters import ( + CHANNEL_ALIAS, + THING_ALIAS, + apply_filters, + log_overview, +) + from ._log import log from .file_writer import ItemsFileWriter -from .item_worker import create_item, cleanup_items +from .item_worker import cleanup_items, create_item from .thing_worker import update_thing_cfg diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/str_builder.py b/src/HABApp/openhab/connection/plugins/plugin_things/str_builder.py index 15e8bc87..8b0d3aa0 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/str_builder.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/str_builder.py @@ -1,7 +1,7 @@ import re -from typing import Optional, Dict, Tuple, Pattern, Union +from typing import Dict, Optional, Pattern, Tuple, Union -from .filters import THING_ALIAS, CHANNEL_ALIAS +from .filters import CHANNEL_ALIAS, THING_ALIAS SEPARATOR = ',' diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/thing_config.py b/src/HABApp/openhab/connection/plugins/plugin_things/thing_config.py index cbc730fb..740e8e59 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/thing_config.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/thing_config.py @@ -5,6 +5,7 @@ import HABApp from HABApp.core.logger import log_error + from ._log import log_cfg as log diff --git a/src/HABApp/openhab/connection/plugins/plugin_things/thing_worker.py b/src/HABApp/openhab/connection/plugins/plugin_things/thing_worker.py index 0fe67b46..11abd736 100644 --- a/src/HABApp/openhab/connection/plugins/plugin_things/thing_worker.py +++ b/src/HABApp/openhab/connection/plugins/plugin_things/thing_worker.py @@ -3,6 +3,7 @@ from HABApp.core.logger import HABAppError from HABApp.openhab.definitions.helpers.log_table import Table + from ._log import log_cfg as log from .thing_config import ThingConfigChanger diff --git a/src/HABApp/openhab/definitions/helpers/log_table.py b/src/HABApp/openhab/definitions/helpers/log_table.py index f2f0c1eb..eba968f6 100644 --- a/src/HABApp/openhab/definitions/helpers/log_table.py +++ b/src/HABApp/openhab/definitions/helpers/log_table.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from typing import Optional, Dict, List, Union, Tuple, Any +from typing import Any, Dict, List, Optional, Tuple, Union class Column: diff --git a/src/HABApp/openhab/definitions/helpers/persistence_data.py b/src/HABApp/openhab/definitions/helpers/persistence_data.py index 5cbc5613..187472fc 100644 --- a/src/HABApp/openhab/definitions/helpers/persistence_data.py +++ b/src/HABApp/openhab/definitions/helpers/persistence_data.py @@ -1,6 +1,8 @@ from datetime import datetime from typing import Optional, Union +from fastnumbers import try_real + from HABApp.openhab.definitions.rest import ItemHistoryResp @@ -18,19 +20,7 @@ def from_resp(cls, data: ItemHistoryResp) -> 'OpenhabPersistenceData': for entry in data.data: # calc as timestamp time = entry.time / 1000 - state = entry.state - if '.' in state: - try: - state = float(state) - except ValueError: - pass - else: - try: - state = int(state) - except ValueError: - pass - - c.data[time] = state + c.data[time] = try_real(entry.state) return c def get_data(self, start_date: OPTIONAL_DT = None, end_date: OPTIONAL_DT = None): diff --git a/src/HABApp/openhab/definitions/rest/habapp_data.py b/src/HABApp/openhab/definitions/rest/habapp_data.py index 9256c41f..980aa80c 100644 --- a/src/HABApp/openhab/definitions/rest/habapp_data.py +++ b/src/HABApp/openhab/definitions/rest/habapp_data.py @@ -1,5 +1,5 @@ import typing -from typing import List, Optional, ClassVar +from typing import ClassVar, List, Optional from pydantic import BaseModel diff --git a/src/HABApp/openhab/definitions/rest/persistence.py b/src/HABApp/openhab/definitions/rest/persistence.py index a4459d69..e8373bff 100644 --- a/src/HABApp/openhab/definitions/rest/persistence.py +++ b/src/HABApp/openhab/definitions/rest/persistence.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import List, Optional from msgspec import Struct, field diff --git a/src/HABApp/openhab/definitions/rest/root.py b/src/HABApp/openhab/definitions/rest/root.py index f235f8dd..72e8496b 100644 --- a/src/HABApp/openhab/definitions/rest/root.py +++ b/src/HABApp/openhab/definitions/rest/root.py @@ -2,6 +2,7 @@ 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/definitions/rest/systeminfo.py b/src/HABApp/openhab/definitions/rest/systeminfo.py index cf73e0de..6db4aa92 100644 --- a/src/HABApp/openhab/definitions/rest/systeminfo.py +++ b/src/HABApp/openhab/definitions/rest/systeminfo.py @@ -21,7 +21,7 @@ class SystemInfoResp(Struct, rename='camel', kw_only=True): total_memory: int start_level: int - uptime: int = -1 # todo: remove default if we go OH4.1 only + uptime: int = -1 # TODO: remove default if we go OH4.1 only class SystemInfoRootResp(Struct, rename='camel'): diff --git a/src/HABApp/openhab/definitions/rest/things.py b/src/HABApp/openhab/definitions/rest/things.py index 7b6c9dae..57f9d10e 100644 --- a/src/HABApp/openhab/definitions/rest/things.py +++ b/src/HABApp/openhab/definitions/rest/things.py @@ -1,9 +1,8 @@ -from typing import Dict, List -from typing import Optional, Any +from typing import Any, Dict, List, Optional from msgspec import Struct, field -from HABApp.openhab.definitions import ThingStatusEnum, ThingStatusDetailEnum +from HABApp.openhab.definitions import ThingStatusDetailEnum, ThingStatusEnum class ChannelResp(Struct, kw_only=True): diff --git a/src/HABApp/openhab/definitions/things.py b/src/HABApp/openhab/definitions/things.py index 6f36aabb..1eb2a04b 100644 --- a/src/HABApp/openhab/definitions/things.py +++ b/src/HABApp/openhab/definitions/things.py @@ -1,6 +1,7 @@ -from HABApp.core.const.const import StrEnum from typing import Final +from HABApp.core.const.const import StrEnum + # https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingStatus.java class ThingStatusEnum(StrEnum): diff --git a/src/HABApp/openhab/definitions/values.py b/src/HABApp/openhab/definitions/values.py index 72b34b0d..b6adee6a 100644 --- a/src/HABApp/openhab/definitions/values.py +++ b/src/HABApp/openhab/definitions/values.py @@ -1,12 +1,14 @@ from base64 import b64decode -from typing import Tuple, Union +from typing import Final, Tuple + +from fastnumbers import real from HABApp.core.events import ComplexEventValue class OnOffValue(ComplexEventValue): - ON = 'ON' - OFF = 'OFF' + ON: Final = 'ON' + OFF: Final = 'OFF' def __init__(self, value): super().__init__(value) @@ -28,8 +30,8 @@ def __str__(self): class OpenClosedValue(ComplexEventValue): - OPEN = 'OPEN' - CLOSED = 'CLOSED' + OPEN: Final = 'OPEN' + CLOSED: Final = 'CLOSED' def __init__(self, value): super().__init__(value) @@ -92,10 +94,7 @@ def split_unit(value: str) -> Tuple[str, str]: def __init__(self, value: str): value, unit = QuantityValue.split_unit(value) - try: - val: Union[int, float] = int(value) - except ValueError: - val = float(value) + val = real(value) super().__init__(val) self.unit = unit diff --git a/src/HABApp/openhab/events/event_filters.py b/src/HABApp/openhab/events/event_filters.py index aa89c117..6feda76e 100644 --- a/src/HABApp/openhab/events/event_filters.py +++ b/src/HABApp/openhab/events/event_filters.py @@ -2,10 +2,11 @@ from HABApp.core.const import MISSING from HABApp.core.events.filter.event import TypeBoundEventFilter -from . import ItemStateChangedEvent, ItemStateEvent, ItemStateUpdatedEvent, ItemCommandEvent +from . import ItemCommandEvent, ItemStateChangedEvent, ItemStateEvent, ItemStateUpdatedEvent -# Todo: Drop this when we go OH4.0 only + +# TODO: Drop this when we go OH4.0 only class ItemStateEventFilter(TypeBoundEventFilter): def __init__(self, value: Any = MISSING): super().__init__(ItemStateEvent, value=value) diff --git a/src/HABApp/openhab/events/item_events.py b/src/HABApp/openhab/events/item_events.py index d677bfaf..4a5c3209 100644 --- a/src/HABApp/openhab/events/item_events.py +++ b/src/HABApp/openhab/events/item_events.py @@ -1,8 +1,9 @@ -from typing import Any, FrozenSet, Optional, Final +from typing import Any, Final, FrozenSet, Optional import HABApp.core -from .base_event import OpenhabEvent + from ..map_values import map_openhab_values +from .base_event import OpenhabEvent class ItemStateEvent(OpenhabEvent, HABApp.core.events.ValueUpdateEvent): @@ -100,8 +101,8 @@ def from_dict(cls, topic: str, payload: dict): ) def __repr__(self): - tags = f' {{{", ".join(sorted(self.tags))}}}' if self.tags else "" - grps = f' {{{", ".join(sorted(self.groups))}}}' if self.groups else "" + tags = f' {{{", ".join(sorted(self.tags))}}}' if self.tags else '' + grps = f' {{{", ".join(sorted(self.groups))}}}' if self.groups else '' return f'<{self.__class__.__name__} name: {self.name}, type: {self.type}, tags:{tags}, groups:{grps}>' @@ -142,8 +143,8 @@ def from_dict(cls, topic: str, payload: dict): ) def __repr__(self): - tags = f' {{{", ".join(sorted(self.tags))}}}' if self.tags else "" - grps = f' {{{", ".join(sorted(self.groups))}}}' if self.groups else "" + tags = f' {{{", ".join(sorted(self.tags))}}}' if self.tags else '' + grps = f' {{{", ".join(sorted(self.groups))}}}' if self.groups else '' return f'<{self.__class__.__name__} name: {self.name}, type: {self.type}, tags:{tags}, groups:{grps}>' diff --git a/src/HABApp/openhab/events/thing_events.py b/src/HABApp/openhab/events/thing_events.py index d8d77673..489f2737 100644 --- a/src/HABApp/openhab/events/thing_events.py +++ b/src/HABApp/openhab/events/thing_events.py @@ -1,8 +1,8 @@ -from typing import List, Dict, Any, Final +from typing import Any, Dict, Final, List -from .base_event import OpenhabEvent -from ..definitions import ThingStatusEnum, ThingStatusDetailEnum +from ..definitions import ThingStatusDetailEnum, ThingStatusEnum from ..definitions.things import THING_STATUS_DEFAULT, THING_STATUS_DETAIL_DEFAULT +from .base_event import OpenhabEvent class ThingStatusInfoEvent(OpenhabEvent): diff --git a/src/HABApp/openhab/interface_async.py b/src/HABApp/openhab/interface_async.py index 1dd6915b..9a56dd65 100644 --- a/src/HABApp/openhab/interface_async.py +++ b/src/HABApp/openhab/interface_async.py @@ -1,8 +1,17 @@ -from HABApp.openhab.connection.handler.func_async import \ - async_get_root, async_get_system_info, async_get_uuid, \ - async_get_things, async_get_thing, async_set_thing_cfg, async_set_thing_enabled,\ - async_get_items, async_item_exists, async_get_item, async_remove_item, async_create_item, \ - async_set_metadata, async_remove_metadata - - -from HABApp.openhab.connection.plugins import async_send_command, async_post_update +from HABApp.openhab.connection.handler.func_async import ( + async_create_item, + async_get_item, + async_get_items, + async_get_root, + async_get_system_info, + async_get_thing, + async_get_things, + async_get_uuid, + async_item_exists, + async_remove_item, + async_remove_metadata, + async_set_metadata, + async_set_thing_cfg, + async_set_thing_enabled, +) +from HABApp.openhab.connection.plugins import async_post_update, async_send_command diff --git a/src/HABApp/openhab/interface_sync.py b/src/HABApp/openhab/interface_sync.py index 3b2fd95d..3db76135 100644 --- a/src/HABApp/openhab/interface_sync.py +++ b/src/HABApp/openhab/interface_sync.py @@ -1,9 +1,17 @@ -from HABApp.openhab.connection.handler.func_sync import \ - get_thing, set_thing_enabled,\ - item_exists, get_item, remove_item, create_item, \ - set_metadata, remove_metadata, \ - get_persistence_services, get_persistence_data, set_persistence_data, \ - get_link, remove_link, create_link - - -from HABApp.openhab.connection.plugins import send_command, post_update +from HABApp.openhab.connection.handler.func_sync import ( + create_item, + create_link, + get_item, + get_link, + get_persistence_data, + get_persistence_services, + get_thing, + item_exists, + remove_item, + remove_link, + remove_metadata, + set_metadata, + set_persistence_data, + set_thing_enabled, +) +from HABApp.openhab.connection.plugins import post_update, send_command diff --git a/src/HABApp/openhab/item_to_reg.py b/src/HABApp/openhab/item_to_reg.py index d1050604..248ecfb2 100644 --- a/src/HABApp/openhab/item_to_reg.py +++ b/src/HABApp/openhab/item_to_reg.py @@ -1,14 +1,18 @@ import logging -from typing import Dict, Set, Tuple, TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Dict, Set, Tuple, Union from immutables import Map import HABApp - from HABApp.core.internals import uses_item_registry from HABApp.core.logger import log_warning -from HABApp.openhab.definitions.things import THING_STATUS_DEFAULT, THING_STATUS_DETAIL_DEFAULT, ThingStatusEnum, \ - ThingStatusDetailEnum +from HABApp.openhab.definitions.things import ( + THING_STATUS_DEFAULT, + THING_STATUS_DETAIL_DEFAULT, + ThingStatusDetailEnum, + ThingStatusEnum, +) + if TYPE_CHECKING: import HABApp.openhab.definitions.rest diff --git a/src/HABApp/openhab/items/color_item.py b/src/HABApp/openhab/items/color_item.py index 52b92a61..db4c005e 100644 --- a/src/HABApp/openhab/items/color_item.py +++ b/src/HABApp/openhab/items/color_item.py @@ -5,8 +5,10 @@ from HABApp.core.lib import hsb_to_rgb, rgb_to_hsb from HABApp.openhab.items.base_item import MetaData, OpenhabItem from HABApp.openhab.items.commands import OnOffCommand, PercentCommand + from ..definitions import HSBValue, OnOffValue, PercentValue + HUE_FACTOR = 360 PERCENT_FACTOR = 100 diff --git a/src/HABApp/openhab/items/commands.py b/src/HABApp/openhab/items/commands.py index 5452ef4e..e668ce3a 100644 --- a/src/HABApp/openhab/items/commands.py +++ b/src/HABApp/openhab/items/commands.py @@ -1,10 +1,17 @@ +from typing import Final + from HABApp.core.const.hints import HasNameAttr as _HasNameAttr from HABApp.openhab.definitions import OnOffValue, UpDownValue from HABApp.openhab.interface_sync import send_command -class OnOffCommand: +ON: Final = OnOffValue.ON +OFF: Final = OnOffValue.OFF +UP: Final = UpDownValue.UP +DOWN: Final = UpDownValue.DOWN + +class OnOffCommand: def is_on(self: _HasNameAttr) -> bool: """Test value against on-value""" raise NotImplementedError() @@ -15,11 +22,11 @@ def is_off(self: _HasNameAttr) -> bool: def on(self: _HasNameAttr): """Command item on""" - send_command(self.name, OnOffValue.ON) + send_command(self.name, ON) def off(self: _HasNameAttr): """Command item off""" - send_command(self.name, OnOffValue.OFF) + send_command(self.name, OFF) class PercentCommand: @@ -32,11 +39,11 @@ def percent(self: _HasNameAttr, value: float): class UpDownCommand: def up(self: _HasNameAttr): """Command up""" - send_command(self.name, UpDownValue.UP) + send_command(self.name, UP) def down(self: _HasNameAttr): """Command down""" - send_command(self.name, UpDownValue.DOWN) + send_command(self.name, DOWN) def is_up(self: _HasNameAttr) -> bool: """Test value against on-value""" diff --git a/src/HABApp/openhab/items/contact_item.py b/src/HABApp/openhab/items/contact_item.py index 5714b9fc..71716674 100644 --- a/src/HABApp/openhab/items/contact_item.py +++ b/src/HABApp/openhab/items/contact_item.py @@ -1,11 +1,13 @@ -from typing import Any, TYPE_CHECKING, Optional, FrozenSet, Mapping +from typing import TYPE_CHECKING, Any, FrozenSet, Mapping, Optional -from HABApp.openhab.items.base_item import OpenhabItem, MetaData -from ..definitions import OpenClosedValue -from ...core.const import MISSING -from ..errors import SendCommandNotSupported from HABApp.openhab.interface_sync import post_update +from HABApp.openhab.items.base_item import MetaData, OpenhabItem + +from ...core.const import MISSING from ...core.errors import InvalidItemValue +from ..definitions import OpenClosedValue +from ..errors import SendCommandNotSupported + if TYPE_CHECKING: Optional = Optional @@ -66,8 +68,8 @@ def closed(self): """Post an update to the item with the closed value""" return post_update(self.name, CLOSED) - def __str__(self): - return self.value + def __str__(self) -> str: + return str(self.value) def __eq__(self, other): if isinstance(other, ContactItem): diff --git a/src/HABApp/openhab/items/datetime_item.py b/src/HABApp/openhab/items/datetime_item.py index 890cf0fa..234cb539 100644 --- a/src/HABApp/openhab/items/datetime_item.py +++ b/src/HABApp/openhab/items/datetime_item.py @@ -1,8 +1,9 @@ from datetime import datetime -from typing import TYPE_CHECKING, Optional, FrozenSet, Mapping +from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional from HABApp.core.const.const import PYTHON_311 -from HABApp.openhab.items.base_item import OpenhabItem, MetaData +from HABApp.openhab.items.base_item import MetaData, OpenhabItem + if TYPE_CHECKING: Optional = Optional diff --git a/src/HABApp/openhab/items/dimmer_item.py b/src/HABApp/openhab/items/dimmer_item.py index 8e419a11..1e153fe8 100644 --- a/src/HABApp/openhab/items/dimmer_item.py +++ b/src/HABApp/openhab/items/dimmer_item.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Union +from fastnumbers import real + from HABApp.openhab.items.base_item import MetaData, OpenhabItem from HABApp.openhab.items.commands import OnOffCommand, PercentCommand @@ -29,10 +31,7 @@ class DimmerItem(OpenhabItem, OnOffCommand, PercentCommand): @staticmethod def _state_from_oh_str(state: str): - try: - return int(state) - except ValueError: - return float(state) + return real(state) def set_value(self, new_value) -> bool: @@ -58,8 +57,8 @@ def is_off(self) -> bool: """Test value against off-value""" return self.value is not None and not self.value - def __str__(self): - return self.value + def __str__(self) -> str: + return str(self.value) def __bool__(self): if self.value is None: diff --git a/src/HABApp/openhab/items/image_item.py b/src/HABApp/openhab/items/image_item.py index dc3b882e..a5da4408 100644 --- a/src/HABApp/openhab/items/image_item.py +++ b/src/HABApp/openhab/items/image_item.py @@ -1,11 +1,13 @@ from base64 import b64encode -from typing import Any, TYPE_CHECKING, Optional, FrozenSet, Mapping +from typing import TYPE_CHECKING, Any, FrozenSet, Mapping, Optional from immutables import Map -from HABApp.openhab.items.base_item import OpenhabItem, MetaData +from HABApp.openhab.items.base_item import MetaData, OpenhabItem + from ..definitions import RawValue + if TYPE_CHECKING: Optional = Optional FrozenSet = FrozenSet diff --git a/src/HABApp/openhab/items/number_item.py b/src/HABApp/openhab/items/number_item.py index 3ec37fe3..9926d315 100644 --- a/src/HABApp/openhab/items/number_item.py +++ b/src/HABApp/openhab/items/number_item.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Union +from fastnumbers import real + from HABApp.core.errors import InvalidItemValue, ItemValueIsNoneError from HABApp.openhab.definitions import QuantityValue from HABApp.openhab.items.base_item import MetaData, OpenhabItem @@ -33,10 +35,7 @@ def unit(self) -> Optional[str]: @staticmethod def _state_from_oh_str(state: str): - try: - return int(state) - except ValueError: - return float(state) + return real(state) def set_value(self, new_value) -> bool: diff --git a/src/HABApp/openhab/items/rollershutter_item.py b/src/HABApp/openhab/items/rollershutter_item.py index 71717881..d3d56b9f 100644 --- a/src/HABApp/openhab/items/rollershutter_item.py +++ b/src/HABApp/openhab/items/rollershutter_item.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Union +from fastnumbers import real + from HABApp.core.errors import InvalidItemValue from HABApp.openhab.definitions import PercentValue, UpDownValue from HABApp.openhab.items.base_item import MetaData, OpenhabItem @@ -28,10 +30,7 @@ class RollershutterItem(OpenhabItem, UpDownCommand, PercentCommand): @staticmethod def _state_from_oh_str(state: str): - try: - return int(state) - except ValueError: - return float(state) + return real(state) def set_value(self, new_value) -> bool: @@ -55,5 +54,5 @@ def is_up(self) -> bool: def is_down(self) -> bool: return self.value >= 100 - def __str__(self): - return self.value + def __str__(self) -> str: + return str(self.value) diff --git a/src/HABApp/openhab/items/string_item.py b/src/HABApp/openhab/items/string_item.py index 9a6ffbb0..0e4f3912 100644 --- a/src/HABApp/openhab/items/string_item.py +++ b/src/HABApp/openhab/items/string_item.py @@ -1,6 +1,7 @@ -from typing import TYPE_CHECKING, Optional, FrozenSet, Mapping +from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional + +from HABApp.openhab.items.base_item import MetaData, OpenhabItem -from HABApp.openhab.items.base_item import OpenhabItem, MetaData if TYPE_CHECKING: MetaData = MetaData diff --git a/src/HABApp/openhab/items/switch_item.py b/src/HABApp/openhab/items/switch_item.py index add0db75..b6851d84 100644 --- a/src/HABApp/openhab/items/switch_item.py +++ b/src/HABApp/openhab/items/switch_item.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Final, FrozenSet, Mapping, Optional, Tuple -from HABApp.core.errors import ItemValueIsNoneError, InvalidItemValue +from HABApp.core.errors import InvalidItemValue, ItemValueIsNoneError from HABApp.openhab.definitions import OnOffValue from HABApp.openhab.items.base_item import MetaData, OpenhabItem from HABApp.openhab.items.commands import OnOffCommand @@ -65,8 +65,8 @@ def toggle(self): else: raise InvalidItemValue.from_item(self, self.value) - def __str__(self): - return self.value + def __str__(self) -> str: + return str(self.value) def __eq__(self, other): if isinstance(other, SwitchItem): diff --git a/src/HABApp/openhab/items/thing_item.py b/src/HABApp/openhab/items/thing_item.py index 20a87a07..409096d1 100644 --- a/src/HABApp/openhab/items/thing_item.py +++ b/src/HABApp/openhab/items/thing_item.py @@ -5,7 +5,7 @@ from pendulum import now as pd_now from HABApp.core.items import BaseItem -from HABApp.openhab.definitions import ThingStatusEnum, ThingStatusDetailEnum +from HABApp.openhab.definitions import ThingStatusDetailEnum, ThingStatusEnum from HABApp.openhab.definitions.things import THING_STATUS_DEFAULT, THING_STATUS_DETAIL_DEFAULT from HABApp.openhab.events import ThingConfigStatusInfoEvent, ThingStatusInfoEvent, ThingUpdatedEvent from HABApp.openhab.interface_sync import set_thing_enabled @@ -73,7 +73,7 @@ def process_event(self, event): self.properties = new_properties = Map(event.properties) self.__update_timestamps( - old_label != new_label or old_location != new_location or # noqa: W504 + old_label != new_label or old_location != new_location or old_configuration != new_configuration or old_properties != new_properties ) elif isinstance(event, ThingConfigStatusInfoEvent): diff --git a/src/HABApp/openhab/items/tuple_items.py b/src/HABApp/openhab/items/tuple_items.py index afdc0ec8..189534c8 100644 --- a/src/HABApp/openhab/items/tuple_items.py +++ b/src/HABApp/openhab/items/tuple_items.py @@ -1,7 +1,8 @@ -from typing import FrozenSet, Mapping, Optional, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Tuple from HABApp.openhab.definitions.values import PointValue -from HABApp.openhab.items.base_item import OpenhabItem, MetaData +from HABApp.openhab.items.base_item import MetaData, OpenhabItem + if TYPE_CHECKING: Tuple = Tuple diff --git a/src/HABApp/openhab/map_events.py b/src/HABApp/openhab/map_events.py index e685318b..b6023393 100644 --- a/src/HABApp/openhab/map_events.py +++ b/src/HABApp/openhab/map_events.py @@ -1,14 +1,29 @@ from typing import Dict, Type + from HABApp.core.const.json import load_json -from .events import OpenhabEvent, \ - ItemStateEvent, ItemStateUpdatedEvent, ItemStateChangedEvent, ItemCommandEvent, ItemAddedEvent, \ - ItemUpdatedEvent, ItemRemovedEvent, ItemStatePredictedEvent, \ - GroupStateUpdatedEvent, GroupStateChangedEvent, \ - ChannelTriggeredEvent, ChannelDescriptionChangedEvent, \ - ThingAddedEvent, ThingRemovedEvent, ThingUpdatedEvent, \ - ThingStatusInfoChangedEvent, ThingStatusInfoEvent, ThingFirmwareStatusInfoEvent, \ - ThingConfigStatusInfoEvent +from .events import ( + ChannelDescriptionChangedEvent, + ChannelTriggeredEvent, + GroupStateChangedEvent, + GroupStateUpdatedEvent, + ItemAddedEvent, + ItemCommandEvent, + ItemRemovedEvent, + ItemStateChangedEvent, + ItemStateEvent, + ItemStatePredictedEvent, + ItemStateUpdatedEvent, + ItemUpdatedEvent, + OpenhabEvent, + ThingAddedEvent, + ThingConfigStatusInfoEvent, + ThingFirmwareStatusInfoEvent, + ThingRemovedEvent, + ThingStatusInfoChangedEvent, + ThingStatusInfoEvent, + ThingUpdatedEvent, +) EVENT_LIST = [ diff --git a/src/HABApp/openhab/map_items.py b/src/HABApp/openhab/map_items.py index a65db9ef..9ec7b45d 100644 --- a/src/HABApp/openhab/map_items.py +++ b/src/HABApp/openhab/map_items.py @@ -6,8 +6,19 @@ from HABApp.core.wrapper import process_exception from HABApp.openhab.definitions.values import QuantityValue from HABApp.openhab.items import ( - CallItem, ColorItem, ContactItem, DatetimeItem, DimmerItem, GroupItem, ImageItem, - LocationItem, NumberItem, PlayerItem, RollershutterItem, StringItem, SwitchItem, + CallItem, + ColorItem, + ContactItem, + DatetimeItem, + DimmerItem, + GroupItem, + ImageItem, + LocationItem, + NumberItem, + PlayerItem, + RollershutterItem, + StringItem, + SwitchItem, ) from HABApp.openhab.items.base_item import HINT_TYPE_OPENHAB_ITEM, MetaData, OpenhabItem diff --git a/src/HABApp/openhab/map_values.py b/src/HABApp/openhab/map_values.py index 19cdcf84..13537584 100644 --- a/src/HABApp/openhab/map_values.py +++ b/src/HABApp/openhab/map_values.py @@ -1,8 +1,17 @@ from datetime import datetime +from fastnumbers import real + from HABApp.core.const.const import PYTHON_311 -from HABApp.openhab.definitions import HSBValue, OnOffValue, OpenClosedValue, PercentValue, QuantityValue, RawValue, \ - UpDownValue +from HABApp.openhab.definitions import ( + HSBValue, + OnOffValue, + OpenClosedValue, + PercentValue, + QuantityValue, + RawValue, + UpDownValue, +) from HABApp.openhab.definitions.values import PointValue @@ -13,22 +22,19 @@ def map_openhab_values(openhab_type: str, openhab_value: str): if openhab_type == 'UnDef' or openhab_value == 'NULL': return None - if openhab_type == "Number": + if openhab_type == 'Number': return int(openhab_value) - if openhab_type == "Decimal": - try: - return int(openhab_value) - except ValueError: - return float(openhab_value) + if openhab_type == 'Decimal': + return real(openhab_value) - if openhab_type == "String": + if openhab_type == 'String': return openhab_value - if openhab_type == "HSB": + if openhab_type == 'HSB': return HSBValue(openhab_value) - if openhab_type == "DateTime": + if openhab_type == 'DateTime': # see implementation im datetime_item.py if PYTHON_311: dt = datetime.fromisoformat(openhab_value) diff --git a/src/HABApp/openhab/transformations/_map/registry.py b/src/HABApp/openhab/transformations/_map/registry.py index 61cf7622..7bef0568 100644 --- a/src/HABApp/openhab/transformations/_map/registry.py +++ b/src/HABApp/openhab/transformations/_map/registry.py @@ -1,10 +1,10 @@ -from typing import Dict, Union, Tuple, Any, Final +from typing import Any, Dict, Final, Tuple, Union from javaproperties import loads as load_map_file from HABApp.openhab.errors import MapTransformationNotFound from HABApp.openhab.transformations._map.classes import MapTransformation, MapTransformationWithDefault -from HABApp.openhab.transformations.base import TransformationRegistryBase, TransformationFactoryBase, log +from HABApp.openhab.transformations.base import TransformationFactoryBase, TransformationRegistryBase, log class MapTransformationRegistry(TransformationRegistryBase): diff --git a/src/HABApp/openhab/transformations/base.py b/src/HABApp/openhab/transformations/base.py index 8ccab0f9..9b89c7ac 100644 --- a/src/HABApp/openhab/transformations/base.py +++ b/src/HABApp/openhab/transformations/base.py @@ -1,5 +1,5 @@ import logging -from typing import Dict, Any, Final, TypeVar, Tuple, Generic +from typing import Any, Dict, Final, Generic, Tuple, TypeVar T = TypeVar('T') diff --git a/src/HABApp/parameters/parameter_files.py b/src/HABApp/parameters/parameter_files.py index aad6688e..a8d06452 100644 --- a/src/HABApp/parameters/parameter_files.py +++ b/src/HABApp/parameters/parameter_files.py @@ -5,8 +5,10 @@ import HABApp from HABApp.core.files.file import HABAppFile from HABApp.core.files.folders import add_folder as add_habapp_folder + from .parameters import get_parameter_file, remove_parameter_file, set_parameter_file + log = logging.getLogger('HABApp.RuleParameters') LOCK = threading.Lock() diff --git a/src/HABApp/parameters/parameters.py b/src/HABApp/parameters/parameters.py index ecd6eb4a..6f32e3eb 100644 --- a/src/HABApp/parameters/parameters.py +++ b/src/HABApp/parameters/parameters.py @@ -2,6 +2,7 @@ import voluptuous + _PARAMETERS: typing.Dict[str, dict] = {} _VALIDATORS: typing.Dict[str, voluptuous.Schema] = {} @@ -90,4 +91,4 @@ def get_value(file: str, *keys) -> typing.Any: # Import here to prevent cyclic imports -from .parameter_files import save_file, reload_param_file # noqa: E402 +from .parameter_files import reload_param_file, save_file # noqa: E402 diff --git a/src/HABApp/rule/interfaces/_http.py b/src/HABApp/rule/interfaces/_http.py index a43d0c9e..8c53285e 100644 --- a/src/HABApp/rule/interfaces/_http.py +++ b/src/HABApp/rule/interfaces/_http.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Mapping +from typing import Any, Mapping, Optional import aiohttp diff --git a/src/HABApp/rule/interfaces/http_interface.py b/src/HABApp/rule/interfaces/http_interface.py index 6b29f1cc..c34369be 100644 --- a/src/HABApp/rule/interfaces/http_interface.py +++ b/src/HABApp/rule/interfaces/http_interface.py @@ -1 +1 @@ -from HABApp.rule.interfaces._http import get, post, put, delete, get_client_session +from HABApp.rule.interfaces._http import delete, get, get_client_session, post, put diff --git a/src/HABApp/rule/rule.py b/src/HABApp/rule/rule.py index 6079a593..faa1dd3f 100644 --- a/src/HABApp/rule/rule.py +++ b/src/HABApp/rule/rule.py @@ -37,6 +37,7 @@ ) from .rule_hook import get_rule_hook as _get_rule_hook + if PYTHON_310: from typing import ParamSpec else: diff --git a/src/HABApp/rule/rule_hook.py b/src/HABApp/rule/rule_hook.py index 8b07f15b..bda9759a 100644 --- a/src/HABApp/rule/rule_hook.py +++ b/src/HABApp/rule/rule_hook.py @@ -1,9 +1,11 @@ import logging + # noinspection PyProtectedMember from sys import _getframe as sys_get_frame from types import FrameType from typing import TYPE_CHECKING, Any, Callable, Final, Optional + if TYPE_CHECKING: import HABApp import HABApp.rule_manager diff --git a/src/HABApp/rule/scheduler/executor.py b/src/HABApp/rule/scheduler/executor.py index 91067a03..3ca07f72 100644 --- a/src/HABApp/rule/scheduler/executor.py +++ b/src/HABApp/rule/scheduler/executor.py @@ -1,8 +1,9 @@ from typing import Callable -from HABApp.core.internals.wrapped_function import WrappedFunctionBase from eascheduler.executors import ExecutorBase +from HABApp.core.internals.wrapped_function import WrappedFunctionBase + class WrappedFunctionExecutor(ExecutorBase): def __init__(self, func: Callable, *args, **kwargs): diff --git a/src/HABApp/rule/scheduler/habappschedulerview.py b/src/HABApp/rule/scheduler/habappschedulerview.py index 6ca26eaa..eb7aa5a6 100644 --- a/src/HABApp/rule/scheduler/habappschedulerview.py +++ b/src/HABApp/rule/scheduler/habappschedulerview.py @@ -2,23 +2,23 @@ from datetime import datetime as dt_datetime from datetime import time as dt_time from datetime import timedelta as dt_timedelta -from typing import Callable, Any -from typing import Iterable, Union +from typing import Any, Callable, Iterable, Union + +from eascheduler import SchedulerView import HABApp.rule_ctx from HABApp.core.const.const import PYTHON_310 -from HABApp.core.internals import ContextProvidingObj, Context -from HABApp.core.internals import wrap_func +from HABApp.core.internals import Context, ContextProvidingObj, wrap_func from HABApp.rule.scheduler.executor import WrappedFunctionExecutor from HABApp.rule.scheduler.scheduler import HABAppScheduler as _HABAppScheduler -from eascheduler import SchedulerView -from .jobs import CountdownJob, DawnJob, DayOfWeekJob, DuskJob, OneTimeJob, ReoccurringJob, SunriseJob, \ - SunsetJob + +from .jobs import CountdownJob, DawnJob, DayOfWeekJob, DuskJob, OneTimeJob, ReoccurringJob, SunriseJob, SunsetJob + if PYTHON_310: - from typing import TypeAlias, ParamSpec + from typing import ParamSpec, TypeAlias else: - from typing_extensions import TypeAlias, ParamSpec + from typing_extensions import ParamSpec, TypeAlias HINT_CB_P = ParamSpec('HINT_CB_P') diff --git a/src/HABApp/rule/scheduler/scheduler.py b/src/HABApp/rule/scheduler/scheduler.py index 4cc4765a..d8ca324c 100644 --- a/src/HABApp/rule/scheduler/scheduler.py +++ b/src/HABApp/rule/scheduler/scheduler.py @@ -1,10 +1,11 @@ from asyncio import run_coroutine_threadsafe -from HABApp.core.const import loop -from HABApp.core.asyncio import async_context from eascheduler.jobs.job_base import ScheduledJobBase from eascheduler.schedulers import AsyncScheduler +from HABApp.core.asyncio import async_context +from HABApp.core.const import loop + class HABAppScheduler(AsyncScheduler): def __init__(self): diff --git a/src/HABApp/rule_ctx/rule_ctx.py b/src/HABApp/rule_ctx/rule_ctx.py index 1558c377..826568f2 100644 --- a/src/HABApp/rule_ctx/rule_ctx.py +++ b/src/HABApp/rule_ctx/rule_ctx.py @@ -6,6 +6,7 @@ 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() item_registry = uses_item_registry() @@ -15,7 +16,7 @@ class HABAppRuleContext(Context): def __init__(self, rule: 'HABApp.rule.Rule'): super().__init__() - self.rule: Optional['HABApp.rule.Rule'] = rule + self.rule: Optional[HABApp.rule.Rule] = rule def get_callback_name(self, callback: Callable) -> Optional[str]: return f'{self.rule.rule_name}.{callback.__name__}' if self.rule.rule_name else None diff --git a/src/HABApp/rule_manager/benchmark/bench_file.py b/src/HABApp/rule_manager/benchmark/bench_file.py index 31cbb654..187f531a 100644 --- a/src/HABApp/rule_manager/benchmark/bench_file.py +++ b/src/HABApp/rule_manager/benchmark/bench_file.py @@ -3,6 +3,7 @@ import HABApp from HABApp.rule.rule_hook import HABAppRuleHook from HABApp.rule_manager import RuleFile + from .bench_habapp import HABAppBenchRule from .bench_mqtt import MqttBenchRule from .bench_oh import OpenhabBenchRule diff --git a/src/HABApp/rule_manager/benchmark/bench_habapp.py b/src/HABApp/rule_manager/benchmark/bench_habapp.py index 5f6ec634..586ab9a5 100644 --- a/src/HABApp/rule_manager/benchmark/bench_habapp.py +++ b/src/HABApp/rule_manager/benchmark/bench_habapp.py @@ -5,9 +5,11 @@ import HABApp from HABApp.core.events import ValueUpdateEvent + from .bench_base import BenchBaseRule from .bench_times import BenchContainer, BenchTime + LOCK = Lock() @@ -36,7 +38,6 @@ def set_up(self): self.cleanup() def tear_down(self): - pass self.cleanup() def run_bench(self): diff --git a/src/HABApp/rule_manager/benchmark/bench_mqtt.py b/src/HABApp/rule_manager/benchmark/bench_mqtt.py index 302439ae..6f0b76ec 100644 --- a/src/HABApp/rule_manager/benchmark/bench_mqtt.py +++ b/src/HABApp/rule_manager/benchmark/bench_mqtt.py @@ -5,9 +5,11 @@ import HABApp from HABApp.core.events import ValueUpdateEvent, ValueUpdateEventFilter +from HABApp.mqtt.interface_sync import publish + from .bench_base import BenchBaseRule from .bench_times import BenchContainer, BenchTime -from HABApp.mqtt.interface_sync import publish + LOCK = Lock() @@ -37,7 +39,6 @@ def set_up(self): self.cleanup() def tear_down(self): - pass self.cleanup() def run_bench(self): diff --git a/src/HABApp/rule_manager/benchmark/bench_oh.py b/src/HABApp/rule_manager/benchmark/bench_oh.py index c403135c..3dba20ba 100644 --- a/src/HABApp/rule_manager/benchmark/bench_oh.py +++ b/src/HABApp/rule_manager/benchmark/bench_oh.py @@ -5,9 +5,11 @@ import HABApp from HABApp.core.events import ValueUpdateEvent, ValueUpdateEventFilter + from .bench_base import BenchBaseRule from .bench_times import BenchContainer, BenchTime + LOCK = Lock() diff --git a/src/HABApp/rule_manager/benchmark/bench_times.py b/src/HABApp/rule_manager/benchmark/bench_times.py index 0660cc00..4c3fc7f4 100644 --- a/src/HABApp/rule_manager/benchmark/bench_times.py +++ b/src/HABApp/rule_manager/benchmark/bench_times.py @@ -46,7 +46,7 @@ class BenchTime: @classmethod def show_table(cls, indent_name=0): - print(f'{"":{indent_name}s} | {format_duration("dur")} | {"per sec":7s} | 'f'{format_duration("median")} | ' + print(f'{"":{indent_name}s} | {format_duration("dur")} | {"per sec":7s} | {format_duration("median")} | ' f'{format_duration("min")} | {format_duration("max")} | {format_duration("mean")}') def __init__(self, name: str, factor: int = 1): diff --git a/src/HABApp/rule_manager/rule_file.py b/src/HABApp/rule_manager/rule_file.py index 05deaee3..e6e16dfe 100644 --- a/src/HABApp/rule_manager/rule_file.py +++ b/src/HABApp/rule_manager/rule_file.py @@ -56,7 +56,7 @@ def unload(self): return None def __process_tc(self, tb: list): - tb.insert(0, f"Could not load {self.path}!") + tb.insert(0, f'Could not load {self.path}!') return [line.replace('', self.path.name) for line in tb] def create_rules(self, created_rules: list): @@ -87,7 +87,7 @@ def load(self) -> bool: return False if not created_rules: - log.warning(f'Found no instances of HABApp.Rule in {str(self.path)}') + log.warning(f'Found no instances of HABApp.Rule in {self.path!s}') return True with ign: diff --git a/src/HABApp/runtime/runtime.py b/src/HABApp/runtime/runtime.py index 8a7d9457..7cd474fa 100644 --- a/src/HABApp/runtime/runtime.py +++ b/src/HABApp/runtime/runtime.py @@ -1,25 +1,25 @@ import asyncio from pathlib import Path +import eascheduler + import HABApp import HABApp.config import HABApp.core +import HABApp.mqtt.connection as mqtt_connection import HABApp.parameters.parameter_files import HABApp.rule.interfaces._http import HABApp.rule_manager -from HABApp.core import Connections import HABApp.util -import eascheduler +from HABApp.core import Connections from HABApp.core.asyncio import async_context from HABApp.core.internals import setup_internals from HABApp.core.internals.proxy import ConstProxyObj from HABApp.core.wrapper import process_exception -import HABApp.mqtt.connection as mqtt_connection from HABApp.openhab import connection as openhab_connection from HABApp.runtime import shutdown - class Runtime: def __init__(self): diff --git a/src/HABApp/runtime/shutdown.py b/src/HABApp/runtime/shutdown.py index 5355dfee..bdfadbd7 100644 --- a/src/HABApp/runtime/shutdown.py +++ b/src/HABApp/runtime/shutdown.py @@ -49,7 +49,7 @@ def request_shutdown(): async def _shutdown(): global requested - "Request execution of all functions" + 'Request execution of all functions' async_context.set('Shutdown') diff --git a/src/HABApp/util/fade/fade.py b/src/HABApp/util/fade/fade.py index 8ca6041a..6d55ec33 100644 --- a/src/HABApp/util/fade/fade.py +++ b/src/HABApp/util/fade/fade.py @@ -1,8 +1,8 @@ from datetime import timedelta from time import time -from typing import Union, Optional +from typing import Optional, Union -from HABApp.core.internals import wrap_func, AutoContextBoundObj +from HABApp.core.internals import AutoContextBoundObj, wrap_func VAL_TYPE = Union[int, float] @@ -12,7 +12,7 @@ class FadeWorker(AutoContextBoundObj): def __init__(self, parent: 'Fade', interval: float): super().__init__() - self.parent: 'Fade' = parent + self.parent: Fade = parent self.scheduler = self._parent_ctx.rule.run.every(None, interval, self.parent._scheduled_worker) def cancel(self): diff --git a/src/HABApp/util/multimode/mode_base.py b/src/HABApp/util/multimode/mode_base.py index 6bd608c5..57e22aba 100644 --- a/src/HABApp/util/multimode/mode_base.py +++ b/src/HABApp/util/multimode/mode_base.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, TypeVar, Optional, Any +from typing import TYPE_CHECKING, Any, Optional, TypeVar import HABApp from HABApp.core.internals import AutoContextBoundObj diff --git a/src/HABApp/util/multimode/mode_switch.py b/src/HABApp/util/multimode/mode_switch.py index 7e96ee67..c8b47f8d 100644 --- a/src/HABApp/util/multimode/mode_switch.py +++ b/src/HABApp/util/multimode/mode_switch.py @@ -2,6 +2,7 @@ import typing import HABApp + from . import ValueMode diff --git a/src/HABApp/util/rate_limiter/limiter.py b/src/HABApp/util/rate_limiter/limiter.py index 7b6aaead..e8b8dc61 100644 --- a/src/HABApp/util/rate_limiter/limiter.py +++ b/src/HABApp/util/rate_limiter/limiter.py @@ -2,10 +2,16 @@ from typing import Final, List, Literal, Tuple, Union, get_args from HABApp.core.const.const import PYTHON_310 -from HABApp.util.rate_limiter.limits import BaseRateLimit, FixedWindowElasticExpiryLimit, \ - FixedWindowElasticExpiryLimitInfo, LeakyBucketLimit, LeakyBucketLimitInfo +from HABApp.util.rate_limiter.limits import ( + BaseRateLimit, + FixedWindowElasticExpiryLimit, + FixedWindowElasticExpiryLimitInfo, + LeakyBucketLimit, + LeakyBucketLimitInfo, +) from HABApp.util.rate_limiter.parser import parse_limit + if PYTHON_310: from typing import TypeAlias else: diff --git a/src/HABApp/util/rate_limiter/parser.py b/src/HABApp/util/rate_limiter/parser.py index 44e8b4a1..b04e3514 100644 --- a/src/HABApp/util/rate_limiter/parser.py +++ b/src/HABApp/util/rate_limiter/parser.py @@ -1,13 +1,14 @@ import re from typing import Tuple + LIMIT_REGEX = re.compile( - r""" + r''' \s* ([1-9][0-9]*) \s* (/|per|in) \s* ([1-9][0-9]*)? \s* (s|sec|second|m|min|minute|h|hour|day|month|year)s? - \s*""", + \s*''', re.IGNORECASE | re.VERBOSE, ) diff --git a/tests/conftest.py b/tests/conftest.py index 5414a6c2..c7fa3705 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,14 +34,14 @@ def f(*args, **kwargs): return f -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True) def show_errors(monkeypatch): # Patch the wrapper so that we always raise the exception monkeypatch.setattr(HABApp.core.wrapper, 'ignore_exception', raise_err) monkeypatch.setattr(HABApp.core.wrapper, 'log_exception', raise_err) -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True) def use_dummy_cfg(monkeypatch): cfg = get_dummy_cfg() monkeypatch.setattr(HABApp, 'CONFIG', cfg) @@ -59,12 +59,12 @@ def event_loop(): async_context.reset(token) -@pytest.fixture(scope='function') +@pytest.fixture() def ir(): return ItemRegistry() -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True) def clean_objs(ir: ItemRegistry, eb: EventBus, request): markers = request.node.own_markers for marker in markers: @@ -83,7 +83,7 @@ def clean_objs(ir: ItemRegistry, eb: EventBus, request): r.restore() -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True) def test_logs(caplog, request): caplog.set_level(logging.DEBUG) diff --git a/tests/helpers/event_bus.py b/tests/helpers/event_bus.py index 9cbdd164..8acfb064 100644 --- a/tests/helpers/event_bus.py +++ b/tests/helpers/event_bus.py @@ -4,7 +4,7 @@ from HABApp.core.const.topics import TOPIC_ERRORS from HABApp.core.events.habapp_events import HABAppException -from HABApp.core.internals import EventFilterBase, EventBusListener, EventBus, wrap_func +from HABApp.core.internals import EventBus, EventBusListener, EventFilterBase, wrap_func class TestEventBus(EventBus): diff --git a/tests/helpers/habapp_config.py b/tests/helpers/habapp_config.py index 2c23baab..6a4c66dd 100644 --- a/tests/helpers/habapp_config.py +++ b/tests/helpers/habapp_config.py @@ -1,6 +1,7 @@ +from easyconfig import create_app_config + import HABApp import HABApp.config.models -from easyconfig import create_app_config def get_dummy_cfg(): diff --git a/tests/helpers/inspect/classes.py b/tests/helpers/inspect/classes.py index 380fc119..61c768d0 100644 --- a/tests/helpers/inspect/classes.py +++ b/tests/helpers/inspect/classes.py @@ -1,5 +1,5 @@ import inspect -from typing import Iterable, Optional, Any, get_type_hints, Dict, Type +from typing import Any, Dict, Iterable, Optional, Type, get_type_hints import pytest diff --git a/tests/helpers/inspect/docstr.py b/tests/helpers/inspect/docstr.py index 606549cc..7d411b83 100644 --- a/tests/helpers/inspect/docstr.py +++ b/tests/helpers/inspect/docstr.py @@ -1,10 +1,11 @@ import importlib import inspect import re -from typing import Any, Type, Dict, Optional +from typing import Any, Dict, Optional, Type import pytest + RE_IVAR = re.compile(r':ivar\s+([^:]+?)\s+(\w+)\s*:', re.IGNORECASE) @@ -67,6 +68,6 @@ class Child(Parent): with pytest.raises(ExpectedHintNotFound) as e: get_ivars_from_docstring(Child, {'a': Dict[str, str]}) - assert str(e.value) == "Expected hint not found for Child.a: typing.Dict[str, str]" + assert str(e.value) == 'Expected hint not found for Child.a: typing.Dict[str, str]' get_ivars_from_docstring(Child, {'a': Optional[Dict[str, str]]}) diff --git a/tests/helpers/inspect/habapp.py b/tests/helpers/inspect/habapp.py index ad14859e..e7dc6e95 100644 --- a/tests/helpers/inspect/habapp.py +++ b/tests/helpers/inspect/habapp.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from inspect import getmembers from types import ModuleType -from typing import List, Any, Iterable, Optional, Tuple, Type +from typing import Any, Iterable, List, Optional, Tuple, Type import HABApp diff --git a/tests/helpers/inspect/module.py b/tests/helpers/inspect/module.py index a9c56baf..4c3002c6 100644 --- a/tests/helpers/inspect/module.py +++ b/tests/helpers/inspect/module.py @@ -2,7 +2,7 @@ import inspect import sys import typing -from typing import Iterable, Optional, Union, Tuple, List, Callable +from typing import Callable, Iterable, List, Optional, Tuple, Union def get_module_classes(module_name: str, /, exclude: Optional[Iterable[Union[str, type]]] = None, include_imported=True, diff --git a/tests/helpers/log/log_collector.py b/tests/helpers/log/log_collector.py index 224cb5e4..2796b4cd 100644 --- a/tests/helpers/log/log_collector.py +++ b/tests/helpers/log/log_collector.py @@ -1,6 +1,7 @@ import logging -from operator import ge as ge_func, eq as eq_func -from typing import List, Callable, Final, Any, Iterable, Dict, Union, Optional +from operator import eq as eq_func +from operator import ge as ge_func +from typing import Any, Callable, Dict, Final, Iterable, List, Optional, Union import pytest from pytest import LogCaptureFixture @@ -9,6 +10,7 @@ from .log_matcher import LogEntryMatcherBase, create_matcher from .log_utils import SimpleLogRecord + ALL_PYTEST_PHASES = ('setup', 'call', 'teardown') @@ -97,7 +99,7 @@ def update(self) -> Self: continue # emit warning only on dev machine until we fix asyncio handling - # todo: remove this once we fixed asyncio handling + # TODO: remove this once we fixed asyncio handling import os if os.name != 'nt' and record.name == 'asyncio': record.unlink() @@ -132,7 +134,7 @@ def assert_ok(self): msg += '\n - ' + '\n - '.join(map(str, missing)) msg += '\nLog:\n' + '\n'.join(self.get_messages()) - pytest.fail(msg=msg) + pytest.fail(reason=msg) for rec in self.res_records: if not self.is_expected_record(rec): diff --git a/tests/helpers/log/log_matcher.py b/tests/helpers/log/log_matcher.py index 0f993f8e..fdd2d223 100644 --- a/tests/helpers/log/log_matcher.py +++ b/tests/helpers/log/log_matcher.py @@ -1,9 +1,9 @@ import logging import os import re -from typing import List, Final, Iterable, Union, Optional +from typing import Final, Iterable, List, Optional, Union -from .log_utils import SimpleLogRecord, get_log_level_no, get_log_level_name +from .log_utils import SimpleLogRecord, get_log_level_name, get_log_level_no class LogEntryMatcherBase: diff --git a/tests/helpers/log/log_utils.py b/tests/helpers/log/log_utils.py index 16316ecc..c3ad2637 100644 --- a/tests/helpers/log/log_utils.py +++ b/tests/helpers/log/log_utils.py @@ -1,7 +1,7 @@ import logging from dataclasses import dataclass from logging import LogRecord -from typing import Union, Optional +from typing import Optional, Union def get_log_level_no(level: Union[str, int]) -> int: diff --git a/tests/helpers/parameters.py b/tests/helpers/parameters.py index 16d979b5..92c0c068 100644 --- a/tests/helpers/parameters.py +++ b/tests/helpers/parameters.py @@ -7,7 +7,7 @@ import HABApp.parameters.parameters as Parameters -@pytest.fixture(scope="function") +@pytest.fixture(scope='function') def params(): class DummyCfg: class directories: diff --git a/tests/helpers/parent_rule.py b/tests/helpers/parent_rule.py index 6b72fae5..db9a475d 100644 --- a/tests/helpers/parent_rule.py +++ b/tests/helpers/parent_rule.py @@ -11,7 +11,7 @@ def __init__(self): self.rule_name = 'DummyRule' -@fixture +@fixture() def parent_rule(monkeypatch): rule = DummyRule() @@ -24,4 +24,4 @@ def ret_dummy_rule_context(): monkeypatch.setattr(HABApp.core.items.base_item_watch, 'get_current_context', ret_dummy_rule_context) - yield rule + return rule diff --git a/tests/helpers/sync_worker.py b/tests/helpers/sync_worker.py index e29e356b..9a849cd5 100644 --- a/tests/helpers/sync_worker.py +++ b/tests/helpers/sync_worker.py @@ -1,6 +1,7 @@ import pytest from HABApp.core.internals.wrapped_function import wrapped_thread +from HABApp.core.internals.wrapped_function import wrapper as wrapper_module class SyncTestWorker: @@ -9,7 +10,9 @@ def submit(callback, *args, **kwargs): callback(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture() def sync_worker(monkeypatch): monkeypatch.setattr(wrapped_thread, 'POOL', SyncTestWorker()) - yield + + assert not hasattr(wrapper_module, 'SYNC_CLS') + monkeypatch.setattr(wrapper_module, 'SYNC_CLS', wrapper_module.WrappedThreadFunction, raising=False) diff --git a/tests/helpers/traceback.py b/tests/helpers/traceback.py index 0b36d92b..2504f164 100644 --- a/tests/helpers/traceback.py +++ b/tests/helpers/traceback.py @@ -9,7 +9,7 @@ def remove_dyn_parts_from_traceback(traceback: str) -> str: # File path for m in re.finditer(r'File\s+"([^"]+)"', traceback): - fname = "/".join(Path(m.group(1)).parts[-3:]) + fname = '/'.join(Path(m.group(1)).parts[-3:]) traceback = traceback.replace(m.group(0), f'File "{fname}"') return traceback @@ -17,17 +17,17 @@ def remove_dyn_parts_from_traceback(traceback: str) -> str: def test_remove_dyn_parts_from_traceback(): - traceback = """ + traceback = ''' File "C:\\My\\Folder\\HABApp\\tests\\test_core\\test_lib\\test_format_traceback.py", line 19 in exec_func File "/My/Folder/HABApp/tests/test_core/test_lib/test_format_traceback.py", line 19 in exec_func func = File "C:\\My\\Folder\\HABApp\\tests\\test_core\\test_lib\\test_format_traceback.py", line 19, in exec_func -""" +''' processed = remove_dyn_parts_from_traceback(traceback) - assert processed == """ + assert processed == ''' File "test_core/test_lib/test_format_traceback.py", line 19 in exec_func File "test_core/test_lib/test_format_traceback.py", line 19 in exec_func func = File "test_core/test_lib/test_format_traceback.py", line 19, in exec_func -""" +''' diff --git a/tests/rule_runner/rule_runner.py b/tests/rule_runner/rule_runner.py index dd54f8bf..1fe206b9 100644 --- a/tests/rule_runner/rule_runner.py +++ b/tests/rule_runner/rule_runner.py @@ -7,7 +7,7 @@ import HABApp.rule.rule as rule_module import HABApp.rule.scheduler.habappschedulerview as ha_sched from HABApp.core.asyncio import async_context -from HABApp.core.internals import setup_internals, ItemRegistry, EventBus +from HABApp.core.internals import EventBus, ItemRegistry, setup_internals from HABApp.core.internals.proxy import ConstProxyObj from HABApp.core.internals.wrapped_function import wrapped_thread, wrapper from HABApp.core.internals.wrapped_function.wrapped_thread import WrappedThreadFunction diff --git a/tests/rule_runner/test_rule_runner.py b/tests/rule_runner/test_rule_runner.py index fc2f30dc..64e07f74 100644 --- a/tests/rule_runner/test_rule_runner.py +++ b/tests/rule_runner/test_rule_runner.py @@ -1,8 +1,9 @@ import pytest + import HABApp.core.lib.exceptions.format -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_doc_run(): calls = [] @@ -31,7 +32,7 @@ def say_something(self): assert len(calls) == 4 -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_doc_run_exception(monkeypatch): """Check that the RuleRunner propagates exceptions which happen during exception formatting""" diff --git a/tests/test_all/test_items.py b/tests/test_all/test_items.py index cc5be62f..cc016790 100644 --- a/tests/test_all/test_items.py +++ b/tests/test_all/test_items.py @@ -1,6 +1,6 @@ import pytest -from HABApp.core.items import BaseValueItem, HINT_TYPE_ITEM_OBJ +from HABApp.core.items import HINT_TYPE_ITEM_OBJ, BaseValueItem from HABApp.mqtt.items import MqttBaseItem from tests.helpers.inspect import get_module_classes diff --git a/tests/test_config/test_platform.py b/tests/test_config/test_platform.py index 81e232d9..3c4fd637 100644 --- a/tests/test_config/test_platform.py +++ b/tests/test_config/test_platform.py @@ -1,8 +1,7 @@ from pathlib import Path -from HABApp.config.logging.config import _yaml_safe from HABApp.config.logging import default_logfile - +from HABApp.config.logging.config import _yaml_safe from HABApp.config.platform_defaults import get_log_folder diff --git a/tests/test_core/test_connections.py b/tests/test_core/test_connections.py index 48eaedab..be93ae2e 100644 --- a/tests/test_core/test_connections.py +++ b/tests/test_core/test_connections.py @@ -3,8 +3,8 @@ import pytest -from HABApp.core.connections import BaseConnectionPlugin, BaseConnection, PluginCallbackHandler -from HABApp.core.connections.status_transitions import StatusTransitions, ConnectionStatus +from HABApp.core.connections import BaseConnection, BaseConnectionPlugin, PluginCallbackHandler +from HABApp.core.connections.status_transitions import ConnectionStatus, StatusTransitions def test_transitions(): diff --git a/tests/test_core/test_context.py b/tests/test_core/test_context.py index 42e93da4..596a06a5 100644 --- a/tests/test_core/test_context.py +++ b/tests/test_core/test_context.py @@ -1,6 +1,6 @@ import pytest -from HABApp.core.asyncio import async_context, AsyncContextError +from HABApp.core.asyncio import AsyncContextError, async_context async def test_error_msg(): diff --git a/tests/test_core/test_event_bus.py b/tests/test_core/test_event_bus.py index 341654cb..70be987f 100644 --- a/tests/test_core/test_event_bus.py +++ b/tests/test_core/test_event_bus.py @@ -1,7 +1,7 @@ from unittest.mock import MagicMock from HABApp.core.events import ComplexEventValue, ValueChangeEvent, ValueUpdateEvent -from HABApp.core.events.filter import NoEventFilter, EventFilter, OrFilterGroup +from HABApp.core.events.filter import EventFilter, NoEventFilter, OrFilterGroup from HABApp.core.internals import EventBus, EventBusListener, wrap_func diff --git a/tests/test_core/test_events/test_core_filters.py b/tests/test_core/test_events/test_core_filters.py index 2d067ce9..828b66d5 100644 --- a/tests/test_core/test_events/test_core_filters.py +++ b/tests/test_core/test_events/test_core_filters.py @@ -1,11 +1,17 @@ from typing import Any import pytest +from tests.helpers.inspect import check_class_annotations, get_module_classes from HABApp.core.events import ValueChangeEvent, ValueUpdateEvent -from HABApp.core.events.filter import EventFilter, ValueChangeEventFilter, ValueUpdateEventFilter, NoEventFilter, \ - OrFilterGroup, AndFilterGroup -from tests.helpers.inspect import get_module_classes, check_class_annotations +from HABApp.core.events.filter import ( + AndFilterGroup, + EventFilter, + NoEventFilter, + OrFilterGroup, + ValueChangeEventFilter, + ValueUpdateEventFilter, +) def test_class_annotations(): diff --git a/tests/test_core/test_files/test_file_dependencies.py b/tests/test_core/test_files/test_file_dependencies.py index ec7f3a9b..6f25238d 100644 --- a/tests/test_core/test_files/test_file_dependencies.py +++ b/tests/test_core/test_files/test_file_dependencies.py @@ -55,7 +55,7 @@ def create_file(self, name, path) -> HABAppFile: return self.cls(name, MockFile(name), self.properties[name]) -@pytest.fixture +@pytest.fixture() def cfg(monkeypatch): obj = CfgObj() diff --git a/tests/test_core/test_files/test_file_properties.py b/tests/test_core/test_files/test_file_properties.py index 10369678..8d326c09 100644 --- a/tests/test_core/test_files/test_file_properties.py +++ b/tests/test_core/test_files/test_file_properties.py @@ -1,37 +1,37 @@ -from HABApp.core.files.file.properties import get_properties as get_props -from HABApp.core.files.file.file import HABAppFile, CircularReferenceError, FileProperties, FILES, FileState import pytest +from HABApp.core.files.file.file import FILES, CircularReferenceError, FileProperties, FileState, HABAppFile +from HABApp.core.files.file.properties import get_properties as get_props from tests.helpers import LogCollector def test_prop_case(): - _in = """# habapp: + _in = '''# habapp: # depends on: # - my_Param.yml # reloads on: # - my_File.py # - other_file.py - """ + ''' p = get_props(_in) assert p.depends_on == ['my_Param.yml'] assert p.reloads_on == ['my_File.py', 'other_file.py'] - _in = """# + _in = '''# # habapp: # depends on: # - my_Param.yml # reloads on: # - my_File.py # - other_file.py -""" +''' p = get_props(_in) assert p.depends_on == ['my_Param.yml'] assert p.reloads_on == ['my_File.py', 'other_file.py'] def test_prop_1(): - _in = """# HABApp: + _in = '''# HABApp: # depends on: # - my_Param.yml # @@ -39,14 +39,14 @@ def test_prop_1(): # - my_File.py # This is my comment # - other_file.py -""" +''' p = get_props(_in) assert p.depends_on == ['my_Param.yml'] assert p.reloads_on == ['my_File.py'] def test_prop_2(): - _in = """ + _in = ''' # # HABApp: @@ -58,14 +58,14 @@ def test_prop_2(): # reloads on: # - my_file.py # This is my comment -""" +''' p = get_props(_in) assert p.depends_on == ['my_param.yml'] assert p.reloads_on == [] def test_prop_3(): - _in = """ + _in = ''' # # HABApp: @@ -73,15 +73,15 @@ def test_prop_3(): # - my_param1.yml import asdf # - my_param2.yml -""" +''' p = get_props(_in) assert p.depends_on == ['my_param1.yml'] assert p.reloads_on == [] def test_prop_missing(): - _in = """import bla bla bla -""" + _in = '''import bla bla bla +''' p = get_props(_in) assert p.depends_on == [] assert p.reloads_on == [] diff --git a/tests/test_core/test_files/test_rel_name.py b/tests/test_core/test_files/test_rel_name.py index 7ea70e35..36bde82c 100644 --- a/tests/test_core/test_files/test_rel_name.py +++ b/tests/test_core/test_files/test_rel_name.py @@ -2,11 +2,11 @@ import pytest -from HABApp.core.files.folders import get_path, get_name, add_folder, get_prefixes +from HABApp.core.files.folders import add_folder, get_name, get_path, get_prefixes from HABApp.core.files.folders.folders import FOLDERS -@pytest.fixture +@pytest.fixture() def cfg(): FOLDERS.clear() add_folder('rules/', Path('c:/HABApp/my_rules/'), 0) diff --git a/tests/test_core/test_item_registry.py b/tests/test_core/test_item_registry.py index 1925359f..be415102 100644 --- a/tests/test_core/test_item_registry.py +++ b/tests/test_core/test_item_registry.py @@ -1,5 +1,5 @@ -from HABApp.core.items import Item from HABApp.core.internals import ItemRegistry +from HABApp.core.items import Item def test_basics(): diff --git a/tests/test_core/test_item_watch.py b/tests/test_core/test_item_watch.py index db2509fb..9c8d19f2 100644 --- a/tests/test_core/test_item_watch.py +++ b/tests/test_core/test_item_watch.py @@ -4,7 +4,7 @@ import pytest -from HABApp.core.events import ItemNoUpdateEvent, ItemNoChangeEvent +from HABApp.core.events import ItemNoChangeEvent, ItemNoUpdateEvent from HABApp.core.items import Item from tests.helpers import LogCollector from tests.helpers.parent_rule import DummyRule diff --git a/tests/test_core/test_items/test_item.py b/tests/test_core/test_items/test_item.py index 71fa15df..fc2cfe03 100644 --- a/tests/test_core/test_items/test_item.py +++ b/tests/test_core/test_items/test_item.py @@ -5,6 +5,7 @@ from pendulum import now as pd_now from HABApp.core.items import Item + from . import ItemTests diff --git a/tests/test_core/test_items/test_item_color.py b/tests/test_core/test_items/test_item_color.py index 79ce69fc..0cc95335 100644 --- a/tests/test_core/test_items/test_item_color.py +++ b/tests/test_core/test_items/test_item_color.py @@ -2,7 +2,7 @@ import pytest -from HABApp.core.events import ValueChangeEvent, ValueUpdateEvent, NoEventFilter +from HABApp.core.events import NoEventFilter, ValueChangeEvent, ValueUpdateEvent from HABApp.core.items import ColorItem from tests.helpers import TestEventBus @@ -24,9 +24,9 @@ def test_init(): assert ColorItem('', brightness=22).value == (0, 0, 22) -@pytest.mark.parametrize("func_name", ['set_value', 'post_value']) +@pytest.mark.parametrize('func_name', ['set_value', 'post_value']) @pytest.mark.parametrize( - "test_vals", [ + 'test_vals', [ ((45, 46, 47), (45, 46, 47)), ((10, None, None), (10, 22.22, 33.33)), ((None, 50, None), (11.11, 50, 33.33)), diff --git a/tests/test_core/test_items/test_item_interface.py b/tests/test_core/test_items/test_item_interface.py index cad96bc4..48949cc1 100644 --- a/tests/test_core/test_items/test_item_interface.py +++ b/tests/test_core/test_items/test_item_interface.py @@ -1,6 +1,6 @@ import pytest -from HABApp.core.errors import ItemNotFoundException, ItemAlreadyExistsError +from HABApp.core.errors import ItemAlreadyExistsError, ItemNotFoundException from HABApp.core.internals import ItemRegistry from HABApp.core.items import Item diff --git a/tests/test_core/test_items/test_item_times.py b/tests/test_core/test_items/test_item_times.py index c35cbc87..5cb8a6bb 100644 --- a/tests/test_core/test_items/test_item_times.py +++ b/tests/test_core/test_items/test_item_times.py @@ -8,13 +8,13 @@ import HABApp import HABApp.core.items.tmp_data from HABApp.core.events import NoEventFilter -from HABApp.core.items.base_item import ChangedTime, UpdatedTime -from tests.helpers import TestEventBus, LogCollector -from HABApp.core.internals import wrap_func, ItemRegistry, EventBus +from HABApp.core.internals import EventBus, ItemRegistry, wrap_func from HABApp.core.items import Item +from HABApp.core.items.base_item import ChangedTime, UpdatedTime +from tests.helpers import LogCollector, TestEventBus -@pytest.fixture(scope="function") +@pytest.fixture(scope='function') def u(): a = UpdatedTime('test', pd_now(UTC)) w1 = a.add_watch(1) @@ -29,7 +29,7 @@ def u(): w2.cancel() -@pytest.fixture(scope="function") +@pytest.fixture(scope='function') def c(): a = ChangedTime('test', pd_now(UTC)) w1 = a.add_watch(1) @@ -181,7 +181,7 @@ async def test_watcher_update_restore(parent_rule, ir: ItemRegistry): ir.pop_item(name) -@pytest.mark.ignore_log_warnings +@pytest.mark.ignore_log_warnings() async def test_watcher_update_cleanup(monkeypatch, parent_rule, c: ChangedTime, sync_worker, eb: TestEventBus, ir: ItemRegistry): monkeypatch.setattr(HABApp.core.items.tmp_data.CLEANUP, 'secs', 0.7) diff --git a/tests/test_core/test_lib/test_format_traceback.py b/tests/test_core/test_lib/test_format_traceback.py index 4af845ff..98897bc4 100644 --- a/tests/test_core/test_lib/test_format_traceback.py +++ b/tests/test_core/test_lib/test_format_traceback.py @@ -1,17 +1,18 @@ import logging +from pathlib import Path from typing import Optional, Union import pytest +from easyconfig import create_app_config from pydantic import BaseModel import HABApp from HABApp.core.const.const import PYTHON_311 -from HABApp.core.const.json import load_json, dump_json +from HABApp.core.const.json import dump_json, load_json from HABApp.core.lib import format_exception -from easyconfig import create_app_config -from tests.helpers.traceback import remove_dyn_parts_from_traceback from HABApp.core.lib.exceptions.format_frame import SUPPRESSED_HABAPP_PATHS, is_lib_file, is_suppressed_habapp_file -from pathlib import Path +from tests.helpers.traceback import remove_dyn_parts_from_traceback + log = logging.getLogger('TestLogger') @@ -27,8 +28,8 @@ def exec_func(func) -> str: def func_obj_def_multilines(): - item = HABApp.core.items.Item # noqa: F841 - a = [ # noqa: F841 + item = HABApp.core.items.Item + a = [ 1, 2, 3, @@ -59,8 +60,8 @@ def func_obj_def_multilines(): # File "test_core/test_lib/test_format_traceback.py", line 37 in func_obj_def_multilines # -------------------------------------------------------------------------------- # 25 | def func_obj_def_multilines(): -# 26 | item = HABApp.core.items.Item # noqa: F841 -# 27 | a = [ # noqa: F841 +# 26 | item = HABApp.core.items.Item +# 27 | a = [ # 28 | 1, # (...) # 35 | 8 @@ -108,26 +109,26 @@ def test_exception_expression_remove_py310(): log.setLevel(logging.WARNING) msg = exec_func(func_test_assert_none) assert msg == r''' -File "test_core/test_lib/test_format_traceback.py", line 21 in exec_func +File "test_core/test_lib/test_format_traceback.py", line 22 in exec_func -------------------------------------------------------------------------------- - 19 | def exec_func(func) -> str: - 20 | try: ---> 21 | func() - 22 | except Exception as e: + 20 | def exec_func(func) -> str: + 21 | try: +--> 22 | func() + 23 | except Exception as e: ------------------------------------------------------------ e = ZeroDivisionError('division by zero') func = ------------------------------------------------------------ -File "test_core/test_lib/test_format_traceback.py", line 97 in func_test_assert_none +File "test_core/test_lib/test_format_traceback.py", line 98 in func_test_assert_none -------------------------------------------------------------------------------- - 91 | def func_test_assert_none(a: Optional[str] = None, b: Optional[str] = None, c: Union[str, int] = 3): + 92 | def func_test_assert_none(a: Optional[str] = None, b: Optional[str] = None, c: Union[str, int] = 3): (...) - 94 | assert isinstance(c, (str, int)), type(c) - 95 | CONFIGURATION = '3' - 96 | my_dict = {'key_a': 'val_a'} ---> 97 | 1 / 0 - 98 | log.error('Error message') + 95 | assert isinstance(c, (str, int)), type(c) + 96 | CONFIGURATION = '3' + 97 | my_dict = {'key_a': 'val_a'} +--> 98 | 1 / 0 + 99 | log.error('Error message') ------------------------------------------------------------ CONFIG.a = 3 a = None @@ -142,9 +143,9 @@ def test_exception_expression_remove_py310(): -------------------------------------------------------------------------------- Traceback (most recent call last): - File "test_core/test_lib/test_format_traceback.py", line 21, in exec_func + File "test_core/test_lib/test_format_traceback.py", line 22, in exec_func func() - File "test_core/test_lib/test_format_traceback.py", line 97, in func_test_assert_none + File "test_core/test_lib/test_format_traceback.py", line 98, in func_test_assert_none 1 / 0 ZeroDivisionError: division by zero''' @@ -154,26 +155,26 @@ def test_exception_expression_remove(): log.setLevel(logging.WARNING) msg = exec_func(func_test_assert_none) assert msg == r''' -File "test_core/test_lib/test_format_traceback.py", line 21 in exec_func +File "test_core/test_lib/test_format_traceback.py", line 22 in exec_func -------------------------------------------------------------------------------- - 19 | def exec_func(func) -> str: - 20 | try: ---> 21 | func() - 22 | except Exception as e: + 20 | def exec_func(func) -> str: + 21 | try: +--> 22 | func() + 23 | except Exception as e: ------------------------------------------------------------ e = ZeroDivisionError('division by zero') func = ------------------------------------------------------------ -File "test_core/test_lib/test_format_traceback.py", line 97 in func_test_assert_none +File "test_core/test_lib/test_format_traceback.py", line 98 in func_test_assert_none -------------------------------------------------------------------------------- - 91 | def func_test_assert_none(a: Optional[str] = None, b: Optional[str] = None, c: Union[str, int] = 3): + 92 | def func_test_assert_none(a: Optional[str] = None, b: Optional[str] = None, c: Union[str, int] = 3): (...) - 94 | assert isinstance(c, (str, int)), type(c) - 95 | CONFIGURATION = '3' - 96 | my_dict = {'key_a': 'val_a'} ---> 97 | 1 / 0 - 98 | log.error('Error message') + 95 | assert isinstance(c, (str, int)), type(c) + 96 | CONFIGURATION = '3' + 97 | my_dict = {'key_a': 'val_a'} +--> 98 | 1 / 0 + 99 | log.error('Error message') ------------------------------------------------------------ CONFIG.a = 3 a = None @@ -188,9 +189,9 @@ def test_exception_expression_remove(): -------------------------------------------------------------------------------- Traceback (most recent call last): - File "test_core/test_lib/test_format_traceback.py", line 21, in exec_func + File "test_core/test_lib/test_format_traceback.py", line 22, in exec_func func() - File "test_core/test_lib/test_format_traceback.py", line 97, in func_test_assert_none + File "test_core/test_lib/test_format_traceback.py", line 98, in func_test_assert_none 1 / 0 ~~^~~ ZeroDivisionError: division by zero''' diff --git a/tests/test_core/test_lib/test_single_task.py b/tests/test_core/test_lib/test_single_task.py index 0d09130e..16552c9a 100644 --- a/tests/test_core/test_lib/test_single_task.py +++ b/tests/test_core/test_lib/test_single_task.py @@ -1,7 +1,7 @@ import asyncio +from unittest.mock import Mock from HABApp.core.lib import SingleTask -from unittest.mock import Mock async def test_single_task_start(): diff --git a/tests/test_core/test_logger.py b/tests/test_core/test_logger.py index 68ff68ef..e9c692ac 100644 --- a/tests/test_core/test_logger.py +++ b/tests/test_core/test_logger.py @@ -1,7 +1,7 @@ from logging import getLogger from unittest.mock import Mock -from HABApp.core.logger import HABAppLogger, HABAppError, HABAppInfo, HABAppWarning +from HABApp.core.logger import HABAppError, HABAppInfo, HABAppLogger, HABAppWarning from tests.helpers import TestEventBus diff --git a/tests/test_core/test_wrapped_func.py b/tests/test_core/test_wrapped_func.py index 7c6ba1a2..4e2e4dec 100644 --- a/tests/test_core/test_wrapped_func.py +++ b/tests/test_core/test_wrapped_func.py @@ -12,15 +12,15 @@ def test_error(): - with pytest.raises(ValueError) as e: + with pytest.raises(TypeError) as e: wrap_func(None) assert str(e.value) == 'Callable or coroutine function expected! Got "None" (type NoneType)' - with pytest.raises(ValueError) as e: + with pytest.raises(TypeError) as e: wrap_func(6) assert str(e.value) == 'Callable or coroutine function expected! Got "6" (type int)' - with pytest.raises(ValueError) as e: + with pytest.raises(TypeError) as e: wrap_func(date(2023, 12, 24)) assert str(e.value) == 'Callable or coroutine function expected! Got "2023-12-24" (type date)' diff --git a/tests/test_core/test_wrapper.py b/tests/test_core/test_wrapper.py index c046bf78..29a286ad 100644 --- a/tests/test_core/test_wrapper.py +++ b/tests/test_core/test_wrapper.py @@ -8,14 +8,15 @@ import HABApp from HABApp.core.wrapper import ExceptionToHABApp, ignore_exception + log = Mock() -@pytest.fixture +@pytest.fixture() def p_mock(monkeypatch): m = Mock() monkeypatch.setattr(HABApp.core.wrapper, 'post_event', m) - yield m + return m def test_error_catch(p_mock): @@ -51,12 +52,12 @@ def func_a(_l): 1 / 0 -@pytest.mark.ignore_log_errors +@pytest.mark.ignore_log_errors() def test_func_wrapper(p_mock): func_a(['asdf', 'asdf']) -@pytest.mark.skip(reason="Behavior still unclear") +@pytest.mark.skip(reason='Behavior still unclear') def test_exception_format_included_files(p_mock): async def test(): async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(0.01)) as session: diff --git a/tests/test_docs.py b/tests/test_docs.py index 7831197e..3fa30520 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -1,11 +1,11 @@ from inspect import getmembers, isclass from pathlib import Path +from easyconfig import yaml from pydantic import BaseModel import HABApp.config.models from HABApp.config import CONFIG -from easyconfig import yaml def test_sample_yaml(pytestconfig): diff --git a/tests/test_mqtt/test_interface.py b/tests/test_mqtt/test_interface.py index 1be9af7f..20d2ffe4 100644 --- a/tests/test_mqtt/test_interface.py +++ b/tests/test_mqtt/test_interface.py @@ -1,5 +1,5 @@ from HABApp.mqtt.connection.publish import async_publish, publish -from HABApp.mqtt.connection.subscribe import async_subscribe, subscribe, async_unsubscribe, unsubscribe +from HABApp.mqtt.connection.subscribe import async_subscribe, async_unsubscribe, subscribe, unsubscribe from tests.helpers.inspect import assert_same_signature diff --git a/tests/test_mqtt/test_mqtt_filters.py b/tests/test_mqtt/test_mqtt_filters.py index 2a4aeb99..a4eea4e2 100644 --- a/tests/test_mqtt/test_mqtt_filters.py +++ b/tests/test_mqtt/test_mqtt_filters.py @@ -1,6 +1,10 @@ -from HABApp.mqtt.events import MqttValueChangeEvent, MqttValueChangeEventFilter, MqttValueUpdateEvent, \ - MqttValueUpdateEventFilter -from tests.helpers.inspect import get_module_classes, check_class_annotations +from HABApp.mqtt.events import ( + MqttValueChangeEvent, + MqttValueChangeEventFilter, + MqttValueUpdateEvent, + MqttValueUpdateEventFilter, +) +from tests.helpers.inspect import check_class_annotations, get_module_classes def test_class_annotations(): diff --git a/tests/test_openhab/test_events/test_from_dict.py b/tests/test_openhab/test_events/test_from_dict.py index b111cf95..51368f2c 100644 --- a/tests/test_openhab/test_events/test_from_dict.py +++ b/tests/test_openhab/test_events/test_from_dict.py @@ -4,11 +4,27 @@ import pytest from HABApp.openhab.definitions import QuantityValue -from HABApp.openhab.events import ChannelTriggeredEvent, GroupStateChangedEvent, ItemAddedEvent, ItemCommandEvent, \ - ItemStateChangedEvent, ItemStateEvent, ItemStateUpdatedEvent, ItemStatePredictedEvent, ItemUpdatedEvent, \ - ThingStatusInfoChangedEvent, ThingStatusInfoEvent, ThingFirmwareStatusInfoEvent, ChannelDescriptionChangedEvent, \ - ThingAddedEvent, ThingRemovedEvent, ThingUpdatedEvent, ThingConfigStatusInfoEvent, GroupStateUpdatedEvent -from HABApp.openhab.map_events import get_event, EVENT_LIST +from HABApp.openhab.events import ( + ChannelDescriptionChangedEvent, + ChannelTriggeredEvent, + GroupStateChangedEvent, + GroupStateUpdatedEvent, + ItemAddedEvent, + ItemCommandEvent, + ItemStateChangedEvent, + ItemStateEvent, + ItemStatePredictedEvent, + ItemStateUpdatedEvent, + ItemUpdatedEvent, + ThingAddedEvent, + ThingConfigStatusInfoEvent, + ThingFirmwareStatusInfoEvent, + ThingRemovedEvent, + ThingStatusInfoChangedEvent, + ThingStatusInfoEvent, + ThingUpdatedEvent, +) +from HABApp.openhab.map_events import EVENT_LIST, get_event # noinspection PyPep8Naming @@ -80,7 +96,7 @@ def test_ItemAddedEvent2(): assert event.name == 'TestColor_OFF' assert event.type == 'Color' assert event.tags == frozenset() - assert event.groups == frozenset(["TestGroup"]) + assert event.groups == frozenset(['TestGroup']) assert str(event) == '' event = get_event({ @@ -197,9 +213,9 @@ def test_GroupItemStateChangedEvent(): # noinspection PyPep8Naming def test_channel_ChannelTriggeredEvent(): d = { - "topic": "openhab/channels/mihome:sensor_switch:00000000000000:button/triggered", - "payload": "{\"event\":\"SHORT_PRESSED\",\"channel\":\"mihome:sensor_switch:11111111111111:button\"}", - "type": "ChannelTriggeredEvent" + 'topic': 'openhab/channels/mihome:sensor_switch:00000000000000:button/triggered', + 'payload': '{\"event\":\"SHORT_PRESSED\",\"channel\":\"mihome:sensor_switch:11111111111111:button\"}', + 'type': 'ChannelTriggeredEvent' } event = get_event(d) @@ -254,10 +270,10 @@ def test_thing_ThingStatusInfoEvent(): 'status: ONLINE, detail: NONE>' data = { - "topic": "openhab/things/fsinternetradio:radio:fsRadioStation/status", - "payload": "{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\",\"description\":" - "\"java.util.concurrent.ExecutionException: java.net.NoRouteToHostException\"}", - "type": "ThingStatusInfoEvent" + 'topic': 'openhab/things/fsinternetradio:radio:fsRadioStation/status', + 'payload': '{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\",\"description\":' + '\"java.util.concurrent.ExecutionException: java.net.NoRouteToHostException\"}', + 'type': 'ThingStatusInfoEvent' } event = get_event(data) assert isinstance(event, ThingStatusInfoEvent) @@ -287,12 +303,12 @@ def test_thing_ThingStatusInfoChangedEvent(): assert event.old_detail == 'NONE' data = { - "topic": "openhab/things/fsinternetradio:radio:fsRadioStation/statuschanged", - "payload": "[{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\"," - "\"description\":\"java.util.concurrent.ExecutionException: java.net.NoRouteToHostException\"}," - "{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\"," - "\"description\":\"java.util.concurrent.TimeoutException: Total timeout 5000 ms elapsed\"}]", - "type": "ThingStatusInfoChangedEvent" + 'topic': 'openhab/things/fsinternetradio:radio:fsRadioStation/statuschanged', + 'payload': '[{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\",' + '\"description\":\"java.util.concurrent.ExecutionException: java.net.NoRouteToHostException\"},' + '{\"status\":\"OFFLINE\",\"statusDetail\":\"COMMUNICATION_ERROR\",' + '\"description\":\"java.util.concurrent.TimeoutException: Total timeout 5000 ms elapsed\"}]', + 'type': 'ThingStatusInfoChangedEvent' } event = get_event(data) assert isinstance(event, ThingStatusInfoChangedEvent) @@ -363,9 +379,9 @@ def test_thing_ThingRemovedEvent(): # noinspection PyPep8Naming def test_thing_ThingUpdatedEvent(): data = { - "topic": "openhab/things/astro:sun:local/updated", - "payload": "[{\"channels\":[{\"uid\":\"astro:sun:local:rise#start\",\"id\":\"rise#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#end\",\"id\":\"rise#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#duration\",\"id\":\"rise#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:rise#event\",\"id\":\"rise#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#start\",\"id\":\"set#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#end\",\"id\":\"set#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#duration\",\"id\":\"set#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:set#event\",\"id\":\"set#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#start\",\"id\":\"noon#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#end\",\"id\":\"noon#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#duration\",\"id\":\"noon#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:noon#event\",\"id\":\"noon#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#start\",\"id\":\"night#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#end\",\"id\":\"night#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#duration\",\"id\":\"night#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:night#event\",\"id\":\"night#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#start\",\"id\":\"morningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#end\",\"id\":\"morningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#duration\",\"id\":\"morningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:morningNight#event\",\"id\":\"morningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#start\",\"id\":\"astroDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#end\",\"id\":\"astroDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#duration\",\"id\":\"astroDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDawn#event\",\"id\":\"astroDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#start\",\"id\":\"nauticDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#end\",\"id\":\"nauticDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#duration\",\"id\":\"nauticDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDawn#event\",\"id\":\"nauticDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#start\",\"id\":\"civilDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#end\",\"id\":\"civilDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#duration\",\"id\":\"civilDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDawn#event\",\"id\":\"civilDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#start\",\"id\":\"astroDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#end\",\"id\":\"astroDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#duration\",\"id\":\"astroDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDusk#event\",\"id\":\"astroDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#start\",\"id\":\"nauticDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#end\",\"id\":\"nauticDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#duration\",\"id\":\"nauticDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDusk#event\",\"id\":\"nauticDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#start\",\"id\":\"civilDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#end\",\"id\":\"civilDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#duration\",\"id\":\"civilDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDusk#event\",\"id\":\"civilDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#start\",\"id\":\"eveningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#end\",\"id\":\"eveningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#duration\",\"id\":\"eveningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eveningNight#event\",\"id\":\"eveningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#start\",\"id\":\"daylight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#end\",\"id\":\"daylight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#duration\",\"id\":\"daylight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:daylight#event\",\"id\":\"daylight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:position#azimuth\",\"id\":\"position#azimuth\",\"channelTypeUID\":\"astro:azimuth\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Azimut\",\"description\":\"Das Azimut des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#elevation\",\"id\":\"position#elevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#shadeLength\",\"id\":\"position#shadeLength\",\"channelTypeUID\":\"astro:shadeLength\",\"itemType\":\"Number\",\"kind\":\"STATE\",\"label\":\"Schattenlängenverhältnis\",\"description\":\"Projiziertes Schattenlängenverhältnis (Abgeleitet vom Höhenwinkel)\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#direct\",\"id\":\"radiation#direct\",\"channelTypeUID\":\"astro:directRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Direkte Strahlung\",\"description\":\"Höhe der Strahlung nach Eindringen in die atmosphärische Schicht\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#diffuse\",\"id\":\"radiation#diffuse\",\"channelTypeUID\":\"astro:diffuseRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Diffuse Strahlung\",\"description\":\"Höhe der Strahlung, nach Beugung durch Wolken und Atmosphäre\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#total\",\"id\":\"radiation#total\",\"channelTypeUID\":\"astro:totalRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Gesamtstrahlung\",\"description\":\"Gesamtmenge der Strahlung auf dem Boden\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:zodiac#start\",\"id\":\"zodiac#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#end\",\"id\":\"zodiac#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#sign\",\"id\":\"zodiac#sign\",\"channelTypeUID\":\"astro:sign\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sternzeichen\",\"description\":\"Das Sternzeichen\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#name\",\"id\":\"season#name\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Jahreszeit\",\"description\":\"Der Name der aktuellen Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#spring\",\"id\":\"season#spring\",\"channelTypeUID\":\"astro:spring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Frühling\",\"description\":\"Frühlingsanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#summer\",\"id\":\"season#summer\",\"channelTypeUID\":\"astro:summer\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Sommer\",\"description\":\"Sommeranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#autumn\",\"id\":\"season#autumn\",\"channelTypeUID\":\"astro:autumn\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Herbst\",\"description\":\"Herbstanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#winter\",\"id\":\"season#winter\",\"channelTypeUID\":\"astro:winter\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Winter\",\"description\":\"Winteranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#nextName\",\"id\":\"season#nextName\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Nächste Jahreszeit\",\"description\":\"Der Name der nächsten Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#timeLeft\",\"id\":\"season#timeLeft\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Verbleibende Zeit\",\"description\":\"Die verbleibende Zeit bis zum nächsten Jahreszeitwechsel\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#total\",\"id\":\"eclipse#total\",\"channelTypeUID\":\"astro:total\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Totale Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten totalen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#totalElevation\",\"id\":\"eclipse#totalElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partial\",\"id\":\"eclipse#partial\",\"channelTypeUID\":\"astro:partial\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Partielle Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten partiellen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partialElevation\",\"id\":\"eclipse#partialElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ring\",\"id\":\"eclipse#ring\",\"channelTypeUID\":\"astro:ring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Ringförmige Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten ringförmigen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ringElevation\",\"id\":\"eclipse#ringElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#event\",\"id\":\"eclipse#event\",\"channelTypeUID\":\"astro:sunEclipseEvent\",\"kind\":\"TRIGGER\",\"label\":\"Sonnenfinsternisereignis\",\"description\":\"Sonnenfinsternisereignis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:phase#name\",\"id\":\"phase#name\",\"channelTypeUID\":\"astro:sunPhaseName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sonnenphase\",\"description\":\"Der Name der aktuellen Sonnenphase\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}}],\"label\":\"Lokale Sonnendaten\",\"configuration\":{\"useMeteorologicalSeason\":false,\"interval\":300,\"geolocation\":\"52.175458141853085,12.015266418457033\"},\"properties\":{},\"UID\":\"astro:sun:local\",\"thingTypeUID\":\"astro:sun\"},{\"channels\":[{\"uid\":\"astro:sun:local:rise#start\",\"id\":\"rise#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#end\",\"id\":\"rise#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#duration\",\"id\":\"rise#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:rise#event\",\"id\":\"rise#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#start\",\"id\":\"set#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#end\",\"id\":\"set#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#duration\",\"id\":\"set#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:set#event\",\"id\":\"set#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#start\",\"id\":\"noon#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#end\",\"id\":\"noon#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#duration\",\"id\":\"noon#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:noon#event\",\"id\":\"noon#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#start\",\"id\":\"night#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#end\",\"id\":\"night#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#duration\",\"id\":\"night#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:night#event\",\"id\":\"night#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#start\",\"id\":\"morningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#end\",\"id\":\"morningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#duration\",\"id\":\"morningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:morningNight#event\",\"id\":\"morningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#start\",\"id\":\"astroDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#end\",\"id\":\"astroDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#duration\",\"id\":\"astroDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDawn#event\",\"id\":\"astroDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#start\",\"id\":\"nauticDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#end\",\"id\":\"nauticDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#duration\",\"id\":\"nauticDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDawn#event\",\"id\":\"nauticDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#start\",\"id\":\"civilDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#end\",\"id\":\"civilDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#duration\",\"id\":\"civilDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDawn#event\",\"id\":\"civilDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#start\",\"id\":\"astroDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#end\",\"id\":\"astroDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#duration\",\"id\":\"astroDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDusk#event\",\"id\":\"astroDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#start\",\"id\":\"nauticDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#end\",\"id\":\"nauticDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#duration\",\"id\":\"nauticDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDusk#event\",\"id\":\"nauticDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#start\",\"id\":\"civilDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#end\",\"id\":\"civilDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#duration\",\"id\":\"civilDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDusk#event\",\"id\":\"civilDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#start\",\"id\":\"eveningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#end\",\"id\":\"eveningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#duration\",\"id\":\"eveningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eveningNight#event\",\"id\":\"eveningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#start\",\"id\":\"daylight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#end\",\"id\":\"daylight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#duration\",\"id\":\"daylight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:daylight#event\",\"id\":\"daylight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:position#azimuth\",\"id\":\"position#azimuth\",\"channelTypeUID\":\"astro:azimuth\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Azimut\",\"description\":\"Das Azimut des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#elevation\",\"id\":\"position#elevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#shadeLength\",\"id\":\"position#shadeLength\",\"channelTypeUID\":\"astro:shadeLength\",\"itemType\":\"Number\",\"kind\":\"STATE\",\"label\":\"Schattenlängenverhältnis\",\"description\":\"Projiziertes Schattenlängenverhältnis (Abgeleitet vom Höhenwinkel)\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#direct\",\"id\":\"radiation#direct\",\"channelTypeUID\":\"astro:directRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Direkte Strahlung\",\"description\":\"Höhe der Strahlung nach Eindringen in die atmosphärische Schicht\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#diffuse\",\"id\":\"radiation#diffuse\",\"channelTypeUID\":\"astro:diffuseRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Diffuse Strahlung\",\"description\":\"Höhe der Strahlung, nach Beugung durch Wolken und Atmosphäre\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#total\",\"id\":\"radiation#total\",\"channelTypeUID\":\"astro:totalRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Gesamtstrahlung\",\"description\":\"Gesamtmenge der Strahlung auf dem Boden\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:zodiac#start\",\"id\":\"zodiac#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#end\",\"id\":\"zodiac#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#sign\",\"id\":\"zodiac#sign\",\"channelTypeUID\":\"astro:sign\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sternzeichen\",\"description\":\"Das Sternzeichen\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#name\",\"id\":\"season#name\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Jahreszeit\",\"description\":\"Der Name der aktuellen Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#spring\",\"id\":\"season#spring\",\"channelTypeUID\":\"astro:spring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Frühling\",\"description\":\"Frühlingsanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#summer\",\"id\":\"season#summer\",\"channelTypeUID\":\"astro:summer\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Sommer\",\"description\":\"Sommeranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#autumn\",\"id\":\"season#autumn\",\"channelTypeUID\":\"astro:autumn\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Herbst\",\"description\":\"Herbstanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#winter\",\"id\":\"season#winter\",\"channelTypeUID\":\"astro:winter\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Winter\",\"description\":\"Winteranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#nextName\",\"id\":\"season#nextName\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Nächste Jahreszeit\",\"description\":\"Der Name der nächsten Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#timeLeft\",\"id\":\"season#timeLeft\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Verbleibende Zeit\",\"description\":\"Die verbleibende Zeit bis zum nächsten Jahreszeitwechsel\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#total\",\"id\":\"eclipse#total\",\"channelTypeUID\":\"astro:total\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Totale Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten totalen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#totalElevation\",\"id\":\"eclipse#totalElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partial\",\"id\":\"eclipse#partial\",\"channelTypeUID\":\"astro:partial\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Partielle Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten partiellen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partialElevation\",\"id\":\"eclipse#partialElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ring\",\"id\":\"eclipse#ring\",\"channelTypeUID\":\"astro:ring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Ringförmige Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten ringförmigen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ringElevation\",\"id\":\"eclipse#ringElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#event\",\"id\":\"eclipse#event\",\"channelTypeUID\":\"astro:sunEclipseEvent\",\"kind\":\"TRIGGER\",\"label\":\"Sonnenfinsternisereignis\",\"description\":\"Sonnenfinsternisereignis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:phase#name\",\"id\":\"phase#name\",\"channelTypeUID\":\"astro:sunPhaseName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sonnenphase\",\"description\":\"Der Name der aktuellen Sonnenphase\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}}],\"label\":\"Lokale Sonnendaten\",\"configuration\":{\"useMeteorologicalSeason\":false,\"interval\":300,\"geolocation\":\"52.17651083497927,12.022132873535158\"},\"properties\":{},\"UID\":\"astro:sun:local\",\"thingTypeUID\":\"astro:sun\"}]", # noqa: E501 - "type": "ThingUpdatedEvent" + 'topic': 'openhab/things/astro:sun:local/updated', + 'payload': '[{\"channels\":[{\"uid\":\"astro:sun:local:rise#start\",\"id\":\"rise#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#end\",\"id\":\"rise#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#duration\",\"id\":\"rise#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:rise#event\",\"id\":\"rise#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#start\",\"id\":\"set#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#end\",\"id\":\"set#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#duration\",\"id\":\"set#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:set#event\",\"id\":\"set#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#start\",\"id\":\"noon#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#end\",\"id\":\"noon#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#duration\",\"id\":\"noon#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:noon#event\",\"id\":\"noon#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#start\",\"id\":\"night#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#end\",\"id\":\"night#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#duration\",\"id\":\"night#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:night#event\",\"id\":\"night#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#start\",\"id\":\"morningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#end\",\"id\":\"morningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#duration\",\"id\":\"morningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:morningNight#event\",\"id\":\"morningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#start\",\"id\":\"astroDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#end\",\"id\":\"astroDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#duration\",\"id\":\"astroDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDawn#event\",\"id\":\"astroDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#start\",\"id\":\"nauticDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#end\",\"id\":\"nauticDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#duration\",\"id\":\"nauticDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDawn#event\",\"id\":\"nauticDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#start\",\"id\":\"civilDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#end\",\"id\":\"civilDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#duration\",\"id\":\"civilDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDawn#event\",\"id\":\"civilDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#start\",\"id\":\"astroDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#end\",\"id\":\"astroDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#duration\",\"id\":\"astroDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDusk#event\",\"id\":\"astroDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#start\",\"id\":\"nauticDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#end\",\"id\":\"nauticDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#duration\",\"id\":\"nauticDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDusk#event\",\"id\":\"nauticDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#start\",\"id\":\"civilDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#end\",\"id\":\"civilDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#duration\",\"id\":\"civilDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDusk#event\",\"id\":\"civilDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#start\",\"id\":\"eveningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#end\",\"id\":\"eveningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#duration\",\"id\":\"eveningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eveningNight#event\",\"id\":\"eveningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#start\",\"id\":\"daylight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#end\",\"id\":\"daylight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#duration\",\"id\":\"daylight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:daylight#event\",\"id\":\"daylight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:position#azimuth\",\"id\":\"position#azimuth\",\"channelTypeUID\":\"astro:azimuth\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Azimut\",\"description\":\"Das Azimut des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#elevation\",\"id\":\"position#elevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#shadeLength\",\"id\":\"position#shadeLength\",\"channelTypeUID\":\"astro:shadeLength\",\"itemType\":\"Number\",\"kind\":\"STATE\",\"label\":\"Schattenlängenverhältnis\",\"description\":\"Projiziertes Schattenlängenverhältnis (Abgeleitet vom Höhenwinkel)\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#direct\",\"id\":\"radiation#direct\",\"channelTypeUID\":\"astro:directRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Direkte Strahlung\",\"description\":\"Höhe der Strahlung nach Eindringen in die atmosphärische Schicht\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#diffuse\",\"id\":\"radiation#diffuse\",\"channelTypeUID\":\"astro:diffuseRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Diffuse Strahlung\",\"description\":\"Höhe der Strahlung, nach Beugung durch Wolken und Atmosphäre\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#total\",\"id\":\"radiation#total\",\"channelTypeUID\":\"astro:totalRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Gesamtstrahlung\",\"description\":\"Gesamtmenge der Strahlung auf dem Boden\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:zodiac#start\",\"id\":\"zodiac#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#end\",\"id\":\"zodiac#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#sign\",\"id\":\"zodiac#sign\",\"channelTypeUID\":\"astro:sign\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sternzeichen\",\"description\":\"Das Sternzeichen\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#name\",\"id\":\"season#name\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Jahreszeit\",\"description\":\"Der Name der aktuellen Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#spring\",\"id\":\"season#spring\",\"channelTypeUID\":\"astro:spring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Frühling\",\"description\":\"Frühlingsanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#summer\",\"id\":\"season#summer\",\"channelTypeUID\":\"astro:summer\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Sommer\",\"description\":\"Sommeranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#autumn\",\"id\":\"season#autumn\",\"channelTypeUID\":\"astro:autumn\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Herbst\",\"description\":\"Herbstanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#winter\",\"id\":\"season#winter\",\"channelTypeUID\":\"astro:winter\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Winter\",\"description\":\"Winteranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#nextName\",\"id\":\"season#nextName\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Nächste Jahreszeit\",\"description\":\"Der Name der nächsten Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#timeLeft\",\"id\":\"season#timeLeft\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Verbleibende Zeit\",\"description\":\"Die verbleibende Zeit bis zum nächsten Jahreszeitwechsel\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#total\",\"id\":\"eclipse#total\",\"channelTypeUID\":\"astro:total\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Totale Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten totalen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#totalElevation\",\"id\":\"eclipse#totalElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partial\",\"id\":\"eclipse#partial\",\"channelTypeUID\":\"astro:partial\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Partielle Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten partiellen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partialElevation\",\"id\":\"eclipse#partialElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ring\",\"id\":\"eclipse#ring\",\"channelTypeUID\":\"astro:ring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Ringförmige Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten ringförmigen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ringElevation\",\"id\":\"eclipse#ringElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#event\",\"id\":\"eclipse#event\",\"channelTypeUID\":\"astro:sunEclipseEvent\",\"kind\":\"TRIGGER\",\"label\":\"Sonnenfinsternisereignis\",\"description\":\"Sonnenfinsternisereignis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:phase#name\",\"id\":\"phase#name\",\"channelTypeUID\":\"astro:sunPhaseName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sonnenphase\",\"description\":\"Der Name der aktuellen Sonnenphase\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}}],\"label\":\"Lokale Sonnendaten\",\"configuration\":{\"useMeteorologicalSeason\":false,\"interval\":300,\"geolocation\":\"52.175458141853085,12.015266418457033\"},\"properties\":{},\"UID\":\"astro:sun:local\",\"thingTypeUID\":\"astro:sun\"},{\"channels\":[{\"uid\":\"astro:sun:local:rise#start\",\"id\":\"rise#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#end\",\"id\":\"rise#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:rise#duration\",\"id\":\"rise#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:rise#event\",\"id\":\"rise#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#start\",\"id\":\"set#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#end\",\"id\":\"set#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:set#duration\",\"id\":\"set#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:set#event\",\"id\":\"set#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#start\",\"id\":\"noon#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#end\",\"id\":\"noon#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:noon#duration\",\"id\":\"noon#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:noon#event\",\"id\":\"noon#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#start\",\"id\":\"night#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#end\",\"id\":\"night#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:night#duration\",\"id\":\"night#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:night#event\",\"id\":\"night#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#start\",\"id\":\"morningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#end\",\"id\":\"morningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:morningNight#duration\",\"id\":\"morningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:morningNight#event\",\"id\":\"morningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#start\",\"id\":\"astroDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#end\",\"id\":\"astroDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDawn#duration\",\"id\":\"astroDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDawn#event\",\"id\":\"astroDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#start\",\"id\":\"nauticDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#end\",\"id\":\"nauticDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDawn#duration\",\"id\":\"nauticDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDawn#event\",\"id\":\"nauticDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#start\",\"id\":\"civilDawn#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#end\",\"id\":\"civilDawn#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDawn#duration\",\"id\":\"civilDawn#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDawn#event\",\"id\":\"civilDawn#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#start\",\"id\":\"astroDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#end\",\"id\":\"astroDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:astroDusk#duration\",\"id\":\"astroDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:astroDusk#event\",\"id\":\"astroDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#start\",\"id\":\"nauticDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#end\",\"id\":\"nauticDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:nauticDusk#duration\",\"id\":\"nauticDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:nauticDusk#event\",\"id\":\"nauticDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#start\",\"id\":\"civilDusk#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#end\",\"id\":\"civilDusk#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:civilDusk#duration\",\"id\":\"civilDusk#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:civilDusk#event\",\"id\":\"civilDusk#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#start\",\"id\":\"eveningNight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#end\",\"id\":\"eveningNight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:eveningNight#duration\",\"id\":\"eveningNight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eveningNight#event\",\"id\":\"eveningNight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#start\",\"id\":\"daylight#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#end\",\"id\":\"daylight#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:daylight#duration\",\"id\":\"daylight#duration\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Dauer\",\"description\":\"Die Dauer des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:daylight#event\",\"id\":\"daylight#event\",\"channelTypeUID\":\"astro:rangeEvent\",\"kind\":\"TRIGGER\",\"label\":\"Zeitraum\",\"description\":\"Zeitraum für ein Ereignis.\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:position#azimuth\",\"id\":\"position#azimuth\",\"channelTypeUID\":\"astro:azimuth\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Azimut\",\"description\":\"Das Azimut des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#elevation\",\"id\":\"position#elevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:position#shadeLength\",\"id\":\"position#shadeLength\",\"channelTypeUID\":\"astro:shadeLength\",\"itemType\":\"Number\",\"kind\":\"STATE\",\"label\":\"Schattenlängenverhältnis\",\"description\":\"Projiziertes Schattenlängenverhältnis (Abgeleitet vom Höhenwinkel)\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#direct\",\"id\":\"radiation#direct\",\"channelTypeUID\":\"astro:directRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Direkte Strahlung\",\"description\":\"Höhe der Strahlung nach Eindringen in die atmosphärische Schicht\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#diffuse\",\"id\":\"radiation#diffuse\",\"channelTypeUID\":\"astro:diffuseRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Diffuse Strahlung\",\"description\":\"Höhe der Strahlung, nach Beugung durch Wolken und Atmosphäre\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:radiation#total\",\"id\":\"radiation#total\",\"channelTypeUID\":\"astro:totalRadiation\",\"itemType\":\"Number:Intensity\",\"kind\":\"STATE\",\"label\":\"Gesamtstrahlung\",\"description\":\"Gesamtmenge der Strahlung auf dem Boden\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:zodiac#start\",\"id\":\"zodiac#start\",\"channelTypeUID\":\"astro:start\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Startzeit\",\"description\":\"Die Startzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#end\",\"id\":\"zodiac#end\",\"channelTypeUID\":\"astro:end\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Endzeit\",\"description\":\"Die Endzeit des Ereignisses\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:zodiac#sign\",\"id\":\"zodiac#sign\",\"channelTypeUID\":\"astro:sign\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sternzeichen\",\"description\":\"Das Sternzeichen\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#name\",\"id\":\"season#name\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Jahreszeit\",\"description\":\"Der Name der aktuellen Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#spring\",\"id\":\"season#spring\",\"channelTypeUID\":\"astro:spring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Frühling\",\"description\":\"Frühlingsanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#summer\",\"id\":\"season#summer\",\"channelTypeUID\":\"astro:summer\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Sommer\",\"description\":\"Sommeranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#autumn\",\"id\":\"season#autumn\",\"channelTypeUID\":\"astro:autumn\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Herbst\",\"description\":\"Herbstanfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#winter\",\"id\":\"season#winter\",\"channelTypeUID\":\"astro:winter\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Winter\",\"description\":\"Winteranfang\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#nextName\",\"id\":\"season#nextName\",\"channelTypeUID\":\"astro:seasonName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Nächste Jahreszeit\",\"description\":\"Der Name der nächsten Jahreszeit\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:season#timeLeft\",\"id\":\"season#timeLeft\",\"channelTypeUID\":\"astro:duration\",\"itemType\":\"Number:Time\",\"kind\":\"STATE\",\"label\":\"Verbleibende Zeit\",\"description\":\"Die verbleibende Zeit bis zum nächsten Jahreszeitwechsel\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#total\",\"id\":\"eclipse#total\",\"channelTypeUID\":\"astro:total\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Totale Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten totalen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#totalElevation\",\"id\":\"eclipse#totalElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partial\",\"id\":\"eclipse#partial\",\"channelTypeUID\":\"astro:partial\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Partielle Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten partiellen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#partialElevation\",\"id\":\"eclipse#partialElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ring\",\"id\":\"eclipse#ring\",\"channelTypeUID\":\"astro:ring\",\"itemType\":\"DateTime\",\"kind\":\"STATE\",\"label\":\"Ringförmige Sonnenfinsternis\",\"description\":\"Zeitpunkt der nächsten ringförmigen Sonnenfinsternis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#ringElevation\",\"id\":\"eclipse#ringElevation\",\"channelTypeUID\":\"astro:elevation\",\"itemType\":\"Number:Angle\",\"kind\":\"STATE\",\"label\":\"Höhenwinkel\",\"description\":\"Der Höhenwinkel des Himmelskörpers\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}},{\"uid\":\"astro:sun:local:eclipse#event\",\"id\":\"eclipse#event\",\"channelTypeUID\":\"astro:sunEclipseEvent\",\"kind\":\"TRIGGER\",\"label\":\"Sonnenfinsternisereignis\",\"description\":\"Sonnenfinsternisereignis\",\"defaultTags\":[],\"properties\":{},\"configuration\":{\"offset\":0}},{\"uid\":\"astro:sun:local:phase#name\",\"id\":\"phase#name\",\"channelTypeUID\":\"astro:sunPhaseName\",\"itemType\":\"String\",\"kind\":\"STATE\",\"label\":\"Sonnenphase\",\"description\":\"Der Name der aktuellen Sonnenphase\",\"defaultTags\":[],\"properties\":{},\"configuration\":{}}],\"label\":\"Lokale Sonnendaten\",\"configuration\":{\"useMeteorologicalSeason\":false,\"interval\":300,\"geolocation\":\"52.17651083497927,12.022132873535158\"},\"properties\":{},\"UID\":\"astro:sun:local\",\"thingTypeUID\":\"astro:sun\"}]', # noqa: E501 + 'type': 'ThingUpdatedEvent' } event = get_event(data) diff --git a/tests/test_openhab/test_events/test_oh_filters.py b/tests/test_openhab/test_events/test_oh_filters.py index 6f04a9ac..46f0b605 100644 --- a/tests/test_openhab/test_events/test_oh_filters.py +++ b/tests/test_openhab/test_events/test_oh_filters.py @@ -1,6 +1,13 @@ -from HABApp.openhab.events import ItemStateChangedEvent, ItemStateChangedEventFilter, ItemStateUpdatedEventFilter, \ - ItemCommandEventFilter, ItemCommandEvent, ItemStateUpdatedEvent -from tests.helpers.inspect import get_module_classes, check_class_annotations +from tests.helpers.inspect import check_class_annotations, get_module_classes + +from HABApp.openhab.events import ( + ItemCommandEvent, + ItemCommandEventFilter, + ItemStateChangedEvent, + ItemStateChangedEventFilter, + ItemStateUpdatedEvent, + ItemStateUpdatedEventFilter, +) def test_class_annotations(): diff --git a/tests/test_openhab/test_helpers/test_table.py b/tests/test_openhab/test_helpers/test_table.py index 1e9d9a76..8a1bf69a 100644 --- a/tests/test_openhab/test_helpers/test_table.py +++ b/tests/test_openhab/test_helpers/test_table.py @@ -1,4 +1,4 @@ -from HABApp.openhab.definitions.helpers.log_table import Table, Column +from HABApp.openhab.definitions.helpers.log_table import Column, Table def test_col(): diff --git a/tests/test_openhab/test_interface_sync.py b/tests/test_openhab/test_interface_sync.py index 9488ee1a..73949bba 100644 --- a/tests/test_openhab/test_interface_sync.py +++ b/tests/test_openhab/test_interface_sync.py @@ -4,13 +4,25 @@ import pytest import HABApp.openhab.interface_sync -from HABApp.core.asyncio import async_context, AsyncContextError -from HABApp.openhab.interface_sync import \ - post_update, send_command, \ - get_item, item_exists, remove_item, create_item, \ - get_thing, get_persistence_data, get_persistence_services, set_persistence_data, set_thing_enabled, \ - remove_metadata, set_metadata, \ - get_link, remove_link, create_link +from HABApp.core.asyncio import AsyncContextError, async_context +from HABApp.openhab.interface_sync import ( + create_item, + create_link, + get_item, + get_link, + get_persistence_data, + get_persistence_services, + get_thing, + item_exists, + post_update, + remove_item, + remove_link, + remove_metadata, + send_command, + set_metadata, + set_persistence_data, + set_thing_enabled, +) @pytest.mark.parametrize('func', [ diff --git a/tests/test_openhab/test_items/test_all.py b/tests/test_openhab/test_items/test_all.py index f0c83ed4..2d394c2f 100644 --- a/tests/test_openhab/test_items/test_all.py +++ b/tests/test_openhab/test_items/test_all.py @@ -1,15 +1,30 @@ import inspect from datetime import datetime -from typing import Union, Tuple, Optional, Any +from typing import Any, Optional, Tuple, Union import pytest from HABApp.core.items import Item -from HABApp.openhab.items import Thing, ColorItem, ImageItem, StringItem, NumberItem, SwitchItem, ContactItem, \ - RollershutterItem, DimmerItem, DatetimeItem, PlayerItem, LocationItem, CallItem, GroupItem +from HABApp.openhab.items import ( + CallItem, + ColorItem, + ContactItem, + DatetimeItem, + DimmerItem, + GroupItem, + ImageItem, + LocationItem, + NumberItem, + PlayerItem, + RollershutterItem, + StringItem, + SwitchItem, + Thing, +) from HABApp.openhab.items.base_item import OpenhabItem from HABApp.openhab.map_items import _items as item_dict -from ...helpers.inspect import check_class_annotations, get_ivars_from_docstring, assert_same_signature + +from ...helpers.inspect import assert_same_signature, check_class_annotations, get_ivars_from_docstring @pytest.mark.parametrize('cls', (c for c in item_dict.values())) diff --git a/tests/test_openhab/test_items/test_commands.py b/tests/test_openhab/test_items/test_commands.py index c4668f54..e78c607c 100644 --- a/tests/test_openhab/test_items/test_commands.py +++ b/tests/test_openhab/test_items/test_commands.py @@ -9,11 +9,11 @@ from HABApp.openhab.map_items import _items as item_dict -@pytest.mark.parametrize("cls", [cls for cls in item_dict.values() if issubclass(cls, OnOffCommand)]) +@pytest.mark.parametrize('cls', [cls for cls in item_dict.values() if issubclass(cls, OnOffCommand)]) def test_OnOff(cls): c = cls('item_name') assert not c.is_on() - if not __version__.startswith('24.02.0'): + if not __version__.startswith('24.08.0'): assert not c.is_off() c.set_value(OnOffValue('ON')) @@ -26,7 +26,7 @@ def test_OnOff(cls): assert not c.is_on() -@pytest.mark.parametrize("cls", [cls for cls in item_dict.values() if issubclass(cls, UpDownCommand)]) +@pytest.mark.parametrize('cls', [cls for cls in item_dict.values() if issubclass(cls, UpDownCommand)]) def test_UpDown(cls): c = cls('item_name') c.set_value(UpDownValue('UP')) @@ -39,7 +39,7 @@ def test_UpDown(cls): assert c.is_down() -@pytest.mark.parametrize("cls", (ContactItem, )) +@pytest.mark.parametrize('cls', (ContactItem, )) def test_OpenClosed(cls: typing.Type[ContactItem]): c = cls('item_name') assert not c.is_closed() diff --git a/tests/test_openhab/test_items/test_image.py b/tests/test_openhab/test_items/test_image.py index 9bcd07cb..e47bd1b6 100644 --- a/tests/test_openhab/test_items/test_image.py +++ b/tests/test_openhab/test_items/test_image.py @@ -7,7 +7,7 @@ def test_image_load(): i = map_item( 'localCurrentConditionIcon', 'Image', - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8CAYAAABTq8lnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDAgNzkuMTYwNDUxLCAyMDE3LzA1LzA2LTAxOjA4OjIxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTgtMDgtMTdUMTQ6MTc6NTAtMDQ6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE4LTA4LTIwVDA3OjM4OjE2LTA0OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE4LTA4LTIwVDA3OjM4OjE2LTA0OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjJiNzE4NDBmLTE2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", # noqa: E501 + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8CAYAAABTq8lnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDAgNzkuMTYwNDUxLCAyMDE3LzA1LzA2LTAxOjA4OjIxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTgtMDgtMTdUMTQ6MTc6NTAtMDQ6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE4LTA4LTIwVDA3OjM4OjE2LTA0OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE4LTA4LTIwVDA3OjM4OjE2LTA0OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjJiNzE4NDBmLTE2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', label='', tags=frozenset(), groups=frozenset(), diff --git a/tests/test_openhab/test_items/test_mapping.py b/tests/test_openhab/test_items/test_mapping.py index 9709a28c..03e0de36 100644 --- a/tests/test_openhab/test_items/test_mapping.py +++ b/tests/test_openhab/test_items/test_mapping.py @@ -2,16 +2,16 @@ from functools import partial import pytest +from eascheduler.const import local_tz from immutables import Map -from HABApp.openhab.items import NumberItem, DatetimeItem +from HABApp.openhab.items import DatetimeItem, NumberItem from HABApp.openhab.items.base_item import MetaData from HABApp.openhab.map_items import map_item -from eascheduler.const import local_tz from tests.helpers import TestEventBus -@pytest.mark.ignore_log_errors +@pytest.mark.ignore_log_errors() def test_exception(eb: TestEventBus): eb.allow_errors = True assert map_item('test', 'Number', 'asdf', 'my_label', frozenset(), frozenset(), {}) is None diff --git a/tests/test_openhab/test_items/test_thing.py b/tests/test_openhab/test_items/test_thing.py index 62176c9b..7775091f 100644 --- a/tests/test_openhab/test_items/test_thing.py +++ b/tests/test_openhab/test_items/test_thing.py @@ -1,19 +1,19 @@ -from typing import List, Dict, Any +from typing import Any, Dict, List from unittest.mock import Mock import pytest from immutables import Map -from pendulum import set_test_now, DateTime, UTC +from pendulum import UTC, DateTime, set_test_now import HABApp from HABApp.core.internals import ItemRegistry from HABApp.openhab import process_events as process_events_module -from HABApp.openhab.events import ThingStatusInfoEvent, ThingUpdatedEvent, ThingAddedEvent +from HABApp.openhab.events import ThingAddedEvent, ThingStatusInfoEvent, ThingUpdatedEvent from HABApp.openhab.items import Thing from HABApp.openhab.map_events import get_event -@pytest.fixture(scope="function") +@pytest.fixture(scope='function') def test_thing(ir: ItemRegistry): set_test_now(DateTime(2000, 1, 1, tzinfo=UTC)) thing = HABApp.openhab.items.Thing('test_thing') diff --git a/tests/test_openhab/test_openhab_datatypes.py b/tests/test_openhab/test_openhab_datatypes.py index 7ec4b4e0..45504ff5 100644 --- a/tests/test_openhab/test_openhab_datatypes.py +++ b/tests/test_openhab/test_openhab_datatypes.py @@ -23,7 +23,7 @@ def test_type_number(value: str, target: int): @pytest.mark.parametrize( - 'value, target', (('0.0', 0.0), ('-99.99', -99.99), ('99.99', 99.99), ('0', 0), ('-15', -15), ('55', 55), ) + 'value, target', (('0.0', 0), ('-99.99', -99.99), ('99.99', 99.99), ('0', 0), ('-15', -15), ('55', 55), ) ) def test_type_decimal(value: str, target: int): ret = NumberItem._state_from_oh_str(value) diff --git a/tests/test_openhab/test_plugins/test_broken_links.py b/tests/test_openhab/test_plugins/test_broken_links.py index bfe9b79d..37032334 100644 --- a/tests/test_openhab/test_plugins/test_broken_links.py +++ b/tests/test_openhab/test_plugins/test_broken_links.py @@ -6,8 +6,8 @@ import HABApp.openhab.connection.plugins.overview_broken_links as plugin_module from HABApp.core.internals import ItemRegistry from HABApp.core.items import Item -from HABApp.openhab.definitions.rest import ThingResp, ItemChannelLinkResp -from HABApp.openhab.definitions.rest.things import ThingStatusResp, ChannelResp +from HABApp.openhab.definitions.rest import ItemChannelLinkResp, ThingResp +from HABApp.openhab.definitions.rest.things import ChannelResp, ThingStatusResp async def _mock_things() -> list[ThingResp]: diff --git a/tests/test_openhab/test_plugins/test_load_items.py b/tests/test_openhab/test_plugins/test_load_items.py index 36fced3e..17699231 100644 --- a/tests/test_openhab/test_plugins/test_load_items.py +++ b/tests/test_openhab/test_plugins/test_load_items.py @@ -4,53 +4,53 @@ import msgspec.json +import HABApp.openhab.connection.plugins.load_items as load_items_module from HABApp.core.internals import ItemRegistry -from HABApp.openhab.connection.plugins import LoadOpenhabItemsPlugin from HABApp.openhab.connection.connection import OpenhabContext -import HABApp.openhab.connection.plugins.load_items as load_items_module -from HABApp.openhab.definitions.rest import ShortItemResp, ItemResp +from HABApp.openhab.connection.plugins import LoadOpenhabItemsPlugin +from HABApp.openhab.definitions.rest import ItemResp, ShortItemResp async def _mock_get_all_items(): resp = [ { - "link": "link length", - "state": "NULL", - "stateDescription": { - "pattern": "%d", - "readOnly": True, - "options": [] + 'link': 'link length', + 'state': 'NULL', + 'stateDescription': { + 'pattern': '%d', + 'readOnly': True, + 'options': [] }, - "metadata": { - "autoupdate": { - "value": "false" + 'metadata': { + 'autoupdate': { + 'value': 'false' } }, - "editable": False, - "type": "Number:Length", - "name": "ItemLength", - "label": "Label length", - "tags": [], - "groupNames": ["grp1"] + 'editable': False, + 'type': 'Number:Length', + 'name': 'ItemLength', + 'label': 'Label length', + 'tags': [], + 'groupNames': ['grp1'] }, { - "link": "link plain", - "state": "NULL", - "editable": False, - "type": "Number", - "name": "ItemPlain", - "label": "Label plain", - "tags": [], - "groupNames": [] + 'link': 'link plain', + 'state': 'NULL', + 'editable': False, + 'type': 'Number', + 'name': 'ItemPlain', + 'label': 'Label plain', + 'tags': [], + 'groupNames': [] }, { - "link": "link no update", - "state": "NULL", - "editable": False, - "type": "Number", - "name": "ItemNoUpdate", - "tags": [], - "groupNames": [] + 'link': 'link no update', + 'state': 'NULL', + 'editable': False, + 'type': 'Number', + 'name': 'ItemNoUpdate', + 'tags': [], + 'groupNames': [] }, ] @@ -59,8 +59,8 @@ async def _mock_get_all_items(): async def _mock_get_all_items_state(): return [ - ShortItemResp("Number:Length", "ItemLength", "5 m"), - ShortItemResp("Number", "ItemPlain", "3.14") + ShortItemResp('Number:Length', 'ItemLength', '5 m'), + ShortItemResp('Number', 'ItemPlain', '3.14') ] diff --git a/tests/test_openhab/test_plugins/test_thing/test_errors.py b/tests/test_openhab/test_plugins/test_thing/test_errors.py index 8d7921a0..3a63bce2 100644 --- a/tests/test_openhab/test_plugins/test_thing/test_errors.py +++ b/tests/test_openhab/test_plugins/test_thing/test_errors.py @@ -1,7 +1,7 @@ import time from HABApp.openhab.connection.plugins.plugin_things.plugin_things import TextualThingConfigPlugin -from tests.helpers import MockFile, TestEventBus, LogCollector +from tests.helpers import LogCollector, MockFile, TestEventBus async def test_errors(test_logs: LogCollector, eb: TestEventBus): @@ -9,16 +9,16 @@ async def test_errors(test_logs: LogCollector, eb: TestEventBus): cfg = TextualThingConfigPlugin() - data = [{"statusInfo": {"status": "ONLINE", "statusDetail": "NONE"}, "editable": True, - "label": "Astronomische Sonnendaten", - "configuration": {"interval": 120, "geolocation": "26.399750112407446,34.980468750000014"}, - "properties": {}, "UID": "astro:sun:1d5f16df", "thingTypeUID": "astro:sun", "channels": [ - {"linkedItems": [], "uid": "astro:sun:1d5f16df:rise#start", "id": "rise#start", - "channelTypeUID": "astro:start", "itemType": "DateTime", "kind": "STATE", "label": "Startzeit", - "description": "Die Startzeit des Ereignisses", "defaultTags": [], "properties": {}, - "configuration": {"offset": 0}}, ]}] + data = [{'statusInfo': {'status': 'ONLINE', 'statusDetail': 'NONE'}, 'editable': True, + 'label': 'Astronomische Sonnendaten', + 'configuration': {'interval': 120, 'geolocation': '26.399750112407446,34.980468750000014'}, + 'properties': {}, 'UID': 'astro:sun:1d5f16df', 'thingTypeUID': 'astro:sun', 'channels': [ + {'linkedItems': [], 'uid': 'astro:sun:1d5f16df:rise#start', 'id': 'rise#start', + 'channelTypeUID': 'astro:start', 'itemType': 'DateTime', 'kind': 'STATE', 'label': 'Startzeit', + 'description': 'Die Startzeit des Ereignisses', 'defaultTags': [], 'properties': {}, + 'configuration': {'offset': 0}}, ]}] - text = """ + text = ''' test: False filter: @@ -32,7 +32,7 @@ async def test_errors(test_logs: LogCollector, eb: TestEventBus): name: Name1 - type: Number name: Name1 - """ + ''' file = MockFile('/thing_test.yml', data=text) file.warn_on_delete = False @@ -42,7 +42,7 @@ async def test_errors(test_logs: LogCollector, eb: TestEventBus): test_logs.add_expected(None, 'ERROR', 'Duplicate item: Name1') - text = """ + text = ''' test: False filter: @@ -54,7 +54,7 @@ async def test_errors(test_logs: LogCollector, eb: TestEventBus): link items: - type: Number name: â ß { ) - """ + ''' file = MockFile('/thing_test.yml', data=text) file.warn_on_delete = False diff --git a/tests/test_openhab/test_plugins/test_thing/test_file_format.py b/tests/test_openhab/test_plugins/test_thing/test_file_format.py index b7d63a22..7ee6ea22 100644 --- a/tests/test_openhab/test_plugins/test_thing/test_file_format.py +++ b/tests/test_openhab/test_plugins/test_thing/test_file_format.py @@ -1,6 +1,6 @@ import pytest -from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import validate_cfg, UserItemCfg +from HABApp.openhab.connection.plugins.plugin_things.cfg_validator import UserItemCfg, validate_cfg from tests.helpers import TestEventBus @@ -23,7 +23,7 @@ def test_thing_cfg_types(): }) -@pytest.mark.ignore_log_errors +@pytest.mark.ignore_log_errors() def test_cfg_err(eb: TestEventBus): eb.allow_errors = True assert None is validate_cfg({'test': True, 'filter1': {}}, 'filename') diff --git a/tests/test_openhab/test_plugins/test_thing/test_file_writer/test_builder.py b/tests/test_openhab/test_plugins/test_thing/test_file_writer/test_builder.py index 9e84b96f..ffbc32d8 100644 --- a/tests/test_openhab/test_plugins/test_thing/test_file_writer/test_builder.py +++ b/tests/test_openhab/test_plugins/test_thing/test_file_writer/test_builder.py @@ -1,6 +1,10 @@ -from HABApp.openhab.connection.plugins.plugin_things.file_writer.formatter_builder import ValueFormatterBuilder, \ - EmptyFormatter, \ - MultipleValueFormatterBuilder, LinkFormatter, MetadataFormatter +from HABApp.openhab.connection.plugins.plugin_things.file_writer.formatter_builder import ( + EmptyFormatter, + LinkFormatter, + MetadataFormatter, + MultipleValueFormatterBuilder, + ValueFormatterBuilder, +) def test_value_formatter(): diff --git a/tests/test_openhab/test_plugins/test_thing/test_thing_cfg.py b/tests/test_openhab/test_plugins/test_thing/test_thing_cfg.py index 70c777d2..d004dd2b 100644 --- a/tests/test_openhab/test_plugins/test_thing/test_thing_cfg.py +++ b/tests/test_openhab/test_plugins/test_thing/test_thing_cfg.py @@ -3,7 +3,7 @@ from HABApp.openhab.connection.plugins.plugin_things.thing_config import ThingConfigChanger -@fixture +@fixture() def cfg(): return ThingConfigChanger.from_dict('UID', { 'config_1_1': 0, # 1 byte @@ -12,16 +12,16 @@ def cfg(): 'config_100_4_000000FF': 0, # 4 byte with bitmask 0xFF # sometimes parameters are inside the dict multiple times with a bitmask - "config_154_4_00FF0000": 255, - "config_154_4": 4294967295, - "config_154_4_7F000000": 127, - "config_154_4_000000FF": 255, - "config_154_4_0000FF00": 255, + 'config_154_4_00FF0000': 255, + 'config_154_4': 4294967295, + 'config_154_4_7F000000': 127, + 'config_154_4_000000FF': 255, + 'config_154_4_0000FF00': 255, 'group_1': ['controller'], 'binding_cmdrepollperiod': 2600, - "wakeup_interval": 3600, + 'wakeup_interval': 3600, }) @@ -63,7 +63,7 @@ def test_set_keys(cfg: ThingConfigChanger): def test_set_wrong_type(cfg: ThingConfigChanger): with raises(ValueError) as e: - cfg[1] = "asdf" + cfg[1] = 'asdf' assert str(e.value) == "Datatype of parameter '1' must be 'int' but is 'str': 'asdf'" with raises(ValueError): diff --git a/tests/test_openhab/test_rest/test_grp_func.py b/tests/test_openhab/test_rest/test_grp_func.py index 654d148f..a17692ac 100644 --- a/tests/test_openhab/test_rest/test_grp_func.py +++ b/tests/test_openhab/test_rest/test_grp_func.py @@ -7,10 +7,10 @@ def test_or(): _in = { - "name": "OR", - "params": [ - "ON", - "OFF" + 'name': 'OR', + 'params': [ + 'ON', + 'OFF' ] } o = decode(dumps(_in), type=GroupFunctionResp) @@ -19,7 +19,7 @@ def test_or(): def test_eq(): - _in = {"name": "EQUALITY"} + _in = {'name': 'EQUALITY'} o = decode(dumps(_in), type=GroupFunctionResp) assert o.name == 'EQUALITY' assert o.params == [] diff --git a/tests/test_openhab/test_rest/test_items.py b/tests/test_openhab/test_rest/test_items.py index 6fccc503..4f736777 100644 --- a/tests/test_openhab/test_rest/test_items.py +++ b/tests/test_openhab/test_rest/test_items.py @@ -1,32 +1,32 @@ from msgspec import convert -from HABApp.openhab.definitions.rest.items import ItemResp, StateOptionResp, CommandOptionResp +from HABApp.openhab.definitions.rest.items import CommandOptionResp, ItemResp, StateOptionResp def test_item_1(): _in = { - "link": "http://ip:port/rest/items/Item1Name", - "state": "CLOSED", - "transformedState": "zu", - "stateDescription": { - "pattern": "MAP(de.map):%s", - "readOnly": True, - "options": [ - {"value": "OPEN", "label": "Open"}, - {"value": "CLOSED", "label": "Closed"}] + 'link': 'http://ip:port/rest/items/Item1Name', + 'state': 'CLOSED', + 'transformedState': 'zu', + 'stateDescription': { + 'pattern': 'MAP(de.map):%s', + 'readOnly': True, + 'options': [ + {'value': 'OPEN', 'label': 'Open'}, + {'value': 'CLOSED', 'label': 'Closed'}] }, - "commandDescription": { - "commandOptions": [ - {"command": "OPEN", "label": "Open"}, - {"command": "CLOSED", "label": "Closed"} + 'commandDescription': { + 'commandOptions': [ + {'command': 'OPEN', 'label': 'Open'}, + {'command': 'CLOSED', 'label': 'Closed'} ] }, - "editable": False, - "type": "Contact", - "name": "Item1Name", - "label": "Item1Label", - "tags": ["Tag1"], - "groupNames": ["Group1", "Group2"] + 'editable': False, + 'type': 'Contact', + 'name': 'Item1Name', + 'label': 'Item1Label', + 'tags': ['Tag1'], + 'groupNames': ['Group1', 'Group2'] } item = convert(_in, type=ItemResp) @@ -34,30 +34,30 @@ def test_item_1(): assert item.label == 'Item1Label' assert item.state == 'CLOSED' assert item.transformed_state == 'zu' - assert item.tags == ["Tag1"] - assert item.groups == ["Group1", "Group2"] + assert item.tags == ['Tag1'] + assert item.groups == ['Group1', 'Group2'] def test_item_2(): d1 = 'DASDING 98.9 (Euro-Hits)' d2 = 'SWR3 95.5 (Top 40/Pop)' - _in = {"link": "http://openhabian:8080/rest/items/iSbPlayer_Favorit", - "state": "6", - "stateDescription": { - "pattern": "%s", - "readOnly": False, - "options": [{"value": "0", "label": d1}, {"value": "1", "label": d2}] + _in = {'link': 'http://openhabian:8080/rest/items/iSbPlayer_Favorit', + 'state': '6', + 'stateDescription': { + 'pattern': '%s', + 'readOnly': False, + 'options': [{'value': '0', 'label': d1}, {'value': '1', 'label': d2}] }, - "commandDescription": { - "commandOptions": [{"command": "0", "label": d1}, {"command": "1", "label": d2}] + 'commandDescription': { + 'commandOptions': [{'command': '0', 'label': d1}, {'command': '1', 'label': d2}] }, - "editable": False, - "type": "String", - "name": "iSbPlayer_Favorit", - "label": "Senderliste", - "category": None, - "tags": [], "groupNames": []} + 'editable': False, + 'type': 'String', + 'name': 'iSbPlayer_Favorit', + 'label': 'Senderliste', + 'category': None, + 'tags': [], 'groupNames': []} item = convert(_in, type=ItemResp) assert item.name == 'iSbPlayer_Favorit' @@ -76,50 +76,50 @@ def test_item_2(): def test_group_item(): _in = { - "members": [ + 'members': [ { - "link": "http://ip:port/rest/items/christmasTree", - "state": "100", - "stateDescription": { - "minimum": 0, "maximum": 100, "step": 1, "pattern": "%d%%", "readOnly": False, "options": [] + 'link': 'http://ip:port/rest/items/christmasTree', + 'state': '100', + 'stateDescription': { + 'minimum': 0, 'maximum': 100, 'step': 1, 'pattern': '%d%%', 'readOnly': False, 'options': [] }, - "type": "Dimmer", - "name": "christmasTree", - "label": "Christmas Tree", - "category": "christmas_tree", - "tags": [], - "groupNames": ["Group1", "Group2"], + 'type': 'Dimmer', + 'name': 'christmasTree', + 'label': 'Christmas Tree', + 'category': 'christmas_tree', + 'tags': [], + 'groupNames': ['Group1', 'Group2'], }, { - "link": "http://ip:port/rest/items/frontgardenPower", - "state": "OFF", - "stateDescription": {"pattern": "%s", "readOnly": False, "options": []}, - "type": "Switch", - "name": "frontgardenPower", - "label": "Outside Power", - "category": "poweroutlet", - "tags": [], - "groupNames": ["Group1", "Group2"], + 'link': 'http://ip:port/rest/items/frontgardenPower', + 'state': 'OFF', + 'stateDescription': {'pattern': '%s', 'readOnly': False, 'options': []}, + 'type': 'Switch', + 'name': 'frontgardenPower', + 'label': 'Outside Power', + 'category': 'poweroutlet', + 'tags': [], + 'groupNames': ['Group1', 'Group2'], } ], - "groupType": "Switch", - "function": { - "name": "OR", - "params": [ - "ON", - "OFF" + 'groupType': 'Switch', + 'function': { + 'name': 'OR', + 'params': [ + 'ON', + 'OFF' ] }, - "link": "http://ip:port/rest/items/SwitchGroup", - "state": "ON", - "editable": False, - "type": "Group", - "name": "SwitchGroup", - "label": "Switch Group", - "category": "my_category", - "tags": [], - "groupNames": [ - "ALL_TOPICS" + 'link': 'http://ip:port/rest/items/SwitchGroup', + 'state': 'ON', + 'editable': False, + 'type': 'Group', + 'name': 'SwitchGroup', + 'label': 'Switch Group', + 'category': 'my_category', + 'tags': [], + 'groupNames': [ + 'ALL_TOPICS' ] } item = convert(_in, type=ItemResp) diff --git a/tests/test_openhab/test_rest/test_links.py b/tests/test_openhab/test_rest/test_links.py index c44b8bbb..02924b18 100644 --- a/tests/test_openhab/test_rest/test_links.py +++ b/tests/test_openhab/test_rest/test_links.py @@ -1,12 +1,13 @@ -from HABApp.openhab.definitions.rest import ItemChannelLinkResp from msgspec import convert +from HABApp.openhab.definitions.rest import ItemChannelLinkResp + def test_simple(): _in = { - "channelUID": "zwave:device:controller:node15:sensor_luminance", - "configuration": {}, - "itemName": "ZWaveItem1", + 'channelUID': 'zwave:device:controller:node15:sensor_luminance', + 'configuration': {}, + 'itemName': 'ZWaveItem1', 'editable': False, } o = convert(_in, type=ItemChannelLinkResp) @@ -16,12 +17,12 @@ def test_simple(): def test_configuration(): _in = { - "channelUID": "zwave:device:controller:node15:sensor_luminance", - "configuration": { + 'channelUID': 'zwave:device:controller:node15:sensor_luminance', + 'configuration': { 'profile': 'follow', 'offset': 1, }, - "itemName": "ZWaveItem1", + 'itemName': 'ZWaveItem1', 'editable': False, } o = convert(_in, type=ItemChannelLinkResp) diff --git a/tests/test_openhab/test_rest/test_things.py b/tests/test_openhab/test_rest/test_things.py index 95306268..aeca77e5 100644 --- a/tests/test_openhab/test_rest/test_things.py +++ b/tests/test_openhab/test_rest/test_things.py @@ -1,18 +1,20 @@ -from HABApp.openhab.definitions.rest.things import ThingResp -from msgspec.json import decode from json import dumps +from msgspec.json import decode + +from HABApp.openhab.definitions.rest.things import ThingResp + def test_thing_summary(): _in = { - "statusInfo": { - "status": "UNINITIALIZED", - "statusDetail": "NONE" + 'statusInfo': { + 'status': 'UNINITIALIZED', + 'statusDetail': 'NONE' }, - "editable": True, - "label": "Astronomische Sonnendaten", - "UID": "astro:sun:d522ba4b56", - "thingTypeUID": "astro:sun" + 'editable': True, + 'label': 'Astronomische Sonnendaten', + 'UID': 'astro:sun:d522ba4b56', + 'thingTypeUID': 'astro:sun' } thing = decode(dumps(_in), type=ThingResp) @@ -28,75 +30,75 @@ def test_thing_summary(): def test_thing_full(): _in = { - "channels": [ + 'channels': [ { - "linkedItems": [ - "LinkedItem1", - "LinkedItem2" + 'linkedItems': [ + 'LinkedItem1', + 'LinkedItem2' ], - "uid": "astro:sun:d522ba4b56:rise#start", - "id": "rise#start", - "channelTypeUID": "astro:start", - "itemType": "DateTime", - "kind": "STATE", - "label": "Startzeit", - "description": "Die Startzeit des Ereignisses", - "defaultTags": [], - "properties": {}, - "configuration": { - "offset": 0 + 'uid': 'astro:sun:d522ba4b56:rise#start', + 'id': 'rise#start', + 'channelTypeUID': 'astro:start', + 'itemType': 'DateTime', + 'kind': 'STATE', + 'label': 'Startzeit', + 'description': 'Die Startzeit des Ereignisses', + 'defaultTags': [], + 'properties': {}, + 'configuration': { + 'offset': 0 }, }, { - "linkedItems": [], - "uid": "astro:sun:d522ba4b56:eveningNight#duration", - "id": "eveningNight#duration", - "channelTypeUID": "astro:duration", - "itemType": "Number:Time", - "kind": "STATE", - "label": "Dauer", - "description": "Die Dauer des Ereignisses", - "defaultTags": [], - "properties": {}, - "configuration": {} + 'linkedItems': [], + 'uid': 'astro:sun:d522ba4b56:eveningNight#duration', + 'id': 'eveningNight#duration', + 'channelTypeUID': 'astro:duration', + 'itemType': 'Number:Time', + 'kind': 'STATE', + 'label': 'Dauer', + 'description': 'Die Dauer des Ereignisses', + 'defaultTags': [], + 'properties': {}, + 'configuration': {} }, { - "linkedItems": [], - "uid": "astro:sun:d522ba4b56:eclipse#event", - "id": "eclipse#event", - "channelTypeUID": "astro:sunEclipseEvent", - "kind": "TRIGGER", - "label": "Sonnenfinsternisereignis", - "description": "Sonnenfinsternisereignis", - "defaultTags": [], - "properties": {}, - "configuration": { - "offset": 0 + 'linkedItems': [], + 'uid': 'astro:sun:d522ba4b56:eclipse#event', + 'id': 'eclipse#event', + 'channelTypeUID': 'astro:sunEclipseEvent', + 'kind': 'TRIGGER', + 'label': 'Sonnenfinsternisereignis', + 'description': 'Sonnenfinsternisereignis', + 'defaultTags': [], + 'properties': {}, + 'configuration': { + 'offset': 0 } }, ], - "statusInfo": { - "status": "UNINITIALIZED", - "statusDetail": "NONE" + 'statusInfo': { + 'status': 'UNINITIALIZED', + 'statusDetail': 'NONE' }, - "editable": True, - "label": "Astronomische Sonnendaten", - "configuration": { - "useMeteorologicalSeason": False, - "interval": 300, - "geolocation": "46.123,2.123" + 'editable': True, + 'label': 'Astronomische Sonnendaten', + 'configuration': { + 'useMeteorologicalSeason': False, + 'interval': 300, + 'geolocation': '46.123,2.123' }, - "properties": {}, - "UID": "astro:sun:d522ba4b56", - "thingTypeUID": "astro:sun" + 'properties': {}, + 'UID': 'astro:sun:d522ba4b56', + 'thingTypeUID': 'astro:sun' } thing = decode(dumps(_in), type=ThingResp) c0, c1, c2 = thing.channels - assert c0.linked_items == ["LinkedItem1", "LinkedItem2"] - assert c0.configuration == {"offset": 0} + assert c0.linked_items == ['LinkedItem1', 'LinkedItem2'] + assert c0.configuration == {'offset': 0} assert c1.linked_items == [] assert c1.configuration == {} @@ -107,7 +109,7 @@ def test_thing_full(): assert thing.editable is True assert thing.label == 'Astronomische Sonnendaten' - assert thing.configuration == {"useMeteorologicalSeason": False, "interval": 300, "geolocation": "46.123,2.123"} + assert thing.configuration == {'useMeteorologicalSeason': False, 'interval': 300, 'geolocation': '46.123,2.123'} assert thing.properties == {} assert thing.uid == 'astro:sun:d522ba4b56' diff --git a/tests/test_openhab/test_transformations/test_map.py b/tests/test_openhab/test_transformations/test_map.py index 12e74d77..5b589a42 100644 --- a/tests/test_openhab/test_transformations/test_map.py +++ b/tests/test_openhab/test_transformations/test_map.py @@ -1,8 +1,10 @@ import pytest from HABApp.openhab.errors import MapTransformationError + # noinspection PyProtectedMember from HABApp.openhab.transformations._map import MapTransformation, MapTransformationWithDefault + # noinspection PyProtectedMember from HABApp.openhab.transformations._map.registry import MapTransformationRegistry diff --git a/tests/test_openhab/test_values.py b/tests/test_openhab/test_values.py index c5a7582b..59442e5a 100644 --- a/tests/test_openhab/test_values.py +++ b/tests/test_openhab/test_values.py @@ -1,11 +1,18 @@ import pytest -from HABApp.openhab.definitions import HSBValue, OnOffValue, OpenClosedValue, PercentValue, QuantityValue, RawValue, \ - UpDownValue +from HABApp.openhab.definitions import ( + HSBValue, + OnOffValue, + OpenClosedValue, + PercentValue, + QuantityValue, + RawValue, + UpDownValue, +) @pytest.mark.parametrize( - "cls,values", [ + 'cls,values', [ (UpDownValue, (UpDownValue.DOWN, UpDownValue.UP)), (OnOffValue, (OnOffValue.ON, OnOffValue.OFF)), (OpenClosedValue, (OpenClosedValue.OPEN, OpenClosedValue.CLOSED)), @@ -17,7 +24,7 @@ def test_val_same_type(cls, values): @pytest.mark.parametrize( - "cls,values", [ + 'cls,values', [ (PercentValue, (('0', 0.0), ('5', 5.0), ('55.5', 55.5), ('100.0', 100), )), (HSBValue, ( ('0,0,0', (0, 0, 0)), ('5,0,0', (5, 0, 0)), diff --git a/tests/test_packages.py b/tests/test_packages.py index c898274e..dfe545ca 100644 --- a/tests/test_packages.py +++ b/tests/test_packages.py @@ -1,6 +1,8 @@ import re from pathlib import Path + from packaging.version import VERSION_PATTERN + import HABApp.__check_dependency_packages__ from HABApp import __version__ diff --git a/tests/test_parameters/test_base.py b/tests/test_parameters/test_base.py index b25b552f..3701fcc0 100644 --- a/tests/test_parameters/test_base.py +++ b/tests/test_parameters/test_base.py @@ -1,9 +1,11 @@ -import HABApp import typing -from HABApp import Parameter from tests.conftest import params +import HABApp +from HABApp import Parameter + + if typing.TYPE_CHECKING: params = params diff --git a/tests/test_parameters/test_dict_parameter.py b/tests/test_parameters/test_dict_parameter.py index 1e449ae8..48dc95b9 100644 --- a/tests/test_parameters/test_dict_parameter.py +++ b/tests/test_parameters/test_dict_parameter.py @@ -1,9 +1,11 @@ -import pytest import typing +import pytest +from tests.conftest import params + import HABApp from HABApp import DictParameter -from tests.conftest import params + if typing.TYPE_CHECKING: params = params diff --git a/tests/test_parameters/test_parameter.py b/tests/test_parameters/test_parameter.py index f68c9669..4ebc8f25 100644 --- a/tests/test_parameters/test_parameter.py +++ b/tests/test_parameters/test_parameter.py @@ -1,9 +1,11 @@ -import HABApp import typing -from HABApp import Parameter from tests.conftest import params +import HABApp +from HABApp import Parameter + + if typing.TYPE_CHECKING: params = params diff --git a/tests/test_rule/__exec_python_file.py b/tests/test_rule/__exec_python_file.py index 604c120a..1f319590 100644 --- a/tests/test_rule/__exec_python_file.py +++ b/tests/test_rule/__exec_python_file.py @@ -2,6 +2,7 @@ import os import sys + if len(sys.argv) == 1: print(json.dumps({'cwd': os.getcwd()})) else: diff --git a/tests/test_rule/test_item_search.py b/tests/test_rule/test_item_search.py index 533e3482..19d1d59a 100644 --- a/tests/test_rule/test_item_search.py +++ b/tests/test_rule/test_item_search.py @@ -2,7 +2,7 @@ from HABApp import Rule from HABApp.core.internals import ItemRegistry -from HABApp.core.items import Item, BaseValueItem +from HABApp.core.items import BaseValueItem, Item from HABApp.openhab.items import OpenhabItem, SwitchItem from HABApp.openhab.items.base_item import MetaData diff --git a/tests/test_rule/test_process.py b/tests/test_rule/test_process.py index 34bf0c91..fcff461c 100644 --- a/tests/test_rule/test_process.py +++ b/tests/test_rule/test_process.py @@ -8,11 +8,13 @@ import pytest import HABApp.rule -from HABApp.rule import Rule, FinishedProcessInfo +from HABApp.rule import FinishedProcessInfo, Rule from HABApp.rule.interfaces import rule_subprocess + from ..helpers import LogCollector from ..rule_runner import SimpleRuleRunner + # It's either subprocesses or async-mqtt but never both pytestmark = pytest.mark.skipif( get_event_loop_policy().__class__.__name__ == 'WindowsSelectorEventLoopPolicy', @@ -27,7 +29,7 @@ def __init__(self): self.cb.__name__ = 'mock_callback' -@pytest.fixture(scope="function") +@pytest.fixture(scope='function') def rule(monkeypatch): monkeypatch.setattr(HABApp.CONFIG, '_file_path', Path(__file__).with_name('config.yml')) @@ -41,21 +43,21 @@ def rule(monkeypatch): runner.tear_down() -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_run_func_arg_errors(rule): with pytest.raises(TypeError) as e: - rule.execute_subprocess(rule.cb, sys.executable, "asfd", 123) + rule.execute_subprocess(rule.cb, sys.executable, 'asfd', 123) assert str(e.value) == 'args[2] is not of type str! "123" (int)' with pytest.raises(TypeError) as e: rule.execute_subprocess( - rule.cb, sys.executable, "asfd", additional_python_path=[Path(__file__).parent, 123] + rule.cb, sys.executable, 'asfd', additional_python_path=[Path(__file__).parent, 123] ) assert str(e.value) == 'additional_python_path[1] is not of type str! "123" (int)' @pytest.mark.parametrize('flag,result', [[True, FinishedProcessInfo(0, 'OK', '')], [False, 'OK']]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_run_func(rule, flag, result): await rule.execute_subprocess( @@ -66,7 +68,7 @@ async def test_run_func(rule, flag, result): @pytest.mark.parametrize('flag,result', [[True, FinishedProcessInfo(0, None, None)], [False, '']]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_run_func_no_cap(rule, flag: bool, result): await rule.execute_subprocess( rule.cb, sys.executable, '-c', 'import datetime; print("OK", end="")', capture_output=False, raw_info=flag @@ -76,7 +78,7 @@ async def test_run_func_no_cap(rule, flag: bool, result): @pytest.mark.parametrize('flag,result', [[True, FinishedProcessInfo(0, None, None)], [False, '']]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_run_func_cancel(rule, flag, result, test_logs: LogCollector): task = rule.execute_subprocess( @@ -96,7 +98,7 @@ async def test_run_func_cancel(rule, flag, result, test_logs: LogCollector): @pytest.mark.parametrize('flag', [True, False]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_invalid_program(rule, test_logs, flag): parent_dir = Path(__file__).parent await rule.execute_subprocess(rule.cb, 'ProgramThatDoesNotExist', capture_output=True, raw_info=flag) @@ -112,7 +114,7 @@ async def test_invalid_program(rule, test_logs, flag): @pytest.mark.parametrize('raw_info', [True, False]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_exec_python_file(rule, caplog, raw_info): parent_dir = Path(__file__).parent @@ -131,7 +133,7 @@ async def test_exec_python_file(rule, caplog, raw_info): assert _json['cwd'] == str(parent_dir) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_exec_python_file_relative(rule): parent_dir = Path(__file__).parent @@ -142,7 +144,7 @@ async def test_exec_python_file_relative(rule): assert _json['cwd'] == str(parent_dir) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_exec_python_file_error_stderr(rule, test_logs: LogCollector): folder = Path(__file__).parent file = folder / '__exec_python_file.py' @@ -159,7 +161,7 @@ async def test_exec_python_file_error_stderr(rule, test_logs: LogCollector): rule.cb.assert_not_called() -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_exec_python_file_error_stdout(rule, test_logs: LogCollector): folder = Path(__file__).parent file = folder / '__exec_python_file.py' @@ -175,7 +177,7 @@ async def test_exec_python_file_error_stdout(rule, test_logs: LogCollector): @pytest.mark.parametrize('raw_info, result', [[True, FinishedProcessInfo(0, 'module ok', '')], [False, 'module ok']]) -@pytest.mark.no_internals +@pytest.mark.no_internals() async def test_exec_python_module(rule, raw_info, result): folder = Path(__file__).parent await rule.execute_python( @@ -184,7 +186,7 @@ async def test_exec_python_module(rule, raw_info, result): rule.cb.assert_called_once_with(result) -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_param_pythonpath(monkeypatch): monkeypatch.setattr(HABApp.CONFIG, '_file_path', Path(__file__)) folder = str(Path(__file__).parent) diff --git a/tests/test_rule/test_rule_factory.py b/tests/test_rule/test_rule_factory.py index c12cd2ea..fd1301fc 100644 --- a/tests/test_rule/test_rule_factory.py +++ b/tests/test_rule/test_rule_factory.py @@ -1,6 +1,6 @@ import pytest -from HABApp.rule import create_rule, Rule +from HABApp.rule import Rule, create_rule from tests import SimpleRuleRunner @@ -11,7 +11,7 @@ class MyRule(Rule): assert create_rule(MyRule) is None -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_rule_create(): class MyRule(Rule): pass diff --git a/tests/test_rule/test_rule_funcs.py b/tests/test_rule/test_rule_funcs.py index df7b948a..56ffdb90 100644 --- a/tests/test_rule/test_rule_funcs.py +++ b/tests/test_rule/test_rule_funcs.py @@ -4,10 +4,11 @@ from HABApp import Rule from tests.helpers import TestEventBus + from ..rule_runner import SimpleRuleRunner -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_unload_function(): with SimpleRuleRunner(): @@ -18,8 +19,8 @@ def test_unload_function(): assert m.called -@pytest.mark.ignore_log_errors -@pytest.mark.no_internals +@pytest.mark.ignore_log_errors() +@pytest.mark.no_internals() def test_unload_function_exception(eb: TestEventBus): eb.allow_errors = True @@ -32,7 +33,7 @@ def test_unload_function_exception(eb: TestEventBus): assert m.called -@pytest.mark.no_internals +@pytest.mark.no_internals() def test_repr(): class Abc(Rule): diff --git a/tests/test_utils/test_functions.py b/tests/test_utils/test_functions.py index 07def503..0a01d92b 100644 --- a/tests/test_utils/test_functions.py +++ b/tests/test_utils/test_functions.py @@ -26,7 +26,7 @@ def test_max(): assert max([2, 3, None]) == 3 -@pytest.mark.parametrize("rgb,hsv", [ +@pytest.mark.parametrize('rgb,hsv', [ ((224, 201, 219), (313.04, 10.27, 87.84)), (( 0, 201, 219), (184.93, 100.00, 85.88)), ((128, 138, 33), ( 65.71, 76.09, 54.12)), @@ -36,7 +36,7 @@ def test_rgb_to_hsv(rgb, hsv): assert hsv == rgb_to_hsb(*rgb) -@pytest.mark.parametrize("hsv,rgb", [ +@pytest.mark.parametrize('hsv,rgb', [ (( 75, 75, 75), (155, 191, 48)), ((150, 40, 100), (153, 255, 204)), ((234, 46, 72), ( 99, 108, 184)), diff --git a/tests/test_utils/test_multivalue.py b/tests/test_utils/test_multivalue.py index b6aad4d2..74bca650 100644 --- a/tests/test_utils/test_multivalue.py +++ b/tests/test_utils/test_multivalue.py @@ -4,10 +4,11 @@ import pytest from HABApp.core.const import MISSING -from HABApp.util.multimode import BaseMode, ValueMode, MultiModeItem -from ..test_core import ItemTests +from HABApp.util.multimode import BaseMode, MultiModeItem, ValueMode from tests.helpers.parent_rule import DummyRule +from ..test_core import ItemTests + class TestMultiModeItem(ItemTests): CLS = MultiModeItem diff --git a/tests/test_utils/test_rate_limiter.py b/tests/test_utils/test_rate_limiter.py index a43d4b63..9c2c52bb 100644 --- a/tests/test_utils/test_rate_limiter.py +++ b/tests/test_utils/test_rate_limiter.py @@ -161,7 +161,7 @@ def test_limiter_add(time): with pytest.raises(ValueError) as e: limiter.add_limit(3, 5, initial_hits=5) - assert str(e.value) == "Parameter hits must be <= parameter allowed! 5 <= 3!" + assert str(e.value) == 'Parameter hits must be <= parameter allowed! 5 <= 3!' def test_fixed_window_info(time): diff --git a/tests/test_utils/test_threshold.py b/tests/test_utils/test_threshold.py index 369dc72e..d5ea678a 100644 --- a/tests/test_utils/test_threshold.py +++ b/tests/test_utils/test_threshold.py @@ -14,28 +14,28 @@ def test_constructor(self): def test_a(self): t = Threshold(10, 20) - self.assertFalse( 19 > t) + self.assertFalse( t < 19) # Über obere Grenze - self.assertTrue(21 > t) - self.assertTrue(20 > t) - self.assertTrue(11 > t) + self.assertTrue(t < 21) + self.assertTrue(t < 20) + self.assertTrue(t < 11) # unter untere Grenze - self.assertFalse(9 > t) - self.assertFalse(19 > t) + self.assertFalse(t < 9) + self.assertFalse(t < 19) def test_b(self): t = Threshold(10, 20) self.assertEqual(t.current_threshold, 20) - self.assertFalse(19 >= t) + self.assertFalse(t <= 19) # Über obere Grenze - self.assertTrue(21 >= t) - self.assertTrue(10 >= t) + self.assertTrue(t <= 21) + self.assertTrue(t <= 10) # unter untere Grenze - self.assertFalse(9 >= t) - self.assertFalse(19 >= t) + self.assertFalse(t <= 9) + self.assertFalse(t <= 19) if __name__ == '__main__':