Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/api_core/retry/retry_unary.py: 40%

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

45 statements  

1# Copyright 2017 Google LLC 

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. 

14 

15"""Helpers for retrying functions with exponential back-off. 

16 

17The :class:`Retry` decorator can be used to retry functions that raise 

18exceptions using exponential backoff. Because a exponential sleep algorithm is 

19used, the retry is limited by a `timeout`. The timeout determines the window 

20in which retries will be attempted. This is used instead of total number of retries 

21because it is difficult to ascertain the amount of time a function can block 

22when using total number of retries and exponential backoff. 

23 

24By default, this decorator will retry transient 

25API errors (see :func:`if_transient_error`). For example: 

26 

27.. code-block:: python 

28 

29 @retry.Retry() 

30 def call_flaky_rpc(): 

31 return client.flaky_rpc() 

32 

33 # Will retry flaky_rpc() if it raises transient API errors. 

34 result = call_flaky_rpc() 

35 

36You can pass a custom predicate to retry on different exceptions, such as 

37waiting for an eventually consistent item to be available: 

38 

39.. code-block:: python 

40 

41 @retry.Retry(predicate=if_exception_type(exceptions.NotFound)) 

42 def check_if_exists(): 

43 return client.does_thing_exist() 

44 

45 is_available = check_if_exists() 

46 

47Some client library methods apply retry automatically. These methods can accept 

48a ``retry`` parameter that allows you to configure the behavior: 

49 

50.. code-block:: python 

51 

52 my_retry = retry.Retry(timeout=60) 

53 result = client.some_method(retry=my_retry) 

54 

55""" 

56 

57from __future__ import annotations 

58 

59import functools 

60import sys 

61import time 

62import inspect 

63import warnings 

64from typing import Any, Callable, Iterable, TypeVar, TYPE_CHECKING 

65 

66from google.api_core.retry.retry_base import _BaseRetry 

67from google.api_core.retry.retry_base import _retry_error_helper 

68from google.api_core.retry.retry_base import exponential_sleep_generator 

69from google.api_core.retry.retry_base import build_retry_error 

70from google.api_core.retry.retry_base import RetryFailureReason 

71 

72 

73if TYPE_CHECKING: 

74 if sys.version_info >= (3, 10): 

75 from typing import ParamSpec 

76 else: 

77 from typing_extensions import ParamSpec 

78 

79 _P = ParamSpec("_P") # target function call parameters 

80 _R = TypeVar("_R") # target function returned value 

81 

82_ASYNC_RETRY_WARNING = "Using the synchronous google.api_core.retry.Retry with asynchronous calls may lead to unexpected results. Please use google.api_core.retry_async.AsyncRetry instead." 

83 

84 

85def retry_target( 

86 target: Callable[[], _R], 

87 predicate: Callable[[Exception], bool], 

88 sleep_generator: Iterable[float], 

89 timeout: float | None = None, 

90 on_error: Callable[[Exception], None] | None = None, 

91 exception_factory: Callable[ 

92 [list[Exception], RetryFailureReason, float | None], 

93 tuple[Exception, Exception | None], 

94 ] = build_retry_error, 

95 **kwargs, 

96): 

97 """Call a function and retry if it fails. 

98 

99 This is the lowest-level retry helper. Generally, you'll use the 

100 higher-level retry helper :class:`Retry`. 

101 

102 Args: 

103 target(Callable): The function to call and retry. This must be a 

104 nullary function - apply arguments with `functools.partial`. 

105 predicate (Callable[Exception]): A callable used to determine if an 

106 exception raised by the target should be considered retryable. 

107 It should return True to retry or False otherwise. 

108 sleep_generator (Iterable[float]): An infinite iterator that determines 

109 how long to sleep between retries. 

110 timeout (Optional[float]): How long to keep retrying the target. 

111 Note: timeout is only checked before initiating a retry, so the target may 

112 run past the timeout value as long as it is healthy. 

113 on_error (Optional[Callable[Exception]]): If given, the on_error 

114 callback will be called with each retryable exception raised by the 

115 target. Any error raised by this function will *not* be caught. 

116 exception_factory: A function that is called when the retryable reaches 

117 a terminal failure state, used to construct an exception to be raised. 

118 It takes a list of all exceptions encountered, a retry.RetryFailureReason 

119 enum indicating the failure cause, and the original timeout value 

120 as arguments. It should return a tuple of the exception to be raised, 

121 along with the cause exception if any. The default implementation will raise 

122 a RetryError on timeout, or the last exception encountered otherwise. 

123 deadline (float): DEPRECATED: use ``timeout`` instead. For backward 

124 compatibility, if specified it will override ``timeout`` parameter. 

125 

126 Returns: 

127 Any: the return value of the target function. 

128 

129 Raises: 

130 ValueError: If the sleep generator stops yielding values. 

131 Exception: a custom exception specified by the exception_factory if provided. 

132 If no exception_factory is provided: 

133 google.api_core.RetryError: If the timeout is exceeded while retrying. 

134 Exception: If the target raises an error that isn't retryable. 

135 """ 

136 

137 timeout = kwargs.get("deadline", timeout) 

138 

139 deadline = time.monotonic() + timeout if timeout is not None else None 

140 error_list: list[Exception] = [] 

141 sleep_iter = iter(sleep_generator) 

142 

143 # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper 

144 # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535 

145 while True: 

146 try: 

147 result = target() 

148 if inspect.isawaitable(result): 

149 warnings.warn(_ASYNC_RETRY_WARNING) 

150 return result 

151 

152 # pylint: disable=broad-except 

153 # This function explicitly must deal with broad exceptions. 

154 except Exception as exc: 

155 # defer to shared logic for handling errors 

156 next_sleep = _retry_error_helper( 

157 exc, 

158 deadline, 

159 sleep_iter, 

160 error_list, 

161 predicate, 

162 on_error, 

163 exception_factory, 

164 timeout, 

165 ) 

166 # if exception not raised, sleep before next attempt 

167 time.sleep(next_sleep) 

168 

169 

170class Retry(_BaseRetry): 

171 """Exponential retry decorator for unary synchronous RPCs. 

172 

173 This class is a decorator used to add retry or polling behavior to an RPC 

174 call. 

175 

176 Although the default behavior is to retry transient API errors, a 

177 different predicate can be provided to retry other exceptions. 

178 

179 There are two important concepts that retry/polling behavior may operate on, 

180 Deadline and Timeout, which need to be properly defined for the correct 

181 usage of this class and the rest of the library. 

182 

183 Deadline: a fixed point in time by which a certain operation must 

184 terminate. For example, if a certain operation has a deadline 

185 "2022-10-18T23:30:52.123Z" it must terminate (successfully or with an 

186 error) by that time, regardless of when it was started or whether it 

187 was started at all. 

188 

189 Timeout: the maximum duration of time after which a certain operation 

190 must terminate (successfully or with an error). The countdown begins right 

191 after an operation was started. For example, if an operation was started at 

192 09:24:00 with timeout of 75 seconds, it must terminate no later than 

193 09:25:15. 

194 

195 Unfortunately, in the past this class (and the api-core library as a whole) has not 

196 been properly distinguishing the concepts of "timeout" and "deadline", and the 

197 ``deadline`` parameter has meant ``timeout``. That is why 

198 ``deadline`` has been deprecated and ``timeout`` should be used instead. If the 

199 ``deadline`` parameter is set, it will override the ``timeout`` parameter. 

200 In other words, ``retry.deadline`` should be treated as just a deprecated alias for 

201 ``retry.timeout``. 

202 

203 Said another way, it is safe to assume that this class and the rest of this 

204 library operate in terms of timeouts (not deadlines) unless explicitly 

205 noted the usage of deadline semantics. 

206 

207 It is also important to 

208 understand the three most common applications of the Timeout concept in the 

209 context of this library. 

210 

211 Usually the generic Timeout term may stand for one of the following actual 

212 timeouts: RPC Timeout, Retry Timeout, or Polling Timeout. 

213 

214 RPC Timeout: a value supplied by the client to the server so 

215 that the server side knows the maximum amount of time it is expected to 

216 spend handling that specific RPC. For example, in the case of gRPC transport, 

217 RPC Timeout is represented by setting "grpc-timeout" header in the HTTP2 

218 request. The `timeout` property of this class normally never represents the 

219 RPC Timeout as it is handled separately by the ``google.api_core.timeout`` 

220 module of this library. 

221 

222 Retry Timeout: this is the most common meaning of the ``timeout`` property 

223 of this class, and defines how long a certain RPC may be retried in case 

224 the server returns an error. 

225 

226 Polling Timeout: defines how long the 

227 client side is allowed to call the polling RPC repeatedly to check a status of a 

228 long-running operation. Each polling RPC is 

229 expected to succeed (its errors are supposed to be handled by the retry 

230 logic). The decision as to whether a new polling attempt needs to be made is based 

231 not on the RPC status code but on the status of the returned 

232 status of an operation. In other words: we will poll a long-running operation until 

233 the operation is done or the polling timeout expires. Each poll will inform us of 

234 the status of the operation. The poll consists of an RPC to the server that may 

235 itself be retried as per the poll-specific retry settings in case of errors. The 

236 operation-level retry settings do NOT apply to polling-RPC retries. 

237 

238 With the actual timeout types being defined above, the client libraries 

239 often refer to just Timeout without clarifying which type specifically 

240 that is. In that case the actual timeout type (sometimes also referred to as 

241 Logical Timeout) can be determined from the context. If it is a unary rpc 

242 call (i.e. a regular one) Timeout usually stands for the RPC Timeout (if 

243 provided directly as a standalone value) or Retry Timeout (if provided as 

244 ``retry.timeout`` property of the unary RPC's retry config). For 

245 ``Operation`` or ``PollingFuture`` in general Timeout stands for 

246 Polling Timeout. 

247 

248 Args: 

249 predicate (Callable[Exception]): A callable that should return ``True`` 

250 if the given exception is retryable. 

251 initial (float): The minimum amount of time to delay in seconds. This 

252 must be greater than 0. 

253 maximum (float): The maximum amount of time to delay in seconds. 

254 multiplier (float): The multiplier applied to the delay. 

255 timeout (Optional[float]): How long to keep retrying, in seconds. 

256 Note: timeout is only checked before initiating a retry, so the target may 

257 run past the timeout value as long as it is healthy. 

258 on_error (Callable[Exception]): A function to call while processing 

259 a retryable exception. Any error raised by this function will 

260 *not* be caught. 

261 deadline (float): DEPRECATED: use `timeout` instead. For backward 

262 compatibility, if specified it will override the ``timeout`` parameter. 

263 """ 

264 

265 def __call__( 

266 self, 

267 func: Callable[_P, _R], 

268 on_error: Callable[[Exception], Any] | None = None, 

269 ) -> Callable[_P, _R]: 

270 """Wrap a callable with retry behavior. 

271 

272 Args: 

273 func (Callable): The callable to add retry behavior to. 

274 on_error (Optional[Callable[Exception]]): If given, the 

275 on_error callback will be called with each retryable exception 

276 raised by the wrapped function. Any error raised by this 

277 function will *not* be caught. If on_error was specified in the 

278 constructor, this value will be ignored. 

279 

280 Returns: 

281 Callable: A callable that will invoke ``func`` with retry 

282 behavior. 

283 """ 

284 if self._on_error is not None: 

285 on_error = self._on_error 

286 

287 @functools.wraps(func) 

288 def retry_wrapped_func(*args: _P.args, **kwargs: _P.kwargs) -> _R: 

289 """A wrapper that calls target function with retry.""" 

290 target = functools.partial(func, *args, **kwargs) 

291 sleep_generator = exponential_sleep_generator( 

292 self._initial, self._maximum, multiplier=self._multiplier 

293 ) 

294 return retry_target( 

295 target, 

296 self._predicate, 

297 sleep_generator, 

298 timeout=self._timeout, 

299 on_error=on_error, 

300 ) 

301 

302 return retry_wrapped_func