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.

@@ -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 info_property
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
- @info_property(log_context=True)
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
@@ -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 info_property
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
- @info_property(log_context=True)
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
- @info_property(log_context=True)
225
+ @hm_property(log_context=True)
226
226
  def tls(self) -> bool:
227
227
  """Return tls."""
228
228
  return self._tls
@@ -42,7 +42,7 @@ from aiohomematic.exceptions import (
42
42
  NoConnectionException,
43
43
  UnsupportedException,
44
44
  )
45
- from aiohomematic.property_decorators import info_property
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
- @info_property(log_context=True)
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
- @info_property(log_context=True)
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.2"
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 cached_slot_property, config_property, state_property
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
- @cached_slot_property
150
+ @cached_property
151
151
  def dpk(self) -> DataPointKey:
152
152
  """Return data_point key value."""
153
153
  return DataPointKey(
@@ -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 cached_slot_property, config_property, info_property, state_property
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
- @cached_slot_property
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
- @cached_slot_property
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
- @cached_slot_property
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
- @info_property(log_context=True)
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
- @cached_slot_property
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
- @info_property(log_context=True)
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
- @cached_slot_property
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
- @cached_slot_property
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:
@@ -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 cached_slot_property, info_property, state_property
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
- @info_property(log_context=True)
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
- @cached_slot_property
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
- @info_property(log_context=True)
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
- @info_property(log_context=True)
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 cached_slot_property
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
- @cached_slot_property
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
- """Decorators for data points used within aiohomematic."""
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
- "get_attributes_for_config_property",
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
- """Generic property implementation."""
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
- """Init the generic property."""
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)(fget, self.fset, self.fdel, self.__doc__) # pragma: no cover
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)(self.fget, fset, self.fdel, self.__doc__)
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)(self.fget, self.fset, fdel, self.__doc__)
62
-
63
- def __get__(self, obj: Any, gtype: type | None = None, /) -> GETTER: # type: ignore[override]
64
- """Return the attribute."""
65
- if obj is None:
66
- return self # type: ignore[return-value]
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
- def __set__(self, obj: Any, value: Any, /) -> None:
72
- """Set the attribute."""
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(obj, value)
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(obj)
190
+ self.fdel(instance)
82
191
 
83
192
 
84
- # ----- config_property -----
193
+ # ----- hm_property -----
85
194
 
86
195
 
87
- class _ConfigProperty[GETTER, SETTER](_GenericProperty[GETTER, SETTER]):
88
- """Decorate to mark own config properties."""
196
+ @overload
197
+ def hm_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
89
198
 
90
199
 
91
200
  @overload
92
- def config_property[PR](func: Callable[[Any], PR], /) -> _ConfigProperty[PR, Any]: ...
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 config_property(*, log_context: bool = ...) -> Callable[[Callable[[Any], R]], _ConfigProperty[R, Any]]: ...
242
+ def cached_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
97
243
 
98
244
 
99
- def config_property[PR](
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
- ) -> _ConfigProperty[PR, Any] | Callable[[Callable[[Any], PR]], _ConfigProperty[PR, Any]]:
253
+ ) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
104
254
  """
105
- Return an instance of _ConfigProperty wrapping the given function.
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]) -> _ConfigProperty[PR, Any]:
114
- return _ConfigProperty(f, log_context=log_context)
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 _ConfigProperty(func, log_context=log_context)
274
+ return _GenericProperty(func, kind=Kind.SIMPLE, cached=True, log_context=log_context)
118
275
 
119
276
 
120
- # Expose the underlying property class for discovery
121
- setattr(config_property, "__property_class__", _ConfigProperty)
277
+ # ----- config_property -----
122
278
 
123
279
 
124
- # ----- info_property -----
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
- class _InfoProperty[GETTER, SETTER](_GenericProperty[GETTER, SETTER]):
128
- """Decorate to mark own info properties."""
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], /) -> _InfoProperty[PR, Any]: ...
324
+ def info_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
133
325
 
134
326
 
135
327
  @overload
136
- def info_property(*, log_context: bool = ...) -> Callable[[Callable[[Any], R]], _InfoProperty[R, Any]]: ...
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
- ) -> _InfoProperty[PR, Any] | Callable[[Callable[[Any], PR]], _InfoProperty[PR, Any]]:
338
+ ) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
144
339
  """
145
- Return an instance of _InfoProperty wrapping the given function.
340
+ Decorate a method as an informational/metadata property.
146
341
 
147
- Decorator for info properties supporting both usages:
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]) -> _InfoProperty[PR, Any]:
154
- return _InfoProperty(f, log_context=log_context)
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 _InfoProperty(func, log_context=log_context)
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], /) -> _StateProperty[PR, Any]: ...
367
+ def state_property[PR](func: Callable[[Any], PR], /) -> _GenericProperty[PR, Any]: ...
173
368
 
