pydiagral 1.5.0b1__tar.gz → 1.5.0b2__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 (41) hide show
  1. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/CHANGELOG.md +7 -0
  2. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/PKG-INFO +1 -1
  3. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/example_code.py +75 -39
  4. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/api.py +23 -22
  5. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/models.py +24 -0
  6. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.devcontainer/Dockerfile.dev +0 -0
  7. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.devcontainer/devcontainer.json +0 -0
  8. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/FUNDING.yml +0 -0
  9. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  10. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  11. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/copilot-commit-message-instructions.md +0 -0
  12. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/dependabot.yml +0 -0
  13. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/labels.yml +0 -0
  14. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/labeler.yml +0 -0
  15. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/lint.yaml +0 -0
  16. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/lock.yml +0 -0
  17. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/pytest.yaml +0 -0
  18. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/release_and_doc.yaml +0 -0
  19. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.github/workflows/semantic-prs.yml +0 -0
  20. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.gitignore +0 -0
  21. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.releaserc +0 -0
  22. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.vscode/extensions.json +0 -0
  23. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/.vscode/settings.json +0 -0
  24. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/LICENSE +0 -0
  25. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/README.md +0 -0
  26. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/api.md +0 -0
  27. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/exceptions.md +0 -0
  28. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/how-to-find-diagral-serial.png +0 -0
  29. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/index.md +0 -0
  30. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/models.md +0 -0
  31. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/docs/pydiagral-Logo.png +0 -0
  32. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/mkdocs.yml +0 -0
  33. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/pyproject.toml +0 -0
  34. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/__init__.py +0 -0
  35. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/constants.py +0 -0
  36. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/exceptions.py +0 -0
  37. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/src/pydiagral/utils.py +0 -0
  38. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/tests/__init__.py +0 -0
  39. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/tests/data/configuration_sample.json +0 -0
  40. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/tests/data/system_details_sample.json +0 -0
  41. {pydiagral-1.5.0b1 → pydiagral-1.5.0b2}/tests/test_pydiagral_api.py +0 -0
@@ -1,3 +1,10 @@
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
+
1
8
  # [1.5.0-beta.1](https://github.com/mguyard/pydiagral/compare/v1.4.0...v1.5.0-beta.1) (2025-02-28)
2
9
 
3
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydiagral
3
- Version: 1.5.0b1
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
@@ -9,6 +9,7 @@ from pydiagral.models import (
9
9
  ApiKeyWithSecret,
10
10
  SystemDetails,
11
11
  SystemStatus,
12
+ TryConnectResult,
12
13
  Webhook,
13
14
  )
14
15
  from src.pydiagral import DiagralAPI, DiagralAPIError
@@ -22,7 +23,7 @@ logging.basicConfig(
22
23
  level=getattr(logging, log_level, logging.INFO),
23
24
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
24
25
  )
25
- logger: logging.Logger = logging.getLogger(__name__)
26
+ _LOGGER: logging.Logger = logging.getLogger(__name__)
26
27
 
27
28
  # Connection information (replace with your own data)
28
29
  # For this, create a .env file in the root of the project with the following content:
@@ -47,9 +48,11 @@ WEBHOOK_URL: str | None = os.getenv("WEBHOOK_URL") # Optional
47
48
  # Switch to True the actions you want to test
48
49
  TRY_CONNECTION_WITH_KEYS = False
49
50
  TRY_CONNECTION_WITHOUT_KEYS = False
50
- APIKEY_CREATION = True
51
- APIKEY_DELETION = True
52
- LIST_GROUPS = True
51
+ TRY_CONNECTION_WITH_KEYS_EPHEMERAL = False
52
+ TRY_CONNECTION_WITHOUT_KEYS_EPHEMERAL = False
53
+ APIKEY_CREATION = False
54
+ APIKEY_DELETION = False
55
+ LIST_GROUPS = False
53
56
  LIST_SYSTEM_DETAILS = False
54
57
  LIST_SYSTEM_STATUS = False
55
58
  LIST_SYSTEM_CONFIGURATION = False
@@ -62,12 +65,16 @@ TEST_PARTIAL_ACTIONS = False
62
65
 
63
66
  ######################################## DON'T MODIFY BELOW THIS LINE ########################################
64
67
 
