pyg90alarm 1.17.1__tar.gz → 1.17.2__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-1.17.1 → pyg90alarm-1.17.2}/.github/workflows/main.yml +4 -2
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/PKG-INFO +1 -1
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/alarm.py +39 -27
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_alarm.py +51 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tox.ini +1 -6
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/.github/CODEOWNERS +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/.gitignore +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/.pylintrc +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/.readthedocs.yaml +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/LICENSE +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/MANIFEST.in +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/README.rst +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/.DS_Store +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/.gitignore +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/api-docs.rst +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/conf.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/index.rst +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/protocol.rst +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/docs/requirements.txt +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/pyproject.toml +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/setup.cfg +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/setup.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/sonar-project.properties +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/__init__.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/base_cmd.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/callback.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/config.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/const.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/definitions/__init__.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/definitions/sensors.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/device_notifications.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/discovery.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/entities/__init__.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/entities/device.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/entities/sensor.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/exceptions.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/history.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/host_info.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/host_status.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/paginated_cmd.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/paginated_result.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/py.typed +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/targeted_discovery.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm/user_data_crc.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm.egg-info/SOURCES.txt +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm.egg-info/requires.txt +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/src/pyg90alarm.egg-info/top_level.txt +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/__init__.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/conftest.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/device_mock.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_base_commands.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_discovery.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_history.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_notifications.py +0 -0
- {pyg90alarm-1.17.1 → pyg90alarm-1.17.2}/tests/test_paginated_commands.py +0 -0
|
@@ -75,6 +75,8 @@ jobs:
|
|
|
75
75
|
name: Publish to PyPi
|
|
76
76
|
runs-on: ubuntu-latest
|
|
77
77
|
needs: [tests]
|
|
78
|
+
permissions:
|
|
79
|
+
id-token: write # Required for trusted publishing
|
|
78
80
|
steps:
|
|
79
81
|
- name: Checkout the code
|
|
80
82
|
uses: actions/checkout@v3
|
|
@@ -94,8 +96,8 @@ jobs:
|
|
|
94
96
|
if: github.event_name != 'release'
|
|
95
97
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
96
98
|
with:
|
|
97
|
-
password: ${{ secrets.TEST_PYPI_TOKEN }}
|
|
98
99
|
repository_url: https://test.pypi.org/legacy/
|
|
100
|
+
attestations: true
|
|
99
101
|
- name: Publish the release to PyPi
|
|
100
102
|
# Publish to production PyPi only happens when a release published out
|
|
101
103
|
# of the main branch
|
|
@@ -106,4 +108,4 @@ jobs:
|
|
|
106
108
|
|| github.event.release.target_commitish == 'master')
|
|
107
109
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
108
110
|
with:
|
|
109
|
-
|
|
111
|
+
attestations: true
|
|
@@ -161,7 +161,9 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
161
161
|
self._host: str = host
|
|
162
162
|
self._port: int = port
|
|
163
163
|
self._sensors: List[G90Sensor] = []
|
|
164
|
+
self._sensors_lock = asyncio.Lock()
|
|
164
165
|
self._devices: List[G90Device] = []
|
|
166
|
+
self._devices_lock = asyncio.Lock()
|
|
165
167
|
self._notifications: Optional[G90DeviceNotifications] = None
|
|
166
168
|
self._sensor_cb: Optional[SensorCallback] = None
|
|
167
169
|
self._armdisarm_cb: Optional[ArmDisarmCallback] = None
|
|
@@ -258,20 +260,26 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
258
260
|
|
|
259
261
|
:return: List of sensors
|
|
260
262
|
"""
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
proto_idx=sensor.proto_idx
|
|
263
|
+
# Use lock around the operation, to ensure no duplicated entries in the
|
|
264
|
+
# resulting list or redundant exchanges with panel are made when the
|
|
265
|
+
# method is called concurrently
|
|
266
|
+
async with self._sensors_lock:
|
|
267
|
+
if not self._sensors:
|
|
268
|
+
sensors = self.paginated_result(
|
|
269
|
+
G90Commands.GETSENSORLIST
|
|
269
270
|
)
|
|
270
|
-
|
|
271
|
+
async for sensor in sensors:
|
|
272
|
+
obj = G90Sensor(
|
|
273
|
+
*sensor.data, parent=self, subindex=0,
|
|
274
|
+
proto_idx=sensor.proto_idx
|
|
275
|
+
)
|
|
276
|
+
self._sensors.append(obj)
|
|
271
277
|
|
|
272
|
-
|
|
278
|
+
_LOGGER.debug(
|
|
279
|
+
'Total number of sensors: %s', len(self._sensors)
|
|
280
|
+
)
|
|
273
281
|
|
|
274
|
-
|
|
282
|
+
return self._sensors
|
|
275
283
|
|
|
276
284
|
async def find_sensor(self, idx: int, name: str) -> Optional[G90Sensor]:
|
|
277
285
|
"""
|
|
@@ -316,28 +324,32 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
316
324
|
|
|
317
325
|
:return: List of devices
|
|
318
326
|
"""
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
obj = G90Device(
|
|
325
|
-
*device.data, parent=self, subindex=0,
|
|
326
|
-
proto_idx=device.proto_idx
|
|
327
|
+
# See `get_sensors` method for the rationale behind the lock usage
|
|
328
|
+
async with self._devices_lock:
|
|
329
|
+
if not self._devices:
|
|
330
|
+
devices = self.paginated_result(
|
|
331
|
+
G90Commands.GETDEVICELIST
|
|
327
332
|
)
|
|
328
|
-
|
|
329
|
-
# Multi-node devices (first node has already been added
|
|
330
|
-
# above
|
|
331
|
-
for node in range(1, obj.node_count):
|
|
333
|
+
async for device in devices:
|
|
332
334
|
obj = G90Device(
|
|
333
|
-
*device.data, parent=self,
|
|
334
|
-
|
|
335
|
+
*device.data, parent=self, subindex=0,
|
|
336
|
+
proto_idx=device.proto_idx
|
|
335
337
|
)
|
|
336
338
|
self._devices.append(obj)
|
|
339
|
+
# Multi-node devices (first node has already been added
|
|
340
|
+
# above
|
|
341
|
+
for node in range(1, obj.node_count):
|
|
342
|
+
obj = G90Device(
|
|
343
|
+
*device.data, parent=self,
|
|
344
|
+
subindex=node, proto_idx=device.proto_idx
|
|
345
|
+
)
|
|
346
|
+
self._devices.append(obj)
|
|
337
347
|
|
|
338
|
-
|
|
348
|
+
_LOGGER.debug(
|
|
349
|
+
'Total number of devices: %s', len(self._devices)
|
|
350
|
+
)
|
|
339
351
|
|
|
340
|
-
|
|
352
|
+
return self._devices
|
|
341
353
|
|
|
342
354
|
@property
|
|
343
355
|
async def host_info(self) -> G90HostInfo:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Tests for G90Alarm class
|
|
3
3
|
"""
|
|
4
4
|
import asyncio
|
|
5
|
+
from itertools import cycle
|
|
5
6
|
from unittest.mock import MagicMock
|
|
6
7
|
import pytest
|
|
7
8
|
|
|
@@ -128,6 +129,35 @@ async def test_devices(mock_device: DeviceMock) -> None:
|
|
|
128
129
|
assert isinstance(devices[0]._asdict(), dict)
|
|
129
130
|
|
|
130
131
|
|
|
132
|
+
# Provide an endless sequence of simulated panel responses for the devices
|
|
133
|
+
# list. Each attempt will simulate a single device. This sequence will prevent
|
|
134
|
+
# `G90TimeoutError` if the code under test initiates more exchanges with the
|
|
135
|
+
# panel than the simulated data contains.
|
|
136
|
+
@pytest.mark.g90device(sent_data=cycle([
|
|
137
|
+
b'ISTART[138,'
|
|
138
|
+
b'[[1,1,1],["Switch",10,0,10,1,0,32,0,0,16,1,0,""]]]IEND\0',
|
|
139
|
+
]))
|
|
140
|
+
async def test_get_devices_concurrent(mock_device: DeviceMock) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Tests for concurrently retrieving list of devices produces consistent
|
|
143
|
+
results.
|
|
144
|
+
"""
|
|
145
|
+
g90 = G90Alarm(host=mock_device.host, port=mock_device.port)
|
|
146
|
+
g90.paginated_result = MagicMock( # type: ignore[method-assign]
|
|
147
|
+
spec=g90.paginated_result, wraps=g90.paginated_result
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Issue two concurrent requests to retrieve devices
|
|
151
|
+
res = await asyncio.gather(g90.get_devices(), g90.get_devices())
|
|
152
|
+
# Ensure only single exchange with the panel
|
|
153
|
+
g90.paginated_result.assert_called_once()
|
|
154
|
+
# While `pylint` demands use of generator, the comprehension is used
|
|
155
|
+
# instead for ease of trroubleshooting test failures as it will show the
|
|
156
|
+
# list elements, not just generator instance
|
|
157
|
+
# pylint: disable=use-a-generator
|
|
158
|
+
assert all([len(x) == 1 for x in res])
|
|
159
|
+
|
|
160
|
+
|
|
131
161
|
@pytest.mark.g90device(sent_data=[
|
|
132
162
|
b'ISTART[138,'
|
|
133
163
|
b'[[1,1,1],["Switch",10,0,10,1,0,32,0,0,16,2,0,""]]]IEND\0'
|
|
@@ -207,6 +237,27 @@ async def test_single_sensor(mock_device: DeviceMock) -> None:
|
|
|
207
237
|
assert isinstance(sensors[0]._asdict(), dict)
|
|
208
238
|
|
|
209
239
|
|
|
240
|
+
# See `test_get_devices_concurrent` for the explanation of the test
|
|
241
|
+
@pytest.mark.g90device(sent_data=cycle([
|
|
242
|
+
b'ISTART[102,'
|
|
243
|
+
b'[[1,1,1],["Remote",10,0,10,1,0,32,0,0,16,1,0,""]]]IEND\0',
|
|
244
|
+
]))
|
|
245
|
+
async def test_get_sensors_concurrent(mock_device: DeviceMock) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Tests for concurrently retrieving list of sensors produces consistent
|
|
248
|
+
results.
|
|
249
|
+
"""
|
|
250
|
+
g90 = G90Alarm(host=mock_device.host, port=mock_device.port)
|
|
251
|
+
g90.paginated_result = MagicMock( # type: ignore[method-assign]
|
|
252
|
+
spec=g90.paginated_result, wraps=g90.paginated_result
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
res = await asyncio.gather(g90.get_sensors(), g90.get_sensors())
|
|
256
|
+
g90.paginated_result.assert_called_once()
|
|
257
|
+
# pylint: disable=use-a-generator
|
|
258
|
+
assert all([len(x) == 1 for x in res])
|
|
259
|
+
|
|
260
|
+
|
|
210
261
|
@pytest.mark.g90device(sent_data=[
|
|
211
262
|
b'ISTART[102,'
|
|
212
263
|
b'[[3,1,3],["Remote 1",10,0,10,1,0,32,0,0,16,1,0,""],'
|
|
@@ -16,19 +16,14 @@ isolated_build = true
|
|
|
16
16
|
deps =
|
|
17
17
|
-r requirements_dev.txt
|
|
18
18
|
|
|
19
|
-
allowlist_externals =
|
|
20
|
-
cat
|
|
21
19
|
commands =
|
|
22
20
|
check-manifest --ignore 'tox.ini,tests/**,docs/**,.pylintrc,.readthedocs.yaml,sonar-project.properties'
|
|
23
21
|
flake8 --tee --output-file=flake8.txt src/pyg90alarm/ tests/
|
|
24
|
-
pylint --output-format=parseable
|
|
22
|
+
pylint --output-format=text,parseable:pylint.txt src/pyg90alarm/ tests/
|
|
25
23
|
mypy --strict --cobertura-xml-report=mypy/ src/pyg90alarm/ tests/
|
|
26
24
|
# Ensure only traces for in-repository module is processed, not for one
|
|
27
25
|
# installed by `tox` (see above for more details)
|
|
28
26
|
pytest --cov=src/pyg90alarm --cov-append --cov-report=term-missing -v tests []
|
|
29
|
-
commands_post =
|
|
30
|
-
# Show the `pylint` report to the standard output, to ease fixing the issues reported
|
|
31
|
-
cat pylint.txt
|
|
32
27
|
|
|
33
28
|
[flake8]
|
|
34
29
|
exclude = .tox,*.egg,build,data,scripts,docs
|
|
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
|