33import uuid
44import urllib
55import requests
6- from .__version__ import __version__ as cxone_api_version
7- from .exceptions import AuthException , CommunicationException
6+ import logging
7+ import random
8+ from requests .exceptions import ProxyError , HTTPError , ConnectionError , ReadTimeout , ConnectTimeout
9+ from cxone_api .__version__ import __version__ as cxone_api_version
10+ from cxone_api .exceptions import AuthException , CommunicationException
811
912
1013class CxOneClient :
1114 __AGENT_NAME = 'CxOne PyClient'
1215
1316 def __common__init (self , agent_name , tenant_auth_endpoint ,
14- api_endpoint , timeout , retries , proxy , ssl_verify ):
17+ api_endpoint , timeout , retries , retry_delay_s , randomize_retry_delay , proxy , ssl_verify ):
1518
1619 self .__version = cxone_api_version
1720 self .__agent = f"{ agent_name } /({ CxOneClient .__AGENT_NAME } /{ self .__version } )"
@@ -24,16 +27,19 @@ def __common__init(self, agent_name, tenant_auth_endpoint,
2427 self .__api_endpoint = api_endpoint
2528 self .__timeout = timeout
2629 self .__retries = retries
30+ self .__retry_delay = retry_delay_s
31+ self .__randomize_retry_delay = randomize_retry_delay
2732
2833 self .__auth_result = None
2934
3035
3136 @staticmethod
3237 def create_with_oauth (oauth_id , oauth_secret , agent_name , tenant_auth_endpoint ,
33- api_endpoint , timeout = 60 , retries = 3 , proxy = None , ssl_verify = True ):
38+ api_endpoint , timeout = 60 , retries = 3 , retry_delay_s = 15 , randomize_retry_delay = True ,
39+ proxy = None , ssl_verify = True ):
3440 inst = CxOneClient ()
3541 inst .__common__init (agent_name , tenant_auth_endpoint , api_endpoint , timeout ,
36- retries , proxy , ssl_verify )
42+ retries , retry_delay_s , randomize_retry_delay , proxy , ssl_verify )
3743
3844 inst .__auth_content = urllib .parse .urlencode ( {
3945 "grant_type" : "client_credentials" ,
@@ -45,10 +51,12 @@ def create_with_oauth(oauth_id, oauth_secret, agent_name, tenant_auth_endpoint,
4551
4652 @staticmethod
4753 def create_with_api_key (api_key , agent_name , tenant_auth_endpoint ,
48- api_endpoint , timeout = 60 , retries = 3 , proxy = None , ssl_verify = True ):
54+ api_endpoint , timeout = 60 , retries = 3 , retry_delay_s = 15 , randomize_retry_delay = True ,
55+ proxy = None , ssl_verify = True ):
4956 inst = CxOneClient ()
5057 inst .__common__init (agent_name , tenant_auth_endpoint ,
51- api_endpoint , timeout , retries , proxy , ssl_verify )
58+ api_endpoint , timeout , retries , retry_delay_s ,
59+ randomize_retry_delay , proxy , ssl_verify )
5260
5361 inst .__auth_content = urllib .parse .urlencode ( {
5462 "grant_type" : "refresh_token" ,
@@ -78,24 +86,61 @@ async def __get_request_headers(self):
7886 if self .__auth_result is None :
7987 await self .__do_auth ()
8088
89+ if self .__auth_result is None :
90+ return None
91+
8192 return {
8293 "Authorization" : f"Bearer { self .__auth_result ['access_token' ]} " ,
8394 "Accept" : "*/*; version=1.0" ,
8495 "User-Agent" : self .__agent ,
8596 "CorrelationId" : self .__corelation_id
8697 }
8798
99+ async def __delay (self ):
100+ if self .__retry_delay > 0 :
101+ delay = random .randint (1 , self .__retry_delay ) if self .__randomize_retry_delay else self .__retry_delay
102+ await asyncio .sleep (delay )
103+
104+ async def __should_continue_retry (self , response : requests .Response , log : logging .Logger , try_attempt : int , exception : BaseException = None ) -> bool :
105+ if exception is not None :
106+ log .exception (exception )
107+
108+ if (response is not None and response .status_code in [500 , 502 , 503 ]) or exception is not None :
109+ if try_attempt < self .__retries - 1 :
110+ msg = ""
111+
112+ if response is not None :
113+ msg = f" after response error { response .status_code } for { response .request .method } { response .url } "
114+ elif exception is not None :
115+ msg = f" after exception { type (exception ).__name__ } ."
116+
117+ log .warning (f"Delaying before retry{ msg } " )
118+ await self .__delay ()
119+ return True
120+
121+ return False
122+
88123 async def __auth_task (self ):
89- response = None
90- for _ in range (0 , self .__retries ):
91- response = await asyncio .to_thread (requests .post , self .auth_endpoint ,
92- data = self .__auth_content , timeout = self .__timeout ,
93- proxies = self .__proxy , verify = self .__ssl_verify , headers = {
94- "Content-Type" : "application/x-www-form-urlencoded" ,
95- "Accept" : "application/json"
96- })
97- if response .ok :
98- return response .json ()
124+ _log = logging .getLogger ("CxOneClient.__auth_task" )
125+
126+ for attempt in range (0 , self .__retries ):
127+ response = None
128+ try :
129+ response = await asyncio .to_thread (requests .post , self .auth_endpoint ,
130+ data = self .__auth_content , timeout = self .__timeout ,
131+ proxies = self .__proxy , verify = self .__ssl_verify , headers = {
132+ "Content-Type" : "application/x-www-form-urlencoded" ,
133+ "Accept" : "application/json"
134+ })
135+ except (ProxyError , HTTPError , ConnectionError , ReadTimeout , ConnectTimeout ) as ex :
136+ if not await self .__should_continue_retry (response , _log , attempt , ex ):
137+ raise
138+ else :
139+ if response .ok :
140+ return response .json ()
141+
142+ if not await self .__should_continue_retry (response , _log , attempt ):
143+ break
99144
100145 raise AuthException ("CheckmarxOne response: "
101146 f"{ response .reason if not response is None else 'Unknown error' } " )
@@ -113,26 +158,35 @@ async def __do_auth(self):
113158 self .__auth_result = await self .__auth_task ()
114159
115160 async def exec_request (self , verb_func , * args , ** kwargs ):
161+ _log = logging .getLogger ("CxOneClient.exec_request" )
162+
116163 if not self .__proxy is None :
117164 kwargs ['proxies' ] = self .__proxy
118165
119166 kwargs ['verify' ] = self .__ssl_verify
120-
121- for _ in range (0 , self .__retries ):
122- auth_headers = await self .__get_request_headers ()
123-
124- if 'headers' in kwargs .keys ():
125- for h in auth_headers .keys ():
126- kwargs ['headers' ][h ] = auth_headers [h ]
167+ kwargs ['timeout' ] = self .__timeout
168+
169+ for attempt in range (0 , self .__retries ):
170+ response = None
171+ try :
172+ auth_headers = await self .__get_request_headers ()
173+
174+ if 'headers' in kwargs .keys ():
175+ for h in auth_headers .keys ():
176+ kwargs ['headers' ][h ] = auth_headers [h ]
177+ else :
178+ kwargs ['headers' ] = auth_headers
179+
180+ response = await asyncio .to_thread (verb_func , * args , ** kwargs )
181+ except (ProxyError , HTTPError , ConnectionError , ReadTimeout , ConnectTimeout ) as ex :
182+ if not await self .__should_continue_retry (response , _log , attempt , ex ):
183+ raise
127184 else :
128- kwargs [ 'headers' ] = auth_headers
129-
130- response = await asyncio . to_thread ( verb_func , * args , ** kwargs )
185+ if response . status_code == 401 :
186+ await self . __do_auth ()
187+ continue
131188
132- if response .status_code == 401 :
133- await self .__do_auth ()
134- else :
135- return response
189+ if response .ok or not await self .__should_continue_retry (response , _log , attempt ):
190+ return response
136191
137192 raise CommunicationException (verb_func , * args , ** kwargs )
138-
0 commit comments