pyg90alarm 2.4.1__tar.gz → 2.5.0__tar.gz
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.
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/PKG-INFO +1 -1
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/__init__.py +6 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/const.py +2 -0
- pyg90alarm-2.4.1/src/pyg90alarm/local/dataclass_load_save.py → pyg90alarm-2.5.0/src/pyg90alarm/dataclass/load_save.py +125 -5
- pyg90alarm-2.5.0/src/pyg90alarm/dataclass/validation.py +572 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/sensor.py +2 -2
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/alarm_phones.py +39 -12
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/host_config.py +58 -16
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/net_config.py +39 -13
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/paginated_result.py +5 -3
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm.egg-info/SOURCES.txt +7 -1
- pyg90alarm-2.5.0/tests/__init__.py +0 -0
- pyg90alarm-2.5.0/tests/test_alarm_phones.py +132 -0
- pyg90alarm-2.5.0/tests/test_host_config.py +166 -0
- pyg90alarm-2.5.0/tests/test_net_config.py +153 -0
- pyg90alarm-2.5.0/tests/unit/dataclass/test_dataclass_load_save.py +183 -0
- pyg90alarm-2.5.0/tests/unit/dataclass/test_dataclass_load_save_descriptor.py +270 -0
- pyg90alarm-2.5.0/tests/unit/dataclass/test_dataclass_load_save_serialize.py +254 -0
- pyg90alarm-2.5.0/tests/unit/dataclass/test_validation.py +455 -0
- pyg90alarm-2.4.1/tests/test_alarm_phones.py +0 -51
- pyg90alarm-2.4.1/tests/test_host_config.py +0 -54
- pyg90alarm-2.4.1/tests/test_net_config.py +0 -52
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.github/CODEOWNERS +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.github/dependabot.yml +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.github/workflows/main.yml +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.gitignore +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.pylintrc +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/.readthedocs.yaml +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/LICENSE +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/MANIFEST.in +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/README.rst +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/.DS_Store +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/.gitignore +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/api-docs.rst +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/cloud-protocol.rst +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/conf.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/index.rst +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/local-protocol.rst +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/docs/requirements.txt +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/pyproject.toml +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/setup.cfg +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/setup.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/sonar-project.properties +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/alarm.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/callback.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/cloud/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/cloud/const.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/cloud/messages.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/cloud/notifications.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/cloud/protocol.py +0 -0
- {pyg90alarm-2.4.1/src/pyg90alarm/local → pyg90alarm-2.5.0/src/pyg90alarm/dataclass}/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/definitions/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/definitions/base.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/definitions/devices.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/definitions/sensors.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/base_entity.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/base_list.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/device.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/device_list.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/entities/sensor_list.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/event_mapping.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/exceptions.py +0 -0
- {pyg90alarm-2.4.1/src/pyg90alarm/notifications → pyg90alarm-2.5.0/src/pyg90alarm/local}/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/alert_config.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/base_cmd.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/config.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/discovery.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/history.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/host_info.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/host_status.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/notifications.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/paginated_cmd.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/targeted_discovery.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/local/user_data_crc.py +0 -0
- {pyg90alarm-2.4.1/tests → pyg90alarm-2.5.0/src/pyg90alarm/notifications}/__init__.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/notifications/base.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/notifications/protocol.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm/py.typed +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm.egg-info/requires.txt +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/src/pyg90alarm.egg-info/top_level.txt +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/conftest.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/device_mock.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_alarm.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_base_commands.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_cloud_notifications.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_config.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_devices.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_discovery.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_history.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_local_notifications.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_paginated_commands.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/test_sensor.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tests/unit/entities/test_base_list.py +0 -0
- {pyg90alarm-2.4.1 → pyg90alarm-2.5.0}/tox.ini +0 -0
|
@@ -52,6 +52,10 @@ from .local.host_config import (
|
|
|
52
52
|
from .local.alarm_phones import G90AlarmPhones
|
|
53
53
|
from .local.net_config import G90NetConfig, G90APNAuth
|
|
54
54
|
from .local.history import G90History
|
|
55
|
+
|
|
56
|
+
from .dataclass.validation import (
|
|
57
|
+
get_field_validation_constraints,
|
|
58
|
+
)
|
|
55
59
|
from .const import (
|
|
56
60
|
G90MessageTypes,
|
|
57
61
|
G90NotificationTypes,
|
|
@@ -95,4 +99,6 @@ __all__ = [
|
|
|
95
99
|
'G90AlarmPhones',
|
|
96
100
|
# History
|
|
97
101
|
'G90History',
|
|
102
|
+
# Dataclass validation
|
|
103
|
+
'get_field_validation_constraints',
|
|
98
104
|
]
|
|
@@ -22,16 +22,106 @@
|
|
|
22
22
|
Base class for loading/saving dataclasses to a device.
|
|
23
23
|
"""
|
|
24
24
|
from __future__ import annotations
|
|
25
|
-
from typing import
|
|
25
|
+
from typing import (
|
|
26
|
+
TYPE_CHECKING, Type, TypeVar, Optional, ClassVar, Any, Dict, List, cast
|
|
27
|
+
)
|
|
26
28
|
import logging
|
|
27
|
-
from dataclasses import dataclass,
|
|
29
|
+
from dataclasses import dataclass, asdict, field, fields
|
|
30
|
+
from .validation import ValidatorBase
|
|
28
31
|
from ..const import G90Commands
|
|
29
32
|
if TYPE_CHECKING:
|
|
30
33
|
from ..alarm import G90Alarm
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
_LOGGER = logging.getLogger(__name__)
|
|
34
|
-
|
|
37
|
+
DataclassLoadSaveT = TypeVar('DataclassLoadSaveT', bound='DataclassLoadSave')
|
|
38
|
+
T = TypeVar('T')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Metadata:
|
|
42
|
+
"""
|
|
43
|
+
Metadata keys for DataclassLoadSave fields.
|
|
44
|
+
"""
|
|
45
|
+
# pylint: disable=too-few-public-methods
|
|
46
|
+
NO_SERIALIZE = 'no_serialize'
|
|
47
|
+
SKIP_NONE = 'skip_none'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReadOnlyIfNotProvided(ValidatorBase[T]):
|
|
51
|
+
"""
|
|
52
|
+
Descriptor for dataclass fields to be read-only if not provided during
|
|
53
|
+
initialization.
|
|
54
|
+
|
|
55
|
+
The field can be read, but attempts to modify it will raise a
|
|
56
|
+
ValueError if the field was not provided during initialization. In
|
|
57
|
+
other words, the only way to set the value is during object creation.
|
|
58
|
+
|
|
59
|
+
Example usage:
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Example:
|
|
63
|
+
read_only_field: Optional[int] = field_readonly_if_not_provided(
|
|
64
|
+
default=None
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Works ok
|
|
68
|
+
ex = Example(read_only_field=42)
|
|
69
|
+
print(ex.read_only_field) # Outputs: 42
|
|
70
|
+
ex.read_only_field = 100 # Works ok
|
|
71
|
+
|
|
72
|
+
# Raises ValueError
|
|
73
|
+
ex2 = Example()
|
|
74
|
+
print(ex2.read_only_field) # Outputs: None
|
|
75
|
+
ex2.read_only_field = 100 # Raises ValueError
|
|
76
|
+
|
|
77
|
+
:param default: Default value to return upon read if not provided during
|
|
78
|
+
initialization.
|
|
79
|
+
"""
|
|
80
|
+
# pylint: disable=too-few-public-methods
|
|
81
|
+
|
|
82
|
+
def __validate__(self, obj: Any, value: T) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Validation method.
|
|
85
|
+
"""
|
|
86
|
+
# Prevent setting the value if it was not provided during
|
|
87
|
+
# initialization. The condition is determined by checking if the
|
|
88
|
+
# current value is `self` - i.e. the descriptor instance hasn't been
|
|
89
|
+
# replaced with an actual value
|
|
90
|
+
if getattr(obj, self.__field_name__, self._default) is self:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f'Field {self.__unmangled_name__} is read-only because'
|
|
93
|
+
' it was not provided during initialization'
|
|
94
|
+
)
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def field_readonly_if_not_provided(
|
|
99
|
+
*args: Any, default: Optional[T] = None, **kwargs: Any
|
|
100
|
+
) -> T:
|
|
101
|
+
"""
|
|
102
|
+
Helper function to create a dataclass field with ReadOnlyIfNotProvided
|
|
103
|
+
descriptor.
|
|
104
|
+
|
|
105
|
+
:param args: Positional arguments to pass to `dataclasses.field()`.
|
|
106
|
+
:param default: Default value to return upon read if not provided during
|
|
107
|
+
initialization.
|
|
108
|
+
:param kwargs: Keyword arguments to pass to `dataclasses.field()`.
|
|
109
|
+
:return: A dataclass field with ReadOnlyIfNotProvided descriptor attached.
|
|
110
|
+
"""
|
|
111
|
+
# Also set SKIP_NONE metadata if default is None, so that the field is
|
|
112
|
+
# skipped during serialization when its value is None
|
|
113
|
+
if default is None:
|
|
114
|
+
if 'metadata' not in kwargs:
|
|
115
|
+
kwargs['metadata'] = {}
|
|
116
|
+
kwargs['metadata'][Metadata.SKIP_NONE] = True
|
|
117
|
+
|
|
118
|
+
# Instantiate the field with ReadOnlyIfNotProvided descriptor and rest of
|
|
119
|
+
# the provided arguments
|
|
120
|
+
# pylint: disable=invalid-field-call
|
|
121
|
+
return cast(T, field(
|
|
122
|
+
*args, **kwargs,
|
|
123
|
+
default=ReadOnlyIfNotProvided[T](default)
|
|
124
|
+
))
|
|
35
125
|
|
|
36
126
|
|
|
37
127
|
@dataclass
|
|
@@ -79,6 +169,34 @@ class DataclassLoadSave:
|
|
|
79
169
|
# declared here to avoid being part of dataclass fields
|
|
80
170
|
self._parent: Optional[G90Alarm] = None
|
|
81
171
|
|
|
172
|
+
def serialize(self) -> List[Any]:
|
|
173
|
+
"""
|
|
174
|
+
Returns the dataclass fields as a list.
|
|
175
|
+
|
|
176
|
+
Handles specific metadata for the fields.
|
|
177
|
+
:seealso:`Metadata`.
|
|
178
|
+
|
|
179
|
+
:return: Dataclass serialized as list.
|
|
180
|
+
"""
|
|
181
|
+
result = []
|
|
182
|
+
|
|
183
|
+
for f in fields(self):
|
|
184
|
+
# Skip fields marked with NO_SERIALIZE metadata
|
|
185
|
+
if f.metadata.get(Metadata.NO_SERIALIZE, False):
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# Skip fields with None value if SKIP_NONE metadata is set
|
|
189
|
+
if (
|
|
190
|
+
f.metadata.get(Metadata.SKIP_NONE, False)
|
|
191
|
+
and getattr(self, f.name) is None
|
|
192
|
+
):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# Append field value to the result list
|
|
196
|
+
result.append(getattr(self, f.name))
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
82
200
|
async def save(self) -> None:
|
|
83
201
|
"""
|
|
84
202
|
Save the current data to the device.
|
|
@@ -89,11 +207,13 @@ class DataclassLoadSave:
|
|
|
89
207
|
_LOGGER.debug('Setting data to the device: %s', str(self))
|
|
90
208
|
await self._parent.command(
|
|
91
209
|
self.SAVE_COMMAND,
|
|
92
|
-
|
|
210
|
+
self.serialize()
|
|
93
211
|
)
|
|
94
212
|
|
|
95
213
|
@classmethod
|
|
96
|
-
async def load(
|
|
214
|
+
async def load(
|
|
215
|
+
cls: Type[DataclassLoadSaveT], parent: G90Alarm
|
|
216
|
+
) -> DataclassLoadSaveT:
|
|
97
217
|
"""
|
|
98
218
|
Create an instance with values loaded from the device.
|
|
99
219
|
|