22
33from __future__ import annotations
44
5+ import asyncio
56import hashlib
67import json
78import math
2122 ServerDisconnectedError ,
2223 TCPConnector ,
2324)
25+ from .action_error_exception_handler import ActionErrorHandler
2426
2527from .const import (
2628 API_ENDPOINT ,
2729 DEFAULT_TIMEOUT ,
2830 DEFAULT_USER_AGENT ,
2931 UINT_MAX ,
30- XMO_ACCESS_RESTRICTION_ERR ,
31- XMO_AUTHENTICATION_ERR ,
3232 XMO_INVALID_SESSION_ERR ,
33- XMO_LOGIN_RETRY_ERR ,
34- XMO_MAX_SESSION_COUNT_ERR ,
3533 XMO_NO_ERR ,
36- XMO_NON_WRITABLE_PARAMETER_ERR ,
3734 XMO_REQUEST_ACTION_ERR ,
3835 XMO_REQUEST_NO_ERR ,
39- XMO_UNKNOWN_PATH_ERR ,
4036)
4137from .enums import EncryptionMethod
4238from .exceptions import (
43- AccessRestrictionException ,
4439 AuthenticationException ,
4540 BadRequestException ,
4641 InvalidSessionException ,
4742 LoginRetryErrorException ,
4843 LoginTimeoutException ,
49- MaximumSessionCountException ,
50- NonWritableParameterException ,
5144 UnauthorizedException ,
5245 UnknownException ,
5346 UnknownPathException ,
@@ -191,8 +184,19 @@ def __get_response(self, response, index=0):
191184
192185 return value
193186
194- def __get_response_value (self , response , index = 0 ):
187+ def __get_response_value (self , response , index = 0 , throw_on_action_error : bool = False ):
195188 """Retrieve response value from value."""
189+ if throw_on_action_error :
190+ try :
191+ error = response ["reply" ]["actions" ][index ]["error" ]
192+ except (KeyError , IndexError ):
193+ error = None
194+
195+ if error is not None :
196+ error_description = error ["description" ]
197+ if error_description != XMO_NO_ERR :
198+ raise ActionErrorHandler .from_error_description (error , error_description )
199+
196200 try :
197201 value = self .__get_response (response , index )["value" ]
198202 except KeyError :
@@ -239,37 +243,10 @@ async def __post(self, url, data):
239243 self ._request_id = - 1
240244 raise InvalidSessionException (error )
241245
242- # Error in one of the actions
246+ # Unknown error in one of the actions
243247 if error ["description" ] == XMO_REQUEST_ACTION_ERR :
244- # pylint:disable=fixme
245- # TODO How to support multiple actions + error handling?
246- actions = result ["reply" ]["actions" ]
247- for action in actions :
248- action_error = action ["error" ]
249- action_error_desc = action_error ["description" ]
250-
251- if action_error_desc == XMO_NO_ERR :
252- continue
253-
254- if action_error_desc == XMO_AUTHENTICATION_ERR :
255- raise AuthenticationException (action_error )
256-
257- if action_error_desc == XMO_ACCESS_RESTRICTION_ERR :
258- raise AccessRestrictionException (action_error )
259-
260- if action_error_desc == XMO_NON_WRITABLE_PARAMETER_ERR :
261- raise NonWritableParameterException (action_error )
262-
263- if action_error_desc == XMO_UNKNOWN_PATH_ERR :
264- raise UnknownPathException (action_error )
265-
266- if action_error_desc == XMO_MAX_SESSION_COUNT_ERR :
267- raise MaximumSessionCountException (action_error )
268-
269- if action_error_desc == XMO_LOGIN_RETRY_ERR :
270- raise LoginRetryErrorException (action_error )
271-
272- raise UnknownException (action_error )
248+ # leave this to the layer above as there may be multiple actions
249+ pass
273250
274251 return result
275252
@@ -331,7 +308,9 @@ async def login(self):
331308
332309 try :
333310 response = await self .__api_request_async ([actions ], True )
334- except TimeoutError as exception :
311+ ActionErrorHandler .throw_if (response )
312+
313+ except asyncio .TimeoutError as exception :
335314 raise LoginTimeoutException (
336315 "Login request timed-out. This could be caused by using the wrong encryption method, or using a (non) SSL connection."
337316 ) from exception
@@ -349,7 +328,8 @@ async def logout(self):
349328 """Log out of the Sagemcom F@st device."""
350329 actions = {"id" : 0 , "method" : "logOut" }
351330
352- await self .__api_request_async ([actions ], False )
331+ response = await self .__api_request_async ([actions ], False )
332+ ActionErrorHandler .throw_if (response )
353333
354334 self ._session_id = - 1
355335 self ._server_nonce = ""
@@ -390,6 +370,12 @@ async def get_encryption_method(self):
390370 on_backoff = retry_login ,
391371 )
392372 async def get_value_by_xpath (self , xpath : str , options : dict | None = None ) -> dict :
373+ async def get_value_by_xpath (
374+ self ,
375+ xpath : str ,
376+ options : dict | None = None ,
377+ suppress_action_errors : bool = False ,
378+ ) -> dict :
393379 """Retrieve raw value from router using XPath.
394380
395381 :param xpath: path expression
@@ -403,6 +389,9 @@ async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> d
403389 }
404390
405391 response = await self .__api_request_async ([actions ], False )
392+ if not suppress_action_errors :
393+ ActionErrorHandler .throw_if (response )
394+
406395 data = self .__get_response_value (response )
407396
408397 return data
@@ -419,6 +408,12 @@ async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> d
419408 on_backoff = retry_login ,
420409 )
421410 async def get_values_by_xpaths (self , xpaths , options : dict | None = None ) -> dict :
411+ async def get_values_by_xpaths (
412+ self ,
413+ xpaths ,
414+ options : dict | None = None ,
415+ suppress_action_errors : bool = False ,
416+ ) -> dict :
422417 """Retrieve raw values from router using XPath.
423418
424419 :param xpaths: Dict of key to xpath expression
@@ -435,6 +430,9 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic
435430 ]
436431
437432 response = await self .__api_request_async (actions , False )
433+ if not suppress_action_errors :
434+ ActionErrorHandler .throw_if (response )
435+
438436 values = [self .__get_response_value (response , i ) for i in range (len (xpaths ))]
439437 data = dict (zip (xpaths .keys (), values , strict = True ))
440438
@@ -467,6 +465,7 @@ async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None
467465 }
468466
469467 response = await self .__api_request_async ([actions ], False )
468+ ActionErrorHandler .throw_if (response )
470469
471470 return response
472471
@@ -495,7 +494,9 @@ async def get_device_info(self) -> DeviceInfo:
495494 "product_class" : "Device/DeviceInfo/ProductClass" ,
496495 "serial_number" : "Device/DeviceInfo/SerialNumber" ,
497496 "software_version" : "Device/DeviceInfo/SoftwareVersion" ,
498- }
497+ },
498+ # missing values converted to empty string
499+ suppress_action_errors = True
499500 )
500501 data ["manufacturer" ] = "Sagemcom"
501502
@@ -562,6 +563,8 @@ async def reboot(self):
562563 }
563564
564565 response = await self .__api_request_async ([action ], False )
566+ ActionErrorHandler .throw_if (response )
567+
565568 data = self .__get_response_value (response )
566569
567570 return data
0 commit comments