65
- if APIKEY_CREATION or TRY_CONNECTION_WITHOUT_KEYS:
68
+ if (
69
+ APIKEY_CREATION
70
+ or TRY_CONNECTION_WITHOUT_KEYS
71
+ or TRY_CONNECTION_WITHOUT_KEYS_EPHEMERAL
72
+ ):
66
73
  API_KEY = None
67
74
  SECRET_KEY = None
68
75
 
69
76
 
70
- async def test_diagral_api() -> None: # noqa: D103
77
+ async def test_diagral_api() -> None: # noqa: D103,C901
71
78
  try:
72
79
  # Initialization of the DiagralAPI object
73
80
  diagral = DiagralAPI(
@@ -78,47 +85,76 @@ async def test_diagral_api() -> None: # noqa: D103
78
85
  secret_key=SECRET_KEY,
79
86
  pincode=PIN_CODE,
80
87
  )
81
- logger.info("Initialisation de DiagralAPI réussie")
88
+ _LOGGER.info("Initialisation de DiagralAPI réussie")
82
89
 
83
90
  # Connection to the Diagral API
84
91
  async with diagral as alarm:
85
- logger.info("Connection to the Diagral API successful")
92
+ _LOGGER.info("Connection to the Diagral API successful")
86
93
  if APIKEY_CREATION and (not API_KEY or not SECRET_KEY):
87
94
  await alarm.login() # Login to the API
88
95
  # await asyncio.sleep(3700) # Wait for the access token to expire
89
96
  api_keys: ApiKeyWithSecret = (
90
97
  await alarm.set_apikey()
91
98
  ) # Create a new API key
