pyg90alarm 2.3.0__tar.gz → 2.3.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 (79) hide show
  1. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/PKG-INFO +1 -1
  2. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/pyproject.toml +1 -0
  3. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/base_list.py +15 -10
  4. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
  5. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/SOURCES.txt +2 -1
  6. pyg90alarm-2.3.1/tests/unit/entities/test_base_list.py +192 -0
  7. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/CODEOWNERS +0 -0
  8. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/dependabot.yml +0 -0
  9. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/workflows/main.yml +0 -0
  10. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.gitignore +0 -0
  11. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.pylintrc +0 -0
  12. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.readthedocs.yaml +0 -0
  13. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/LICENSE +0 -0
  14. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/MANIFEST.in +0 -0
  15. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/README.rst +0 -0
  16. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/.DS_Store +0 -0
  17. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/.gitignore +0 -0
  18. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/api-docs.rst +0 -0
  19. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/cloud-protocol.rst +0 -0
  20. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/conf.py +0 -0
  21. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/index.rst +0 -0
  22. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/local-protocol.rst +0 -0
  23. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/requirements.txt +0 -0
  24. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/setup.cfg +0 -0
  25. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/setup.py +0 -0
  26. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/sonar-project.properties +0 -0
  27. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/__init__.py +0 -0
  28. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/alarm.py +0 -0
  29. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/callback.py +0 -0
  30. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/__init__.py +0 -0
  31. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/const.py +0 -0
  32. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/messages.py +0 -0
  33. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/notifications.py +0 -0
  34. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/protocol.py +0 -0
  35. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/const.py +0 -0
  36. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/__init__.py +0 -0
  37. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/base.py +0 -0
  38. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/devices.py +0 -0
  39. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/sensors.py +0 -0
  40. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/__init__.py +0 -0
  41. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/base_entity.py +0 -0
  42. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/device.py +0 -0
  43. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/device_list.py +0 -0
  44. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/sensor.py +0 -0
  45. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/sensor_list.py +0 -0
  46. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/exceptions.py +0 -0
  47. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/__init__.py +0 -0
  48. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/base_cmd.py +0 -0
  49. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/config.py +0 -0
  50. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/discovery.py +0 -0
  51. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/history.py +0 -0
  52. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/host_info.py +0 -0
  53. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/host_status.py +0 -0
  54. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/notifications.py +0 -0
  55. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/paginated_cmd.py +0 -0
  56. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/paginated_result.py +0 -0
  57. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/targeted_discovery.py +0 -0
  58. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/user_data_crc.py +0 -0
  59. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/__init__.py +0 -0
  60. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/base.py +0 -0
  61. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/protocol.py +0 -0
  62. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/py.typed +0 -0
  63. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  64. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/requires.txt +0 -0
  65. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  66. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/__init__.py +0 -0
  67. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/conftest.py +0 -0
  68. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/device_mock.py +0 -0
  69. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_alarm.py +0 -0
  70. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_base_commands.py +0 -0
  71. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_cloud_notifications.py +0 -0
  72. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_config.py +0 -0
  73. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_devices.py +0 -0
  74. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_discovery.py +0 -0
  75. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_history.py +0 -0
  76. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_local_notifications.py +0 -0
  77. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_paginated_commands.py +0 -0
  78. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_sensor.py +0 -0
  79. {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -11,6 +11,7 @@ local_scheme = "no-local-version"
11
11
  log_cli = 1
12
12
  log_cli_level = "error"
13
13
  asyncio_mode = "auto"
14
+ asyncio_default_fixture_loop_scope = "function"
14
15
  pythonpath = "src/"
15
16
 
16
17
  [tool.coverage.run]
@@ -68,7 +68,7 @@ class G90BaseList(Generic[T], ABC):
68
68
 
69
69
  :return: Async generator of entities
70
70
  """
71
- yield cast(T, None)
71
+ yield cast(T, None) # pragma: no cover
72
72
 
73
73
  @property
74
74
  async def entities(self) -> List[T]:
@@ -231,25 +231,30 @@ class G90BaseList(Generic[T], ABC):
231
231
  # Collect indexes in use by the existing entities
232
232
  occupied_indexes = set(x.index for x in entities)
233
233
  # Generate a set of possible indexes from 0 to the maximum index in
234
- # use
235
- possible_indexes = set(range(0, max(occupied_indexes)))
234
+ # use, or provide an empty set if there are no existing entities
235
+ if occupied_indexes:
236
+ possible_indexes = set(range(0, max(occupied_indexes)))
237
+ else:
238
+ # No occupied indexes, so possible_indexes is empty
239
+ possible_indexes = set()
236
240
 
237
241
  try:
238
242
  # Find the first free index by taking difference between
239
243
  # possible indexes and occupied ones, and then taking the minimum
240
244
  # value off the difference
241
245
  free_idx = min(
242
- set(possible_indexes).difference(occupied_indexes)
246
+ possible_indexes.difference(occupied_indexes)
243
247
  )
244
- _LOGGER.debug(
245
- 'Found free index: %s out of occupied indexes: %s',
246
- free_idx, occupied_indexes
247
- )
248
- return free_idx
249
248
  except ValueError:
250
249
  # If no gaps in existing indexes, then return the index next to
251
250
  # the last existing entity
252
- return len(entities)
251
+ free_idx = len(entities)
252
+
253
+ _LOGGER.debug(
254
+ 'Found free index=%s out of occupied indexes: %s',
255
+ free_idx, occupied_indexes
256
+ )
257
+ return free_idx
253
258
 
254
259
  @property
255
260
  def list_change_callback(self) -> Optional[ListChangeCallback[T]]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -74,4 +74,5 @@ tests/test_discovery.py
74
74
  tests/test_history.py
75
75
  tests/test_local_notifications.py
76
76
  tests/test_paginated_commands.py
77
- tests/test_sensor.py
77
+ tests/test_sensor.py
78
+ tests/unit/entities/test_base_list.py
@@ -0,0 +1,192 @@
1
+ """
2
+ Tests for G90BaseList class
3
+ """
4
+ from __future__ import annotations
5
+ from typing import AsyncGenerator
6
+ from unittest.mock import MagicMock
7
+
8
+ from pyg90alarm.entities.base_list import G90BaseList
9
+
10
+
11
+ class TestBaseList(G90BaseList[MagicMock]):
12
+ """
13
+ Mock subclass for testing G90BaseList.
14
+ """
15
+
16
+ # Prevent pytest from collecting this class as a test case
17
+ __test__ = False
18
+
19
+ async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
20
+ """Mock _fetch method."""
21
+ if False: # pragma: no cover
22
+ yield MagicMock()
23
+
24
+
25
+ async def test_find_free_idx_empty_list() -> None:
26
+ """
27
+ Tests find_free_idx with empty entities list.
28
+ """
29
+
30
+ parent = MagicMock()
31
+ base_list = TestBaseList(parent)
32
+ base_list._entities = []
33
+
34
+ result = await base_list.find_free_idx()
35
+
36
+ assert result == 0
37
+
38
+
39
+ async def test_find_free_idx_one_entity_at_zero() -> None:
40
+ """
41
+ Tests find_free_idx with one entity at index 0.
42
+ """
43
+
44
+ base_list = TestBaseList(parent=MagicMock())
45
+ base_list._entities = [MagicMock(index=0)]
46
+
47
+ result = await base_list.find_free_idx()
48
+
49
+ assert result == 1
50
+
51
+
52
+ async def test_find_free_idx_returns_lowest_available_index() -> None:
53
+ """
54
+ Tests find_free_idx with two entities at indexes 10 and 11.
55
+ """
56
+
57
+ base_list = TestBaseList(parent=MagicMock())
58
+ base_list._entities = [MagicMock(index=10), MagicMock(index=11)]
59
+
60
+ result = await base_list.find_free_idx()
61
+
62
+ assert result == 0
63
+
64
+
65
+ async def test_find_free_idx_returns_lowest_available_index_over_gap() -> None:
66
+ """
67
+ Tests find_free_idx with two entities at indexes 10 and 11.
68
+ """
69
+
70
+ base_list = TestBaseList(parent=MagicMock())
71
+ base_list._entities = [
72
+ MagicMock(index=0), MagicMock(index=10), MagicMock(index=11)
73
+ ]
74
+
75
+ result = await base_list.find_free_idx()
76
+
77
+ assert result == 1
78
+
79
+
80
+ async def test_find_entity_by_idx_and_name() -> None:
81
+ """
82
+ Tests find with matching index, subindex, and name.
83
+ """
84
+
85
+ base_list = TestBaseList(parent=MagicMock())
86
+ entity = MagicMock()
87
+ entity.index = 0
88
+ entity.subindex = 0
89
+ entity.name = "Test Entity"
90
+ entity.is_unavailable = False
91
+ base_list._entities = [entity]
92
+
93
+ result = await base_list.find(
94
+ idx=0, name="Test Entity", exclude_unavailable=False
95
+ )
96
+
97
+ assert result == entity
98
+
99
+
100
+ async def test_find_entity_name_mismatch() -> None:
101
+ """
102
+ Tests find with matching index but mismatched name.
103
+ """
104
+
105
+ base_list = TestBaseList(parent=MagicMock())
106
+ entity = MagicMock()
107
+ entity.index = 0
108
+ entity.subindex = 0
109
+ entity.name = "Test Entity"
110
+ entity.is_unavailable = False
111
+ base_list._entities = [entity]
112
+
113
+ result = await base_list.find(
114
+ idx=0, name="Wrong Name", exclude_unavailable=False
115
+ )
116
+
117
+ assert result is None
118
+
119
+
120
+ async def test_find_entity_not_found() -> None:
121
+ """
122
+ Tests find with non-existing index.
123
+ """
124
+
125
+ base_list = TestBaseList(parent=MagicMock())
126
+ base_list._entities = []
127
+
128
+ result = await base_list.find(
129
+ idx=0, name="Test Entity", exclude_unavailable=False
130
+ )
131
+
132
+ assert result is None
133
+
134
+
135
+ async def test_find_entity_unavailable_excluded() -> None:
136
+ """
137
+ Tests find with unavailable entity and exclude_unavailable=True.
138
+ """
139
+
140
+ base_list = TestBaseList(parent=MagicMock())
141
+ entity = MagicMock()
142
+ entity.index = 0
143
+ entity.subindex = 0
144
+ entity.name = "Test Entity"
145
+ entity.is_unavailable = True
146
+ base_list._entities = [entity]
147
+
148
+ result = await base_list.find(
149
+ idx=0, name="Test Entity", exclude_unavailable=True
150
+ )
151
+
152
+ assert result is None
153
+
154
+
155
+ async def test_find_entity_unavailable_not_excluded() -> None:
156
+ """
157
+ Tests find with unavailable entity and exclude_unavailable=False.
158
+ """
159
+
160
+ base_list = TestBaseList(parent=MagicMock())
161
+ entity = MagicMock()
162
+ entity.index = 0
163
+ entity.subindex = 0
164
+ entity.name = "Test Entity"
165
+ entity.is_unavailable = True
166
+ base_list._entities = [entity]
167
+
168
+ result = await base_list.find(
169
+ idx=0, name="Test Entity", exclude_unavailable=False
170
+ )
171
+
172
+ assert result == entity
173
+
174
+
175
+ async def test_find_entity_with_subindex() -> None:
176
+ """
177
+ Tests find with specific subindex.
178
+ """
179
+
180
+ base_list = TestBaseList(parent=MagicMock())
181
+ entity = MagicMock()
182
+ entity.index = 0
183
+ entity.subindex = 1
184
+ entity.name = "Test Entity"
185
+ entity.is_unavailable = False
186
+ base_list._entities = [entity]
187
+
188
+ result = await base_list.find(
189
+ idx=0, name="Test Entity", exclude_unavailable=False, subindex=1
190
+ )
191
+
192
+ assert result == entity
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes