22
33import logging
44import ssl
5+ import time
56from abc import abstractmethod
67from collections .abc import Callable
78
8- from attrs import define , field
9- from attrs .setters import frozen
10-
119import requests
1210import urllib3
13-
11+ from attrs import define , field
12+ from attrs .setters import frozen
1413
1514from server_tech .helpers .errors import (
1615 BaseServerTechError ,
1716 RESTAPIServerTechError ,
17+ RESTAPIUnavailableServerTechError ,
1818)
1919
2020logger = logging .getLogger (__name__ )
@@ -127,76 +127,100 @@ def _do_delete(
127127@define
128128class ServerTechAPI (BaseAPIClient ):
129129 BASE_ERRORS = {
130- 404 : RESTAPIServerTechError ,
130+ 404 : RESTAPIUnavailableServerTechError ,
131131 405 : RESTAPIServerTechError ,
132132 503 : RESTAPIServerTechError ,
133133 }
134134 """
135135 404 NOT FOUND Requested resource does not exist or is unavailable
136136 405 METHOD NOT ALLOWED Requested method was not permitted
137- 503 SERVICE UNAVAILABLE The server is too busy to send the resource or resource collection
137+ 503 SERVICE UNAVAILABLE The server is too busy to send the resource or resource collection # noqa E501
138138 """
139139
140+ class Decorators :
141+ @classmethod
142+ def get_data (
143+ cls , retries : int = 6 , timeout : int = 5 , raise_on_timeout : bool = True
144+ ):
145+ def wrapper (decorated ):
146+ def inner (* args , ** kwargs ):
147+ exception = None
148+ attempt = 0
149+ while attempt < retries :
150+ try :
151+ response = decorated (* args , ** kwargs )
152+ if response :
153+ return response .json ()
154+ else :
155+ return response
156+ except RESTAPIUnavailableServerTechError as e :
157+ exception = e
158+ time .sleep (timeout )
159+ attempt += 1
160+
161+ if raise_on_timeout :
162+ if exception :
163+ raise exception
164+ else :
165+ raise RESTAPIServerTechError (
166+ f"Cannot execute request for { retries * timeout } sec."
167+ )
168+
169+ return inner
170+
171+ return wrapper
172+
140173 def _base_url (self ):
141- # return f"{self.scheme}://{self.address}/jaws"
142174 return f"{ self .scheme } ://{ self .address } :{ self .port } /jaws"
143175
144- def get_pdu_info ( self ) -> dict [ str , str ]:
145- """Get information about outlets."""
146- pdu_info = {}
176+ @ Decorators . get_data ()
177+ def get_pdu_units_info ( self ) -> requests . Response :
178+ """Get information about PDU units."""
147179 error_map = {}
148180
149181 units_data = self ._do_get (
150- path = f"config/info/units" ,
151- http_error_map = {** self .BASE_ERRORS , ** error_map }
152- ).json ()
153-
154- for unit in units_data :
155- pdu_info .update (
156- {
157- "model" : unit .get ("model_number" , "" ),
158- "serial" : unit .get ("product_serial_number" , "" ),
159- }
160- )
182+ path = "config/info/units" , http_error_map = {** self .BASE_ERRORS , ** error_map }
183+ )
184+
185+ return units_data
186+
187+ @Decorators .get_data ()
188+ def get_pdu_system_info (self ) -> requests .Response :
189+ """Get basic information about PDU."""
190+ error_map = {}
161191
162192 system_data = self ._do_get (
163- path = f"config/info/system" ,
164- http_error_map = {** self .BASE_ERRORS , ** error_map }
165- ).json ()
166- pdu_info .update ({"fw" : system_data .get ("firmware" , "" )})
167- return pdu_info
193+ path = "config/info/system" , http_error_map = {** self .BASE_ERRORS , ** error_map }
194+ )
195+
196+ return system_data
168197
169- def get_outlets (self ) -> dict [str , str ]:
198+ @Decorators .get_data ()
199+ def get_outlets (self ) -> requests .Response :
170200 """Get information about outlets."""
171201 error_map = {}
172- outlets_info = {}
173202
174- response = self ._do_get (
175- path = f"control/outlets" ,
176- http_error_map = {** self .BASE_ERRORS , ** error_map }
203+ outlets_info = self ._do_get (
204+ path = "control/outlets" , http_error_map = {** self .BASE_ERRORS , ** error_map }
177205 )
178- for data in response .json ():
179- outlets_info .update ({data ["id" ]: data ["control_state" ]})
180206
181207 return outlets_info
182208
183- def set_outlet_state (self , outlet_id : str , outlet_state : str ) -> None :
209+ @Decorators .get_data ()
210+ def set_outlet_state (self , outlet_id : str , outlet_state : str ) -> requests .Response :
184211 """Set outlet state.
185212
186213 Possible outlet states could be on/off/reboot.
187214 """
188- error_map = {
189- 400 : RESTAPIServerTechError ,
190- 409 : RESTAPIServerTechError
191- }
215+ error_map = {400 : RESTAPIServerTechError , 409 : RESTAPIServerTechError }
192216 """
193- 400 BAD REQUEST Malformed patch document; a required patch object member is missing OR an unsupported operation was included.
217+ 400 BAD REQUEST Malformed patch document; a required patch object member is missing OR an unsupported operation was included. # noqa E501
194218 409 CONFLICT Property specified for updating does not exist in resource
195219 """
196- self ._do_patch (
220+ return self ._do_patch (
197221 path = f"control/outlets/{ outlet_id } " ,
198222 json = {"control_action" : outlet_state },
199- http_error_map = {** self .BASE_ERRORS , ** error_map }
223+ http_error_map = {** self .BASE_ERRORS , ** error_map },
200224 )
201225
202226
@@ -217,7 +241,7 @@ def set_outlet_state(self, outlet_id: str, outlet_state: str) -> None:
217241
218242POST
219243201 CREATED Resource created successfully.
220- 400 BAD REQUEST Message contained either bad values (e.g. out of range) for properties, or non-existent properties
244+ 400 BAD REQUEST Message contained either bad values (e.g. out of range) for properties, or non-existent properties # noqa E501
221245404 NOT FOUND Requested resource collection does not exist or is unavailable
222246405 METHOD NOT ALLOWED Requested method was not permitted
223247409 CONFLICT Requested resource already exists
0 commit comments