Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/tenacity/__init__.py: 51%

275 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:48 +0000

1# Copyright 2016-2018 Julien Danjou 

2# Copyright 2017 Elisey Zanko 

3# Copyright 2016 Étienne Bersac 

4# Copyright 2016 Joshua Harlow 

5# Copyright 2013-2014 Ray Holder 

6# 

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

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

9# You may obtain a copy of the License at 

10# 

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

12# 

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

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

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

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

17# limitations under the License. 

18 

19 

20import functools 

21import sys 

22import threading 

23import time 

24import typing as t 

25import warnings 

26from abc import ABC, abstractmethod 

27from concurrent import futures 

28from inspect import iscoroutinefunction 

29 

30# Import all built-in retry strategies for easier usage. 

31from .retry import retry_base # noqa 

32from .retry import retry_all # noqa 

33from .retry import retry_always # noqa 

34from .retry import retry_any # noqa 

35from .retry import retry_if_exception # noqa 

36from .retry import retry_if_exception_type # noqa 

37from .retry import retry_if_exception_cause_type # noqa 

38from .retry import retry_if_not_exception_type # noqa 

39from .retry import retry_if_not_result # noqa 

40from .retry import retry_if_result # noqa 

41from .retry import retry_never # noqa 

42from .retry import retry_unless_exception_type # noqa 

43from .retry import retry_if_exception_message # noqa 

44from .retry import retry_if_not_exception_message # noqa 

45 

46# Import all nap strategies for easier usage. 

47from .nap import sleep # noqa 

48from .nap import sleep_using_event # noqa 

49 

50# Import all built-in stop strategies for easier usage. 

51from .stop import stop_after_attempt # noqa 

52from .stop import stop_after_delay # noqa 

53from .stop import stop_all # noqa 

54from .stop import stop_any # noqa 

55from .stop import stop_never # noqa 

56from .stop import stop_when_event_set # noqa 

57 

58# Import all built-in wait strategies for easier usage. 

59from .wait import wait_chain # noqa 

60from .wait import wait_combine # noqa 

61from .wait import wait_exponential # noqa 

62from .wait import wait_fixed # noqa 

63from .wait import wait_incrementing # noqa 

64from .wait import wait_none # noqa 

65from .wait import wait_random # noqa 

66from .wait import wait_random_exponential # noqa 

67from .wait import wait_random_exponential as wait_full_jitter # noqa 

68from .wait import wait_exponential_jitter # noqa 

69 

70# Import all built-in before strategies for easier usage. 

71from .before import before_log # noqa 

72from .before import before_nothing # noqa 

73 

74# Import all built-in after strategies for easier usage. 

75from .after import after_log # noqa 

76from .after import after_nothing # noqa 

77 

78# Import all built-in after strategies for easier usage. 

79from .before_sleep import before_sleep_log # noqa 

80from .before_sleep import before_sleep_nothing # noqa 

81 

82# Replace a conditional import with a hard-coded None so that pip does 

83# not attempt to use tornado even if it is present in the environment. 

84# If tornado is non-None, tenacity will attempt to execute some code 

85# that is sensitive to the version of tornado, which could break pip 

86# if an old version is found. 

87tornado = None # type: ignore 

88 

89if t.TYPE_CHECKING: 

90 import types 

91 

92 from .retry import RetryBaseT 

93 from .stop import StopBaseT 

94 from .wait import WaitBaseT 

95 

96 

97WrappedFnReturnT = t.TypeVar("WrappedFnReturnT") 

98WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any]) 

99 

100 

101class TryAgain(Exception): 

102 """Always retry the executed function when raised.""" 

103 

104 

105NO_RESULT = object() 

106 

107 

108class DoAttempt: 

109 pass 

110 

111 

112class DoSleep(float): 

113 pass 

114 

115 

116class BaseAction: 

117 """Base class for representing actions to take by retry object. 

118 

119 Concrete implementations must define: 

120 - __init__: to initialize all necessary fields 

121 - REPR_FIELDS: class variable specifying attributes to include in repr(self) 

122 - NAME: for identification in retry object methods and callbacks 

123 """ 

124 

125 REPR_FIELDS: t.Sequence[str] = () 

126 NAME: t.Optional[str] = None 

127 

128 def __repr__(self) -> str: 

129 state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS) 

130 return f"{self.__class__.__name__}({state_str})" 

131 

132 def __str__(self) -> str: 

133 return repr(self) 

134 

135 

136class RetryAction(BaseAction): 

137 REPR_FIELDS = ("sleep",) 

138 NAME = "retry" 

139 

140 def __init__(self, sleep: t.SupportsFloat) -> None: 

141 self.sleep = float(sleep) 

142 

143 

144_unset = object() 

145 

146 

147def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any: 

148 return second if first is _unset else first 

149 

150 

151class RetryError(Exception): 

152 """Encapsulates the last attempt instance right before giving up.""" 

153 

154 def __init__(self, last_attempt: "Future") -> None: 

155 self.last_attempt = last_attempt 

156 super().__init__(last_attempt) 

157 

158 def reraise(self) -> "t.NoReturn": 

159 if self.last_attempt.failed: 

160 raise self.last_attempt.result() 

161 raise self 

162 

163 def __str__(self) -> str: 

164 return f"{self.__class__.__name__}[{self.last_attempt}]" 

165 

166 

167class AttemptManager: 

168 """Manage attempt context.""" 

169 

170 def __init__(self, retry_state: "RetryCallState"): 

171 self.retry_state = retry_state 

172 

173 def __enter__(self) -> None: 

174 pass 

175 

176 def __exit__( 

177 self, 

178 exc_type: t.Optional[t.Type[BaseException]], 

179 exc_value: t.Optional[BaseException], 

180 traceback: t.Optional["types.TracebackType"], 

181 ) -> t.Optional[bool]: 

182 if exc_type is not None and exc_value is not None: 

183 self.retry_state.set_exception((exc_type, exc_value, traceback)) 

184 return True # Swallow exception. 

185 else: 

186 # We don't have the result, actually. 

187 self.retry_state.set_result(None) 

188 return None 

189 

190 

191class BaseRetrying(ABC): 

192 def __init__( 

193 self, 

194 sleep: t.Callable[[t.Union[int, float]], None] = sleep, 

195 stop: "StopBaseT" = stop_never, 

196 wait: "WaitBaseT" = wait_none(), 

197 retry: "RetryBaseT" = retry_if_exception_type(), 

198 before: t.Callable[["RetryCallState"], None] = before_nothing, 

199 after: t.Callable[["RetryCallState"], None] = after_nothing, 

200 before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None, 

201 reraise: bool = False, 

202 retry_error_cls: t.Type[RetryError] = RetryError, 

203 retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, 

204 ): 

205 self.sleep = sleep 

206 self.stop = stop 

207 self.wait = wait 

208 self.retry = retry 

209 self.before = before 

210 self.after = after 

211 self.before_sleep = before_sleep 

212 self.reraise = reraise 

213 self._local = threading.local() 

214 self.retry_error_cls = retry_error_cls 

215 self.retry_error_callback = retry_error_callback 

216 

217 def copy( 

218 self, 

219 sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset, 

220 stop: t.Union["StopBaseT", object] = _unset, 

221 wait: t.Union["WaitBaseT", object] = _unset, 

222 retry: t.Union[retry_base, object] = _unset, 

223 before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, 

224 after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, 

225 before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset, 

226 reraise: t.Union[bool, object] = _unset, 

227 retry_error_cls: t.Union[t.Type[RetryError], object] = _unset, 

228 retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset, 

229 ) -> "BaseRetrying": 

230 """Copy this object with some parameters changed if needed.""" 

231 return self.__class__( 

232 sleep=_first_set(sleep, self.sleep), 

233 stop=_first_set(stop, self.stop), 

234 wait=_first_set(wait, self.wait), 

235 retry=_first_set(retry, self.retry), 

236 before=_first_set(before, self.before), 

237 after=_first_set(after, self.after), 

238 before_sleep=_first_set(before_sleep, self.before_sleep), 

239 reraise=_first_set(reraise, self.reraise), 

240 retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls), 

241 retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback), 

242 ) 

243 

244 def __repr__(self) -> str: 

245 return ( 

246 f"<{self.__class__.__name__} object at 0x{id(self):x} (" 

247 f"stop={self.stop}, " 

248 f"wait={self.wait}, " 

249 f"sleep={self.sleep}, " 

250 f"retry={self.retry}, " 

251 f"before={self.before}, " 

252 f"after={self.after})>" 

253 ) 

254 

255 @property 

256 def statistics(self) -> t.Dict[str, t.Any]: 

257 """Return a dictionary of runtime statistics. 

258 

259 This dictionary will be empty when the controller has never been 

260 ran. When it is running or has ran previously it should have (but 

261 may not) have useful and/or informational keys and values when 

262 running is underway and/or completed. 

263 

264 .. warning:: The keys in this dictionary **should** be some what 

265 stable (not changing), but there existence **may** 

266 change between major releases as new statistics are 

267 gathered or removed so before accessing keys ensure that 

268 they actually exist and handle when they do not. 

269 

270 .. note:: The values in this dictionary are local to the thread 

271 running call (so if multiple threads share the same retrying 

272 object - either directly or indirectly) they will each have 

273 there own view of statistics they have collected (in the 

274 future we may provide a way to aggregate the various 

275 statistics from each thread). 

276 """ 

277 try: 

278 return self._local.statistics # type: ignore[no-any-return] 

279 except AttributeError: 

280 self._local.statistics = t.cast(t.Dict[str, t.Any], {}) 

281 return self._local.statistics 

282 

283 def wraps(self, f: WrappedFn) -> WrappedFn: 

284 """Wrap a function for retrying. 

285 

286 :param f: A function to wraps for retrying. 

287 """ 

288 

289 @functools.wraps(f) 

290 def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: 

291 return self(f, *args, **kw) 

292 

293 def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn: 

294 return self.copy(*args, **kwargs).wraps(f) 

295 

296 wrapped_f.retry = self # type: ignore[attr-defined] 

297 wrapped_f.retry_with = retry_with # type: ignore[attr-defined] 

298 

299 return wrapped_f # type: ignore[return-value] 

300 

301 def begin(self) -> None: 

302 self.statistics.clear() 

303 self.statistics["start_time"] = time.monotonic() 

304 self.statistics["attempt_number"] = 1 

305 self.statistics["idle_for"] = 0 

306 

307 def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa 

308 fut = retry_state.outcome 

309 if fut is None: 

310 if self.before is not None: 

311 self.before(retry_state) 

312 return DoAttempt() 

313 

314 is_explicit_retry = fut.failed and isinstance(fut.exception(), TryAgain) 

315 if not (is_explicit_retry or self.retry(retry_state)): 

316 return fut.result() 

317 

318 if self.after is not None: 

319 self.after(retry_state) 

320 

321 self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start 

322 if self.stop(retry_state): 

323 if self.retry_error_callback: 

324 return self.retry_error_callback(retry_state) 

325 retry_exc = self.retry_error_cls(fut) 

326 if self.reraise: 

327 raise retry_exc.reraise() 

328 raise retry_exc from fut.exception() 

329 

330 if self.wait: 

331 sleep = self.wait(retry_state) 

332 else: 

333 sleep = 0.0 

334 retry_state.next_action = RetryAction(sleep) 

335 retry_state.idle_for += sleep 

336 self.statistics["idle_for"] += sleep 

337 self.statistics["attempt_number"] += 1 

338 

339 if self.before_sleep is not None: 

340 self.before_sleep(retry_state) 

341 

342 return DoSleep(sleep) 

343 

344 def __iter__(self) -> t.Generator[AttemptManager, None, None]: 

345 self.begin() 

346 

347 retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) 

348 while True: 

349 do = self.iter(retry_state=retry_state) 

350 if isinstance(do, DoAttempt): 

351 yield AttemptManager(retry_state=retry_state) 

352 elif isinstance(do, DoSleep): 

353 retry_state.prepare_for_next_attempt() 

354 self.sleep(do) 

