aiohomematic 2025.9.2__py3-none-any.whl → 2025.9.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiohomematic might be problematic. Click here for more details.
- aiohomematic/client/__init__.py +2 -2
- aiohomematic/client/json_rpc.py +3 -3
- aiohomematic/client/xml_rpc.py +3 -3
- aiohomematic/const.py +1 -1
- aiohomematic/model/calculated/data_point.py +2 -2
- aiohomematic/model/data_point.py +9 -9
- aiohomematic/model/device.py +5 -5
- aiohomematic/model/generic/data_point.py +2 -2
- aiohomematic/property_decorators.py +331 -147
- aiohomematic/support.py +11 -10
- {aiohomematic-2025.9.2.dist-info → aiohomematic-2025.9.3.dist-info}/METADATA +1 -1
- {aiohomematic-2025.9.2.dist-info → aiohomematic-2025.9.3.dist-info}/RECORD +15 -15
- {aiohomematic-2025.9.2.dist-info → aiohomematic-2025.9.3.dist-info}/WHEEL +0 -0
- {aiohomematic-2025.9.2.dist-info → aiohomematic-2025.9.3.dist-info}/licenses/LICENSE +0 -0
- {aiohomematic-2025.9.2.dist-info → aiohomematic-2025.9.3.dist-info}/top_level.txt +0 -0
aiohomematic/client/__init__.py
CHANGED
|
@@ -93,7 +93,7 @@ from aiohomematic.decorators import inspector, measure_execution_time
|
|
|
93
93
|
from aiohomematic.exceptions import BaseHomematicException, ClientException, NoConnectionException
|
|
94
94
|
from aiohomematic.model.device import Device
|
|
95
95
|
from aiohomematic.model.support import convert_value
|
|
96
|
-
from aiohomematic.property_decorators import
|
|
96
|
+
from aiohomematic.property_decorators import hm_property
|
|
97
97
|
from aiohomematic.support import (
|
|
98
98
|
LogContextMixin,
|
|
99
99
|
build_xml_rpc_headers,
|
|
@@ -171,7 +171,7 @@ class Client(ABC, LogContextMixin):
|
|
|
171
171
|
"""Return the interface of the client."""
|
|
172
172
|
return self._config.interface
|
|
173
173
|
|
|
174
|
-
@
|
|
174
|
+
@hm_property(log_context=True)
|
|
175
175
|
def interface_id(self) -> str:
|
|
176
176
|
"""Return the interface id of the client."""
|
|
177
177
|
return self._config.interface_id
|
aiohomematic/client/json_rpc.py
CHANGED
|
@@ -91,7 +91,7 @@ from aiohomematic.exceptions import (
|
|
|
91
91
|
UnsupportedException,
|
|
92
92
|
)
|
|
93
93
|
from aiohomematic.model.support import convert_value
|
|
94
|
-
from aiohomematic.property_decorators import
|
|
94
|
+
from aiohomematic.property_decorators import hm_property
|
|
95
95
|
from aiohomematic.support import (
|
|
96
96
|
LogContextMixin,
|
|
97
97
|
cleanup_text_from_html_tags,
|
|
@@ -217,12 +217,12 @@ class JsonRpcAioHttpClient(LogContextMixin):
|
|
|
217
217
|
"""If session exists, then it is activated."""
|
|
218
218
|
return self._session_id is not None
|
|
219
219
|
|
|
220
|
-
@
|
|
220
|
+
@hm_property(log_context=True)
|
|
221
221
|
def url(self) -> str | None:
|
|
222
222
|
"""Return url."""
|
|
223
223
|
return self._url
|
|
224
224
|
|
|
225
|
-
@
|
|
225
|
+
@hm_property(log_context=True)
|
|
226
226
|
def tls(self) -> bool:
|
|
227
227
|
"""Return tls."""
|
|
228
228
|
return self._tls
|
aiohomematic/client/xml_rpc.py
CHANGED
|
@@ -42,7 +42,7 @@ from aiohomematic.exceptions import (
|
|
|
42
42
|
NoConnectionException,
|
|
43
43
|
UnsupportedException,
|
|
44
44
|
)
|
|
45
|
-
from aiohomematic.property_decorators import
|
|
45
|
+
from aiohomematic.property_decorators import hm_property
|
|
46
46
|
from aiohomematic.support import LogContextMixin, extract_exc_args, get_tls_context, log_boundary_error
|
|
47
47
|
|
|
48
48
|
_LOGGER: Final = logging.getLogger(__name__)
|
|
@@ -121,7 +121,7 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy, LogContextMixin):
|
|
|
121
121
|
supported_methods.append(_XmlRpcMethod.PING)
|
|
122
122
|
self._supported_methods = tuple(supported_methods)
|
|
123
123
|
|
|
124
|
-
@
|
|
124
|
+
@hm_property(log_context=True)
|
|
125
125
|
def interface_id(self) -> str:
|
|
126
126
|
"""Return the interface_id."""
|
|
127
127
|
return self._interface_id
|
|
@@ -131,7 +131,7 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy, LogContextMixin):
|
|
|
131
131
|
"""Return the supported methods."""
|
|
132
132
|
return self._supported_methods
|
|
133
133
|
|
|
134
|
-
@
|
|
134
|
+
@hm_property(log_context=True)
|
|
135
135
|
def tls(self) -> bool:
|
|
136
136
|
"""Return tls."""
|
|
137
137
|
return self._tls
|
aiohomematic/const.py
CHANGED
|
@@ -19,7 +19,7 @@ import sys
|
|
|
19
19
|
from types import MappingProxyType
|
|
20
20
|
from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
|
|
21
21
|
|
|
22
|
-
VERSION: Final = "2025.9.
|
|
22
|
+
VERSION: Final = "2025.9.3"
|
|
23
23
|
|
|
24
24
|
# Detect test speedup mode via environment
|
|
25
25
|
_TEST_SPEEDUP: Final = (
|
|
@@ -32,7 +32,7 @@ from aiohomematic.model.support import (
|
|
|
32
32
|
generate_unique_id,
|
|
33
33
|
get_data_point_name_data,
|
|
34
34
|
)
|
|
35
|
-
from aiohomematic.property_decorators import
|
|
35
|
+
from aiohomematic.property_decorators import cached_property, config_property, state_property
|
|
36
36
|
|
|
37
37
|
_LOGGER: Final = logging.getLogger(__name__)
|
|
38
38
|
|
|
@@ -147,7 +147,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
|
|
|
147
147
|
"""Return default value."""
|
|
148
148
|
return self._default
|
|
149
149
|
|
|
150
|
-
@
|
|
150
|
+
@cached_property
|
|
151
151
|
def dpk(self) -> DataPointKey:
|
|
152
152
|
"""Return data_point key value."""
|
|
153
153
|
return DataPointKey(
|
aiohomematic/model/data_point.py
CHANGED
|
@@ -73,7 +73,7 @@ from aiohomematic.model.support import (
|
|
|
73
73
|
convert_value,
|
|
74
74
|
generate_unique_id,
|
|
75
75
|
)
|
|
76
|
-
from aiohomematic.property_decorators import
|
|
76
|
+
from aiohomematic.property_decorators import cached_property, config_property, hm_property, state_property
|
|
77
77
|
from aiohomematic.support import LogContextMixin, PayloadMixin, extract_exc_args, log_boundary_error
|
|
78
78
|
|
|
79
79
|
__all__ = [
|
|
@@ -269,7 +269,7 @@ class CallbackDataPoint(ABC, LogContextMixin):
|
|
|
269
269
|
"""Return the data_point usage."""
|
|
270
270
|
return DataPointUsage.DATA_POINT
|
|
271
271
|
|
|
272
|
-
@
|
|
272
|
+
@cached_property
|
|
273
273
|
def enabled_default(self) -> bool:
|
|
274
274
|
"""Return, if data_point should be enabled based on usage attribute."""
|
|
275
275
|
return self.usage in (
|
|
@@ -295,12 +295,12 @@ class CallbackDataPoint(ABC, LogContextMixin):
|
|
|
295
295
|
return self._path_data.state_path
|
|
296
296
|
|
|
297
297
|
# @property
|
|
298
|
-
@
|
|
298
|
+
@cached_property
|
|
299
299
|
def service_methods(self) -> Mapping[str, Callable]:
|
|
300
300
|
"""Return all service methods."""
|
|
301
301
|
return get_service_calls(obj=self)
|
|
302
302
|
|
|
303
|
-
@
|
|
303
|
+
@cached_property
|
|
304
304
|
def service_method_names(self) -> tuple[str, ...]:
|
|
305
305
|
"""Return all service methods."""
|
|
306
306
|
return tuple(self.service_methods.keys())
|
|
@@ -447,7 +447,7 @@ class BaseDataPoint(CallbackDataPoint, PayloadMixin):
|
|
|
447
447
|
"""Return the availability of the device."""
|
|
448
448
|
return self._device.available
|
|
449
449
|
|
|
450
|
-
@
|
|
450
|
+
@hm_property(log_context=True)
|
|
451
451
|
def channel(self) -> hmd.Channel:
|
|
452
452
|
"""Return the channel the data_point."""
|
|
453
453
|
return self._channel
|
|
@@ -659,7 +659,7 @@ class BaseParameterDataPoint[
|
|
|
659
659
|
"""Return if the parameter is un ignored."""
|
|
660
660
|
return self._is_un_ignored
|
|
661
661
|
|
|
662
|
-
@
|
|
662
|
+
@cached_property
|
|
663
663
|
def dpk(self) -> DataPointKey:
|
|
664
664
|
"""Return data_point key value."""
|
|
665
665
|
return DataPointKey(
|
|
@@ -684,7 +684,7 @@ class BaseParameterDataPoint[
|
|
|
684
684
|
"""Return multiplier value."""
|
|
685
685
|
return self._multiplier
|
|
686
686
|
|
|
687
|
-
@
|
|
687
|
+
@hm_property(log_context=True)
|
|
688
688
|
def parameter(self) -> str:
|
|
689
689
|
"""Return parameter name."""
|
|
690
690
|
return self._parameter
|
|
@@ -699,7 +699,7 @@ class BaseParameterDataPoint[
|
|
|
699
699
|
"""Return raw unit value."""
|
|
700
700
|
return self._raw_unit
|
|
701
701
|
|
|
702
|
-
@
|
|
702
|
+
@cached_property
|
|
703
703
|
def requires_polling(self) -> bool:
|
|
704
704
|
"""Return whether the data_point requires polling."""
|
|
705
705
|
return not self._channel.device.client.supports_push_updates or (
|
|
@@ -785,7 +785,7 @@ class BaseParameterDataPoint[
|
|
|
785
785
|
"""Return the if data_point is visible in ccu."""
|
|
786
786
|
return self._visible
|
|
787
787
|
|
|
788
|
-
@
|
|
788
|
+
@cached_property
|
|
789
789
|
def _enabled_by_channel_operation_mode(self) -> bool | None:
|
|
790
790
|
"""Return, if the data_point/event must be enabled."""
|
|
791
791
|
if self._channel.type_name not in _CONFIGURABLE_CHANNEL:
|
aiohomematic/model/device.py
CHANGED
|
@@ -80,7 +80,7 @@ from aiohomematic.model.support import (
|
|
|
80
80
|
get_device_name,
|
|
81
81
|
)
|
|
82
82
|
from aiohomematic.model.update import DpUpdate
|
|
83
|
-
from aiohomematic.property_decorators import
|
|
83
|
+
from aiohomematic.property_decorators import cached_property, hm_property, info_property, state_property
|
|
84
84
|
from aiohomematic.support import (
|
|
85
85
|
CacheEntry,
|
|
86
86
|
LogContextMixin,
|
|
@@ -327,7 +327,7 @@ class Device(LogContextMixin, PayloadMixin):
|
|
|
327
327
|
"""Return the interface of the device."""
|
|
328
328
|
return self._interface
|
|
329
329
|
|
|
330
|
-
@
|
|
330
|
+
@hm_property(log_context=True)
|
|
331
331
|
def interface_id(self) -> str:
|
|
332
332
|
"""Return the interface_id of the device."""
|
|
333
333
|
return self._interface_id
|
|
@@ -441,7 +441,7 @@ class Device(LogContextMixin, PayloadMixin):
|
|
|
441
441
|
for channel in self._channels.values():
|
|
442
442
|
await channel.remove_central_link()
|
|
443
443
|
|
|
444
|
-
@
|
|
444
|
+
@cached_property
|
|
445
445
|
def relevant_for_central_link_management(self) -> bool:
|
|
446
446
|
"""Return if channel is relevant for central link management."""
|
|
447
447
|
return (
|
|
@@ -762,7 +762,7 @@ class Channel(LogContextMixin, PayloadMixin):
|
|
|
762
762
|
"""Return the device description for the channel."""
|
|
763
763
|
return self._description
|
|
764
764
|
|
|
765
|
-
@
|
|
765
|
+
@hm_property(log_context=True)
|
|
766
766
|
def device(self) -> Device:
|
|
767
767
|
"""Return the device of the channel."""
|
|
768
768
|
return self._device
|
|
@@ -832,7 +832,7 @@ class Channel(LogContextMixin, PayloadMixin):
|
|
|
832
832
|
"""Return the name data of the channel."""
|
|
833
833
|
return self._name_data
|
|
834
834
|
|
|
835
|
-
@
|
|
835
|
+
@hm_property(log_context=True)
|
|
836
836
|
def no(self) -> int | None:
|
|
837
837
|
"""Return the channel_no of the channel."""
|
|
838
838
|
return self._no
|
|
@@ -21,7 +21,7 @@ from aiohomematic.decorators import inspector
|
|
|
21
21
|
from aiohomematic.exceptions import ValidationException
|
|
22
22
|
from aiohomematic.model import data_point as hme, device as hmd
|
|
23
23
|
from aiohomematic.model.support import DataPointNameData, GenericParameterType, get_data_point_name_data
|
|
24
|
-
from aiohomematic.property_decorators import
|
|
24
|
+
from aiohomematic.property_decorators import cached_property
|
|
25
25
|
|
|
26
26
|
_LOGGER: Final = logging.getLogger(__name__)
|
|
27
27
|
|
|
@@ -51,7 +51,7 @@ class GenericDataPoint[ParameterT: GenericParameterType, InputParameterT: Generi
|
|
|
51
51
|
parameter_data=parameter_data,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
@
|
|
54
|
+
@cached_property
|
|
55
55
|
def usage(self) -> DataPointUsage:
|
|
56
56
|
"""Return the data_point usage."""
|
|
57
57
|
if self._is_forced_sensor or self._is_un_ignored:
|
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
# Copyright (c) 2021-2025 Daniel Perna, SukramJ
|
|
3
|
-
"""
|
|
3
|
+
"""
|
|
4
|
+
Decorators and helpers for declaring public attributes on data point classes.
|
|
5
|
+
|
|
6
|
+
This module provides four decorator factories that behave like the built-in
|
|
7
|
+
@property, but additionally annotate properties with a semantic category so they
|
|
8
|
+
can be automatically collected to build payloads and log contexts:
|
|
9
|
+
- cached_property: computed once per instance and cached until the value is
|
|
10
|
+
invalidated by a setter/deleter on the same descriptor.
|
|
11
|
+
- config_property: configuration-related properties.
|
|
12
|
+
- info_property: informational/metadata properties.
|
|
13
|
+
- state_property: dynamic state properties.
|
|
14
|
+
|
|
15
|
+
All decorators accept an optional keyword-only argument log_context. If set to
|
|
16
|
+
True, the property will be included in the LogContextMixin.log_context mapping.
|
|
17
|
+
|
|
18
|
+
Notes on caching
|
|
19
|
+
- cached_property always caches on first access and invalidates on set/delete.
|
|
20
|
+
- The other decorators can be created with cached=True to enable the same
|
|
21
|
+
behavior when desired.
|
|
22
|
+
"""
|
|
4
23
|
|
|
5
24
|
from __future__ import annotations
|
|
6
25
|
|
|
7
26
|
from collections.abc import Callable, Mapping
|
|
8
27
|
from datetime import datetime
|
|
9
|
-
from enum import Enum
|
|
10
|
-
from typing import Any, ParamSpec, TypeVar, cast, overload
|
|
28
|
+
from enum import Enum, StrEnum
|
|
29
|
+
from typing import Any, Final, ParamSpec, TypeVar, cast, overload
|
|
11
30
|
from weakref import WeakKeyDictionary
|
|
12
31
|
|
|
13
32
|
from aiohomematic import support as hms
|
|
14
33
|
|
|
15
34
|
__all__ = [
|
|
16
35
|
"config_property",
|
|
17
|
-
"
|
|
18
|
-
"get_attributes_for_info_property",
|
|
19
|
-
"get_attributes_for_state_property",
|
|
36
|
+
"get_hm_property_by_kind",
|
|
20
37
|
"info_property",
|
|
21
38
|
"state_property",
|
|
22
39
|
]
|
|
@@ -26,8 +43,30 @@ T = TypeVar("T")
|
|
|
26
43
|
R = TypeVar("R")
|
|
27
44
|
|
|
28
45
|
|
|
46
|
+
class Kind(StrEnum):
|
|
47
|
+
"""Enum for property feature flags."""
|
|
48
|
+
|
|
49
|
+
CONFIG = "config"
|
|
50
|
+
INFO = "info"
|
|
51
|
+
SIMPLE = "simple"
|
|
52
|
+
STATE = "state"
|
|
53
|
+
|
|
54
|
+
|
|
29
55
|
class _GenericProperty[GETTER, SETTER](property):
|
|
30
|
-
"""
|
|
56
|
+
"""
|
|
57
|
+
Base descriptor used by all property decorators in this module.
|
|
58
|
+
|
|
59
|
+
Extends the built-in property to optionally cache the computed value on the
|
|
60
|
+
instance and to carry a log_context flag.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
- fget/fset/fdel: Standard property callables.
|
|
64
|
+
- doc: Optional docstring of the property.
|
|
65
|
+
- cached: If True, the computed value is cached per instance and
|
|
66
|
+
invalidated when the descriptor receives a set/delete.
|
|
67
|
+
- log_context: If True, the property is included in get_attributes_for_log_context().
|
|
68
|
+
|
|
69
|
+
"""
|
|
31
70
|
|
|
32
71
|
fget: Callable[[Any], GETTER] | None
|
|
33
72
|
fset: Callable[[Any, SETTER], None] | None
|
|
@@ -39,210 +78,377 @@ class _GenericProperty[GETTER, SETTER](property):
|
|
|
39
78
|
fset: Callable[[Any, SETTER], None] | None = None,
|
|
40
79
|
fdel: Callable[[Any], None] | None = None,
|
|
41
80
|
doc: str | None = None,
|
|
81
|
+
kind: Kind = Kind.SIMPLE,
|
|
82
|
+
cached: bool = False,
|
|
42
83
|
log_context: bool = False,
|
|
43
84
|
) -> None:
|
|
44
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Initialize the descriptor.
|
|
87
|
+
|
|
88
|
+
Mirrors the standard property signature and adds two options:
|
|
89
|
+
- kind: specify the kind of property (e.g. simple, cached, config, info, state).
|
|
90
|
+
- cached: enable per-instance caching of the computed value.
|
|
91
|
+
- log_context: mark this property as relevant for structured logging.
|
|
92
|
+
"""
|
|
45
93
|
super().__init__(fget, fset, fdel, doc)
|
|
46
94
|
if doc is None and fget is not None:
|
|
47
95
|
doc = fget.__doc__
|
|
48
96
|
self.__doc__ = doc
|
|
97
|
+
self.kind: Final = kind
|
|
98
|
+
self._cached: Final = cached
|
|
49
99
|
self.log_context = log_context
|
|
100
|
+
if cached:
|
|
101
|
+
if fget is not None:
|
|
102
|
+
func_name = fget.__name__
|
|
103
|
+
elif fset is not None:
|
|
104
|
+
func_name = fset.__name__
|
|
105
|
+
elif fdel is not None:
|
|
106
|
+
func_name = fdel.__name__
|
|
107
|
+
else:
|
|
108
|
+
func_name = "prop"
|
|
109
|
+
self._cache_attr = f"_cached_{func_name}" # Default name of the cache attribute
|
|
50
110
|
|
|
51
111
|
def getter(self, fget: Callable[[Any], GETTER], /) -> _GenericProperty:
|
|
52
112
|
"""Return generic getter."""
|
|
53
|
-
return type(self)(
|
|
113
|
+
return type(self)(
|
|
114
|
+
fget=fget,
|
|
115
|
+
fset=self.fset,
|
|
116
|
+
fdel=self.fdel,
|
|
117
|
+
doc=self.__doc__,
|
|
118
|
+
kind=self.kind,
|
|
119
|
+
cached=self._cached,
|
|
120
|
+
log_context=self.log_context,
|
|
121
|
+
) # pragma: no cover
|
|
54
122
|
|
|
55
123
|
def setter(self, fset: Callable[[Any, SETTER], None], /) -> _GenericProperty:
|
|
56
124
|
"""Return generic setter."""
|
|
57
|
-
return type(self)(
|
|
125
|
+
return type(self)(
|
|
126
|
+
fget=self.fget,
|
|
127
|
+
fset=fset,
|
|
128
|
+
fdel=self.fdel,
|
|
129
|
+
doc=self.__doc__,
|
|
130
|
+
kind=self.kind,
|
|
131
|
+
cached=self._cached,
|
|
132
|
+
log_context=self.log_context,
|
|
133
|
+
)
|
|
58
134
|
|
|
59
135
|
def deleter(self, fdel: Callable[[Any], None], /) -> _GenericProperty:
|
|
60
136
|
"""Return generic deleter."""
|
|
61
|
-
return type(self)(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
137
|
+
return type(self)(
|
|
138
|
+
fget=self.fget,
|
|
139
|
+
fset=self.fset,
|
|
140
|
+
fdel=fdel,
|
|
141
|
+
doc=self.__doc__,
|
|
142
|
+
kind=self.kind,
|
|
143
|
+
cached=self._cached,
|
|
144
|
+
log_context=self.log_context,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def __get__(self, instance: Any, gtype: type | None = None, /) -> GETTER: # type: ignore[override]
|
|
148
|
+
"""
|
|
149
|
+
Return the attribute value.
|
|
150
|
+
|
|
151
|
+
If caching is enabled, compute on first access and return the per-instance
|
|
152
|
+
cached value on subsequent accesses.
|
|
153
|
+
"""
|
|
154
|
+
if instance is None:
|
|
155
|
+
# Accessed from class, return the descriptor itself
|
|
156
|
+
return cast(GETTER, self)
|
|
67
157
|
if self.fget is None:
|
|
68
158
|
raise AttributeError("unreadable attribute") # pragma: no cover
|
|
69
|
-
return self.fget(obj)
|
|
70
159
|
|
|
71
|
-
|
|
72
|
-
|
|
160
|
+
if not self._cached:
|
|
161
|
+
return self.fget(instance)
|
|
162
|
+
|
|
163
|
+
# If the cached value is not set yet, compute and store it
|
|
164
|
+
if not hasattr(instance, self._cache_attr):
|
|
165
|
+
value = self.fget(instance)
|
|
166
|
+
setattr(instance, self._cache_attr, value)
|
|
167
|
+
|
|
168
|
+
# Return the cached value
|
|
169
|
+
return cast(GETTER, getattr(instance, self._cache_attr))
|
|
170
|
+
|
|
171
|
+
def __set__(self, instance: Any, value: Any, /) -> None:
|
|
172
|
+
"""Set the attribute value and invalidate cache if enabled."""
|
|
173
|
+
# Delete the cached value so it can be recomputed on next access.
|
|
174
|
+
if self._cached and hasattr(instance, self._cache_attr):
|
|
175
|
+
delattr(instance, self._cache_attr)
|
|
176
|
+
|
|
73
177
|
if self.fset is None:
|
|
74
178
|
raise AttributeError("can't set attribute") # pragma: no cover
|
|
75
|
-
self.fset(
|
|
179
|
+
self.fset(instance, value)
|
|
180
|
+
|
|
181
|
+
def __delete__(self, instance: Any, /) -> None:
|
|
182
|
+
"""Delete the attribute and invalidate cache if enabled."""
|
|
183
|
+
|
|
184
|
+
# Delete the cached value so it can be recomputed on next access.
|
|
185
|
+
if self._cached and hasattr(instance, self._cache_attr):
|
|
186
|
+
delattr(instance, self._cache_attr)
|
|
76
187
|
|
|
77
|
-
def __delete__(self, obj: Any, /) -> None:
|
|
78
|
-
"""Delete the attribute."""
|
|
79
188
|
if self.fdel is None:
|
|
80
189
|
raise AttributeError("can't delete attribute") # pragma: no cover
|
|
81
|
-
self.fdel(
|
|
190
|
+
self.fdel(instance)
|
|
82
191
|
|
|
83
192
|
|
|
84
|
-
# -----
|
|
193
|
+
# ----- hm_property -----
|
|
85
194
|
|
|
86
195
|
|
|
87
|
-
|
|
88
|
-
|
|
196
|
+
@overload
|
|
197
|
+
def hm_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
|
|
89
198
|
|
|
90
199
|
|
|
91
200
|
@overload
|
|
92
|
-
def
|
|
201
|
+
def hm_property(
|
|
202
|
+
*, kind: Kind = ..., cached: bool = ..., log_context: bool = ...
|
|
203
|
+
) -> Callable[[Callable[[Any], R]], _GenericProperty[R, Any]]: ...
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def hm_property[PR](
|
|
207
|
+
func: Callable[[Any], PR] | None = None,
|
|
208
|
+
*,
|
|
209
|
+
kind: Kind = Kind.SIMPLE,
|
|
210
|
+
cached: bool = False,
|
|
211
|
+
log_context: bool = False,
|
|
212
|
+
) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
|
|
213
|
+
"""
|
|
214
|
+
Decorate a method as a computed attribute.
|
|
215
|
+
|
|
216
|
+
Supports both usages:
|
|
217
|
+
- @hm_property
|
|
218
|
+
- @hm_property(kind=..., cached=True, log_context=True)
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
func: The function being decorated when used as @hm_property without
|
|
222
|
+
parentheses. When used as a factory (i.e., @hm_property(...)), this
|
|
223
|
+
is None and the returned callable expects the function to decorate.
|
|
224
|
+
kind: Specify the kind of property (e.g. simple, config, info, state).
|
|
225
|
+
cached: Optionally enable per-instance caching for this property.
|
|
226
|
+
log_context: Include this property in structured log context if True.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
if func is None:
|
|
230
|
+
|
|
231
|
+
def wrapper(f: Callable[[Any], PR]) -> _GenericProperty[PR, Any]:
|
|
232
|
+
return _GenericProperty(f, kind=kind, cached=cached, log_context=log_context)
|
|
233
|
+
|
|
234
|
+
return wrapper
|
|
235
|
+
return _GenericProperty(func, kind=kind, cached=cached, log_context=log_context)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ----- cached_property -----
|
|
93
239
|
|
|
94
240
|
|
|
95
241
|
@overload
|
|
96
|
-
def
|
|
242
|
+
def cached_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
|
|
97
243
|
|
|
98
244
|
|
|
99
|
-
|
|
245
|
+
@overload
|
|
246
|
+
def cached_property(*, log_context: bool = ...) -> Callable[[Callable[[Any], R]], _GenericProperty[R, Any]]: ...
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def cached_property[PR](
|
|
100
250
|
func: Callable[[Any], PR] | None = None,
|
|
101
251
|
*,
|
|
102
252
|
log_context: bool = False,
|
|
103
|
-
) ->
|
|
253
|
+
) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
|
|
104
254
|
"""
|
|
105
|
-
|
|
255
|
+
Decorate a method as a computed attribute with per-instance caching.
|
|
256
|
+
|
|
257
|
+
Supports both usages:
|
|
258
|
+
- @cached_property
|
|
259
|
+
- @cached_property(log_context=True)
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
func: The function being decorated when used as @cached_property without
|
|
263
|
+
parentheses. When used as a factory (i.e., @cached_property(...)), this
|
|
264
|
+
is None and the returned callable expects the function to decorate.
|
|
265
|
+
log_context: Include this property in structured log context if True.
|
|
106
266
|
|
|
107
|
-
Decorator for config properties supporting both usages:
|
|
108
|
-
- @config_property
|
|
109
|
-
- @config_property(log_context=True)
|
|
110
267
|
"""
|
|
111
268
|
if func is None:
|
|
112
269
|
|
|
113
|
-
def wrapper(f: Callable[[Any], PR]) ->
|
|
114
|
-
return
|
|
270
|
+
def wrapper(f: Callable[[Any], PR]) -> _GenericProperty[PR, Any]:
|
|
271
|
+
return _GenericProperty(f, kind=Kind.SIMPLE, cached=True, log_context=log_context)
|
|
115
272
|
|
|
116
273
|
return wrapper
|
|
117
|
-
return
|
|
274
|
+
return _GenericProperty(func, kind=Kind.SIMPLE, cached=True, log_context=log_context)
|
|
118
275
|
|
|
119
276
|
|
|
120
|
-
#
|
|
121
|
-
setattr(config_property, "__property_class__", _ConfigProperty)
|
|
277
|
+
# ----- config_property -----
|
|
122
278
|
|
|
123
279
|
|
|
124
|
-
|
|
280
|
+
@overload
|
|
281
|
+
def config_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@overload
|
|
285
|
+
def config_property(
|
|
286
|
+
*, cached: bool = ..., log_context: bool = ...
|
|
287
|
+
) -> Callable[[Callable[[Any], R]], _GenericProperty[R, Any]]: ...
|
|
125
288
|
|
|
126
289
|
|
|
127
|
-
|
|
128
|
-
|
|
290
|
+
def config_property[PR](
|
|
291
|
+
func: Callable[[Any], PR] | None = None,
|
|
292
|
+
*,
|
|
293
|
+
cached: bool = False,
|
|
294
|
+
log_context: bool = False,
|
|
295
|
+
) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
|
|
296
|
+
"""
|
|
297
|
+
Decorate a method as a configuration property.
|
|
298
|
+
|
|
299
|
+
Supports both usages:
|
|
300
|
+
- @config_property
|
|
301
|
+
- @config_property(cached=True, log_context=True)
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
func: The function being decorated when used as @config_property without
|
|
305
|
+
parentheses. When used as a factory (i.e., @config_property(...)), this is
|
|
306
|
+
None and the returned callable expects the function to decorate.
|
|
307
|
+
cached: Enable per-instance caching for this property when True.
|
|
308
|
+
log_context: Include this property in structured log context if True.
|
|
309
|
+
|
|
310
|
+
"""
|
|
311
|
+
if func is None:
|
|
312
|
+
|
|
313
|
+
def wrapper(f: Callable[[Any], PR]) -> _GenericProperty[PR, Any]:
|
|
314
|
+
return _GenericProperty(f, kind=Kind.CONFIG, cached=cached, log_context=log_context)
|
|
315
|
+
|
|
316
|
+
return wrapper
|
|
317
|
+
return _GenericProperty(func, kind=Kind.CONFIG, cached=cached, log_context=log_context)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# ----- info_property -----
|
|
129
321
|
|
|
130
322
|
|
|
131
323
|
@overload
|
|
132
|
-
def info_property[PR](func: Callable[[Any], PR], /) ->
|
|
324
|
+
def info_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
|
|
133
325
|
|
|
134
326
|
|
|
135
327
|
@overload
|
|
136
|
-
def info_property(
|
|
328
|
+
def info_property(
|
|
329
|
+
*, cached: bool = ..., log_context: bool = ...
|
|
330
|
+
) -> Callable[[Callable[[Any], R]], _GenericProperty[R, Any]]: ...
|
|
137
331
|
|
|
138
332
|
|
|
139
333
|
def info_property[PR](
|
|
140
334
|
func: Callable[[Any], PR] | None = None,
|
|
141
335
|
*,
|
|
336
|
+
cached: bool = False,
|
|
142
337
|
log_context: bool = False,
|
|
143
|
-
) ->
|
|
338
|
+
) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
|
|
144
339
|
"""
|
|
145
|
-
|
|
340
|
+
Decorate a method as an informational/metadata property.
|
|
146
341
|
|
|
147
|
-
|
|
342
|
+
Supports both usages:
|
|
148
343
|
- @info_property
|
|
149
|
-
- @info_property(log_context=True)
|
|
344
|
+
- @info_property(cached=True, log_context=True)
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
func: The function being decorated when used as @info_property without
|
|
348
|
+
parentheses. When used as a factory (i.e., @info_property(...)), this is
|
|
349
|
+
None and the returned callable expects the function to decorate.
|
|
350
|
+
cached: Enable per-instance caching for this property when True.
|
|
351
|
+
log_context: Include this property in structured log context if True.
|
|
352
|
+
|
|
150
353
|
"""
|
|
151
354
|
if func is None:
|
|
152
355
|
|
|
153
|
-
def wrapper(f: Callable[[Any], PR]) ->
|
|
154
|
-
return
|
|
356
|
+
def wrapper(f: Callable[[Any], PR]) -> _GenericProperty[PR, Any]:
|
|
357
|
+
return _GenericProperty(f, kind=Kind.INFO, cached=cached, log_context=log_context)
|
|
155
358
|
|
|
156
359
|
return wrapper
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# Expose the underlying property class for discovery
|
|
161
|
-
setattr(info_property, "__property_class__", _InfoProperty)
|
|
360
|
+
return _GenericProperty(func, kind=Kind.INFO, cached=cached, log_context=log_context)
|
|
162
361
|
|
|
163
362
|
|
|
164
363
|
# ----- state_property -----
|
|
165
364
|
|
|
166
365
|
|
|
167
|
-
class _StateProperty[GETTER, SETTER](_GenericProperty[GETTER, SETTER]):
|
|
168
|
-
"""Decorate to mark own config properties."""
|
|
169
|
-
|
|
170
|
-
|
|
171
366
|
@overload
|
|
172
|
-
def state_property[PR](func: Callable[[Any], PR], /) ->
|
|
367
|
+
def state_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
|
|
173
368
|
|
|
174
369
|
|
|
175
370
|
@overload
|
|
176
|
-
def state_property(
|
|
371
|
+
def state_property(
|
|
372
|
+
*, cached: bool = ..., log_context: bool = ...
|
|
373
|
+
) -> Callable[[Callable[[Any], R]], _GenericProperty[R, Any]]: ...
|
|
177
374
|
|
|
178
375
|
|
|
179
376
|
def state_property[PR](
|
|
180
377
|
func: Callable[[Any], PR] | None = None,
|
|
181
378
|
*,
|
|
379
|
+
cached: bool = False,
|
|
182
380
|
log_context: bool = False,
|
|
183
|
-
) ->
|
|
381
|
+
) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
|
|
184
382
|
"""
|
|
185
|
-
|
|
383
|
+
Decorate a method as a dynamic state property.
|
|
186
384
|
|
|
187
|
-
|
|
385
|
+
Supports both usages:
|
|
188
386
|
- @state_property
|
|
189
|
-
- @state_property(log_context=True)
|
|
387
|
+
- @state_property(cached=True, log_context=True)
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
func: The function being decorated when used as @state_property without
|
|
391
|
+
parentheses. When used as a factory (i.e., @state_property(...)), this is
|
|
392
|
+
None and the returned callable expects the function to decorate.
|
|
393
|
+
cached: Enable per-instance caching for this property when True.
|
|
394
|
+
log_context: Include this property in structured log context if True.
|
|
395
|
+
|
|
190
396
|
"""
|
|
191
397
|
if func is None:
|
|
192
398
|
|
|
193
|
-
def wrapper(f: Callable[[Any], PR]) ->
|
|
194
|
-
return
|
|
399
|
+
def wrapper(f: Callable[[Any], PR]) -> _GenericProperty[PR, Any]:
|
|
400
|
+
return _GenericProperty(f, kind=Kind.STATE, cached=cached, log_context=log_context)
|
|
195
401
|
|
|
196
402
|
return wrapper
|
|
197
|
-
return
|
|
403
|
+
return _GenericProperty(func, kind=Kind.STATE, cached=cached, log_context=log_context)
|
|
198
404
|
|
|
199
405
|
|
|
200
|
-
# Expose the underlying property class for discovery
|
|
201
|
-
setattr(state_property, "__property_class__", _StateProperty)
|
|
202
|
-
|
|
203
406
|
# ----------
|
|
204
407
|
|
|
408
|
+
|
|
205
409
|
# Cache for per-class attribute names by decorator to avoid repeated dir() scans
|
|
206
410
|
# Use WeakKeyDictionary to allow classes to be garbage-collected without leaking cache entries.
|
|
207
411
|
# Structure: {cls: {decorator_class: (attr_name1, attr_name2, ...)}}
|
|
208
|
-
_PUBLIC_ATTR_CACHE: WeakKeyDictionary[type, dict[
|
|
412
|
+
_PUBLIC_ATTR_CACHE: WeakKeyDictionary[type, dict[Kind, tuple[str, ...]]] = WeakKeyDictionary()
|
|
209
413
|
|
|
210
414
|
|
|
211
|
-
def
|
|
212
|
-
data_object: Any, decorator: Callable, context: bool = False, only_names: bool = False
|
|
213
|
-
) -> Mapping[str, Any]:
|
|
415
|
+
def get_hm_property_by_kind(data_object: Any, kind: Kind, context: bool = False) -> Mapping[str, Any]:
|
|
214
416
|
"""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
417
|
+
Collect properties from an object that are defined using a specific decorator.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
data_object: The instance to inspect.
|
|
421
|
+
kind: The decorator class to use for filtering.
|
|
422
|
+
context: If True, only include properties where the descriptor has
|
|
423
|
+
log_context=True. When such a property's value is a LogContextMixin, its
|
|
424
|
+
items are flattened into the result using a short prefix of the property
|
|
425
|
+
name (e.g. "p.key").
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Mapping[str, Any]: A mapping of attribute name to normalized value. Values are converted via
|
|
429
|
+
_get_text_value() to provide stable JSON/log-friendly types.
|
|
430
|
+
|
|
431
|
+
Notes:
|
|
432
|
+
Attribute NAMES are cached per (class, decorator) to avoid repeated dir()
|
|
433
|
+
scans. Values are never cached here since they are instance-dependent.
|
|
434
|
+
Getter exceptions are swallowed and represented as None so payload building
|
|
435
|
+
remains robust and side-effect free.
|
|
220
436
|
|
|
221
|
-
To minimize side effects, exceptions raised by property getters are caught
|
|
222
|
-
and the corresponding value is set to None. This ensures that payload
|
|
223
|
-
construction and attribute introspection do not fail due to individual
|
|
224
|
-
properties with transient errors or expensive side effects.
|
|
225
437
|
"""
|
|
226
438
|
cls = data_object.__class__
|
|
227
439
|
|
|
228
|
-
# Resolve function-based decorators to their underlying property class, if provided
|
|
229
|
-
resolved_decorator: Any = decorator
|
|
230
|
-
if not isinstance(decorator, type):
|
|
231
|
-
resolved_decorator = getattr(decorator, "__property_class__", decorator)
|
|
232
|
-
|
|
233
440
|
# Get or create the per-class cache dict
|
|
234
441
|
if (decorator_cache := _PUBLIC_ATTR_CACHE.get(cls)) is None:
|
|
235
442
|
decorator_cache = {}
|
|
236
443
|
_PUBLIC_ATTR_CACHE[cls] = decorator_cache
|
|
237
444
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
445
|
+
if (names := decorator_cache.get(kind)) is None:
|
|
446
|
+
names = tuple(
|
|
447
|
+
y for y in dir(cls) if (gp := getattr(cls, y)) and isinstance(gp, _GenericProperty) and gp.kind == kind
|
|
448
|
+
)
|
|
449
|
+
decorator_cache[kind] = names
|
|
242
450
|
|
|
243
451
|
result: dict[str, Any] = {}
|
|
244
|
-
if only_names:
|
|
245
|
-
return dict.fromkeys(names)
|
|
246
452
|
for name in names:
|
|
247
453
|
if context and getattr(cls, name).log_context is False:
|
|
248
454
|
continue
|
|
@@ -259,7 +465,21 @@ def _get_attributes_by_decorator(
|
|
|
259
465
|
|
|
260
466
|
|
|
261
467
|
def _get_text_value(value: Any) -> Any:
|
|
262
|
-
"""
|
|
468
|
+
"""
|
|
469
|
+
Normalize values for payload/logging purposes.
|
|
470
|
+
|
|
471
|
+
- list/tuple/set are converted to tuples and their items normalized recursively
|
|
472
|
+
- Enum values are converted to their string representation
|
|
473
|
+
- datetime objects are converted to unix timestamps (float)
|
|
474
|
+
- all other types are returned unchanged
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
value: The input value to normalize into a log-/JSON-friendly representation.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Any: The normalized value, potentially converted as described above.
|
|
481
|
+
|
|
482
|
+
"""
|
|
263
483
|
if isinstance(value, list | tuple | set):
|
|
264
484
|
return tuple(_get_text_value(v) for v in value)
|
|
265
485
|
if isinstance(value, Enum):
|
|
@@ -269,59 +489,23 @@ def _get_text_value(value: Any) -> Any:
|
|
|
269
489
|
return value
|
|
270
490
|
|
|
271
491
|
|
|
272
|
-
def
|
|
273
|
-
"""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def get_attributes_for_info_property(data_object: Any) -> Mapping[str, Any]:
|
|
278
|
-
"""Return the object attributes by decorator info_property."""
|
|
279
|
-
return _get_attributes_by_decorator(data_object=data_object, decorator=info_property)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def get_attributes_for_log_context(data_object: Any) -> Mapping[str, Any]:
|
|
283
|
-
"""Return the object attributes by decorator info_property."""
|
|
284
|
-
return (
|
|
285
|
-
dict(_get_attributes_by_decorator(data_object=data_object, decorator=config_property, context=True))
|
|
286
|
-
| dict(_get_attributes_by_decorator(data_object=data_object, decorator=info_property, context=True))
|
|
287
|
-
| dict(_get_attributes_by_decorator(data_object=data_object, decorator=state_property, context=True))
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def get_attributes_for_state_property(data_object: Any) -> Mapping[str, Any]:
|
|
292
|
-
"""Return the object attributes by decorator state_property."""
|
|
293
|
-
return _get_attributes_by_decorator(data_object=data_object, decorator=state_property)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# pylint: disable=invalid-name
|
|
297
|
-
class cached_slot_property[T, R]:
|
|
298
|
-
"""A property-like descriptor that caches the computed value in a slot attribute. Designed to work with classes that use __slots__ and do not define __dict__."""
|
|
492
|
+
def get_hm_property_by_log_context(data_object: Any) -> Mapping[str, Any]:
|
|
493
|
+
"""
|
|
494
|
+
Return combined log context attributes across all property categories.
|
|
299
495
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
self._func = func # The function to compute the value
|
|
303
|
-
self._cache_attr = f"_cached_{func.__name__}" # Default name of the cache attribute
|
|
304
|
-
self._name = func.__name__
|
|
496
|
+
Includes only properties declared with log_context=True and flattens
|
|
497
|
+
values that implement LogContextMixin by prefixing with a short key.
|
|
305
498
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
# Accessed from class, return the descriptor itself
|
|
310
|
-
return cast(R, self)
|
|
499
|
+
Args:
|
|
500
|
+
data_object: The instance from which to collect attributes marked for
|
|
501
|
+
log context across all property categories.
|
|
311
502
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
value = self._func(instance)
|
|
315
|
-
setattr(instance, self._cache_attr, value)
|
|
503
|
+
Returns:
|
|
504
|
+
Mapping[str, Any]: A mapping of attribute name to normalized value for logging.
|
|
316
505
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"""Raise an error to prevent manual assignment to the property."""
|
|
322
|
-
raise AttributeError(f"Can't set read-only cached property '{self._name}'")
|
|
506
|
+
"""
|
|
507
|
+
result: dict[str, Any] = {}
|
|
508
|
+
for kind in Kind:
|
|
509
|
+
result.update(get_hm_property_by_kind(data_object=data_object, kind=kind, context=True))
|
|
323
510
|
|
|
324
|
-
|
|
325
|
-
"""Delete the cached value so it can be recomputed on next access."""
|
|
326
|
-
if hasattr(instance, self._cache_attr):
|
|
327
|
-
delattr(instance, self._cache_attr)
|
|
511
|
+
return result
|
aiohomematic/support.py
CHANGED
|
@@ -50,11 +50,10 @@ from aiohomematic.const import (
|
|
|
50
50
|
)
|
|
51
51
|
from aiohomematic.exceptions import AioHomematicException, BaseHomematicException
|
|
52
52
|
from aiohomematic.property_decorators import (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
get_attributes_for_state_property,
|
|
53
|
+
Kind,
|
|
54
|
+
cached_property,
|
|
55
|
+
get_hm_property_by_kind,
|
|
56
|
+
get_hm_property_by_log_context,
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
_LOGGER: Final = logging.getLogger(__name__)
|
|
@@ -586,11 +585,11 @@ class LogContextMixin:
|
|
|
586
585
|
|
|
587
586
|
__slots__ = ("_cached_log_context",)
|
|
588
587
|
|
|
589
|
-
@
|
|
588
|
+
@cached_property
|
|
590
589
|
def log_context(self) -> Mapping[str, Any]:
|
|
591
590
|
"""Return the log context for this object."""
|
|
592
591
|
return {
|
|
593
|
-
key: value for key, value in
|
|
592
|
+
key: value for key, value in get_hm_property_by_log_context(data_object=self).items() if value is not None
|
|
594
593
|
}
|
|
595
594
|
|
|
596
595
|
|
|
@@ -604,7 +603,7 @@ class PayloadMixin:
|
|
|
604
603
|
"""Return the config payload."""
|
|
605
604
|
return {
|
|
606
605
|
key: value
|
|
607
|
-
for key, value in
|
|
606
|
+
for key, value in get_hm_property_by_kind(data_object=self, kind=Kind.CONFIG).items()
|
|
608
607
|
if value is not None
|
|
609
608
|
}
|
|
610
609
|
|
|
@@ -612,7 +611,9 @@ class PayloadMixin:
|
|
|
612
611
|
def info_payload(self) -> Mapping[str, Any]:
|
|
613
612
|
"""Return the info payload."""
|
|
614
613
|
return {
|
|
615
|
-
key: value
|
|
614
|
+
key: value
|
|
615
|
+
for key, value in get_hm_property_by_kind(data_object=self, kind=Kind.INFO).items()
|
|
616
|
+
if value is not None
|
|
616
617
|
}
|
|
617
618
|
|
|
618
619
|
@property
|
|
@@ -620,7 +621,7 @@ class PayloadMixin:
|
|
|
620
621
|
"""Return the state payload."""
|
|
621
622
|
return {
|
|
622
623
|
key: value
|
|
623
|
-
for key, value in
|
|
624
|
+
for key, value in get_hm_property_by_kind(data_object=self, kind=Kind.STATE).items()
|
|
624
625
|
if value is not None
|
|
625
626
|
}
|
|
626
627
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.9.
|
|
3
|
+
Version: 2025.9.3
|
|
4
4
|
Summary: Homematic interface for Home Assistant running on Python 3.
|
|
5
5
|
Home-page: https://github.com/sukramj/aiohomematic
|
|
6
6
|
Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
aiohomematic/__init__.py,sha256=VPESkjzeVserFI2DDVAxD782cgYRlLK0sXJzLrr3e_k,2283
|
|
2
2
|
aiohomematic/async_support.py,sha256=Xc55KkIV0h8rf936QKyU4OHSZsPEZ8TwuV8gVveeRh8,6106
|
|
3
|
-
aiohomematic/const.py,sha256=
|
|
3
|
+
aiohomematic/const.py,sha256=U6GjonNWYX6PLDyzMZqVeYsrVPgG7QqoBhVjRec0Xwk,25411
|
|
4
4
|
aiohomematic/context.py,sha256=M7gkA7KFT0dp35gzGz2dzKVXu1PP0sAnepgLlmjyRS4,451
|
|
5
5
|
aiohomematic/converter.py,sha256=QTOL8_B6SoCoqLuRSkjtOlfa7BVFSvOfnSBrDngiinA,3558
|
|
6
6
|
aiohomematic/decorators.py,sha256=oHEFSJoJVd-h9RYb6NLTJ60erkpRHPYv7EEipKn1a9Q,10636
|
|
7
7
|
aiohomematic/exceptions.py,sha256=o_H3Z0A2TQ0irNxUM9u8bmivq0L_mwPCB7nhxEawDxE,5018
|
|
8
8
|
aiohomematic/hmcli.py,sha256=wHOLq4IJRSY9RJux_ZfDH1gQ1ZqD0k68ua5agyBkkE8,4933
|
|
9
|
-
aiohomematic/property_decorators.py,sha256=
|
|
9
|
+
aiohomematic/property_decorators.py,sha256=fDuh3sydnlc-8R1gJAh-9ZxW4jApkk-LohA2Rqk-qPI,17609
|
|
10
10
|
aiohomematic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
aiohomematic/support.py,sha256=
|
|
11
|
+
aiohomematic/support.py,sha256=THiA1zqOSVkR6rnPTdNpRZkdEhGgEiP-jtgTU68tEwE,20331
|
|
12
12
|
aiohomematic/validator.py,sha256=vChkYQ9dGKCQEigiBMnoeyPkCJDJlIm6DSgR1gmeHH0,3545
|
|
13
13
|
aiohomematic/caches/__init__.py,sha256=_gI30tbsWgPRaHvP6cRxOQr6n9bYZzU-jp1WbHhWg-A,470
|
|
14
14
|
aiohomematic/caches/dynamic.py,sha256=Kp0bL5JZaPGGfsezuBX3VtADhEcUvu93AEB5j2rgzzY,21326
|
|
@@ -17,19 +17,19 @@ aiohomematic/caches/visibility.py,sha256=uZ1sSCfmEQURllPvSbJ3EzFVFE7TU8XcxMDSHRp
|
|
|
17
17
|
aiohomematic/central/__init__.py,sha256=y8JErLEY4zh8qsGyS1qMQPaPlQn3GEx6q5_L4OvqxlQ,86148
|
|
18
18
|
aiohomematic/central/decorators.py,sha256=Sl-cMDhreiAOKkIHiei-QbIOcvbWGVX-QwB5bLeohak,6904
|
|
19
19
|
aiohomematic/central/xml_rpc_server.py,sha256=DZcE3t_KWgJyQJM5Z9_yPO4s075DRJmnIdkIx0OTL9M,10549
|
|
20
|
-
aiohomematic/client/__init__.py,sha256=
|
|
20
|
+
aiohomematic/client/__init__.py,sha256=6ik0d9VPcwBguL31zdKw_2Zslkra4rnTmwH_sY7oWXo,69964
|
|
21
21
|
aiohomematic/client/_rpc_errors.py,sha256=vu76ZWMwJJEgB0cnXc10JzK8PaiRU7jYOvj1aC21CqE,2971
|
|
22
|
-
aiohomematic/client/json_rpc.py,sha256=
|
|
23
|
-
aiohomematic/client/xml_rpc.py,sha256=
|
|
22
|
+
aiohomematic/client/json_rpc.py,sha256=MR7lpxd3UddNuUojWh8-CZkxVY8_jUPhLXkDQeIXbfA,49307
|
|
23
|
+
aiohomematic/client/xml_rpc.py,sha256=pTnLjY81bJd8goD_zAkSEE7Q_UTKwBmiiyLOwJ1DOMs,9640
|
|
24
24
|
aiohomematic/model/__init__.py,sha256=LBKHws0W-zgFywTsWfklVVCxrO0w6QUY9ptT6LKxJls,5457
|
|
25
|
-
aiohomematic/model/data_point.py,sha256=
|
|
26
|
-
aiohomematic/model/device.py,sha256=
|
|
25
|
+
aiohomematic/model/data_point.py,sha256=Mls6b20e_fzYhB3EPYab0tocRqbl-v5ityV6uKTBOnU,40837
|
|
26
|
+
aiohomematic/model/device.py,sha256=JNRY7mEIZjwdvDnt-kV95QOjWVLrny-PnamitQqjZ-w,52136
|
|
27
27
|
aiohomematic/model/event.py,sha256=EMoKjbOZRXD3jumRrSlSoUr3nZ2QM3GDKKFfhlf-D50,6837
|
|
28
28
|
aiohomematic/model/support.py,sha256=4VQsOlkJuPhq0hLHKt5LB43YgbITjnyJMwucUYGghzM,19616
|
|
29
29
|
aiohomematic/model/update.py,sha256=QVYFhaEqtHZhZ8yGPXEx2hPMne8WAc_SkVaQ0J20oQw,5138
|
|
30
30
|
aiohomematic/model/calculated/__init__.py,sha256=pk56FYa0MNyN0MTsejSy6RdQjOUCKAqzwWSzN6_ENmk,2808
|
|
31
31
|
aiohomematic/model/calculated/climate.py,sha256=0BhrsiVS74_6jo24I8Cg9WzVAD5od3IqgiwTM0ao2n0,8518
|
|
32
|
-
aiohomematic/model/calculated/data_point.py,sha256=
|
|
32
|
+
aiohomematic/model/calculated/data_point.py,sha256=Bnv2R9ViFf48C7-BjmJh6999EcWZdJxYZfLpBg57oQg,11525
|
|
33
33
|
aiohomematic/model/calculated/operating_voltage_level.py,sha256=urlCkHjisCJ-DO6tUE0evfp45tPwsYcb81_2uoHYxS0,13521
|
|
34
34
|
aiohomematic/model/calculated/support.py,sha256=K-oUSSH6k6_J7zZ9cwxt6qIfArWd-SBWo93LuZUs1_s,6674
|
|
35
35
|
aiohomematic/model/custom/__init__.py,sha256=1sWRrAjHnHSakdrrlNW4_SZ2rg5g2WjGdI4X2PUg_h0,6064
|
|
@@ -48,7 +48,7 @@ aiohomematic/model/generic/__init__.py,sha256=goR2uBBGizm-A5Vvd7ii9Ai3OihBl3hIzt
|
|
|
48
48
|
aiohomematic/model/generic/action.py,sha256=5SPWVliRqXlPgtb2bI6le0-V6JR5krzw8z_0FaOXu5U,994
|
|
49
49
|
aiohomematic/model/generic/binary_sensor.py,sha256=U5GvfRYbhwe0jRmaedD4LVZ_24SyyPbVr74HEfZXoxE,887
|
|
50
50
|
aiohomematic/model/generic/button.py,sha256=6jZ49woI9gYJEx__PjguDNbc5vdE1P-YcLMZZFkYRCg,740
|
|
51
|
-
aiohomematic/model/generic/data_point.py,sha256=
|
|
51
|
+
aiohomematic/model/generic/data_point.py,sha256=wlPQQjm3YDQRbSONpSyzLVWeivVHdkoo1lLxRE7AAWE,5923
|
|
52
52
|
aiohomematic/model/generic/number.py,sha256=wHNIIVr41IOoROPOnBXuMRsANG6doguIEWXYnQHJbBk,2669
|
|
53
53
|
aiohomematic/model/generic/select.py,sha256=kxN5BR85Yli7kQF2DYkGiLnTgcY9LBJWI7MMwWpH1mo,1535
|
|
54
54
|
aiohomematic/model/generic/sensor.py,sha256=dWff7yBJfJSjoD4rcYdmH5HhaRCUEKfPFfOu4T1LrPo,2253
|
|
@@ -69,10 +69,10 @@ aiohomematic/rega_scripts/get_serial.fn,sha256=t1oeo-sB_EuVeiY24PLcxFSkdQVgEWGXz
|
|
|
69
69
|
aiohomematic/rega_scripts/get_system_variable_descriptions.fn,sha256=UKXvC0_5lSApdQ2atJc0E5Stj5Zt3lqh0EcliokYu2c,849
|
|
70
70
|
aiohomematic/rega_scripts/set_program_state.fn,sha256=0bnv7lUj8FMjDZBz325tDVP61m04cHjVj4kIOnUUgpY,279
|
|
71
71
|
aiohomematic/rega_scripts/set_system_variable.fn,sha256=sTmr7vkPTPnPkor5cnLKlDvfsYRbGO1iq2z_2pMXq5E,383
|
|
72
|
-
aiohomematic-2025.9.
|
|
72
|
+
aiohomematic-2025.9.3.dist-info/licenses/LICENSE,sha256=q-B0xpREuZuvKsmk3_iyVZqvZ-vJcWmzMZpeAd0RqtQ,1083
|
|
73
73
|
aiohomematic_support/__init__.py,sha256=_0YtF4lTdC_k6-zrM2IefI0u0LMr_WA61gXAyeGLgbY,66
|
|
74
74
|
aiohomematic_support/client_local.py,sha256=4p0-PagFeNgu-r0EjhpHWfImgHMrdSgt8VUMBBC66J0,12514
|
|
75
|
-
aiohomematic-2025.9.
|
|
76
|
-
aiohomematic-2025.9.
|
|
77
|
-
aiohomematic-2025.9.
|
|
78
|
-
aiohomematic-2025.9.
|
|
75
|
+
aiohomematic-2025.9.3.dist-info/METADATA,sha256=vI27ZQjSHKOzV8Qs8e-W7Gk1iHhAG-qLk-W234XkXZo,7075
|
|
76
|
+
aiohomematic-2025.9.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
77
|
+
aiohomematic-2025.9.3.dist-info/top_level.txt,sha256=5TDRlUWQPThIUwQjOj--aUo4UA-ow4m0sNhnoCBi5n8,34
|
|
78
|
+
aiohomematic-2025.9.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|