pydiagral 1.4.0__py3-none-any.whl → 1.5.0b1__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 +94 -60
- pydiagral/exceptions.py +2 -2
- pydiagral/models.py +1 -1
- pydiagral/utils.py +1 -1
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b1.dist-info}/METADATA +62 -20
- pydiagral-1.5.0b1.dist-info/RECORD +10 -0
- pydiagral-1.4.0.dist-info/RECORD +0 -10
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b1.dist-info}/WHEEL +0 -0
- {pydiagral-1.4.0.dist-info → pydiagral-1.5.0b1.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
|
@@ -41,7 +40,7 @@ from .models import (
|
|
41
40
|
)
|
42
41
|
from .utils import generate_hmac_signature
|
43
42
|
|
44
|
-
_LOGGER = logging.getLogger(__name__)
|
43
|
+
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
45
44
|
|
46
45
|
# Minimum Python version: 3.10
|
47
46
|
|
@@ -84,17 +83,17 @@ class DiagralAPI:
|
|
84
83
|
or not self.__is_valid_email(username)
|
85
84
|
):
|
86
85
|
raise ConfigurationError("username must be a valid non-empty email address")
|
87
|
-
self.username = username
|
86
|
+
self.username: str = username
|
88
87
|
|
89
88
|
# Validate password
|
90
89
|
if not password or not isinstance(password, str):
|
91
90
|
raise ConfigurationError("password must be a non-empty string")
|
92
|
-
self.__password = password
|
91
|
+
self.__password: str = password
|
93
92
|
|
94
93
|
# Validate serial_id
|
95
94
|
if not serial_id or not isinstance(serial_id, str):
|
96
95
|
raise ConfigurationError("serial_id must be a non-empty string")
|
97
|
-
self.serial_id = serial_id
|
96
|
+
self.serial_id: str = serial_id
|
98
97
|
|
99
98
|
# Set apikey and secret_key
|
100
99
|
self.__apikey = apikey
|
@@ -104,12 +103,11 @@ class DiagralAPI:
|
|
104
103
|
if pincode is not None:
|
105
104
|
if not isinstance(pincode, int):
|
106
105
|
raise ConfigurationError("pincode must be an integer")
|
107
|
-
self.__pincode = pincode
|
106
|
+
self.__pincode: int | None = pincode
|
108
107
|
|
109
108
|
# Initialize session and access_token
|
110
109
|
self.session: aiohttp.ClientSession | None = None
|
111
110
|
self.__access_token: str | None = None
|
112
|
-
self.access_token_expires: datetime | None = None
|
113
111
|
|
114
112
|
# Set default values for other attributes
|
115
113
|
self.alarm_configuration: AlarmConfiguration | None = None
|
@@ -120,7 +118,7 @@ class DiagralAPI:
|
|
120
118
|
_LOGGER.info("Successfully initialized DiagralAPI session")
|
121
119
|
return self
|
122
120
|
|
123
|
-
async def __aexit__(self, exc_type, exc, tb):
|
121
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
124
122
|
"""Close the aiohttp ClientSession."""
|
125
123
|
if self.session:
|
126
124
|
await self.session.close()
|
@@ -156,8 +154,8 @@ class DiagralAPI:
|
|
156
154
|
_LOGGER.error(error_msg)
|
157
155
|
raise SessionError(error_msg)
|
158
156
|
|
159
|
-
url = f"{BASE_URL}/{API_VERSION}/{endpoint}"
|
160
|
-
headers = kwargs.pop("headers", {})
|
157
|
+
url: str = f"{BASE_URL}/{API_VERSION}/{endpoint}"
|
158
|
+
headers: Any = kwargs.pop("headers", {})
|
161
159
|
_LOGGER.debug(
|
162
160
|
"Sending %s request to %s with headers %s and data %s",
|
163
161
|
method,
|
@@ -170,7 +168,7 @@ class DiagralAPI:
|
|
170
168
|
async with self.session.request(
|
171
169
|
method, url, headers=headers, timeout=timeout, **kwargs
|
172
170
|
) as response:
|
173
|
-
response_data = await response.json()
|
171
|
+
response_data: Any = await response.json()
|
174
172
|
if response.status == 400:
|
175
173
|
response = HTTPErrorResponse(**response_data)
|
176
174
|
raise DiagralAPIError(
|
@@ -236,13 +234,13 @@ class DiagralAPI:
|
|
236
234
|
raise SessionError(error_msg)
|
237
235
|
|
238
236
|
_LOGGER.debug("Attempting to login to Diagral API")
|
239
|
-
_DATA = {"username": self.username, "password": self.__password}
|
237
|
+
_DATA: dict[str, str] = {"username": self.username, "password": self.__password}
|
240
238
|
try:
|
241
239
|
response_data, *_ = await self._request(
|
242
240
|
"POST", "users/authenticate/login?vendor=DIAGRAL", json=_DATA
|
243
241
|
)
|
244
242
|
_LOGGER.debug("Login Response data: %s", response_data)
|
245
|
-
login_response = LoginResponse.from_dict(response_data)
|
243
|
+
login_response: LoginResponse = LoginResponse.from_dict(response_data)
|
246
244
|
_LOGGER.debug("Login response: %s", login_response)
|
247
245
|
|
248
246
|
self.__access_token = login_response.access_token
|
@@ -253,16 +251,14 @@ class DiagralAPI:
|
|
253
251
|
|
254
252
|
_LOGGER.info("Successfully logged in to Diagral API")
|
255
253
|
except DiagralAPIError as e:
|
256
|
-
error_msg = f"Failed to login : {e!s}"
|
254
|
+
error_msg: str = f"Failed to login : {e!s}"
|
257
255
|
_LOGGER.error(error_msg)
|
258
256
|
raise AuthenticationError(error_msg) from e
|
259
257
|
|
260
258
|
async def set_apikey(self) -> ApiKeyWithSecret:
|
261
259
|
"""Asynchronously set the API key for the Diagral API.
|
262
260
|
|
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.
|
261
|
+
It sends a request to create a new API key using the current access token.
|
266
262
|
If the API key is successfully created, it verifies the API key to ensure its validity.
|
267
263
|
|
268
264
|
Returns:
|
@@ -276,16 +272,8 @@ class DiagralAPI:
|
|
276
272
|
if not self.__access_token:
|
277
273
|
await self.login()
|
278
274
|
|
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 = {
|
275
|
+
_DATA: dict[str, str] = {"serial_id": self.serial_id}
|
276
|
+
_HEADERS: dict[str, str] = {
|
289
277
|
"Authorization": f"Bearer {self.__access_token}",
|
290
278
|
}
|
291
279
|
|
@@ -293,7 +281,9 @@ class DiagralAPI:
|
|
293
281
|
response_data, *_ = await self._request(
|
294
282
|
"POST", "users/api_key", json=_DATA, headers=_HEADERS
|
295
283
|
)
|
296
|
-
set_apikey_response = ApiKeyWithSecret.from_dict(
|
284
|
+
set_apikey_response: ApiKeyWithSecret = ApiKeyWithSecret.from_dict(
|
285
|
+
response_data
|
286
|
+
)
|
297
287
|
self.__apikey = set_apikey_response.api_key
|
298
288
|
if not self.__apikey:
|
299
289
|
error_msg = "API key not found in response"
|
@@ -317,7 +307,7 @@ class DiagralAPI:
|
|
317
307
|
self.__apikey = None
|
318
308
|
raise
|
319
309
|
except DiagralAPIError as e:
|
320
|
-
error_msg = f"Failed to create API key: {e!s}"
|
310
|
+
error_msg: str = f"Failed to create API key: {e!s}"
|
321
311
|
_LOGGER.error(error_msg)
|
322
312
|
raise AuthenticationError(error_msg) from e
|
323
313
|
|
@@ -341,7 +331,7 @@ class DiagralAPI:
|
|
341
331
|
|
342
332
|
"""
|
343
333
|
|
344
|
-
apikey_to_validate = apikey or self.__apikey
|
334
|
+
apikey_to_validate: str = apikey or self.__apikey
|
345
335
|
|
346
336
|
if not apikey_to_validate:
|
347
337
|
_LOGGER.warning("No API key provided to validate")
|
@@ -350,7 +340,7 @@ class DiagralAPI:
|
|
350
340
|
if not self.__access_token:
|
351
341
|
await self.login()
|
352
342
|
|
353
|
-
_HEADERS = {
|
343
|
+
_HEADERS: dict[str, str] = {
|
354
344
|
"Authorization": f"Bearer {self.__access_token}",
|
355
345
|
}
|
356
346
|
response_data, *_ = await self._request(
|
@@ -358,7 +348,7 @@ class DiagralAPI:
|
|
358
348
|
f"users/systems/{self.serial_id}/api_keys",
|
359
349
|
headers=_HEADERS,
|
360
350
|
)
|
361
|
-
validate_apikey_response = ApiKeys.from_dict(response_data)
|
351
|
+
validate_apikey_response: ApiKeys = ApiKeys.from_dict(response_data)
|
362
352
|
is_valid = any(
|
363
353
|
key_info.api_key == apikey_to_validate
|
364
354
|
for key_info in validate_apikey_response.api_keys
|
@@ -387,7 +377,7 @@ class DiagralAPI:
|
|
387
377
|
|
388
378
|
"""
|
389
379
|
|
390
|
-
apikey_to_delete = apikey or self.__apikey
|
380
|
+
apikey_to_delete: str = apikey or self.__apikey
|
391
381
|
|
392
382
|
if not apikey_to_delete:
|
393
383
|
raise AuthenticationError("An API key is required to delete it")
|
@@ -395,7 +385,7 @@ class DiagralAPI:
|
|
395
385
|
if not self.__access_token:
|
396
386
|
await self.login()
|
397
387
|
|
398
|
-
_HEADERS = {
|
388
|
+
_HEADERS: dict[str, str] = {
|
399
389
|
"Authorization": f"Bearer {self.__access_token}",
|
400
390
|
}
|
401
391
|
await self._request(
|
@@ -409,6 +399,50 @@ class DiagralAPI:
|
|
409
399
|
self.__apikey = None
|
410
400
|
self.__secret_key = None
|
411
401
|
|
402
|
+
async def try_connection(self, ephemeral: bool = True) -> bool:
|
403
|
+
"""Establish a connection with the Diagral system.
|
404
|
+
|
405
|
+
This method tries to connect to the Diagral API by:
|
406
|
+
1. Checking if API keys are provided
|
407
|
+
2. If not, generating temporary API keys
|
408
|
+
3. Validating the connection by checking system status
|
409
|
+
4. Optionally cleaning up temporary keys if requested
|
410
|
+
|
411
|
+
Args:
|
412
|
+
ephemeral (bool, optional): If True and using temporary API keys,
|
413
|
+
deletes them after validation. Defaults to True.
|
414
|
+
|
415
|
+
Returns:
|
416
|
+
bool: True if connection is successful
|
417
|
+
|
418
|
+
Raises:
|
419
|
+
DiagralAPIError: If connection fails or system status check fails
|
420
|
+
|
421
|
+
"""
|
422
|
+
|
423
|
+
api_keys_provided = bool(self.__apikey and self.__secret_key)
|
424
|
+
_LOGGER.warning("API keys provided: %s", api_keys_provided)
|
425
|
+
try:
|
426
|
+
if not api_keys_provided:
|
427
|
+
api_key_response: ApiKeyWithSecret = await self.set_apikey()
|
428
|
+
_LOGGER.debug(
|
429
|
+
"TEST CONNECTION - Successfully created temporary API key : %s",
|
430
|
+
api_key_response,
|
431
|
+
)
|
432
|
+
if await self.validate_apikey(apikey=api_key_response.api_key):
|
433
|
+
_LOGGER.debug(
|
434
|
+
"TEST CONNECTION - Successfully validated temporary API key"
|
435
|
+
)
|
436
|
+
self.__apikey: str = api_key_response.api_key
|
437
|
+
self.__secret_key: str = api_key_response.secret_key
|
438
|
+
|
439
|
+
await self.get_system_status()
|
440
|
+
if ephemeral and not api_keys_provided:
|
441
|
+
await self.delete_apikey(apikey=self.__apikey)
|
442
|
+
except DiagralAPIError as e:
|
443
|
+
raise DiagralAPIError(f"Failed to connect to the system: {e}") from e
|
444
|
+
return True
|
445
|
+
|
412
446
|
async def get_configuration(self) -> None:
|
413
447
|
"""Asynchronously retrieve the configuration of the Diagral system.
|
414
448
|
|
@@ -433,14 +467,14 @@ class DiagralAPI:
|
|
433
467
|
)
|
434
468
|
|
435
469
|
_TIMESTAMP = str(int(time.time()))
|
436
|
-
_HMAC = generate_hmac_signature(
|
470
|
+
_HMAC: str = generate_hmac_signature(
|
437
471
|
timestamp=_TIMESTAMP,
|
438
472
|
serial_id=self.serial_id,
|
439
473
|
api_key=self.__apikey,
|
440
474
|
secret_key=self.__secret_key,
|
441
475
|
)
|
442
476
|
|
443
|
-
_HEADERS = {
|
477
|
+
_HEADERS: dict[str, str] = {
|
444
478
|
"X-HMAC": _HMAC,
|
445
479
|
"X-TIMESTAMP": _TIMESTAMP,
|
446
480
|
"X-APIKEY": self.__apikey,
|
@@ -475,13 +509,13 @@ class DiagralAPI:
|
|
475
509
|
if not self.alarm_configuration:
|
476
510
|
raise ConfigurationError("Failed to retrieve alarm configuration")
|
477
511
|
|
478
|
-
device_types = sorted(
|
512
|
+
device_types: list[str] = sorted(
|
479
513
|
["cameras", "commands", "sensors", "sirens", "transmitters"]
|
480
514
|
)
|
481
515
|
devices_infos = {}
|
482
516
|
for device_type in device_types:
|
483
517
|
_LOGGER.debug("Retrieving devices information for %s", device_type)
|
484
|
-
devices = getattr(self.alarm_configuration, device_type, None)
|
518
|
+
devices: Any | None = getattr(self.alarm_configuration, device_type, None)
|
485
519
|
if devices is not None:
|
486
520
|
devices_infos[device_type] = [
|
487
521
|
{"index": device.index, "label": device.label} for device in devices
|
@@ -515,14 +549,14 @@ class DiagralAPI:
|
|
515
549
|
raise AuthenticationError("PIN code required to get system details")
|
516
550
|
|
517
551
|
_TIMESTAMP = str(int(time.time()))
|
518
|
-
_HMAC = generate_hmac_signature(
|
552
|
+
_HMAC: str = generate_hmac_signature(
|
519
553
|
timestamp=_TIMESTAMP,
|
520
554
|
serial_id=self.serial_id,
|
521
555
|
api_key=self.__apikey,
|
522
556
|
secret_key=self.__secret_key,
|
523
557
|
)
|
524
558
|
|
525
|
-
_HEADERS = {
|
559
|
+
_HEADERS: dict[str, str] = {
|
526
560
|
"X-PIN-CODE": str(self.__pincode),
|
527
561
|
"X-HMAC": _HMAC,
|
528
562
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -558,14 +592,14 @@ class DiagralAPI:
|
|
558
592
|
raise AuthenticationError("PIN code required to get system details")
|
559
593
|
|
560
594
|
_TIMESTAMP = str(int(time.time()))
|
561
|
-
_HMAC = generate_hmac_signature(
|
595
|
+
_HMAC: str = generate_hmac_signature(
|
562
596
|
timestamp=_TIMESTAMP,
|
563
597
|
serial_id=self.serial_id,
|
564
598
|
api_key=self.__apikey,
|
565
599
|
secret_key=self.__secret_key,
|
566
600
|
)
|
567
601
|
|
568
|
-
_HEADERS = {
|
602
|
+
_HEADERS: dict[str, str] = {
|
569
603
|
"X-PIN-CODE": str(self.__pincode),
|
570
604
|
"X-HMAC": _HMAC,
|
571
605
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -612,14 +646,14 @@ class DiagralAPI:
|
|
612
646
|
raise AuthenticationError(f"PIN code required to do system action {action}")
|
613
647
|
|
614
648
|
_TIMESTAMP = str(int(time.time()))
|
615
|
-
_HMAC = generate_hmac_signature(
|
649
|
+
_HMAC: str = generate_hmac_signature(
|
616
650
|
timestamp=_TIMESTAMP,
|
617
651
|
serial_id=self.serial_id,
|
618
652
|
api_key=self.__apikey,
|
619
653
|
secret_key=self.__secret_key,
|
620
654
|
)
|
621
655
|
|
622
|
-
_HEADERS = {
|
656
|
+
_HEADERS: dict[str, str] = {
|
623
657
|
"X-PIN-CODE": str(self.__pincode),
|
624
658
|
"X-HMAC": _HMAC,
|
625
659
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -736,7 +770,7 @@ class DiagralAPI:
|
|
736
770
|
await self.get_configuration()
|
737
771
|
|
738
772
|
# Check if the groups are valid
|
739
|
-
invalid_groups = [
|
773
|
+
invalid_groups: list[int] = [
|
740
774
|
group
|
741
775
|
for group in groups
|
742
776
|
if group not in [g.index for g in self.alarm_configuration.groups]
|
@@ -747,20 +781,20 @@ class DiagralAPI:
|
|
747
781
|
)
|
748
782
|
|
749
783
|
_TIMESTAMP = str(int(time.time()))
|
750
|
-
_HMAC = generate_hmac_signature(
|
784
|
+
_HMAC: str = generate_hmac_signature(
|
751
785
|
timestamp=_TIMESTAMP,
|
752
786
|
serial_id=self.serial_id,
|
753
787
|
api_key=self.__apikey,
|
754
788
|
secret_key=self.__secret_key,
|
755
789
|
)
|
756
790
|
|
757
|
-
_HEADERS = {
|
791
|
+
_HEADERS: dict[str, str] = {
|
758
792
|
"X-PIN-CODE": str(self.__pincode),
|
759
793
|
"X-HMAC": _HMAC,
|
760
794
|
"X-TIMESTAMP": _TIMESTAMP,
|
761
795
|
"X-APIKEY": self.__apikey,
|
762
796
|
}
|
763
|
-
data = {"groups": groups}
|
797
|
+
data: dict[str, list[int]] = {"groups": groups}
|
764
798
|
response_data, *_ = await self._request(
|
765
799
|
"POST",
|
766
800
|
f"systems/{self.serial_id}/{action}",
|
@@ -854,14 +888,14 @@ class DiagralAPI:
|
|
854
888
|
raise AuthenticationError("PIN code required to get system details")
|
855
889
|
|
856
890
|
_TIMESTAMP = str(int(time.time()))
|
857
|
-
_HMAC = generate_hmac_signature(
|
891
|
+
_HMAC: str = generate_hmac_signature(
|
858
892
|
timestamp=_TIMESTAMP,
|
859
893
|
serial_id=self.serial_id,
|
860
894
|
api_key=self.__apikey,
|
861
895
|
secret_key=self.__secret_key,
|
862
896
|
)
|
863
897
|
|
864
|
-
_HEADERS = {
|
898
|
+
_HEADERS: dict[str, str] = {
|
865
899
|
"X-PIN-CODE": str(self.__pincode),
|
866
900
|
"X-HMAC": _HMAC,
|
867
901
|
"X-TIMESTAMP": _TIMESTAMP,
|
@@ -935,14 +969,14 @@ class DiagralAPI:
|
|
935
969
|
)
|
936
970
|
|
937
971
|
_TIMESTAMP = str(int(time.time()))
|
938
|
-
_HMAC = generate_hmac_signature(
|
972
|
+
_HMAC: str = generate_hmac_signature(
|
939
973
|
timestamp=_TIMESTAMP,
|
940
974
|
serial_id=self.serial_id,
|
941
975
|
api_key=self.__apikey,
|
942
976
|
secret_key=self.__secret_key,
|
943
977
|
)
|
944
978
|
|
945
|
-
_HEADERS = {
|
979
|
+
_HEADERS: dict[str, str] = {
|
946
980
|
"X-HMAC": _HMAC,
|
947
981
|
"X-TIMESTAMP": _TIMESTAMP,
|
948
982
|
"X-APIKEY": self.__apikey,
|
@@ -990,14 +1024,14 @@ class DiagralAPI:
|
|
990
1024
|
)
|
991
1025
|
|
992
1026
|
_TIMESTAMP = str(int(time.time()))
|
993
|
-
_HMAC = generate_hmac_signature(
|
1027
|
+
_HMAC: str = generate_hmac_signature(
|
994
1028
|
timestamp=_TIMESTAMP,
|
995
1029
|
serial_id=self.serial_id,
|
996
1030
|
api_key=self.__apikey,
|
997
1031
|
secret_key=self.__secret_key,
|
998
1032
|
)
|
999
1033
|
|
1000
|
-
_HEADERS = {
|
1034
|
+
_HEADERS: dict[str, str] = {
|
1001
1035
|
"X-HMAC": _HMAC,
|
1002
1036
|
"X-TIMESTAMP": _TIMESTAMP,
|
1003
1037
|
"X-APIKEY": self.__apikey,
|
@@ -1043,14 +1077,14 @@ class DiagralAPI:
|
|
1043
1077
|
)
|
1044
1078
|
|
1045
1079
|
_TIMESTAMP = str(int(time.time()))
|
1046
|
-
_HMAC = generate_hmac_signature(
|
1080
|
+
_HMAC: str = generate_hmac_signature(
|
1047
1081
|
timestamp=_TIMESTAMP,
|
1048
1082
|
serial_id=self.serial_id,
|
1049
1083
|
api_key=self.__apikey,
|
1050
1084
|
secret_key=self.__secret_key,
|
1051
1085
|
)
|
1052
1086
|
|
1053
|
-
_HEADERS = {
|
1087
|
+
_HEADERS: dict[str, str] = {
|
1054
1088
|
"X-HMAC": _HMAC,
|
1055
1089
|
"X-TIMESTAMP": _TIMESTAMP,
|
1056
1090
|
"X-APIKEY": self.__apikey,
|
@@ -1108,14 +1142,14 @@ class DiagralAPI:
|
|
1108
1142
|
)
|
1109
1143
|
|
1110
1144
|
_TIMESTAMP = str(int(time.time()))
|
1111
|
-
_HMAC = generate_hmac_signature(
|
1145
|
+
_HMAC: str = generate_hmac_signature(
|
1112
1146
|
timestamp=_TIMESTAMP,
|
1113
1147
|
serial_id=self.serial_id,
|
1114
1148
|
api_key=self.__apikey,
|
1115
1149
|
secret_key=self.__secret_key,
|
1116
1150
|
)
|
1117
1151
|
|
1118
|
-
_HEADERS = {
|
1152
|
+
_HEADERS: dict[str, str] = {
|
1119
1153
|
"X-HMAC": _HMAC,
|
1120
1154
|
"X-TIMESTAMP": _TIMESTAMP,
|
1121
1155
|
"X-APIKEY": self.__apikey,
|
@@ -1216,14 +1250,14 @@ class DiagralAPI:
|
|
1216
1250
|
)
|
1217
1251
|
|
1218
1252
|
_TIMESTAMP = str(int(time.time()))
|
1219
|
-
_HMAC = generate_hmac_signature(
|
1253
|
+
_HMAC: str = generate_hmac_signature(
|
1220
1254
|
timestamp=_TIMESTAMP,
|
1221
1255
|
serial_id=self.serial_id,
|
1222
1256
|
api_key=self.__apikey,
|
1223
1257
|
secret_key=self.__secret_key,
|
1224
1258
|
)
|
1225
1259
|
|
1226
|
-
_HEADERS = {
|
1260
|
+
_HEADERS: dict[str, str] = {
|
1227
1261
|
"X-HMAC": _HMAC,
|
1228
1262
|
"X-TIMESTAMP": _TIMESTAMP,
|
1229
1263
|
"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
|
#######################################################
|
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.0b1
|
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=LEDHEdf5wRd7QB7flsk3hnb2X_sf1325sD9DPDkBOhM,48587
|
3
|
+
pydiagral/constants.py,sha256=2B0TdKxQHA3cpIBxojo43bMW44wN9xKYsHbBRHWsaBk,119
|
4
|
+
pydiagral/exceptions.py,sha256=Q5wEpNtiykLs3Ck0W8r1IQAJek_omaQ3jpMOtiiwBUg,1030
|
5
|
+
pydiagral/models.py,sha256=AKJd9ywPhislF5R685XNLvEpmJouFlkxWzz1pJXY4tA,54136
|
6
|
+
pydiagral/utils.py,sha256=-VxI-lNaC4bU1K4DSmWDhvbsS2bXv5FAGULGKBf1UMU,449
|
7
|
+
pydiagral-1.5.0b1.dist-info/METADATA,sha256=CudZesyW4jeyR1tR-N7K5tXVt4no-2nzY7voKEgaecc,48531
|
8
|
+
pydiagral-1.5.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
pydiagral-1.5.0b1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
+
pydiagral-1.5.0b1.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
|