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.
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/PKG-INFO +1 -1
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/pyproject.toml +1 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/base_list.py +15 -10
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/SOURCES.txt +2 -1
- pyg90alarm-2.3.1/tests/unit/entities/test_base_list.py +192 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/CODEOWNERS +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/dependabot.yml +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.github/workflows/main.yml +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.gitignore +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.pylintrc +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/.readthedocs.yaml +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/LICENSE +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/MANIFEST.in +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/README.rst +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/.DS_Store +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/.gitignore +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/api-docs.rst +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/cloud-protocol.rst +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/conf.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/index.rst +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/local-protocol.rst +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/docs/requirements.txt +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/setup.cfg +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/setup.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/sonar-project.properties +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/alarm.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/callback.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/const.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/messages.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/notifications.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/cloud/protocol.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/const.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/base.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/devices.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/definitions/sensors.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/base_entity.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/device.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/device_list.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/sensor.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/entities/sensor_list.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/exceptions.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/base_cmd.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/config.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/discovery.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/history.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/host_info.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/host_status.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/notifications.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/paginated_cmd.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/paginated_result.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/targeted_discovery.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/local/user_data_crc.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/base.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/notifications/protocol.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm/py.typed +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/requires.txt +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/src/pyg90alarm.egg-info/top_level.txt +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/__init__.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/conftest.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/device_mock.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_alarm.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_base_commands.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_cloud_notifications.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_config.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_devices.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_discovery.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_history.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_local_notifications.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_paginated_commands.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tests/test_sensor.py +0 -0
- {pyg90alarm-2.3.0 → pyg90alarm-2.3.1}/tox.ini +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]]:
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|