174
369
 
175
370
  @overload
176
- def state_property(*, log_context: bool = ...) -> Callable[[Callable[[Any], R]], _StateProperty[R, Any]]: ...
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
- ) -> _StateProperty[PR, Any] | Callable[[Callable[[Any], PR]], _StateProperty[PR, Any]]:
381
+ ) -> _GenericProperty[PR, Any] | Callable[[Callable[[Any], PR]], _GenericProperty[PR, Any]]:
184
382
  """
185
- Return an instance of _StateProperty wrapping the given function.
383
+ Decorate a method as a dynamic state property.
186
384
 
187
- Decorator for state properties supporting both usages:
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]) -> _StateProperty[PR, Any]:
194
- return _StateProperty(f, log_context=log_context)
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 _StateProperty(func, log_context=log_context)
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[type, tuple[str, ...]]] = WeakKeyDictionary()
412
+ _PUBLIC_ATTR_CACHE: WeakKeyDictionary[type, dict[Kind, tuple[str, ...]]] = WeakKeyDictionary()
209
413
 
210
414
 
211
- def _get_attributes_by_decorator(
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
- Return the object attributes by decorator.
216
-
217
- This caches the attribute names per (class, decorator) to reduce overhead
218
- from repeated dir()/getattr() scans. Values are not cached as they are
219
- instance-dependent and may change over time.
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
- # Get or compute the attribute names for this decorator
239
- if (names := decorator_cache.get(resolved_decorator)) is None:
240
- names = tuple(y for y in dir(cls) if isinstance(getattr(cls, y), resolved_decorator))
241
- decorator_cache[resolved_decorator] = names
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
- """Convert value to text."""
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 get_attributes_for_config_property(data_object: Any) -> Mapping[str, Any]:
273
- """Return the object attributes by decorator config_property."""
274
- return _get_attributes_by_decorator(data_object=data_object, decorator=config_property)
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
- def __init__(self, func: Callable[[T], R]) -> None:
301
- """Init the cached property."""
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
- def __get__(self, instance: T | None, owner: type | None = None) -> R:
307
- """Return the cached value if it exists. Otherwise, compute it using the function and cache it."""
308
- if instance is None:
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
- # If the cached value is not set yet, compute and store it
313
- if not hasattr(instance, self._cache_attr):
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
- # Return the cached value
318
- return cast(R, getattr(instance, self._cache_attr))
319
-
320
- def __set__(self, instance: T, value: Any) -> None:
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
- def __delete__(self, instance: T) -> None:
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
- cached_slot_property,
54
- get_attributes_for_config_property,
55
- get_attributes_for_info_property,
56
- get_attributes_for_log_context,
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
- @cached_slot_property
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 get_attributes_for_log_context(data_object=self).items() if value is not None
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 get_attributes_for_config_property(data_object=self).items()
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 for key, value in get_attributes_for_info_property(data_object=self).items() if value is not None
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 get_attributes_for_state_property(data_object=self).items()
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.2
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=EpW2w2XzvTzzzxByobHD7suUpA--ZkbzCmMTk79GK7I,25411
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=LnWgLCkopkSqdz4dcwxoH2UuoWJmykA9YUFM7Zefw70,11710
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=9CsB27Xz0Q5tgqROceG2MaQuJGN1rI47YIUZM3N_2Ik,20374
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=CX3b5LiWO6kJc1Nr6xQoksenk4mMLIyZFhp-LGOUQ1Q,69968
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=07Vfc9laLHIXKG87jlJE1yiLuu4FD3bg25INsV1twkU,49313
23
- aiohomematic/client/xml_rpc.py,sha256=Z964fzQtq9LaIYZXG02C82CvMU11shxA7yx7UouI_mI,9646
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=QvHLLaq1NByB75mP4yIV0VCq4PPNcKdgVbbx07DEER8,40878
26
- aiohomematic/model/device.py,sha256=OKecVb9hMb-LnbvojMHFKNH1A0-5yIikG9acYCuu3dU,52139
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=DZyd9vBIxxjEU2BBevyMVwCUptu1cy0uCIOxbySw2HI,11535
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=VuRj9C7hm8MHqWt2eO99osiKWCXx1MO7JNT9dNM08DI,5933
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.2.dist-info/licenses/LICENSE,sha256=q-B0xpREuZuvKsmk3_iyVZqvZ-vJcWmzMZpeAd0RqtQ,1083
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.2.dist-info/METADATA,sha256=VefZTtVGMgUabgxX2pfrEWWN7rnqJdM5ydd_Le-aQtg,7075
76
- aiohomematic-2025.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
- aiohomematic-2025.9.2.dist-info/top_level.txt,sha256=5TDRlUWQPThIUwQjOj--aUo4UA-ow4m0sNhnoCBi5n8,34
78
- aiohomematic-2025.9.2.dist-info/RECORD,,
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,,