dev4py.utils.retry
The retry
module provides tools to create retryable callable using exponential backoff
1"""The `retry` module provides tools to create retryable callable using exponential backoff""" 2 3# Copyright 2022 the original author or authors (i.e.: St4rG00se for Dev4py). 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# https://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16from asyncio import sleep as async_sleep 17from dataclasses import dataclass 18from functools import partial, wraps 19from time import sleep 20from typing import Callable, Awaitable, Union, Optional, cast, Any 21 22from dev4py.utils.objects import require_non_none, is_none 23from dev4py.utils.types import T, P, Function 24 25 26############################## 27# PUBLIC MODULE CLASS # 28############################## 29@dataclass(frozen=True) 30class RetryConfiguration: 31 """ 32 RetryConfiguration class that represents a retry configuration 33 34 A retry configuration describes: 35 * The maximum number of tries (first try included. I.e.: 1 means no retry on failure) 36 * The exponential backoff elements (exponent & delay) 37 Note: exponential backoff (/ waiting_interval) = delay * (exponent^retry_number) 38 39 Args: 40 exponent (int): the exponential backoff exponent / an arbitrary multiplier to determine delay between each try 41 (default = 2) 42 delay (float): the exponential backoff delay in second / the initial wait for the first retry (default = 0.1) 43 max_tries (int): max try number (first try included) (default = 3, i.e.: first try and 2 retry) 44 45 Raises: 46 TypeError: if exponent or delay or max_tries is None 47 ValueError: if exponent or delay is less than 0 or max_tries is less than 1 48 """ 49 exponent: int = 2 50 delay: float = 0.1 # seconds 51 max_tries: int = 3 52 53 def get_waiting_interval(self, retry_number: int) -> float: 54 """ 55 Returns the waiting interval in second in case of a retry occurs 56 Args: 57 retry_number: the current retry number 58 59 Returns: 60 float: the waiting interval in second for the given `retry_numbers` 61 62 Raises: 63 TypeError: if retry_number is None 64 ValueError: if retry_number is lesser than or equals to 0 or retry_number greater than max_tries 65 """ 66 if require_non_none(retry_number) <= 0: 67 raise ValueError('retry_number must be greater than 0') 68 69 if retry_number > self.max_tries: 70 raise ValueError('retry_number greater than configured max_tries') 71 72 return self.delay * (self.exponent ** retry_number) 73 74 def __post_init__(self): 75 if require_non_none(self.exponent, "exponent must be non None") < 0: 76 raise ValueError("exponent must be greater than or equals to 0") 77 if require_non_none(self.delay, "delay must be non None") < 0: 78 raise ValueError("delay must be greater than or equals to 0") 79 if require_non_none(self.max_tries, "max_tries must be non None") < 1: 80 raise ValueError("max_tries must be greater than or equals to 1") 81 82 83############################## 84# PRIVATE MODULE FUNCTIONS # 85############################## 86def _default_retry_on_failure(exception: BaseException) -> Any: 87 """ 88 Default value for on_failure parameter 89 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 90 91 Args: 92 exception: The on failure exception 93 94 Raises: 95 Exception: the given exception 96 """ 97 raise exception 98 99 100def _with_retry( 101 sync_callable: Callable[P, T], 102 retry_config: RetryConfiguration, 103 on_failure: Function[BaseException, T], 104 retry_number: int, 105 *args: P.args, 106 **kwargs: P.kwargs 107) -> T: 108 """ 109 Used to define a retryable function from `sync_callable` parameter by using the RetryConfig, on_failure and 110 retry_number parameters 111 112 Note: inner functions are not used in order to be compatible with multiprocessing 113 """ 114 if retry_number > 0: 115 sleep(retry_config.get_waiting_interval(retry_number)) 116 117 try: 118 return sync_callable(*args, **kwargs) 119 except BaseException as e: # pylint: disable=W0703 120 retry_number += 1 # pragma: no mutate 121 122 if retry_number >= retry_config.max_tries: 123 return on_failure(e) 124 125 return _with_retry(sync_callable, retry_config, on_failure, retry_number, *args, **kwargs) 126 127 128async def _with_async_retry( 129 async_callable: Callable[P, Awaitable[T]], 130 retry_config: RetryConfiguration, 131 on_failure: Function[BaseException, T], 132 retry_number: int, 133 *args: P.args, 134 **kwargs: P.kwargs 135) -> T: 136 """ 137 Used to define a retryable function from `async_callable` parameter by using the RetryConfig, on_failure and 138 retry_number parameters 139 140 Note: inner functions are not used in order to be compatible with multiprocessing 141 """ 142 if retry_number > 0: 143 await async_sleep(retry_config.get_waiting_interval(retry_number)) 144 145 try: 146 return await async_callable(*args, **kwargs) 147 except BaseException as e: # pylint: disable=W0703 148 retry_number += 1 # pragma: no mutate 149 150 if retry_number >= retry_config.max_tries: 151 return on_failure(e) 152 153 return await _with_async_retry(async_callable, retry_config, on_failure, retry_number, *args, **kwargs) 154 155 156############################## 157# PUBLIC FUNCTIONS # 158############################## 159 160def retryable( 161 sync_callable: Optional[Callable[P, T]] = None, 162 retry_config: RetryConfiguration = RetryConfiguration(), 163 on_failure: Function[BaseException, T] = _default_retry_on_failure 164) -> Union[Callable[P, T], Function[Callable[P, T], Callable[P, T]]]: 165 """ 166 SINCE: 3.5.0: a `to_retryable` equivalent that can also be used as decorator 167 168 Transforms a callable to a retryable one by using the given RetryConfiguration 169 170 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 171 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 172 then the on_failure function is called in order to return a default value or raise an exception. By default, it 173 raises the last raised exception 174 175 Args: 176 sync_callable: The callable to transform 177 retry_config: The given RetryConfiguration 178 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 179 180 Returns: 181 Function[Callable[P, T], Callable[P, T]]: Corresponding to a partial `to_retryable` function filled with 182 `retry_config` and `on_failure` parameters if sync_callable is None 183 184 Callable[P, Awaitable[T]]: The retryable callable (is equivalent to `to_retryable` call) if sync_callable is not 185 None 186 187 Raises: 188 TypeError: if retry_config or on_failure is None 189 """ 190 require_non_none(retry_config) 191 require_non_none(on_failure) 192 193 def _retryable_decorator(func: Callable[P, T]) -> Callable[P, T]: 194 require_non_none(func) 195 196 @wraps(func) 197 def _retryable_wrapper(*args: P.args, **kwargs: P.kwargs) -> T: 198 return _with_retry(func, retry_config, on_failure, 0, *args, **kwargs) 199 200 return _retryable_wrapper 201 202 return _retryable_decorator if is_none(sync_callable) else _retryable_decorator(cast(Callable[P, T], sync_callable)) 203 204 205def to_retryable( 206 sync_callable: Optional[Callable[P, T]] = None, 207 *, 208 retry_config: RetryConfiguration = RetryConfiguration(), 209 on_failure: Function[BaseException, T] = _default_retry_on_failure 210) -> Callable[P, T]: 211 """ 212 Transforms a callable to a retryable one by using the given RetryConfiguration 213 214 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 215 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 216 then the on_failure function is called in order to return a default value or raise an exception. By default, it 217 raises the last raised exception 218 219 Args: 220 sync_callable: The callable to transform 221 retry_config: The given RetryConfiguration 222 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 223 224 Returns: 225 Callable[P, T]: The retryable callable 226 227 Raises: 228 TypeError: if sync_callable or retry_config or on_failure is None 229 """ 230 require_non_none(sync_callable) 231 require_non_none(retry_config) 232 require_non_none(on_failure) 233 return partial(_with_retry, *[sync_callable, retry_config, on_failure, 0]) 234 235 236def async_retryable( 237 async_callable: Optional[Callable[P, Awaitable[T]]] = None, 238 *, 239 retry_config: RetryConfiguration = RetryConfiguration(), 240 on_failure: Function[BaseException, T] = _default_retry_on_failure 241) -> Union[Function[Callable[P, Awaitable[T]], Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]: 242 """ 243 SINCE: 3.5.0: a `to_async_retryable` equivalent that can also be used as decorator 244 245 Transforms an async callable to an async retryable one by using the given RetryConfiguration 246 247 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 248 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 249 then the on_failure function is called in order to return a default value or raise an exception. By default, it 250 raises the last raised exception 251 252 Args: 253 async_callable: The async callable to transform 254 retry_config: The given RetryConfiguration 255 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 256 257 Returns: 258 Function[Callable[P, Awaitable[T]], Callable[P, Awaitable[T]]]: Corresponding to a partial `to_async_retryable` 259 function filled with `retry_config` and `on_failure` parameters if async_callable is None 260 261 Callable[P, Awaitable[T]]: The async retryable callable (is equivalent to `to_async_retryable` call) if 262 async_callable is not None 263 264 Raises: 265 TypeError: if retry_config or on_failure is None 266 """ 267 require_non_none(retry_config) 268 require_non_none(on_failure) 269 270 def _async_retryable_decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]: 271 require_non_none(func) 272 273 @wraps(func) 274 def _async_retryable_wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[T]: 275 return _with_async_retry(func, retry_config, on_failure, 0, *args, **kwargs) 276 277 return _async_retryable_wrapper 278 279 return _async_retryable_decorator if is_none(async_callable) else \ 280 _async_retryable_decorator(cast(Callable[P, Awaitable[T]], async_callable)) 281 282 283def to_async_retryable( 284 async_callable: Callable[P, Awaitable[T]], 285 retry_config: RetryConfiguration = RetryConfiguration(), 286 on_failure: Function[BaseException, T] = _default_retry_on_failure 287) -> Callable[P, Awaitable[T]]: 288 """ 289 Transforms an async callable to an async retryable one by using the given RetryConfiguration 290 291 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 292 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 293 then the on_failure function is called in order to return a default value or raise an exception. By default, it 294 raises the last raised exception 295 296 Args: 297 async_callable: The async callable to transform 298 retry_config: The given RetryConfiguration 299 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 300 301 Returns: 302 Callable[P, Awaitable[T]]: The async retryable callable 303 304 Raises: 305 TypeError: if async_callable or retry_config or on_failure is None 306 """ 307 require_non_none(async_callable) 308 require_non_none(retry_config) 309 require_non_none(on_failure) 310 return partial(_with_async_retry, *[async_callable, retry_config, on_failure, 0])
30@dataclass(frozen=True) 31class RetryConfiguration: 32 """ 33 RetryConfiguration class that represents a retry configuration 34 35 A retry configuration describes: 36 * The maximum number of tries (first try included. I.e.: 1 means no retry on failure) 37 * The exponential backoff elements (exponent & delay) 38 Note: exponential backoff (/ waiting_interval) = delay * (exponent^retry_number) 39 40 Args: 41 exponent (int): the exponential backoff exponent / an arbitrary multiplier to determine delay between each try 42 (default = 2) 43 delay (float): the exponential backoff delay in second / the initial wait for the first retry (default = 0.1) 44 max_tries (int): max try number (first try included) (default = 3, i.e.: first try and 2 retry) 45 46 Raises: 47 TypeError: if exponent or delay or max_tries is None 48 ValueError: if exponent or delay is less than 0 or max_tries is less than 1 49 """ 50 exponent: int = 2 51 delay: float = 0.1 # seconds 52 max_tries: int = 3 53 54 def get_waiting_interval(self, retry_number: int) -> float: 55 """ 56 Returns the waiting interval in second in case of a retry occurs 57 Args: 58 retry_number: the current retry number 59 60 Returns: 61 float: the waiting interval in second for the given `retry_numbers` 62 63 Raises: 64 TypeError: if retry_number is None 65 ValueError: if retry_number is lesser than or equals to 0 or retry_number greater than max_tries 66 """ 67 if require_non_none(retry_number) <= 0: 68 raise ValueError('retry_number must be greater than 0') 69 70 if retry_number > self.max_tries: 71 raise ValueError('retry_number greater than configured max_tries') 72 73 return self.delay * (self.exponent ** retry_number) 74 75 def __post_init__(self): 76 if require_non_none(self.exponent, "exponent must be non None") < 0: 77 raise ValueError("exponent must be greater than or equals to 0") 78 if require_non_none(self.delay, "delay must be non None") < 0: 79 raise ValueError("delay must be greater than or equals to 0") 80 if require_non_none(self.max_tries, "max_tries must be non None") < 1: 81 raise ValueError("max_tries must be greater than or equals to 1")
RetryConfiguration class that represents a retry configuration
A retry configuration describes:
- The maximum number of tries (first try included. I.e.: 1 means no retry on failure)
- The exponential backoff elements (exponent & delay) Note: exponential backoff (/ waiting_interval) = delay * (exponent^retry_number)
Arguments:
- exponent (int): the exponential backoff exponent / an arbitrary multiplier to determine delay between each try (default = 2)
- delay (float): the exponential backoff delay in second / the initial wait for the first retry (default = 0.1)
- max_tries (int): max try number (first try included) (default = 3, i.e.: first try and 2 retry)
Raises:
- TypeError: if exponent or delay or max_tries is None
- ValueError: if exponent or delay is less than 0 or max_tries is less than 1
54 def get_waiting_interval(self, retry_number: int) -> float: 55 """ 56 Returns the waiting interval in second in case of a retry occurs 57 Args: 58 retry_number: the current retry number 59 60 Returns: 61 float: the waiting interval in second for the given `retry_numbers` 62 63 Raises: 64 TypeError: if retry_number is None 65 ValueError: if retry_number is lesser than or equals to 0 or retry_number greater than max_tries 66 """ 67 if require_non_none(retry_number) <= 0: 68 raise ValueError('retry_number must be greater than 0') 69 70 if retry_number > self.max_tries: 71 raise ValueError('retry_number greater than configured max_tries') 72 73 return self.delay * (self.exponent ** retry_number)
Returns the waiting interval in second in case of a retry occurs
Arguments:
- retry_number: the current retry number
Returns:
float: the waiting interval in second for the given
retry_numbers
Raises:
- TypeError: if retry_number is None
- ValueError: if retry_number is lesser than or equals to 0 or retry_number greater than max_tries
161def retryable( 162 sync_callable: Optional[Callable[P, T]] = None, 163 retry_config: RetryConfiguration = RetryConfiguration(), 164 on_failure: Function[BaseException, T] = _default_retry_on_failure 165) -> Union[Callable[P, T], Function[Callable[P, T], Callable[P, T]]]: 166 """ 167 SINCE: 3.5.0: a `to_retryable` equivalent that can also be used as decorator 168 169 Transforms a callable to a retryable one by using the given RetryConfiguration 170 171 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 172 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 173 then the on_failure function is called in order to return a default value or raise an exception. By default, it 174 raises the last raised exception 175 176 Args: 177 sync_callable: The callable to transform 178 retry_config: The given RetryConfiguration 179 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 180 181 Returns: 182 Function[Callable[P, T], Callable[P, T]]: Corresponding to a partial `to_retryable` function filled with 183 `retry_config` and `on_failure` parameters if sync_callable is None 184 185 Callable[P, Awaitable[T]]: The retryable callable (is equivalent to `to_retryable` call) if sync_callable is not 186 None 187 188 Raises: 189 TypeError: if retry_config or on_failure is None 190 """ 191 require_non_none(retry_config) 192 require_non_none(on_failure) 193 194 def _retryable_decorator(func: Callable[P, T]) -> Callable[P, T]: 195 require_non_none(func) 196 197 @wraps(func) 198 def _retryable_wrapper(*args: P.args, **kwargs: P.kwargs) -> T: 199 return _with_retry(func, retry_config, on_failure, 0, *args, **kwargs) 200 201 return _retryable_wrapper 202 203 return _retryable_decorator if is_none(sync_callable) else _retryable_decorator(cast(Callable[P, T], sync_callable))
SINCE: 3.5.0: a to_retryable
equivalent that can also be used as decorator
Transforms a callable to a retryable one by using the given RetryConfiguration
If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached then the on_failure function is called in order to return a default value or raise an exception. By default, it raises the last raised exception
Arguments:
- sync_callable: The callable to transform
- retry_config: The given RetryConfiguration
- on_failure: The action to perform if max_tries is reached (Note: can return a default value)
Returns:
Function[Callable[P, T], Callable[P, T]]: Corresponding to a partial
to_retryable
function filled withretry_config
andon_failure
parameters if sync_callable is NoneCallable[P, Awaitable[T]]: The retryable callable (is equivalent to
to_retryable
call) if sync_callable is not None
Raises:
- TypeError: if retry_config or on_failure is None
206def to_retryable( 207 sync_callable: Optional[Callable[P, T]] = None, 208 *, 209 retry_config: RetryConfiguration = RetryConfiguration(), 210 on_failure: Function[BaseException, T] = _default_retry_on_failure 211) -> Callable[P, T]: 212 """ 213 Transforms a callable to a retryable one by using the given RetryConfiguration 214 215 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 216 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 217 then the on_failure function is called in order to return a default value or raise an exception. By default, it 218 raises the last raised exception 219 220 Args: 221 sync_callable: The callable to transform 222 retry_config: The given RetryConfiguration 223 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 224 225 Returns: 226 Callable[P, T]: The retryable callable 227 228 Raises: 229 TypeError: if sync_callable or retry_config or on_failure is None 230 """ 231 require_non_none(sync_callable) 232 require_non_none(retry_config) 233 require_non_none(on_failure) 234 return partial(_with_retry, *[sync_callable, retry_config, on_failure, 0])
Transforms a callable to a retryable one by using the given RetryConfiguration
If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached then the on_failure function is called in order to return a default value or raise an exception. By default, it raises the last raised exception
Arguments:
- sync_callable: The callable to transform
- retry_config: The given RetryConfiguration
- on_failure: The action to perform if max_tries is reached (Note: can return a default value)
Returns:
Callable[P, T]: The retryable callable
Raises:
- TypeError: if sync_callable or retry_config or on_failure is None
237def async_retryable( 238 async_callable: Optional[Callable[P, Awaitable[T]]] = None, 239 *, 240 retry_config: RetryConfiguration = RetryConfiguration(), 241 on_failure: Function[BaseException, T] = _default_retry_on_failure 242) -> Union[Function[Callable[P, Awaitable[T]], Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]: 243 """ 244 SINCE: 3.5.0: a `to_async_retryable` equivalent that can also be used as decorator 245 246 Transforms an async callable to an async retryable one by using the given RetryConfiguration 247 248 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 249 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 250 then the on_failure function is called in order to return a default value or raise an exception. By default, it 251 raises the last raised exception 252 253 Args: 254 async_callable: The async callable to transform 255 retry_config: The given RetryConfiguration 256 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 257 258 Returns: 259 Function[Callable[P, Awaitable[T]], Callable[P, Awaitable[T]]]: Corresponding to a partial `to_async_retryable` 260 function filled with `retry_config` and `on_failure` parameters if async_callable is None 261 262 Callable[P, Awaitable[T]]: The async retryable callable (is equivalent to `to_async_retryable` call) if 263 async_callable is not None 264 265 Raises: 266 TypeError: if retry_config or on_failure is None 267 """ 268 require_non_none(retry_config) 269 require_non_none(on_failure) 270 271 def _async_retryable_decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]: 272 require_non_none(func) 273 274 @wraps(func) 275 def _async_retryable_wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[T]: 276 return _with_async_retry(func, retry_config, on_failure, 0, *args, **kwargs) 277 278 return _async_retryable_wrapper 279 280 return _async_retryable_decorator if is_none(async_callable) else \ 281 _async_retryable_decorator(cast(Callable[P, Awaitable[T]], async_callable))
SINCE: 3.5.0: a to_async_retryable
equivalent that can also be used as decorator
Transforms an async callable to an async retryable one by using the given RetryConfiguration
If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached then the on_failure function is called in order to return a default value or raise an exception. By default, it raises the last raised exception
Arguments:
- async_callable: The async callable to transform
- retry_config: The given RetryConfiguration
- on_failure: The action to perform if max_tries is reached (Note: can return a default value)
Returns:
Function[Callable[P, Awaitable[T]], Callable[P, Awaitable[T]]]: Corresponding to a partial
to_async_retryable
function filled withretry_config
andon_failure
parameters if async_callable is NoneCallable[P, Awaitable[T]]: The async retryable callable (is equivalent to
to_async_retryable
call) if async_callable is not None
Raises:
- TypeError: if retry_config or on_failure is None
284def to_async_retryable( 285 async_callable: Callable[P, Awaitable[T]], 286 retry_config: RetryConfiguration = RetryConfiguration(), 287 on_failure: Function[BaseException, T] = _default_retry_on_failure 288) -> Callable[P, Awaitable[T]]: 289 """ 290 Transforms an async callable to an async retryable one by using the given RetryConfiguration 291 292 If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. 293 An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached 294 then the on_failure function is called in order to return a default value or raise an exception. By default, it 295 raises the last raised exception 296 297 Args: 298 async_callable: The async callable to transform 299 retry_config: The given RetryConfiguration 300 on_failure: The action to perform if max_tries is reached (Note: can return a default value) 301 302 Returns: 303 Callable[P, Awaitable[T]]: The async retryable callable 304 305 Raises: 306 TypeError: if async_callable or retry_config or on_failure is None 307 """ 308 require_non_none(async_callable) 309 require_non_none(retry_config) 310 require_non_none(on_failure) 311 return partial(_with_async_retry, *[async_callable, retry_config, on_failure, 0])
Transforms an async callable to an async retryable one by using the given RetryConfiguration
If an error occurs when executing the returned callable the call is retried by using the given RetryConfiguration. An exponential backoff is used in order to determinate the waiting time between each call. If max_tries is reached then the on_failure function is called in order to return a default value or raise an exception. By default, it raises the last raised exception
Arguments:
- async_callable: The async callable to transform
- retry_config: The given RetryConfiguration
- on_failure: The action to perform if max_tries is reached (Note: can return a default value)
Returns:
Callable[P, Awaitable[T]]: The async retryable callable
Raises:
- TypeError: if async_callable or retry_config or on_failure is None