355 else: 

356 break 

357 

358 @abstractmethod 

359 def __call__( 

360 self, 

361 fn: t.Callable[..., WrappedFnReturnT], 

362 *args: t.Any, 

363 **kwargs: t.Any, 

364 ) -> WrappedFnReturnT: 

365 pass 

366 

367 

368class Retrying(BaseRetrying): 

369 """Retrying controller.""" 

370 

371 def __call__( 

372 self, 

373 fn: t.Callable[..., WrappedFnReturnT], 

374 *args: t.Any, 

375 **kwargs: t.Any, 

376 ) -> WrappedFnReturnT: 

377 self.begin() 

378 

379 retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) 

380 while True: 

381 do = self.iter(retry_state=retry_state) 

382 if isinstance(do, DoAttempt): 

383 try: 

384 result = fn(*args, **kwargs) 

385 except BaseException: # noqa: B902 

386 retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type] 

387 else: 

388 retry_state.set_result(result) 

389 elif isinstance(do, DoSleep): 

390 retry_state.prepare_for_next_attempt() 

391 self.sleep(do) 

392 else: 

393 return do # type: ignore[no-any-return] 

394 

395 

396if sys.version_info[1] >= 9: 

397 FutureGenericT = futures.Future[t.Any] 

398else: 

399 FutureGenericT = futures.Future 

400 

401 

402class Future(FutureGenericT): 

403 """Encapsulates a (future or past) attempted call to a target function.""" 

404 

405 def __init__(self, attempt_number: int) -> None: 

406 super().__init__() 

407 self.attempt_number = attempt_number 

408 

409 @property 

410 def failed(self) -> bool: 

411 """Return whether a exception is being held in this future.""" 

412 return self.exception() is not None 

413 

414 @classmethod 

415 def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future": 

416 """Construct a new Future object.""" 

417 fut = cls(attempt_number) 

418 if has_exception: 

419 fut.set_exception(value) 

420 else: 

421 fut.set_result(value) 

422 return fut 

423 

424 

425class RetryCallState: 

426 """State related to a single call wrapped with Retrying.""" 

427 

428 def __init__( 

429 self, 

430 retry_object: BaseRetrying, 

431 fn: t.Optional[WrappedFn], 

432 args: t.Any, 

433 kwargs: t.Any, 

434 ) -> None: 

435 #: Retry call start timestamp 

436 self.start_time = time.monotonic() 

437 #: Retry manager object 

438 self.retry_object = retry_object 

439 #: Function wrapped by this retry call 

440 self.fn = fn 

441 #: Arguments of the function wrapped by this retry call 

442 self.args = args 

443 #: Keyword arguments of the function wrapped by this retry call 

444 self.kwargs = kwargs 

445 

446 #: The number of the current attempt 

447 self.attempt_number: int = 1 

448 #: Last outcome (result or exception) produced by the function 

449 self.outcome: t.Optional[Future] = None 

450 #: Timestamp of the last outcome 

451 self.outcome_timestamp: t.Optional[float] = None 

452 #: Time spent sleeping in retries 

453 self.idle_for: float = 0.0 

454 #: Next action as decided by the retry manager 

455 self.next_action: t.Optional[RetryAction] = None 

456 

457 @property 

458 def seconds_since_start(self) -> t.Optional[float]: 

459 if self.outcome_timestamp is None: 

460 return None 

461 return self.outcome_timestamp - self.start_time 

462 

463 def prepare_for_next_attempt(self) -> None: 

464 self.outcome = None 

465 self.outcome_timestamp = None 

466 self.attempt_number += 1 

467 self.next_action = None 

468 

469 def set_result(self, val: t.Any) -> None: 

470 ts = time.monotonic() 

471 fut = Future(self.attempt_number) 

472 fut.set_result(val) 

473 self.outcome, self.outcome_timestamp = fut, ts 

474 

475 def set_exception( 

476 self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"] 

477 ) -> None: 

478 ts = time.monotonic() 

479 fut = Future(self.attempt_number) 

480 fut.set_exception(exc_info[1]) 

481 self.outcome, self.outcome_timestamp = fut, ts 

