pydiagral 1.4.0__py3-none-any.whl → 1.5.0b2__py3-none-any.whl
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.
- pydiagral/api.py +95 -60
- pydiagral/exceptions.py +2 -2
- pydiagral/models.py +25 -1
- pydiagral/utils.py +1 -1
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b2.dist-info}/METADATA +62 -20
- pydiagral-1.5.0b2.dist-info/RECORD +10 -0
- pydiagral-1.4.0.dist-info/RECORD +0 -10
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b2.dist-info}/WHEEL +0 -0
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b2.dist-info}/licenses/LICENSE +0 -0
pydiagral/api.py
CHANGED
@@ -7,7 +7,6 @@ retrieving system status, and controlling various aspects of the alarm system.
|
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
|
-
from datetime import datetime
|
11
10
|
import logging
|
12
11
|
import re
|
13
12
|
import time
|
@@ -37,11 +36,12 @@ from .models import (
|
|
37
36
|
Rudes,
|
38
37
|
SystemDetails,
|
39
38
|
SystemStatus,
|
39
|
+
TryConnectResult,
|
40
40
|
Webhook,
|
41
41
|
)
|
42
42
|
from .utils import generate_hmac_signature
|
43
43
|
|
44
|
-
_LOGGER = logging.getLogger(__name__)
|
44
|
+
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
45
45
|
|
46
46
|
# Minimum Python version: 3.10
|
47
47
|
|
@@ -84,17 +84,17 @@ class DiagralAPI:
|
|
84
84
|
or not self.__is_valid_email(username)
|
85
85
|
):
|
86
86
|
raise ConfigurationError("username must be a valid non-empty email address")
|
87
|
-
self.username = username
|
87
|
+
self.username: str = username
|
88
88
|
|
89
89
|
# Validate password
|
90
90
|
if not password or not isinstance(password, str):
|
91
91
|
raise ConfigurationError("password must be a non-empty string")
|
92
|
-
self.__password = password
|
92
|
+
self.__password: str = password
|
93
93
|
|
94
94
|
# Validate serial_id
|
95
95
|
if not serial_id or not isinstance(serial_id, str):
|
96
96
|
raise ConfigurationError("serial_id must be a non-empty string")
|
97
|
-
self.serial_id = serial_id
|
97
|
+
self.serial_id: str = serial_id
|
98
98
|
|
99
99
|
# Set apikey and secret_key
|
100
100
|
self.__apikey = apikey
|
@@ -104,12 +104,11 @@ class DiagralAPI:
|
|
104
104
|
if pincode is not None:
|
105
105
|
if not isinstance(pincode, int):
|
106
106
|
raise ConfigurationError("pincode must be an integer")
|
107
|
-
self.__pincode = pincode
|
107
|
+
self.__pincode: int | None = pincode
|
108
108
|
|
109
109
|
# Initialize session and access_token
|
110
110
|
self.session: aiohttp.ClientSession | None = None
|
111
111
|
self.__access_token: str | None = None
|
112
|
-
self.access_token_expires: datetime | None = None
|
113
112
|
|
114
113
|
# Set default values for other attributes
|
115
114
|
self.alarm_configuration: AlarmConfiguration | None = None
|
@@ -120,7 +119,7 @@ class DiagralAPI:
|
|
120
119
|
_LOGGER.info("Successfully initialized DiagralAPI session")
|
121
120
|
return self
|
122
121
|
|
123
|
-
async def __aexit__(self, exc_type, exc, tb):
|
122
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
124
123
|
"""Close the aiohttp ClientSession."""
|
125
124
|
if self.session:
|
126
125
|
await self.session.close()
|
@@ -156,8 +155,8 @@ class DiagralAPI:
|
|
156
155
|
_LOGGER.error(error_msg)
|
157
156
|
raise SessionError(error_msg)
|
158
157
|
|
159
|
-
url = f"{BASE_URL}/{API_VERSION}/{endpoint}"
|
160
|
-
headers = kwargs.pop("headers", {})
|
158
|
+
url: str = f"{BASE_URL}/{API_VERSION}/{endpoint}"
|
159
|
+
headers: Any = kwargs.pop("headers", {})
|
161
160
|
_LOGGER.debug(
|
162
161
|
"Sending %s request to %s with headers %s and data %s",
|
163
162
|
method,
|
@@ -170,7 +169,7 @@ class DiagralAPI:
|
|
170
169
|
async with self.session.request(
|
171
170
|
method, url, headers=headers, timeout=timeout, **kwargs
|
172
171
|
) as response:
|
173
|
-
response_data = await response.json()
|
172
|
+
response_data: Any = await response.json()
|
174
173
|
if response.status == 400:
|
175
174
|
response = HTTPErrorResponse(**response_data)
|
176
175
|
raise DiagralAPIError(
|
@@ -236,13 +235,13 @@ class DiagralAPI:
|
|
236
235
|
raise SessionError(error_msg)
|
237
236
|
|
238
237
|
_LOGGER.debug("Attempting to login to Diagral API")
|
239
|
-
_DATA = {"username": self.username, "password": self.__password}
|
238
|
+
_DATA: dict[str, str] = {"username": self.username, "password": self.__password}
|
240
239
|
try:
|
241
240
|
response_data, *_ = await self._request(
|
242
241
|
"POST", "users/authenticate/login?vendor=DIAGRAL", json=_DATA
|
243
242
|
)
|
244
243
|
_LOGGER.debug("Login Response data: %s", response_data)
|
245
|
-
login_response = LoginResponse.from_dict(response_data)
|
244
|
+
login_response: LoginResponse = LoginResponse.from_dict(response_data)
|
246
245
|
_LOGGER.debug("Login response: %s", login_response)
|
247
246
|
|
248
247
|
self.__access_token = login_response.access_token
|
@@ -253,16 +252,14 @@ class DiagralAPI:
|
|
253
252
|
|
254
253
|
_LOGGER.info("Successfully logged in to Diagral API")
|
255
254
|
except DiagralAPIError as e:
|
256
|
-
error_msg = f"Failed to login : {e!s}"
|
255
|
+
error_msg: str = f"Failed to login : {e!s}"
|
257
256
|
_LOGGER.error(error_msg)
|
258
257
|
raise AuthenticationError(error_msg) from e
|
259
258
|
|
260
259
|
async def set_apikey(self) -> ApiKeyWithSecret:
|
261
260
|
"""Asynchronously set the API key for the Diagral API.
|
262
261
|
|
263
|
-
|
264
|
-
If the access token is expired, it attempts to log in again to refresh it.
|
265
|
-
Then, it sends a request to create a new API key using the current access token.
|
262
|
+
It sends a request to create a new API key using the current access token.
|
266
263
|
If the API key is successfully created, it verifies the API key to ensure its validity.
|
267
264
|
|
268
265
|
Returns:
|
@@ -276,16 +273,8 @@ class DiagralAPI:
|
|
276
273
|
if not self.__access_token:
|
277
274
|
await self.login()
|
278
275
|
|
279
|
-
|
280
|
-
|
281
|
-
and self.access_token_expires
|
282
|
-
and datetime.now() >= self.access_token_expires
|
283
|
-
):
|
284
|
-
_LOGGER.warning("Access token has expired, attempting to login again")
|
285
|
-
await self.login()
|
286
|
-
|
287
|
-
_DATA = {"serial_id": self.serial_id}
|
288
|
-
_HEADERS = {
|
276
|
+
_DATA: dict[str, str] = {"serial_id": self.serial_id}
|
277
|
+
_HEADERS: dict[str, str] = {
|
289
278
|
"Authorization": f"Bearer {self.__access_token}",
|
290
279
|
}
|
291
280
|
|
@@ -293,7 +282,9 @@ class DiagralAPI:
|
|
293
282
|
response_data, *_ = await self._request(
|
294
283
|
"POST", "users/api_key", json=_DATA, headers=_HEADERS
|
295
284
|
)
|
296
|
-
set_apikey_response = ApiKeyWithSecret.from_dict(
|
285
|
+
set_apikey_response: ApiKeyWithSecret = ApiKeyWithSecret.from_dict(
|
286
|
+
response_data
|
287
|
+
)
|
297
288
|
self.__apikey = set_apikey_response.api_key
|
298
289
|
if not self.__apikey:
|
299
290
|
error_msg = "API key not found in response"
|
@@ -317,7 +308,7 @@ class DiagralAPI:
|
|
317
308
|
self.__apikey = None
|
318
309
|
raise
|
319
310
|
except DiagralAPIError as e:
|
320
|
-
error_msg = f"Failed to create API key: {e!s}"
|
311
|
+
error_msg: str = f"Failed to create API key: {e!s}"
|
321
312
|
_LOGGER.error(error_msg)
|
322
313
|
raise AuthenticationError(error_msg) from e
|
323
314
|
|
@@ -341,7 +332,7 @@ class DiagralAPI:
|
|
341
332
|
|
342
333
|
"""
|
343
334
|
|
344
|
-
apikey_to_validate = apikey or self.__apikey
|
335
|
+
apikey_to_validate: str = apikey or self.__apikey
|
345
336
|
|
346
337
|
if not apikey_to_validate:
|
347
338
|
_LOGGER.warning("No API key provided to validate")
|
@@ -350,7 +341,7 @@ class DiagralAPI:
|
|
350
341
|
if not self.__access_token:
|
351
342
|
await self.login()
|
352
343
|
|
353
|
-
_HEADERS = {
|
344
|
+
_HEADERS: dict[str, str] = {
|
354
345
|
"Authorization": f"Bearer {self.__access_token}",
|
355
346
|
}
|
356
347
|
response_data, *_ = await self._request(
|
@@ -358,7 +349,7 @@ class DiagralAPI:
|
|
358
349
|
f"users/systems/{self.serial_id}/api_keys",
|
359
350
|
headers=_HEADERS,
|
360
351
|
)
|
361
|
-
validate_apikey_response = ApiKeys.from_dict(response_data)
|
352
|
+
validate_apikey_response: ApiKeys = ApiKeys.from_dict(response_data)
|
362
353
|
is_valid = any(
|
363
354
|
key_info.api_key == apikey_to_validate
|
364
355
|
for key_info in validate_apikey_response.api_keys
|
@@ -387,7 +378,7 @@ class DiagralAPI:
|
|
387
378
|
|
388
379
|
"""
|
389
380
|
|
390
|
-
apikey_to_delete = apikey or self.__apikey
|
381
|
+
apikey_to_delete: str = apikey or self.__apikey
|
391
382
|
|
392
383
|
if not apikey_to_delete:
|
393
384
|
raise AuthenticationError("An API key is required to delete it")
|
@@ -395,7 +386,7 @@ class DiagralAPI:
|
|
395
386
|
if not self.__access_token:
|
396
387
|
await self.login()
|
397
388
|
|
398
|
-
_HEADERS = {
|
389
|
+
_HEADERS: dict[str, str] = {
|
399
390
|
"Authorization": f"Bearer {self.__access_token}",
|
400
391
|
}
|
401
392
|
await self._request(
|
@@ -409,6 +400,50 @@ class DiagralAPI:
|
|
409
400
|
self.__apikey = None
|
410
401
|
self.__secret_key = None
|
411
402
|
|
403
|
+
async def try_connection(self, ephemeral: bool = True) -> bool:
|
404
|
+
"""Test connection with the Diagral system.
|
405
|
+
|
406
|
+
This method tests the connection by either using provided API credentials or generating
|
407
|
+
temporary ones. It validates the connection by checking the system status.
|
408
|
+
|
409
|
+
Args:
|
410
|
+
ephemeral (bool, optional): If True, temporary API keys will be deleted after
|
411
|
+
connection test. Defaults to True.
|
412
|
+
|
413
|
+
Returns:
|
414
|
+
TryConnectResult: Object containing connection test results and optionally API keys
|
415
|
+
if non-ephemeral temporary keys were generated.
|
416
|
+
|
417
|
+
Raises:
|
418
|
+
DiagralAPIError: If connection attempt fails or system status check fails.
|
419
|
+
|
420
|
+
Note:
|
421
|
+
If API credentials are not provided during client initialization, temporary
|
422
|
+
keys will be generated (if ephemeral) for the connection test. These keys will be:
|
423
|
+
- Deleted after the test if ephemeral=True
|
424
|
+
- Returned in the result if ephemeral=False
|
425
|
+
|
426
|
+
"""
|
427
|
+
|
428
|
+
result: TryConnectResult = TryConnectResult()
|
429
|
+
api_keys_provided = bool(self.__apikey and self.__secret_key)
|
430
|
+
try:
|
431
|
+
# If API keys are not provided, generate temporary keys
|
432
|
+
if not api_keys_provided:
|
433
|
+
api_key_response: ApiKeyWithSecret = await self.set_apikey()
|
434
|
+
|
435
|
+
# Retrieve system status to validate connection
|
436
|
+
await self.get_system_status()
|
437
|
+
# If connection is successful, clean up temporary keys if requested (ephemeral)
|
438
|
+
if ephemeral and not api_keys_provided:
|
439
|
+
await self.delete_apikey(apikey=self.__apikey)
|
440
|
+
elif not ephemeral and not api_keys_provided:
|
441
|
+
result.keys = api_key_response
|
442
|
+
except DiagralAPIError as e:
|
443
|
+
raise DiagralAPIError(f"Failed to connect to the system: {e}") from e
|
444
|
+
result.result = True
|
445
|
+
return result
|
446
|
+
|
412
447
|
async def get_configuration(self) -> None:
|
413
448
|
"""Asynchronously retrieve the configuration of the Diagral system.
|
414
449
|
|
@@ -433,14 +468,14 @@ class DiagralAPI:
|
|
433
468
|
)
|
434
469
|
|
435
470
|
_TIMESTAMP = str(int(time.time()))
|
436
|
-
_HMAC = generate_hmac_signature(
|
471
|
+
_HMAC: str = generate_hmac_signature(
|
437
472
|
timestamp=_TIMESTAMP,
|
438
473
|
serial_id=self.serial_id,
|
439
474
|
api_key=self.__apikey,
|
440
475
|
secret_key=self.__secret_key,
|
441
476
|
)
|
442
477
|
|
443
|
-
_HEADERS = {
|
478
|
+
_HEADERS: dict[str, str] = {
|
444
479
|
"X-HMAC": _HMAC,
|
445
480
|
"X-TIMESTAMP": _TIMESTAMP,
|
446
481
|
"X-APIKEY": self.__apikey,
|
@@ -475,13 +510,13 @@ class DiagralAPI:
|
|
475
510
|
if not self.alarm_configuration:
|
476
511
|
raise ConfigurationError("Failed to retrieve alarm configuration")
|
477
512
|
|
478
|
-
device_types = sorted(
|
513
|
+
device_types: list[str] = sorted(
|
479
514
|
["cameras", "commands", "sensors", "sirens", "transmitters"]
|
480
515
|
)
|
481
516
|
devices_infos = {}
|
482
517
|
for device_type in device_types:
|
483
518
|
_LOGGER.debug("Retrieving devices information for %s", device_type)
|
484
|
-
devices = getattr(self.alarm_configuration, device_type, None)
|
519
|
+
devices: Any | None = getattr(self.alarm_configuration, device_type, None)
|
485
520
|
if devices is not None:
|
486
521
|
devices_infos[device_type] = [
|
487
522
|
{"index": device.index, "label": device.label} for device in devices
|
@@ -515,14 +550,14 @@ class DiagralAPI:
|
|
515
550
|
raise AuthenticationError("PIN code required to get system details")
|
516
551
|
|
517
552
|
_TIMESTAMP = str(int(time.time()))
|
518
|
-
_HMAC = generate_hmac_signature(
|
553
|
+
_HMAC: str = generate_hmac_signature(
|
519
554
|
timestamp=_TIMESTAMP,
|
520
555
|
serial_id=self.serial_id,
|
521
556
|
api_key=self.__apikey,
|
522
557
|
secret_key=self.__secret_key,
|
523
558
|
)
|
524
559
|
|
525
|
-
_HEADERS = {
|
560
|
+
_HEADERS: dict[str, str] = {
|
526
561
|
"X-PIN-CODE": str(self.__pincode),
|
527
562
|
"X-HMAC": _HMAC,
|
528
563
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -558,14 +593,14 @@ class DiagralAPI:
|
|
558
593
|
raise AuthenticationError("PIN code required to get system details")
|
559
594
|
|
560
595
|
_TIMESTAMP = str(int(time.time()))
|
561
|
-
_HMAC = generate_hmac_signature(
|
596
|
+
_HMAC: str = generate_hmac_signature(
|
562
597
|
timestamp=_TIMESTAMP,
|
563
598
|
serial_id=self.serial_id,
|
564
599
|
api_key=self.__apikey,
|
565
600
|
secret_key=self.__secret_key,
|
566
601
|
)
|
567
602
|
|
568
|
-
_HEADERS = {
|
603
|
+
_HEADERS: dict[str, str] = {
|
569
604
|
"X-PIN-CODE": str(self.__pincode),
|
570
605
|
"X-HMAC": _HMAC,
|
571
606
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -612,14 +647,14 @@ class DiagralAPI:
|
|
612
647
|
raise AuthenticationError(f"PIN code required to do system action {action}")
|
613
648
|
|
614
649
|
_TIMESTAMP = str(int(time.time()))
|
615
|
-
_HMAC = generate_hmac_signature(
|
650
|
+
_HMAC: str = generate_hmac_signature(
|
616
651
|
timestamp=_TIMESTAMP,
|
617
652
|
serial_id=self.serial_id,
|
618
653
|
api_key=self.__apikey,
|
619
654
|
secret_key=self.__secret_key,
|
620
655
|
)
|
621
656
|
|
622
|
-
_HEADERS = {
|
657
|
+
_HEADERS: dict[str, str] = {
|
623
658
|
"X-PIN-CODE": str(self.__pincode),
|
624
659
|
"X-HMAC": _HMAC,
|
625
660
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -736,7 +771,7 @@ class DiagralAPI:
|
|
736
771
|
await self.get_configuration()
|
737
772
|
|
738
773
|
# Check if the groups are valid
|
739
|
-
invalid_groups = [
|
774
|
+
invalid_groups: list[int] = [
|
740
775
|
group
|
741
776
|
for group in groups
|
742
777
|
if group not in [g.index for g in self.alarm_configuration.groups]
|
@@ -747,20 +782,20 @@ class DiagralAPI:
|
|
747
782
|
)
|
748
783
|
|
749
784
|
_TIMESTAMP = str(int(time.time()))
|
750
|
-
_HMAC = generate_hmac_signature(
|
785
|
+
_HMAC: str = generate_hmac_signature(
|
751
786
|
timestamp=_TIMESTAMP,
|
752
787
|
serial_id=self.serial_id,
|
753
788
|
api_key=self.__apikey,
|
754
789
|
secret_key=self.__secret_key,
|
755
790
|
)
|
756
791
|
|
757
|
-
_HEADERS = {
|
792
|
+
_HEADERS: dict[str, str] = {
|
758
793
|
"X-PIN-CODE": str(self.__pincode),
|
759
794
|
"X-HMAC": _HMAC,
|
760
795
|
"X-TIMESTAMP": _TIMESTAMP,
|
761
796
|
"X-APIKEY": self.__apikey,
|
762
797
|
}
|
763
|
-
data = {"groups": groups}
|
798
|
+
data: dict[str, list[int]] = {"groups": groups}
|
764
799
|
response_data, *_ = await self._request(
|
765
800
|
"POST",
|
766
801
|
f"systems/{self.serial_id}/{action}",
|
@@ -854,14 +889,14 @@ class DiagralAPI:
|
|
854
889
|
raise AuthenticationError("PIN code required to get system details")
|
855
890
|
|
856
891
|
_TIMESTAMP = str(int(time.time()))
|
857
|
-
_HMAC = generate_hmac_signature(
|
892
|
+
_HMAC: str = generate_hmac_signature(
|
858
893
|
timestamp=_TIMESTAMP,
|
859
894
|
serial_id=self.serial_id,
|
860
895
|
api_key=self.__apikey,
|
861
896
|
secret_key=self.__secret_key,
|
862
897
|
)
|
863
898
|
|
864
|
-
_HEADERS = {
|
899
|
+
_HEADERS: dict[str, str] = {
|
865
900
|
"X-PIN-CODE": str(self.__pincode),
|
866
901
|
"X-HMAC": _HMAC,
|
867
902
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -935,14 +970,14 @@ class DiagralAPI:
|
|
935
970
|
)
|
936
971
|
|
937
972
|
_TIMESTAMP = str(int(time.time()))
|
938
|
-
_HMAC = generate_hmac_signature(
|
973
|
+
_HMAC: str = generate_hmac_signature(
|
939
974
|
timestamp=_TIMESTAMP,
|
940
975
|
serial_id=self.serial_id,
|
941
976
|
api_key=self.__apikey,
|
942
977
|
secret_key=self.__secret_key,
|
943
978
|
)
|
944
979
|
|
945
|
-
_HEADERS = {
|
980
|
+
_HEADERS: dict[str, str] = {
|
946
981
|
"X-HMAC": _HMAC,
|
947
982
|
"X-TIMESTAMP": _TIMESTAMP,
|
948
983
|
"X-APIKEY": self.__apikey,
|
@@ -990,14 +1025,14 @@ class DiagralAPI:
|
|
990
1025
|
)
|
991
1026
|
|
992
1027
|
_TIMESTAMP = str(int(time.time()))
|
993
|
-
_HMAC = generate_hmac_signature(
|
1028
|
+
_HMAC: str = generate_hmac_signature(
|
994
1029
|
timestamp=_TIMESTAMP,
|
995
1030
|
serial_id=self.serial_id,
|
996
1031
|
api_key=self.__apikey,
|
997
1032
|
secret_key=self.__secret_key,
|
998
1033
|
)
|
999
1034
|
|
1000
|
-
_HEADERS = {
|
1035
|
+
_HEADERS: dict[str, str] = {
|
1001
1036
|
"X-HMAC": _HMAC,
|
1002
1037
|
"X-TIMESTAMP": _TIMESTAMP,
|
1003
1038
|
"X-APIKEY": self.__apikey,
|
@@ -1043,14 +1078,14 @@ class DiagralAPI:
|
|
1043
1078
|
)
|
1044
1079
|
|
1045
1080
|
_TIMESTAMP = str(int(time.time()))
|
1046
|
-
_HMAC = generate_hmac_signature(
|
1081
|
+
_HMAC: str = generate_hmac_signature(
|
1047
1082
|
timestamp=_TIMESTAMP,
|
1048
1083
|
serial_id=self.serial_id,
|
1049
1084
|
api_key=self.__apikey,
|
1050
1085
|
secret_key=self.__secret_key,
|
1051
1086
|
)
|
1052
1087
|
|
1053
|
-
_HEADERS = {
|
1088
|
+
_HEADERS: dict[str, str] = {
|
1054
1089
|
"X-HMAC": _HMAC,
|
1055
1090
|
"X-TIMESTAMP": _TIMESTAMP,
|
1056
1091
|
"X-APIKEY": self.__apikey,
|
@@ -1108,14 +1143,14 @@ class DiagralAPI:
|
|
1108
1143
|
)
|
1109
1144
|
|
1110
1145
|
_TIMESTAMP = str(int(time.time()))
|
1111
|
-
_HMAC = generate_hmac_signature(
|
1146
|
+
_HMAC: str = generate_hmac_signature(
|
1112
1147
|
timestamp=_TIMESTAMP,
|
1113
1148
|
serial_id=self.serial_id,
|
1114
1149
|
api_key=self.__apikey,
|
1115
1150
|
secret_key=self.__secret_key,
|
1116
1151
|
)
|
1117
1152
|
|
1118
|
-
_HEADERS = {
|
1153
|
+
_HEADERS: dict[str, str] = {
|
1119
1154
|
"X-HMAC": _HMAC,
|
1120
1155
|
"X-TIMESTAMP": _TIMESTAMP,
|
1121
1156
|
"X-APIKEY": self.__apikey,
|
@@ -1216,14 +1251,14 @@ class DiagralAPI:
|
|
1216
1251
|
)
|
1217
1252
|
|
1218
1253
|
_TIMESTAMP = str(int(time.time()))
|
1219
|
-
_HMAC = generate_hmac_signature(
|
1254
|
+
_HMAC: str = generate_hmac_signature(
|
1220
1255
|
timestamp=_TIMESTAMP,
|
1221
1256
|
serial_id=self.serial_id,
|
1222
1257
|
api_key=self.__apikey,
|
1223
1258
|
secret_key=self.__secret_key,
|
1224
1259
|
)
|
1225
1260
|
|
1226
|
-
_HEADERS = {
|
1261
|
+
_HEADERS: dict[str, str] = {
|
1227
1262
|
"X-HMAC": _HMAC,
|
1228
1263
|
"X-TIMESTAMP": _TIMESTAMP,
|
1229
1264
|
"X-APIKEY": self.__apikey,
|
pydiagral/exceptions.py
CHANGED
@@ -10,8 +10,8 @@ class DiagralAPIError(Exception):
|
|
10
10
|
:param message: The error message.
|
11
11
|
:param status_code: The status code of the error, if any.
|
12
12
|
"""
|
13
|
-
self.message = message
|
14
|
-
self.status_code = status_code
|
13
|
+
self.message: str = message
|
14
|
+
self.status_code: int | None = status_code
|
15
15
|
super().__init__(self.message)
|
16
16
|
|
17
17
|
|
pydiagral/models.py
CHANGED
@@ -15,7 +15,7 @@ import re
|
|
15
15
|
import types
|
16
16
|
from typing import TypeVar, Union, get_args, get_origin, get_type_hints
|
17
17
|
|
18
|
-
logger = logging.getLogger(__name__)
|
18
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
19
19
|
|
20
20
|
|
21
21
|
#######################################################
|
@@ -355,6 +355,30 @@ class ApiKeys(CamelCaseModel):
|
|
355
355
|
)
|
356
356
|
|
357
357
|
|
358
|
+
@dataclass
|
359
|
+
class TryConnectResult(CamelCaseModel):
|
360
|
+
"""A class representing the result of an API connection attempt.
|
361
|
+
|
362
|
+
This class is used to store the result of an API connection attempt
|
363
|
+
and the associated API keys if the connection was successful.
|
364
|
+
|
365
|
+
Attributes:
|
366
|
+
result (bool | None): Whether the connection attempt was successful. Defaults to False.
|
367
|
+
keys (ApiKeyWithSecret | None): The API keys associated with the successful connection. Defaults to None.
|
368
|
+
|
369
|
+
Example:
|
370
|
+
>>> result = TryConnectResult(result=True, keys=api_key_obj)
|
371
|
+
>>> print(result.result)
|
372
|
+
True
|
373
|
+
>>> print(result.keys)
|
374
|
+
ApiKeyWithSecret(api_key='abc123', api_secret='xyz789')
|
375
|
+
|
376
|
+
"""
|
377
|
+
|
378
|
+
result: bool | None = False
|
379
|
+
keys: ApiKeyWithSecret | None = None
|
380
|
+
|
381
|
+
|
358
382
|
#####################################
|
359
383
|
# Data models for alarm configuration
|
360
384
|
#####################################
|
pydiagral/utils.py
CHANGED
@@ -10,5 +10,5 @@ def generate_hmac_signature(
|
|
10
10
|
) -> str:
|
11
11
|
"""Generate an HMAC signature for the given parameters."""
|
12
12
|
timestamp = str(int(time.time()))
|
13
|
-
message = f"{timestamp}.{serial_id}.{api_key}"
|
13
|
+
message: str = f"{timestamp}.{serial_id}.{api_key}"
|
14
14
|
return hmac.new(secret_key.encode(), message.encode(), hashlib.sha256).hexdigest()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pydiagral
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.5.0b2
|
4
4
|
Summary: A Python library for interacting with Diagral systems
|
5
5
|
Project-URL: Homepage, https://github.com/mguyard/pydiagral
|
6
6
|
Project-URL: Documentation, https://github.com/mguyard/pydiagral
|
@@ -685,7 +685,7 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
685
685
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
686
686
|
License-File: LICENSE
|
687
687
|
Keywords: diagral,home automation,python
|
688
|
-
Classifier: Development Status ::
|
688
|
+
Classifier: Development Status :: 5 - Production/Stable
|
689
689
|
Classifier: Intended Audience :: Developers
|
690
690
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
691
691
|
Classifier: Operating System :: OS Independent
|
@@ -702,9 +702,6 @@ Description-Content-Type: text/markdown
|
|
702
702
|
<p align="center">
|
703
703
|
<img src="https://raw.githubusercontent.com/mguyard/pydiagral/main/docs/pydiagral-Logo.png" width="400" />
|
704
704
|
</p>
|
705
|
-
<p align="center">
|
706
|
-
<h1 align="center">PyDiagral</h1>
|
707
|
-
</p>
|
708
705
|
<p align="center">
|
709
706
|
A powerful and easy-to-use Python library for seamless integration with the Diagral alarm system.
|
710
707
|
</p>
|
@@ -729,19 +726,25 @@ Description-Content-Type: text/markdown
|
|
729
726
|
</p>
|
730
727
|
<br /><br />
|
731
728
|
|
732
|
-
#
|
729
|
+
# pydiagral
|
733
730
|
|
734
731
|
Welcome to the documentation for pydiagral, a Python library for interacting with the Diagral API.
|
735
732
|
|
736
|
-
## About pydiagral
|
733
|
+
## 📍 About pydiagral
|
737
734
|
|
738
|
-
pydiagral is an asynchronous Python interface for the Diagral alarm system. This library allows users to control and monitor their Diagral alarm system through the official API.
|
735
|
+
`pydiagral` is an asynchronous Python interface for the Diagral alarm system. This library allows users to control and monitor their Diagral alarm system through the official API.
|
739
736
|
|
740
|
-
|
737
|
+
> [!CAUTION]
|
738
|
+
>
|
739
|
+
> Please note that the Diagral alarm system is a security system, and it may be preferable not to connect it to any automation platform for security reasons.
|
740
|
+
> In no event shall the developer of [`pydiagral`](https://github.com/mguyard/pydiagral) library be held liable for any issues arising from the use of this [`pydiagral`](https://github.com/mguyard/pydiagral) library.
|
741
|
+
> The user installs and uses this integration at their own risk and with full knowledge of the potential implications.
|
742
|
+
|
743
|
+
## ✅ Requirement
|
741
744
|
|
742
745
|
To use this library, which leverages the Diagral APIs, you must have a Diagral box (DIAG56AAX). This box connects your Diagral alarm system to the internet, enabling interaction with the alarm system via the API. You can find more information about the Diagral box [here](https://www.diagral.fr/commande/box-alerte-et-pilotage).
|
743
746
|
|
744
|
-
## Key Features
|
747
|
+
## 📦 Key Features
|
745
748
|
|
746
749
|
The `DiagralAPI` class offers the following functionalities:
|
747
750
|
|
@@ -766,7 +769,7 @@ The `DiagralAPI` class offers the following functionalities:
|
|
766
769
|
- Activate or Desactivate system (partially or globally)
|
767
770
|
- Automatism actions
|
768
771
|
|
769
|
-
## Quick Start
|
772
|
+
## 🚀 Quick Start
|
770
773
|
|
771
774
|
To get started with pydiagral, follow these steps:
|
772
775
|
|
@@ -796,33 +799,72 @@ And run the [example_code.py](https://github.com/mguyard/pydiagral/blob/main/exa
|
|
796
799
|
>
|
797
800
|
> You can customize the actions performed by [example_code.py](https://github.com/mguyard/pydiagral/blob/main/example_code.py) by modifying the parameters in the code, as indicated by the `CUSTOMIZE THE TESTS` section title.
|
798
801
|
|
802
|
+
# 📖 Documentations
|
803
|
+
|
804
|
+
## Package Documentation
|
805
|
+
|
806
|
+
Library documentation is available [here](https://mguyard.github.io/pydiagral/).
|
807
|
+
|
808
|
+
### Package Structure
|
809
|
+
|
810
|
+
For detailed library documentation, please refer to the following sections:
|
811
|
+
|
812
|
+
- [API Reference](https://mguyard.github.io/pydiagral/api/): Comprehensive documentation of the DiagralAPI class and its methods
|
813
|
+
- [Data Models](https://mguyard.github.io/pydiagral/models/): Description of the data structures used
|
814
|
+
- [Exceptions](https://mguyard.github.io/pydiagral/exceptions/): List of package exceptions
|
815
|
+
|
799
816
|
## Diagral API Official documentation
|
800
817
|
|
801
818
|
Official Diagral API is available [here](https://appv3.tt-monitor.com/emerald/redoc).
|
802
819
|
|
820
|
+
# 🙋 FAQ
|
821
|
+
|
803
822
|
## How to find Serial on DIAG56AAX
|
804
823
|
|
805
824
|
The serial number can only be found with physical access to the box. You need to open it, and you will find a label with a QR Code.
|
825
|
+
|
806
826
|
On this label, there is a 15-character code that represents the serial number of the box.
|
807
827
|
|
808
|
-

|
828
|
+

|
809
829
|
|
810
830
|
> [!IMPORTANT]
|
811
831
|
>
|
812
832
|
> This code is necessary to use this library and Diagral API.
|
813
833
|
|
814
|
-
|
834
|
+
# 🤝 Contribution
|
835
|
+
|
836
|
+
Contributions are welcome! Here are several ways you can contribute:
|
837
|
+
|
838
|
+
- **Submit Pull Requests**: Review open PRs, and submit your own PRs.
|
839
|
+
- **Report Issues**: Submit bugs found or log feature requests.
|
815
840
|
|
816
|
-
|
841
|
+
<details closed>
|
842
|
+
<summary>Contributing Guidelines</summary>
|
817
843
|
|
818
|
-
|
819
|
-
|
820
|
-
|
844
|
+
1. **Fork the Repository**: Start by forking the project repository to your GitHub account.
|
845
|
+
2. **Clone Locally**: Clone the forked repository to your local machine using a Git client.
|
846
|
+
```sh
|
847
|
+
git clone https://github.com/mguyard/pydiagral
|
848
|
+
```
|
849
|
+
3. **Create a New Branch**: Always work on a new branch, giving it a descriptive name.
|
850
|
+
```sh
|
851
|
+
git checkout -b new-feature-x
|
852
|
+
```
|
853
|
+
4. **Make Your Changes**: Develop and test your changes locally.
|
854
|
+
5. **Commit Your Changes**: Commit your changes with a clear and concise message that follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines.
|
855
|
+
```sh
|
856
|
+
git commit -m 'feat: Implemented new feature x.'
|
857
|
+
```
|
858
|
+
6. **Push to GitHub**: Push the changes to your forked repository.
|
859
|
+
```sh
|
860
|
+
git push origin new-feature-x
|
861
|
+
```
|
862
|
+
7. **Submit a Pull Request**: Create a PR against the original project repository. Clearly describe the changes and their motivations.
|
821
863
|
|
822
|
-
|
864
|
+
Once your PR is reviewed and approved, it will be merged into the `beta` branch. After final testing, it will be merged into the `main` branch.
|
823
865
|
|
824
|
-
|
866
|
+
</details>
|
825
867
|
|
826
|
-
|
868
|
+
# 📄 License
|
827
869
|
|
828
870
|
pydiagral is distributed under the GPL-v3 License. See the [LICENSE](https://github.com/mguyard/pydiagral/blob/main/LICENSE) file for more details.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
pydiagral/__init__.py,sha256=4uM-RD2GQ6JYJkxu-D6wj3XpqfY5gN2hP8NF6WvRI9k,576
|
2
|
+
pydiagral/api.py,sha256=F00LgyVtayPtZOm9O5DNQ2gLCv36vpsK6gCJXxOZNRk,48736
|
3
|
+
pydiagral/constants.py,sha256=2B0TdKxQHA3cpIBxojo43bMW44wN9xKYsHbBRHWsaBk,119
|
4
|
+
pydiagral/exceptions.py,sha256=Q5wEpNtiykLs3Ck0W8r1IQAJek_omaQ3jpMOtiiwBUg,1030
|
5
|
+
pydiagral/models.py,sha256=vUjxuApjVaMtd7H6Iw5LarwABi30O4FfdObhZRbUNuc,54931
|
6
|
+
pydiagral/utils.py,sha256=-VxI-lNaC4bU1K4DSmWDhvbsS2bXv5FAGULGKBf1UMU,449
|
7
|
+
pydiagral-1.5.0b2.dist-info/METADATA,sha256=ngDa7KSkaHBpcc5EEqf0CS5IlpmQwNF_dvmPDQClfxs,48531
|
8
|
+
pydiagral-1.5.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
pydiagral-1.5.0b2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
+
pydiagral-1.5.0b2.dist-info/RECORD,,
|
pydiagral-1.4.0.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
pydiagral/__init__.py,sha256=4uM-RD2GQ6JYJkxu-D6wj3XpqfY5gN2hP8NF6WvRI9k,576
|
2
|
-
pydiagral/api.py,sha256=sX46Fct3Bwei5yKKQ5PnGDKl261BBqZ0CYMFUnXZ5Mw,46753
|
3
|
-
pydiagral/constants.py,sha256=2B0TdKxQHA3cpIBxojo43bMW44wN9xKYsHbBRHWsaBk,119
|
4
|
-
pydiagral/exceptions.py,sha256=PLo85XJ55q4_dzsyaMYXLIaX8Ws7HH6vDUWNWodzCpM,1013
|
5
|
-
pydiagral/models.py,sha256=shnAf7ojh6y5fGp3l5RbGdp4qpuEyOZI-0ePrWAg2l0,54120
|
6
|
-
pydiagral/utils.py,sha256=weccW18dseD_Ypwe4dKvNz6TlZPA7BQbf5l5qk3GXOg,444
|
7
|
-
pydiagral-1.4.0.dist-info/METADATA,sha256=uomaH_YBT9vnk7pplGjfSA4vnxvxq9jBVAsE5Wc2zAc,46568
|
8
|
-
pydiagral-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
pydiagral-1.4.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
-
pydiagral-1.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|