pyg90alarm 2.4.2__tar.gz → 2.5.1__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.
Files changed (99) hide show
  1. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/PKG-INFO +1 -1
  2. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/__init__.py +9 -0
  3. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/const.py +2 -0
  4. pyg90alarm-2.4.2/src/pyg90alarm/local/dataclass_load_save.py → pyg90alarm-2.5.1/src/pyg90alarm/dataclass/load_save.py +125 -5
  5. pyg90alarm-2.5.1/src/pyg90alarm/dataclass/validation.py +572 -0
  6. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/base_list.py +6 -1
  7. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/exceptions.py +1 -1
  8. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/alarm_phones.py +39 -12
  9. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_config.py +58 -16
  10. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/net_config.py +39 -13
  11. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
  12. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/SOURCES.txt +8 -1
  13. pyg90alarm-2.5.1/tests/__init__.py +0 -0
  14. pyg90alarm-2.5.1/tests/test_alarm_phones.py +132 -0
  15. pyg90alarm-2.5.1/tests/test_host_config.py +166 -0
  16. pyg90alarm-2.5.1/tests/test_net_config.py +153 -0
  17. pyg90alarm-2.5.1/tests/unit/dataclass/test_dataclass_load_save.py +183 -0
  18. pyg90alarm-2.5.1/tests/unit/dataclass/test_dataclass_load_save_descriptor.py +270 -0
  19. pyg90alarm-2.5.1/tests/unit/dataclass/test_dataclass_load_save_serialize.py +254 -0
  20. pyg90alarm-2.5.1/tests/unit/dataclass/test_validation.py +455 -0
  21. pyg90alarm-2.5.1/tests/unit/entities/test_base_list.py +306 -0
  22. pyg90alarm-2.5.1/tests/unit/test_exceptions.py +16 -0
  23. pyg90alarm-2.4.2/tests/test_alarm_phones.py +0 -51
  24. pyg90alarm-2.4.2/tests/test_host_config.py +0 -54
  25. pyg90alarm-2.4.2/tests/test_net_config.py +0 -52
  26. pyg90alarm-2.4.2/tests/unit/entities/test_base_list.py +0 -192
  27. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.github/CODEOWNERS +0 -0
  28. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.github/dependabot.yml +0 -0
  29. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.github/workflows/main.yml +0 -0
  30. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.gitignore +0 -0
  31. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.pylintrc +0 -0
  32. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/.readthedocs.yaml +0 -0
  33. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/LICENSE +0 -0
  34. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/MANIFEST.in +0 -0
  35. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/README.rst +0 -0
  36. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/.DS_Store +0 -0
  37. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/.gitignore +0 -0
  38. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/api-docs.rst +0 -0
  39. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/cloud-protocol.rst +0 -0
  40. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/conf.py +0 -0
  41. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/index.rst +0 -0
  42. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/local-protocol.rst +0 -0
  43. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/docs/requirements.txt +0 -0
  44. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/pyproject.toml +0 -0
  45. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/setup.cfg +0 -0
  46. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/setup.py +0 -0
  47. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/sonar-project.properties +0 -0
  48. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/alarm.py +0 -0
  49. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/callback.py +0 -0
  50. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/__init__.py +0 -0
  51. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/const.py +0 -0
  52. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/messages.py +0 -0
  53. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/notifications.py +0 -0
  54. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/protocol.py +0 -0
  55. {pyg90alarm-2.4.2/src/pyg90alarm/local → pyg90alarm-2.5.1/src/pyg90alarm/dataclass}/__init__.py +0 -0
  56. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/__init__.py +0 -0
  57. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/base.py +0 -0
  58. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/devices.py +0 -0
  59. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/sensors.py +0 -0
  60. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/__init__.py +0 -0
  61. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/base_entity.py +0 -0
  62. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/device.py +0 -0
  63. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/device_list.py +0 -0
  64. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/sensor.py +0 -0
  65. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/sensor_list.py +0 -0
  66. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/event_mapping.py +0 -0
  67. {pyg90alarm-2.4.2/src/pyg90alarm/notifications → pyg90alarm-2.5.1/src/pyg90alarm/local}/__init__.py +0 -0
  68. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/alert_config.py +0 -0
  69. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/base_cmd.py +0 -0
  70. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/config.py +0 -0
  71. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/discovery.py +0 -0
  72. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/history.py +0 -0
  73. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_info.py +0 -0
  74. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_status.py +0 -0
  75. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/notifications.py +0 -0
  76. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/paginated_cmd.py +0 -0
  77. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/paginated_result.py +0 -0
  78. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/targeted_discovery.py +0 -0
  79. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/local/user_data_crc.py +0 -0
  80. {pyg90alarm-2.4.2/tests → pyg90alarm-2.5.1/src/pyg90alarm/notifications}/__init__.py +0 -0
  81. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/notifications/base.py +0 -0
  82. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/notifications/protocol.py +0 -0
  83. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm/py.typed +0 -0
  84. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  85. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/requires.txt +0 -0
  86. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  87. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/conftest.py +0 -0
  88. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/device_mock.py +0 -0
  89. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_alarm.py +0 -0
  90. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_base_commands.py +0 -0
  91. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_cloud_notifications.py +0 -0
  92. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_config.py +0 -0
  93. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_devices.py +0 -0
  94. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_discovery.py +0 -0
  95. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_history.py +0 -0
  96. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_local_notifications.py +0 -0
  97. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_paginated_commands.py +0 -0
  98. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tests/test_sensor.py +0 -0
  99. {pyg90alarm-2.4.2 → pyg90alarm-2.5.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.4.2
3
+ Version: 2.5.1
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -52,6 +52,11 @@ 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.load_save import DataclassLoadSave
57
+ from .dataclass.validation import (
58
+ get_field_validation_constraints,
59
+ )
55
60
  from .const import (
56
61
  G90MessageTypes,
57
62
  G90NotificationTypes,
@@ -95,4 +100,8 @@ __all__ = [
95
100
  'G90AlarmPhones',
96
101
  # History
97
102
  'G90History',
103
+ # Dataclass validation
104
+ 'get_field_validation_constraints',
105
+ # Dataclass load/save
106
+ 'DataclassLoadSave',
98
107
  ]
@@ -39,6 +39,8 @@ ROOM_ID = 0
39
39
 
40
40
  CMD_PAGE_SIZE = 10
41
41
 
42
+ BUG_REPORT_URL = 'https://github.com/hostcc/pyg90alarm/issues'
43
+
42
44
 
43
45
  class G90Commands(IntEnum):
44
46
  """
@@ -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 TYPE_CHECKING, Type, TypeVar, Optional, ClassVar, Any, Dict
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, astuple, asdict
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
- S = TypeVar('S', bound='DataclassLoadSave')
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
- list(astuple(self))
210
+ self.serialize()
93
211
  )
94
212
 
95
213
  @classmethod
96
- async def load(cls: Type[S], parent: G90Alarm) -> S:
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