pydiagral 1.5.0b2__tar.gz → 1.5.0b5__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 (42) hide show
  1. pydiagral-1.5.0b5/CHANGELOG.md +40 -0
  2. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/PKG-INFO +1 -1
  3. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/example_code.py +3 -0
  4. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/api.py +61 -23
  5. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/exceptions.py +8 -0
  6. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/tests/test_pydiagral_api.py +21 -16
  7. pydiagral-1.5.0b2/CHANGELOG.md +0 -19
  8. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.devcontainer/Dockerfile.dev +0 -0
  9. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.devcontainer/devcontainer.json +0 -0
  10. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/FUNDING.yml +0 -0
  11. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  12. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  13. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/copilot-commit-message-instructions.md +0 -0
  14. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/dependabot.yml +0 -0
  15. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/labels.yml +0 -0
  16. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/labeler.yml +0 -0
  17. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/lint.yaml +0 -0
  18. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/lock.yml +0 -0
  19. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/pytest.yaml +0 -0
  20. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/release_and_doc.yaml +0 -0
  21. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.github/workflows/semantic-prs.yml +0 -0
  22. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.gitignore +0 -0
  23. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.releaserc +0 -0
  24. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.vscode/extensions.json +0 -0
  25. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/.vscode/settings.json +0 -0
  26. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/LICENSE +0 -0
  27. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/README.md +0 -0
  28. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/api.md +0 -0
  29. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/exceptions.md +0 -0
  30. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/how-to-find-diagral-serial.png +0 -0
  31. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/index.md +0 -0
  32. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/models.md +0 -0
  33. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/docs/pydiagral-Logo.png +0 -0
  34. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/mkdocs.yml +0 -0
  35. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/pyproject.toml +0 -0
  36. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/__init__.py +0 -0
  37. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/constants.py +0 -0
  38. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/models.py +0 -0
  39. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/src/pydiagral/utils.py +0 -0
  40. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/tests/__init__.py +0 -0
  41. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/tests/data/configuration_sample.json +0 -0
  42. {pydiagral-1.5.0b2 → pydiagral-1.5.0b5}/tests/data/system_details_sample.json +0 -0
