Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/retrying.py: 58%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

168 statements  

1# Copyright 2013-2014 Ray Holder 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14import logging 

15import random 

16import sys 

17import time 

18import traceback 

19from functools import wraps 

20 

21# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint... 

22MAX_WAIT = 1073741823 

23 

24 

25def _retry_if_exception_of_type(retryable_types): 

26 def _retry_if_exception_these_types(exception): 

27 return isinstance(exception, retryable_types) 

28 

29 return _retry_if_exception_these_types 

30 

31 

32def retry(*dargs, **dkw): 

33 """ 

34 Decorator function that instantiates the Retrying object 

35 @param *dargs: positional arguments passed to Retrying object 

36 @param **dkw: keyword arguments passed to the Retrying object 

37 """ 

38 # support both @retry and @retry() as valid syntax 

39 if len(dargs) == 1 and callable(dargs[0]): 

40 

41 def wrap_simple(f): 

42 @wraps(f) 

43 def wrapped_f(*args, **kw): 

44 return Retrying().call(f, *args, **kw) 

45 

46 return wrapped_f 

47 

48 return wrap_simple(dargs[0]) 

49 

50 else: 

51 

52 def wrap(f): 

53 @wraps(f) 

54 def wrapped_f(*args, **kw): 

55 return Retrying(*dargs, **dkw).call(f, *args, **kw) 

56 

57 return wrapped_f 

58 

59 return wrap 

60 

61 

62class Retrying(object): 

63 def __init__( 

64 self, 

65 stop=None, 

66 wait=None, 

67 stop_max_attempt_number=None, 

68 stop_max_delay=None, 

69 wait_fixed=None, 

70 wait_random_min=None, 

71 wait_random_max=None, 

72 wait_incrementing_start=None, 

73 wait_incrementing_increment=None, 

74 wait_incrementing_max=None, 

75 wait_exponential_multiplier=None, 

76 wait_exponential_max=None, 

77 retry_on_exception=None, 

78 retry_on_result=None, 

79 wrap_exception=False, 

80 stop_func=None, 

81 wait_func=None, 

82 wait_jitter_max=None, 

83 before_attempts=None, 

84 after_attempts=None, 

85 logger=None, 

86 ): 

87 

88 self._stop_max_attempt_number = ( 

89 5 if stop_max_attempt_number is None else stop_max_attempt_number 

90 ) 

91 self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay 

92 self._wait_fixed = 1000 if wait_fixed is None else wait_fixed 

93 self._wait_random_min = 0 if wait_random_min is None else wait_random_min 

94 self._wait_random_max = 1000 if wait_random_max is None else wait_random_max 

95 self._wait_incrementing_start = ( 

96 0 if wait_incrementing_start is None else wait_incrementing_start 

97 ) 

98 self._wait_incrementing_increment = ( 

99 100 if wait_incrementing_increment is None else wait_incrementing_increment 

100 ) 

101 self._wait_exponential_multiplier = ( 

102 1 if wait_exponential_multiplier is None else wait_exponential_multiplier 

103 ) 

104 self._wait_exponential_max = ( 

105 MAX_WAIT if wait_exponential_max is None else wait_exponential_max 

106 ) 

107 self._wait_incrementing_max = ( 

108 MAX_WAIT if wait_incrementing_max is None else wait_incrementing_max 

109 ) 

110 self._wait_jitter_max = 0 if wait_jitter_max is None else wait_jitter_max 

111 self._before_attempts = before_attempts 

112 self._after_attempts = after_attempts 

113 

114 if logger in (True, None): 

115 self._logger = logging.getLogger(__name__) 

116 if logger is None: 

117 self._logger.addHandler(logging.NullHandler()) 

118 self._logger.propagate = False 

119 elif logger: 

120 self._logger = logger 

121 

122 # TODO add chaining of stop behaviors 

123 # stop behavior 

124 stop_funcs = [] 

125 if stop_max_attempt_number is not None: 

126 stop_funcs.append(self.stop_after_attempt) 

127 

128 if stop_max_delay is not None: 

129 stop_funcs.append(self.stop_after_delay) 

130 

131 if stop_func is not None: 

132 self.stop = stop_func 

133 

134 elif stop is None: 

135 self.stop = lambda attempts, delay: any( 

136 f(attempts, delay) for f in stop_funcs 

137 ) 

138 

139 else: 

140 self.stop = getattr(self, stop) 

141 

142 # TODO add chaining of wait behaviors 

143 # wait behavior 

144 wait_funcs = [lambda *args, **kwargs: 0] 

145 if wait_fixed is not None: 

146 wait_funcs.append(self.fixed_sleep) 

147 

148 if wait_random_min is not None or wait_random_max is not None: 

149 wait_funcs.append(self.random_sleep) 

150 

151 if ( 

152 wait_incrementing_start is not None 

153 or wait_incrementing_increment is not None 

154 ): 

155 wait_funcs.append(self.incrementing_sleep) 

156 

157 if wait_exponential_multiplier is not None or wait_exponential_max is not None: 

158 wait_funcs.append(self.exponential_sleep) 

159 

160 if wait_func is not None: 

161 self.wait = wait_func 

162 

163 elif wait is None: 

164 self.wait = lambda attempts, delay: max( 

165 f(attempts, delay) for f in wait_funcs 

166 ) 

167 

168 else: 

169 self.wait = getattr(self, wait) 

170 

171 # retry on exception filter 

172 if retry_on_exception is None: 

173 self._retry_on_exception = self.always_reject 

174 else: 