482 

483 def __repr__(self) -> str: 

484 if self.outcome is None: 

485 result = "none yet" 

486 elif self.outcome.failed: 

487 exception = self.outcome.exception() 

488 result = f"failed ({exception.__class__.__name__} {exception})" 

489 else: 

490 result = f"returned {self.outcome.result()}" 

491 

492 slept = float(round(self.idle_for, 2)) 

493 clsname = self.__class__.__name__ 

494 return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>" 

495 

496 

497@t.overload 

498def retry(func: WrappedFn) -> WrappedFn: 

499 ... 

500 

501 

502@t.overload 

503def retry( 

504 sleep: t.Callable[[t.Union[int, float]], None] = sleep, 

505 stop: "StopBaseT" = stop_never, 

506 wait: "WaitBaseT" = wait_none(), 

507 retry: "RetryBaseT" = retry_if_exception_type(), 

508 before: t.Callable[["RetryCallState"], None] = before_nothing, 

509 after: t.Callable[["RetryCallState"], None] = after_nothing, 

510 before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None, 

511 reraise: bool = False, 

512 retry_error_cls: t.Type["RetryError"] = RetryError, 

513 retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, 

514) -> t.Callable[[WrappedFn], WrappedFn]: 

515 ... 

516 

517 

518def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any: 

519 """Wrap a function with a new `Retrying` object. 

520 

521 :param dargs: positional arguments passed to Retrying object 

522 :param dkw: keyword arguments passed to the Retrying object 

523 """ 

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

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

526 return retry()(dargs[0]) 

527 else: 

528 

529 def wrap(f: WrappedFn) -> WrappedFn: 

530 if isinstance(f, retry_base): 

531 warnings.warn( 

532 f"Got retry_base instance ({f.__class__.__name__}) as callable argument, " 

533 f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)" 

534 ) 

535 r: "BaseRetrying" 

536 if iscoroutinefunction(f): 

537 r = AsyncRetrying(*dargs, **dkw) 

538 elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f): 

539 r = TornadoRetrying(*dargs, **dkw) 

540 else: 

541 r = Retrying(*dargs, **dkw) 

542 

543 return r.wraps(f) 

544 

545 return wrap 

546 

547 

548from pip._vendor.tenacity._asyncio import AsyncRetrying # noqa:E402,I100 

549 

550if tornado: 

551 from pip._vendor.tenacity.tornadoweb import TornadoRetrying 

552 

553 

554__all__ = [ 

555 "retry_base", 

556 "retry_all", 

557 "retry_always", 

558 "retry_any", 

559 "retry_if_exception", 

560 "retry_if_exception_type", 

561 "retry_if_exception_cause_type", 

562 "retry_if_not_exception_type", 

563 "retry_if_not_result", 

564 "retry_if_result", 

565 "retry_never", 

566 "retry_unless_exception_type", 

567 "retry_if_exception_message", 

568 "retry_if_not_exception_message", 

569 "sleep", 

570 "sleep_using_event", 

571 "stop_after_attempt", 

572 "stop_after_delay", 

573 "stop_all", 

574 "stop_any", 

575 "stop_never", 

576 "stop_when_event_set", 

577 "wait_chain", 

578 "wait_combine", 

579 "wait_exponential", 

580 "wait_fixed", 

581 "wait_incrementing", 

582 "wait_none", 

583 "wait_random", 

584 "wait_random_exponential", 

585 "wait_full_jitter", 

586 "wait_exponential_jitter", 

587 "before_log", 

588 "before_nothing", 

589 "after_log", 

590 "after_nothing", 

591 "before_sleep_log", 

592 "before_sleep_nothing", 

593 "retry", 

594 "WrappedFn", 

595 "TryAgain", 

596 "NO_RESULT", 

597 "DoAttempt", 

598 "DoSleep", 

599 "BaseAction", 

600 "RetryAction", 

601 "RetryError", 

602 "AttemptManager", 

603 "BaseRetrying", 

604 "Retrying", 

605 "Future", 

606 "RetryCallState", 

607 "AsyncRetrying", 

608]