92
- logger.info("API Key: %s", api_keys.api_key) # Display the API key
93
- logger.info(
99
+ _LOGGER.info("API Key: %s", api_keys.api_key) # Display the API key
100
+ _LOGGER.info(
94
101
  "Secret Key: %s", api_keys.secret_key
95
102
  ) # Display the secret key
96
103
  await (
97
104
  alarm.validate_apikey()
98
105
  ) # Validate the API key - Optional as already done in set_apikey
99
106
 
107
+ if TRY_CONNECTION_WITH_KEYS:
108
+ connection: TryConnectResult = await alarm.try_connection(
109
+ ephemeral=False
110
+ ) # Try to connect to the API with keys
111
+ if connection.result:
112
+ _LOGGER.info("Connection (with provided keys) successful")
113
+ else:
114
+ _LOGGER.error("Connection (with provided keys) failed")
115
+
100
116
  if TRY_CONNECTION_WITHOUT_KEYS:
101
- result: bool = await alarm.try_connection(
117
+ connection: TryConnectResult = await alarm.try_connection(
118
+ ephemeral=False
119
+ ) # Try to connect to the API with keys
120
+ if connection.result:
121
+ _LOGGER.info("Connection (with provided keys) successful")
122
+ _LOGGER.info("Generated keys are : %s", connection.keys)
123
+ _LOGGER.info("Running cleanup by deleting the generated keys")
124
+ await alarm.delete_apikey(connection.keys.api_key) # For cleanup
125
+ else:
126
+ _LOGGER.error("Connection (with provided keys) failed")
127
+
128
+ if TRY_CONNECTION_WITH_KEYS_EPHEMERAL:
129
+ connection: TryConnectResult = await alarm.try_connection(
102
130
  ephemeral=True
103
131
  ) # Try to connect to the API without keys
104
- if result:
105
- logger.info("Connection (without provided keys) successful")
132
+ if connection.result:
133
+ if connection.keys is not None:
134
+ _LOGGER.warning(
135
+ "Keys was returned. Not a normal behavior as keys was provided"
136
+ )
137
+ _LOGGER.info("Connection (without provided keys) successful")
106
138
  else:
107
- logger.error("Connection (without provided keys) failed")
139
+ _LOGGER.error("Connection (without provided keys) failed")
108
140
 
109
- if TRY_CONNECTION_WITH_KEYS:
110
- result = await alarm.try_connection(
111
- ephemeral=False
112
- ) # Try to connect to the API with keys
113
- if result:
114
- logger.info("Connection (with provided keys) successful")
141
+ if TRY_CONNECTION_WITHOUT_KEYS_EPHEMERAL:
142
+ connection: TryConnectResult = await alarm.try_connection(
143
+ ephemeral=True
144
+ ) # Try to connect to the API without keys
145
+ if connection.result:
146
+ if connection.keys is not None:
147
+ _LOGGER.warning(
148
+ "Keys was returned. Not a normal behavior as keys was provided"
149
+ )
150
+ _LOGGER.info("Connection (without provided keys) successful")
115
151
  else:
116
- logger.error("Connection (with provided keys) failed")
152
+ _LOGGER.error("Connection (without provided keys) failed")
117
153
 
118
154
  if LIST_GROUPS:
119
155
  await alarm.get_devices_info()
120
156
  for group in alarm.alarm_configuration.groups:
121
- logger.info(
157
+ _LOGGER.info(
122
158
  "Group %i: %s",
123
159
  group.index,
124
160
  group.name,
@@ -128,80 +164,80 @@ async def test_diagral_api() -> None: # noqa: D103
128
164
  system_details: SystemDetails = (
129
165
  await alarm.get_system_details()
130
166
  ) # Get the system details
131
- logger.info("System Details: %s", system_details)
167
+ _LOGGER.info("System Details: %s", system_details)
132
168
 
133
169
  if LIST_SYSTEM_STATUS:
134
170
  system_status: SystemStatus = (
135
171
  await alarm.get_system_status()
136
172
  ) # Get the system status
137
- logger.info("System Status: %s", system_status)
173
+ _LOGGER.info("System Status: %s", system_status)
138
174
 
139
175
  if LIST_SYSTEM_CONFIGURATION:
140
176
  if not alarm.alarm_configuration:
141
177
  await alarm.get_configuration() # Get the configuration
142
- logger.info(
178
+ _LOGGER.info(
143
179
  "System Configuration: %s",
144
180
  alarm.alarm_configuration.grp_marche_partielle2,
145
181
  )
146
182
 
147
183
  if WEHBOOK_REGISTRATION and WEBHOOK_URL:
148
- logger.info("-----> WEBHOOK <-----")
184
+ _LOGGER.info("-----> WEBHOOK <-----")
149
185
  webhook_register_output: Webhook | None = await alarm.register_webhook(
150
186
  webhook_url=WEBHOOK_URL,
151
187
  subscribe_to_anomaly=True,
152
188
  subscribe_to_alert=True,
153
189
  subscribe_to_state=True,
154
190
  )
155
- logger.info("Webhook Register Output: %s", webhook_register_output)
191
+ _LOGGER.info("Webhook Register Output: %s", webhook_register_output)
156
192
  webhook_update_output: Webhook | None = await alarm.update_webhook(
157
193
  webhook_url=WEBHOOK_URL,
158
194
  subscribe_to_anomaly=True,
159
195
  subscribe_to_alert=True,
160
196
  subscribe_to_state=True,
161
197
  )
162
- logger.info("Webhook Update Output: %s", webhook_update_output)
198
+ _LOGGER.info("Webhook Update Output: %s", webhook_update_output)
163
199
  if WEBHOOK_DELETION:
164
200
  await alarm.delete_webhook()
165
201
  webhook_sub: Webhook = await alarm.get_webhook()
166
202
 
167
- logger.info("Webhook Subscription: %s", webhook_sub)
203
+ _LOGGER.info("Webhook Subscription: %s", webhook_sub)
168
204
 
169
205
  if GET_ANOMALIES:
170
- logger.info("-----> ANOMALIES <-----")
206
+ _LOGGER.info("-----> ANOMALIES <-----")
171
207
  anomalies: Anomalies | dict = await alarm.get_anomalies()
172
- logger.info("Anomalies: %s", anomalies)
208
+ _LOGGER.info("Anomalies: %s", anomalies)
173
209
 
174
210
  if TEST_FULL_ACTIONS:
175
211
  start_result: SystemStatus = (
176
212
  await alarm.start_system()
177
213
  ) # Start the system
178
- logger.info("Start System with result: %s", start_result)
214
+ _LOGGER.info("Start System with result: %s", start_result)
179
215
  await asyncio.sleep(30) # Wait for 30 seconds
180
216
  stop_result: SystemStatus = await alarm.stop_system() # Stop the system
181
- logger.info("Stop System with result: %s", stop_result)
217
+ _LOGGER.info("Stop System with result: %s", stop_result)
182
218
 
183
219
  if TEST_PRESENCE:
184
220
  presence_result: SystemStatus = (
185
221
  await alarm.presence()
186
222
  ) # Activate the presence mode
187
- logger.info("Presence with result: %s", presence_result)
223
+ _LOGGER.info("Presence with result: %s", presence_result)
188
224
  await asyncio.sleep(30) # Wait for 30 seconds
189
225
  stop_result = await alarm.stop_system() # Stop the system
190
- logger.info("Stop System with result: %s", stop_result)
226
+ _LOGGER.info("Stop System with result: %s", stop_result)
191
227
 
192
228
  if TEST_PARTIAL_ACTIONS:
193
229
  activategroup_result: SystemStatus = await alarm.activate_group(
194
230
  groups=[1, 2]
195
231
  )
196
- logger.info("Activate Group with result: %s", activategroup_result)
232
+ _LOGGER.info("Activate Group with result: %s", activategroup_result)
197
233
  await asyncio.sleep(30) # Wait for 30 seconds
198
234
  disablegroup_result: SystemStatus = await alarm.disable_group(
199
235
  groups=[2]
200
236
  )
201
- logger.info("Disable Group with result: %s", disablegroup_result)
237
+ _LOGGER.info("Disable Group with result: %s", disablegroup_result)
202
238
  await asyncio.sleep(30) # Wait for 30 seconds
203
239
  stop_result = await alarm.stop_system()
204
- logger.info("Stop System with result: %s", stop_result)
240
+ _LOGGER.info("Stop System with result: %s", stop_result)
205
241
 
206
242
  if (
207
243
  APIKEY_DELETION and APIKEY_CREATION
@@ -209,7 +245,7 @@ async def test_diagral_api() -> None: # noqa: D103
209
245
  await alarm.delete_apikey() # Delete the API key
210
246
 
211
247
  except DiagralAPIError as e:
212
- logger.error("Erreur : %s", e)
248
+ _LOGGER.error("Erreur : %s", e)
213
249
 
214
250
 
215
251
  if __name__ == "__main__":
@@ -36,6 +36,7 @@ from .models import (
36
36
  Rudes,
37
37
  SystemDetails,
38
38
  SystemStatus,
39
+ TryConnectResult,
39
40
  Webhook,
40
41
  )
41
42
  from .utils import generate_hmac_signature
@@ -400,48 +401,48 @@ class DiagralAPI:
400
401
  self.__secret_key = None
401
402
 
402
403
  async def try_connection(self, ephemeral: bool = True) -> bool:
403
- """Establish a connection with the Diagral system.
404
+ """Test connection with the Diagral system.
404
405
 
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
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.
410
408
 
411
409
  Args:
412
- ephemeral (bool, optional): If True and using temporary API keys,
413
- deletes them after validation. Defaults to True.
410
+ ephemeral (bool, optional): If True, temporary API keys will be deleted after
411
+ connection test. Defaults to True.
414
412
 
415
413
  Returns:
416
- bool: True if connection is successful
414
+ TryConnectResult: Object containing connection test results and optionally API keys
415
+ if non-ephemeral temporary keys were generated.
417
416
 
418
417
  Raises:
419
- DiagralAPIError: If connection fails or system status check fails
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
420
425
 
421
426
  """
422
427
 
428
+ result: TryConnectResult = TryConnectResult()
423
429
  api_keys_provided = bool(self.__apikey and self.__secret_key)
424
- _LOGGER.warning("API keys provided: %s", api_keys_provided)
425
430
  try:
431
+ # If API keys are not provided, generate temporary keys
426
432
  if not api_keys_provided:
427
433
  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
434
 
435
+ # Retrieve system status to validate connection
439
436
  await self.get_system_status()
437
+ # If connection is successful, clean up temporary keys if requested (ephemeral)
440
438
  if ephemeral and not api_keys_provided:
441
439
  await self.delete_apikey(apikey=self.__apikey)
440
+ elif not ephemeral and not api_keys_provided:
441
+ result.keys = api_key_response
442
442
  except DiagralAPIError as e:
443
443
  raise DiagralAPIError(f"Failed to connect to the system: {e}") from e
444
- return True
444
+ result.result = True
445
+ return result
445
446
 
446
447
  async def get_configuration(self) -> None:
447
448
  """Asynchronously retrieve the configuration of the Diagral system.
@@ -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
  #####################################
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