175 # this allows for providing a tuple of exception types that 

176 # should be allowed to retry on, and avoids having to create 

177 # a callback that does the same thing 

178 if isinstance(retry_on_exception, (tuple, Exception)): 

179 retry_on_exception = _retry_if_exception_of_type(retry_on_exception) 

180 self._retry_on_exception = retry_on_exception 

181 

182 # retry on result filter 

183 if retry_on_result is None: 

184 self._retry_on_result = self.never_reject 

185 else: 

186 self._retry_on_result = retry_on_result 

187 

188 self._wrap_exception = wrap_exception 

189 

190 def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms): 

191 """Stop after the previous attempt >= stop_max_attempt_number.""" 

192 return previous_attempt_number >= self._stop_max_attempt_number 

193 

194 def stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms): 

195 """Stop after the time from the first attempt >= stop_max_delay.""" 

196 return delay_since_first_attempt_ms >= self._stop_max_delay 

197 

198 @staticmethod 

199 def no_sleep(previous_attempt_number, delay_since_first_attempt_ms): 

200 """Don't sleep at all before retrying.""" 

201 return 0 

202 

203 def fixed_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): 

204 """Sleep a fixed amount of time between each retry.""" 

205 return self._wait_fixed 

206 

207 def random_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): 

208 """Sleep a random amount of time between wait_random_min and wait_random_max""" 

209 return random.randint(self._wait_random_min, self._wait_random_max) 

210 

211 def incrementing_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): 

212 """ 

213 Sleep an incremental amount of time after each attempt, starting at 

214 wait_incrementing_start and incrementing by wait_incrementing_increment 

215 """ 

216 result = self._wait_incrementing_start + ( 

217 self._wait_incrementing_increment * (previous_attempt_number - 1) 

218 ) 

219 if result > self._wait_incrementing_max: 

220 result = self._wait_incrementing_max 

221 if result < 0: 

222 result = 0 

223 return result 

224 

225 def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): 

226 exp = 2**previous_attempt_number 

227 result = self._wait_exponential_multiplier * exp 

228 if result > self._wait_exponential_max: 

229 result = self._wait_exponential_max 

230 if result < 0: 

231 result = 0 

232 return result 

233 

234 @staticmethod 

235 def never_reject(result): 

236 return False 

237 

238 @staticmethod 

239 def always_reject(result): 

240 return True 

241 

242 def should_reject(self, attempt): 

243 reject = False 

244 if attempt.has_exception: 

245 reject |= self._retry_on_exception(attempt.value[1]) 

246 else: 

247 reject |= self._retry_on_result(attempt.value) 

248 

249 return reject 

250 

251 def call(self, fn, *args, **kwargs): 

252 start_time = int(round(time.time() * 1000)) 

253 attempt_number = 1 

254 while True: 

255 if self._before_attempts: 

256 self._before_attempts(attempt_number) 

257 

258 try: 

259 attempt = Attempt(fn(*args, **kwargs), attempt_number, False) 

260 except Exception: 

261 tb = sys.exc_info() 

262 attempt = Attempt(tb, attempt_number, True) 

263 

264 if not self.should_reject(attempt): 

265 return attempt.get(self._wrap_exception) 

266 

267 self._logger.warn(attempt) 

268 if self._after_attempts: 

269 self._after_attempts(attempt_number) 

270 

271 delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time 

272 if self.stop(attempt_number, delay_since_first_attempt_ms): 

273 if not self._wrap_exception and attempt.has_exception: 

274 # get() on an attempt with an exception should cause it to be raised, but raise just in case 

275 raise attempt.get() 

276 else: 

277 raise RetryError(attempt) 

278 else: 

279 sleep = self.wait(attempt_number, delay_since_first_attempt_ms) 

280 if self._wait_jitter_max: 

281 jitter = random.random() * self._wait_jitter_max 

282 sleep = sleep + max(0, jitter) 

283 self._logger.info("Retrying in {0:.2f} seconds.".format(sleep / 1000.0)) 

284 time.sleep(sleep / 1000.0) 

285 

286 attempt_number += 1 

287 

288 

289class Attempt(object): 

290 """ 

291 An Attempt encapsulates a call to a target function that may end as a 

292 normal return value from the function or an Exception depending on what 

293 occurred during the execution. 

294 """ 

295 

296 def __init__(self, value, attempt_number, has_exception): 

297 self.value = value 

298 self.attempt_number = attempt_number 

299 self.has_exception = has_exception 

300 

301 def get(self, wrap_exception=False): 

302 """ 

303 Return the return value of this Attempt instance or raise an Exception. 

304 If wrap_exception is true, this Attempt is wrapped inside of a 

305 RetryError before being raised. 

306 """ 

307 if self.has_exception: 

308 if wrap_exception: 

309 raise RetryError(self) 

310 else: 

311 exc_type, exc, tb = self.value 

312 raise exc.with_traceback(tb) 

313 else: 

314 return self.value 

315 

316 def __repr__(self): 

317 if self.has_exception: 

318 return "Attempts: {0}, Error:\n{1}".format( 

319 self.attempt_number, "".join(traceback.format_tb(self.value[2])) 

320 ) 

321 else: 

322 return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value) 

323 

324 

325class RetryError(Exception): 

326 """ 

327 A RetryError encapsulates the last Attempt instance right before giving up. 

328 """ 

329 

330 def __init__(self, last_attempt): 

331 self.last_attempt = last_attempt 

332 

333 def __str__(self): 

334 return "RetryError[{0}]".format(self.last_attempt)