@@ -0,0 +1,40 @@
1
+ # [1.5.0-beta.5](https://github.com/mguyard/pydiagral/compare/v1.5.0-beta.4...v1.5.0-beta.5) (2025-03-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **api:** 🔧 Refactor API key handling and introduce new exceptions ([b1f6d06](https://github.com/mguyard/pydiagral/commit/b1f6d060e52c0c660e760c1dc86c5fa75613e4b2))
7
+
8
+ # [1.5.0-beta.4](https://github.com/mguyard/pydiagral/compare/v1.5.0-beta.3...v1.5.0-beta.4) (2025-02-28)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **api:** 🔧 Update log message for alarm name retrieval ([fca40e4](https://github.com/mguyard/pydiagral/commit/fca40e44bccd59f6399ae2b89a29c2b4fcc80172))
14
+
15
+ # [1.5.0-beta.3](https://github.com/mguyard/pydiagral/compare/v1.5.0-beta.2...v1.5.0-beta.3) (2025-02-28)
16
+
17
+
18
+ ### Features
19
+
20
+ * **api:** 🚀 Add `get_alarm_name` method and log alarm name in tests ([96e727f](https://github.com/mguyard/pydiagral/commit/96e727fe851a1086d8689c6fb7204efdb702938d))
21
+
22
+ # [1.5.0-beta.2](https://github.com/mguyard/pydiagral/compare/v1.5.0-beta.1...v1.5.0-beta.2) (2025-02-28)
23
+
24
+
25
+ ### Features
26
+
27
+ * **api:** 🚀 Add connection testing functionality with result handling ([a07bbfc](https://github.com/mguyard/pydiagral/commit/a07bbfc22a1f41418dad0347394cadcab31685e8))
28
+
29
+ # [1.5.0-beta.1](https://github.com/mguyard/pydiagral/compare/v1.4.0...v1.5.0-beta.1) (2025-02-28)
30
+
31
+
32
+ ### Features
33
+
34
+ * **api:** 🚀 Add testing connection feature ([53981f6](https://github.com/mguyard/pydiagral/commit/53981f6bdb8b797b7f4408fbaf2264b66a58ab38))
35
+
36
+ # 1.4.0 (2025-02-23)
37
+
38
+ ### Features
39
+
40
+ - Initial Public release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydiagral
3
- Version: 1.5.0b2
3
+ Version: 1.5.0b5
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
@@ -239,6 +239,9 @@ async def test_diagral_api() -> None: # noqa: D103,C901
239
239
  stop_result = await alarm.stop_system()
240
240
  _LOGGER.info("Stop System with result: %s", stop_result)
241
241
 
242
+ alarm_name: str = await alarm.get_alarm_name() # Get the alarm name
243
+ _LOGGER.info("Alarm Name is: %s", alarm_name)
244
+
242
245
  if (
243
246
  APIKEY_DELETION and APIKEY_CREATION
244
247
  ): # Only when the API key has been created
@@ -16,6 +16,8 @@ import aiohttp
16
16
 
17
17
  from .constants import API_VERSION, BASE_URL
18
18
  from .exceptions import (
19
+ APIKeyCreationError,
20
+ APIValidationError,
19
21
  AuthenticationError,
20
22
  ClientError,
21
23
  ConfigurationError,
@@ -266,7 +268,8 @@ class DiagralAPI:
266
268
  ApiKeyWithSecret: An instance of ApiKeyWithSecret containing the created API key and secret key.
267
269
 
268
270
  Raises:
269
- AuthenticationError: If the API key is not found in the response or if the created API key fails validation.
271
+ APIKeyCreationError: If the API key creation fails.
272
+ APIValidationError: If the API key validation fails.
270
273
 
271
274
  """
272
275
 
@@ -285,16 +288,16 @@ class DiagralAPI:
285
288
  set_apikey_response: ApiKeyWithSecret = ApiKeyWithSecret.from_dict(
286
289
  response_data
287
290
  )
288
- self.__apikey = set_apikey_response.api_key
291
+ self.__apikey: str = set_apikey_response.api_key
289
292
  if not self.__apikey:
290
293
  error_msg = "API key not found in response"
291
294
  _LOGGER.error(error_msg)
292
- raise AuthenticationError(error_msg)
293
- self.__secret_key = set_apikey_response.secret_key
295
+ raise APIKeyCreationError(error_msg)
296
+ self.__secret_key: str = set_apikey_response.secret_key
294
297
  if not self.__secret_key:
295
298
  error_msg = "Secret key not found in response"
296
299
  _LOGGER.error(error_msg)
297
- raise AuthenticationError(error_msg)
300
+ raise APIKeyCreationError(error_msg)
298
301
 
299
302
  _LOGGER.info("Successfully created new API key: ...%s", self.__apikey[-4:])
300
303
  # Verify if the API key is valid
@@ -303,14 +306,14 @@ class DiagralAPI:
303
306
  _LOGGER.info(
304
307
  "Successfully verified new API key: ...%s", self.__apikey[-4:]
305
308
  )
306
- except AuthenticationError as e:
309
+ except APIValidationError as e:
307
310
  _LOGGER.error("Created API key failed validation: %s", e)
308
311
  self.__apikey = None
309
312
  raise
310
313
  except DiagralAPIError as e:
311
314
  error_msg: str = f"Failed to create API key: {e!s}"
312
315
  _LOGGER.error(error_msg)
313
- raise AuthenticationError(error_msg) from e
316
+ raise APIKeyCreationError(error_msg) from e
314
317
 
315
318
  return ApiKeyWithSecret(api_key=self.__apikey, secret_key=self.__secret_key)
316
319
 
@@ -329,6 +332,7 @@ class DiagralAPI:
329
332
 
330
333
  Raises:
331
334
  ConfigurationError: If no API key is provided or if the API key is invalid.
335
+ APIValidationError: If the API key is invalid or not found in the list of valid keys.
332
336
 
333
337
  """
334
338
 
@@ -357,7 +361,7 @@ class DiagralAPI:
357
361
  if is_valid:
358
362
  _LOGGER.info("API key successfully validated")
359
363
  else:
360
- raise AuthenticationError(
364
+ raise APIValidationError(
361
365
  "API key is invalid or not found in the list of valid keys"
362
366
  )
363
367
 
@@ -400,7 +404,7 @@ class DiagralAPI:
400
404
  self.__apikey = None
401
405
  self.__secret_key = None
402
406
 
403
- async def try_connection(self, ephemeral: bool = True) -> bool:
407
+ async def try_connection(self, ephemeral: bool = True) -> TryConnectResult:
404
408
  """Test connection with the Diagral system.
405
409
 
406
410
  This method tests the connection by either using provided API credentials or generating
@@ -415,7 +419,14 @@ class DiagralAPI:
415
419
  if non-ephemeral temporary keys were generated.
416
420
 
417
421
  Raises:
418
- DiagralAPIError: If connection attempt fails or system status check fails.
422
+ APIKeyCreationError: If creation of temporary API keys fails
423
+ APIValidationError: If API key validation fails
424
+ SessionError: If the session is not initialized.
425
+ DiagralAPIError: If the request results in a 400 status code or other API errors.
426
+ AuthenticationError: If authentication fails or request results in a 401 or 403 status code.
427
+ ValidationError: If the request results in a 422 status code.
428
+ ServerError: If the request results in a 500 or 503 status code.
429
+ ClientError: If there is a network error.
419
430
 
420
431
  Note:
421
432
  If API credentials are not provided during client initialization, temporary
@@ -427,20 +438,25 @@ class DiagralAPI:
427
438
 
428
439
  result: TryConnectResult = TryConnectResult()
429
440
  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
441
 
435
- # Retrieve system status to validate connection
442
+ # If API keys are not provided, generate temporary keys
443
+ if not api_keys_provided:
444
+ api_key_response: ApiKeyWithSecret = await self.set_apikey()
445
+
446
+ # Retrieve system status to validate connection
447
+ try:
436
448
  await self.get_system_status()
437
- # If connection is successful, clean up temporary keys if requested (ephemeral)
449
+ except DiagralAPIError:
438
450
  if ephemeral and not api_keys_provided:
439
451
  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
452
+ raise
453
+
454
+ # If connection is successful, clean up temporary keys if requested (ephemeral)
455
+ if ephemeral and not api_keys_provided:
456
+ await self.delete_apikey(apikey=self.__apikey)
457
+ elif not ephemeral and not api_keys_provided:
458
+ result.keys = api_key_response
459
+
444
460
  result.result = True
445
461
  return result
446
462
 
@@ -489,6 +505,28 @@ class DiagralAPI:
489
505
  )
490
506
  return self.alarm_configuration
491
507
 
508
+ async def get_alarm_name(self) -> str:
509
+ """Get the name of the alarm from the configuration.
510
+
511
+ Returns:
512
+ str: The name of the alarm from the configuration.
513
+
514
+ Raises:
515
+ ConfigurationError: If unable to retrieve the alarm configuration.
516
+
517
+ Note:
518
+ This method will attempt to fetch the configuration if it hasn't been loaded yet.
519
+
520
+ """
521
+
522
+ if not self.alarm_configuration:
523
+ await self.get_configuration()
524
+
525
+ if not self.alarm_configuration:
526
+ raise ConfigurationError("Failed to retrieve alarm configuration")
527
+
528
+ return self.alarm_configuration.alarm.name
529
+
492
530
  async def get_devices_info(self) -> DeviceList:
493
531
  """Asynchronously retrieves information about various device types from the alarm configuration.
494
532
 
@@ -580,17 +618,17 @@ class DiagralAPI:
580
618
  SystemStatus: An instance of SystemStatus containing the retrieved system status.
581
619
 
582
620
  Raises:
583
- AuthenticationError: If the API key, secret key, or PIN code is not provided.
621
+ ConfigurationError: If the API key, secret key, or PIN code is not provided.
584
622
 
585
623
  """
586
624
 
587
625
  if not self.__apikey or not self.__secret_key:
588
- raise AuthenticationError(
626
+ raise ConfigurationError(
589
627
  "API key and secret key required to get system details"
590
628
  )
591
629
 
592
630
  if not self.__pincode:
593
- raise AuthenticationError("PIN code required to get system details")
631
+ raise ConfigurationError("PIN code required to get system details")
594
632
 
595
633
  _TIMESTAMP = str(int(time.time()))
596
634
  _HMAC: str = generate_hmac_signature(
@@ -37,3 +37,11 @@ class ServerError(DiagralAPIError):
37
37
 
38
38
  class ClientError(DiagralAPIError):
39
39
  """Raised when client returns error."""
40
+
41
+
42
+ class APIKeyCreationError(DiagralAPIError):
43
+ """Raised when API key creation fails."""
44
+
45
+
46
+ class APIValidationError(DiagralAPIError):
47
+ """Raised when API validation fails."""
@@ -8,7 +8,12 @@ from unittest.mock import AsyncMock, patch
8
8
 
9
9
  import pytest
10
10
 
11
- from pydiagral.exceptions import ConfigurationError, DiagralAPIError
11
+ from pydiagral.exceptions import (
12
+ APIKeyCreationError,
13
+ APIValidationError,
14
+ ConfigurationError,
15
+ DiagralAPIError,
16
+ )
12
17
 
13
18
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
14
19
  import json
@@ -186,7 +191,7 @@ async def test_set_apikey(mock_request):
186
191
  pincode=PIN_CODE,
187
192
  ) as diagral:
188
193
  await diagral.login()
189
- api_keys = await diagral.set_apikey()
194
+ api_keys: ApiKeyWithSecret = await diagral.set_apikey()
190
195
  assert isinstance(api_keys, ApiKeyWithSecret)
191
196
  assert api_keys.api_key == API_KEY
192
197
  assert api_keys.secret_key == SECRET_KEY
@@ -198,22 +203,22 @@ async def test_set_apikey_with_invalid_user_rights(mock_request):
198
203
  """Test the `set_apikey` method of the `DiagralAPI` class when the user has invalid permissions.
199
204
 
200
205
  This test simulates the scenario where a user with invalid permissions tries to set an API key.
201
- It mocks the `mock_request` to raise an `AuthenticationError` indicating that the user does not have the right permissions.
206
+ It mocks the `mock_request` to raise an `APIKeyCreationError` indicating that the user does not have the right permissions.
202
207
 
203
208
  Args:
204
209
  mock_request (Mock): A mock object for simulating API requests.
205
210
 
206
211
  Raises:
207
- AuthenticationError: If the user does not have the right permissions.
212
+ APIKeyCreationError: If the user does not have the right permissions.
208
213
 
209
214
  """
210
215
 
211
- mock_request.side_effect = AuthenticationError(
216
+ mock_request.side_effect = APIKeyCreationError(
212
217
  "The user does not have the right permissions"
213
218
  )
214
219
  mock_request.side_effect = [
215
220
  ({"access_token": FAKE_TOKEN}, 200), # Response for login
216
- AuthenticationError(
221
+ APIKeyCreationError(
217
222
  "The user does not have the right permissions"
218
223
  ), # Response for set_apikey
219
224
  ]
@@ -226,7 +231,7 @@ async def test_set_apikey_with_invalid_user_rights(mock_request):
226
231
  pincode=PIN_CODE,
227
232
  ) as diagral:
228
233
  await diagral.login()
229
- with pytest.raises(AuthenticationError):
234
+ with pytest.raises(APIKeyCreationError):
230
235
  await diagral.set_apikey()
231
236
 
232
237
 
@@ -310,14 +315,14 @@ async def test_validate_apikey_not_existing(mock_request):
310
315
 
311
316
  This test mocks the responses for the login and validate_apikey requests. It first simulates a successful login
312
317
  response and then simulates a response for the validate_apikey request with a list of API keys that do not match
313
- the provided API key. The test expects an `AuthenticationError` to be raised when the `validate_apikey` method
318
+ the provided API key. The test expects an `APIValidationError` to be raised when the `validate_apikey` method
314
319
  is called with a non-existing API key.
315
320
 
316
321
  Args:
317
322
  mock_request (MagicMock): A mock object to simulate HTTP requests and responses.
318
323
 
319
324
  Raises:
320
- AuthenticationError: If the API key validation fails.
325
+ APIValidationError: If the API key validation fails.
321
326
 
322
327
  """
323
328
  mock_request.side_effect = [
@@ -336,7 +341,7 @@ async def test_validate_apikey_not_existing(mock_request):
336
341
  pincode=PIN_CODE,
337
342
  ) as diagral:
338
343
  await diagral.login()
339
- with pytest.raises(AuthenticationError):
344
+ with pytest.raises(APIValidationError):
340
345
  await diagral.validate_apikey()
341
346
 
342
347
 
@@ -799,10 +804,10 @@ async def test_get_system_status_without_apikey(mock_request):
799
804
  1. Set up the mock responses for the login and get_system_status API calls.
800
805
  2. Create an instance of `DiagralAPI` with the provided credentials.
801
806
  3. Perform the login operation.
802
- 4. Attempt to get the system status and expect an `AuthenticationError` to be raised.
807
+ 4. Attempt to get the system status and expect an `ConfigurationError` to be raised.
803
808
 
804
809
  Raises:
805
- AuthenticationError: If the API key is not provided or invalid.
810
+ ConfigurationError: If the API key is not provided or invalid.
806
811
 
807
812
  """
808
813
  mock_request.side_effect = [
@@ -815,7 +820,7 @@ async def test_get_system_status_without_apikey(mock_request):
815
820
  pincode=PIN_CODE,
816
821
  ) as diagral:
817
822
  await diagral.login()
818
- with pytest.raises(AuthenticationError):
823
+ with pytest.raises(ConfigurationError):
819
824
  await diagral.get_system_status()
820
825
 
821
826
 
@@ -840,10 +845,10 @@ async def test_get_system_status_without_pincode(mock_request):
840
845
  1. Create an instance of `DiagralAPI` with the provided credentials.
841
846
  2. Call the `login` method to authenticate and obtain an access token.
842
847
  3. Attempt to call the `get_system_status` method without providing a pincode.
843
- 4. Verify that an `AuthenticationError` is raised, indicating that a pincode is required.
848
+ 4. Verify that an `ConfigurationError` is raised, indicating that a pincode is required.
844
849
 
845
850
  Expected Result:
846
- - An `AuthenticationError` is raised when attempting to get the system status without a pincode.
851
+ - An `ConfigurationError` is raised when attempting to get the system status without a pincode.
847
852
 
848
853
  """
849
854
  mock_request.side_effect = [
@@ -857,7 +862,7 @@ async def test_get_system_status_without_pincode(mock_request):
857
862
  secret_key=SECRET_KEY,
858
863
  ) as diagral:
859
864
  await diagral.login()
860
- with pytest.raises(AuthenticationError):
865
+ with pytest.raises(ConfigurationError):
861
866
  await diagral.get_system_status()
862
867
 
863
868
 
@@ -1,19 +0,0 @@
1
- # [1.5.0-beta.2](https://github.com/mguyard/pydiagral/compare/v1.5.0-beta.1...v1.5.0-beta.2) (2025-02-28)
2
-
3
-
4
- ### Features
5
-
6
- * **api:** 🚀 Add connection testing functionality with result handling ([a07bbfc](https://github.com/mguyard/pydiagral/commit/a07bbfc22a1f41418dad0347394cadcab31685e8))
7
-
8
- # [1.5.0-beta.1](https://github.com/mguyard/pydiagral/compare/v1.4.0...v1.5.0-beta.1) (2025-02-28)
9
-
10
-
11
- ### Features
12
-
13
- * **api:** 🚀 Add testing connection feature ([53981f6](https://github.com/mguyard/pydiagral/commit/53981f6bdb8b797b7f4408fbaf2264b66a58ab38))
14
-
15
- # 1.4.0 (2025-02-23)
16
-
17
- ### Features
18
-
19
- - Initial Public release
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