tenacity 8.3.0__py3-none-any.whl → 8.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tenacity/__init__.py +19 -9
- tenacity/_utils.py +12 -0
- tenacity/retry.py +8 -2
- {tenacity-8.3.0.dist-info → tenacity-8.4.0.dist-info}/METADATA +1 -1
- {tenacity-8.3.0.dist-info → tenacity-8.4.0.dist-info}/RECORD +8 -9
- tenacity/_asyncio.py +0 -148
- {tenacity-8.3.0.dist-info → tenacity-8.4.0.dist-info}/LICENSE +0 -0
- {tenacity-8.3.0.dist-info → tenacity-8.4.0.dist-info}/WHEEL +0 -0
- {tenacity-8.3.0.dist-info → tenacity-8.4.0.dist-info}/top_level.txt +0 -0
tenacity/__init__.py
CHANGED
|
@@ -24,7 +24,8 @@ import typing as t
|
|
|
24
24
|
import warnings
|
|
25
25
|
from abc import ABC, abstractmethod
|
|
26
26
|
from concurrent import futures
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
from . import _utils
|
|
28
29
|
|
|
29
30
|
# Import all built-in retry strategies for easier usage.
|
|
30
31
|
from .retry import retry_base # noqa
|
|
@@ -87,6 +88,7 @@ except ImportError:
|
|
|
87
88
|
if t.TYPE_CHECKING:
|
|
88
89
|
import types
|
|
89
90
|
|
|
91
|
+
from . import asyncio as tasyncio
|
|
90
92
|
from .retry import RetryBaseT
|
|
91
93
|
from .stop import StopBaseT
|
|
92
94
|
from .wait import WaitBaseT
|
|
@@ -593,16 +595,24 @@ def retry(func: WrappedFn) -> WrappedFn: ...
|
|
|
593
595
|
|
|
594
596
|
@t.overload
|
|
595
597
|
def retry(
|
|
596
|
-
sleep: t.Callable[[t.Union[int, float]], t.
|
|
598
|
+
sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
|
|
597
599
|
stop: "StopBaseT" = stop_never,
|
|
598
600
|
wait: "WaitBaseT" = wait_none(),
|
|
599
|
-
retry: "RetryBaseT" = retry_if_exception_type(),
|
|
600
|
-
before: t.Callable[
|
|
601
|
-
|
|
602
|
-
|
|
601
|
+
retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
|
|
602
|
+
before: t.Callable[
|
|
603
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
604
|
+
] = before_nothing,
|
|
605
|
+
after: t.Callable[
|
|
606
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
607
|
+
] = after_nothing,
|
|
608
|
+
before_sleep: t.Optional[
|
|
609
|
+
t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
|
|
610
|
+
] = None,
|
|
603
611
|
reraise: bool = False,
|
|
604
612
|
retry_error_cls: t.Type["RetryError"] = RetryError,
|
|
605
|
-
retry_error_callback: t.Optional[
|
|
613
|
+
retry_error_callback: t.Optional[
|
|
614
|
+
t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
|
|
615
|
+
] = None,
|
|
606
616
|
) -> t.Callable[[WrappedFn], WrappedFn]: ...
|
|
607
617
|
|
|
608
618
|
|
|
@@ -624,7 +634,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
|
|
|
624
634
|
f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
|
|
625
635
|
)
|
|
626
636
|
r: "BaseRetrying"
|
|
627
|
-
if
|
|
637
|
+
if _utils.is_coroutine_callable(f):
|
|
628
638
|
r = AsyncRetrying(*dargs, **dkw)
|
|
629
639
|
elif (
|
|
630
640
|
tornado
|
|
@@ -640,7 +650,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
|
|
|
640
650
|
return wrap
|
|
641
651
|
|
|
642
652
|
|
|
643
|
-
from tenacity.
|
|
653
|
+
from tenacity.asyncio import AsyncRetrying # noqa:E402,I100
|
|
644
654
|
|
|
645
655
|
if tornado:
|
|
646
656
|
from tenacity.tornadoweb import TornadoRetrying
|
tenacity/_utils.py
CHANGED
|
@@ -87,3 +87,15 @@ def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
|
|
|
87
87
|
partial_call = isinstance(call, functools.partial) and call.func
|
|
88
88
|
dunder_call = partial_call or getattr(call, "__call__", None)
|
|
89
89
|
return inspect.iscoroutinefunction(dunder_call)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def wrap_to_async_func(
|
|
93
|
+
call: typing.Callable[..., typing.Any],
|
|
94
|
+
) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
|
|
95
|
+
if is_coroutine_callable(call):
|
|
96
|
+
return call
|
|
97
|
+
|
|
98
|
+
async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
99
|
+
return call(*args, **kwargs)
|
|
100
|
+
|
|
101
|
+
return inner
|
tenacity/retry.py
CHANGED
|
@@ -30,10 +30,16 @@ class retry_base(abc.ABC):
|
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
def __and__(self, other: "retry_base") -> "retry_all":
|
|
33
|
-
return
|
|
33
|
+
return other.__rand__(self)
|
|
34
|
+
|
|
35
|
+
def __rand__(self, other: "retry_base") -> "retry_all":
|
|
36
|
+
return retry_all(other, self)
|
|
34
37
|
|
|
35
38
|
def __or__(self, other: "retry_base") -> "retry_any":
|
|
36
|
-
return
|
|
39
|
+
return other.__ror__(self)
|
|
40
|
+
|
|
41
|
+
def __ror__(self, other: "retry_base") -> "retry_any":
|
|
42
|
+
return retry_any(other, self)
|
|
37
43
|
|
|
38
44
|
|
|
39
45
|
RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
tenacity/__init__.py,sha256=
|
|
2
|
-
tenacity/
|
|
3
|
-
tenacity/_utils.py,sha256=GGNfSHJRrfph7kPySn-2xnY_dVUopdl6LUZdXRPuGVg,2601
|
|
1
|
+
tenacity/__init__.py,sha256=dT216tDrncc6QKV6_1AfuhzmCMfUzM_hIwW34ivIRsQ,23651
|
|
2
|
+
tenacity/_utils.py,sha256=5AwPoFrGOIfPkCtqeJdFBi7KKQNcJXJ3erbtGOXtn6w,2916
|
|
4
3
|
tenacity/after.py,sha256=NR4rGyslG7xF1hDJZb2Wf8wVApafX0HZwz2nFVLvaqE,1658
|
|
5
4
|
tenacity/before.py,sha256=7zDTpZ3b6rkY9sOdS-qbpjBgSjVr3xBqcIqdYAh9ZKM,1544
|
|
6
5
|
tenacity/before_sleep.py,sha256=upKssiY5poOO3Bv0amADZA-a5CrY5tnb4_97s8_88SM,2360
|
|
7
6
|
tenacity/nap.py,sha256=fRWvnz1aIzbIq9Ap3gAkAZgDH6oo5zxMrU6ZOVByq0I,1383
|
|
8
7
|
tenacity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
tenacity/retry.py,sha256=
|
|
8
|
+
tenacity/retry.py,sha256=PVnZfXBeLN2DzsytKexw23SPLn82AdZfFPC2OhVQ_1Y,8986
|
|
10
9
|
tenacity/stop.py,sha256=wQuwGfCLw8OH1C3x0G9lH9xtJCyhgviePQ40HRAUg54,4113
|
|
11
10
|
tenacity/tornadoweb.py,sha256=vS1ONfPYoGzPx1asQaVbfoo6D9tPIzSysJipm61Yqw8,2125
|
|
12
11
|
tenacity/wait.py,sha256=Q9XoZCtFra53aQOyfABpvRDuUeB-NpUUXImHsUiRQI0,8042
|
|
13
|
-
tenacity-8.
|
|
14
|
-
tenacity-8.
|
|
15
|
-
tenacity-8.
|
|
16
|
-
tenacity-8.
|
|
17
|
-
tenacity-8.
|
|
12
|
+
tenacity-8.4.0.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
13
|
+
tenacity-8.4.0.dist-info/METADATA,sha256=rnsKmqyLFhyN5Ydqjehhxuls6kPQ27yu6M4l2t5WeuM,1155
|
|
14
|
+
tenacity-8.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
15
|
+
tenacity-8.4.0.dist-info/top_level.txt,sha256=Zf8AOZMN7hr1EEcUo9U5KzXsM4TOC1pBZ22D8913JYs,9
|
|
16
|
+
tenacity-8.4.0.dist-info/RECORD,,
|
tenacity/_asyncio.py
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# Copyright 2016 Étienne Bersac
|
|
2
|
-
# Copyright 2016 Julien Danjou
|
|
3
|
-
# Copyright 2016 Joshua Harlow
|
|
4
|
-
# Copyright 2013-2014 Ray Holder
|
|
5
|
-
#
|
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
-
# you may not use this file except in compliance with the License.
|
|
8
|
-
# You may obtain a copy of the License at
|
|
9
|
-
#
|
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
#
|
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
-
# See the License for the specific language governing permissions and
|
|
16
|
-
# limitations under the License.
|
|
17
|
-
|
|
18
|
-
import functools
|
|
19
|
-
import sys
|
|
20
|
-
import typing as t
|
|
21
|
-
|
|
22
|
-
from tenacity import AttemptManager
|
|
23
|
-
from tenacity import BaseRetrying
|
|
24
|
-
from tenacity import DoAttempt
|
|
25
|
-
from tenacity import DoSleep
|
|
26
|
-
from tenacity import RetryCallState
|
|
27
|
-
from tenacity import _utils
|
|
28
|
-
|
|
29
|
-
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
|
|
30
|
-
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def asyncio_sleep(duration: float) -> t.Awaitable[None]:
|
|
34
|
-
# Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
|
|
35
|
-
import asyncio
|
|
36
|
-
|
|
37
|
-
return asyncio.sleep(duration)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class AsyncRetrying(BaseRetrying):
|
|
41
|
-
sleep: t.Callable[[float], t.Awaitable[t.Any]]
|
|
42
|
-
|
|
43
|
-
def __init__(
|
|
44
|
-
self,
|
|
45
|
-
sleep: t.Callable[[float], t.Awaitable[t.Any]] = asyncio_sleep,
|
|
46
|
-
**kwargs: t.Any,
|
|
47
|
-
) -> None:
|
|
48
|
-
super().__init__(**kwargs)
|
|
49
|
-
self.sleep = sleep
|
|
50
|
-
|
|
51
|
-
async def __call__( # type: ignore[override]
|
|
52
|
-
self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
|
|
53
|
-
) -> WrappedFnReturnT:
|
|
54
|
-
self.begin()
|
|
55
|
-
|
|
56
|
-
retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
|
|
57
|
-
while True:
|
|
58
|
-
do = await self.iter(retry_state=retry_state)
|
|
59
|
-
if isinstance(do, DoAttempt):
|
|
60
|
-
try:
|
|
61
|
-
result = await fn(*args, **kwargs)
|
|
62
|
-
except BaseException: # noqa: B902
|
|
63
|
-
retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
|
|
64
|
-
else:
|
|
65
|
-
retry_state.set_result(result)
|
|
66
|
-
elif isinstance(do, DoSleep):
|
|
67
|
-
retry_state.prepare_for_next_attempt()
|
|
68
|
-
await self.sleep(do)
|
|
69
|
-
else:
|
|
70
|
-
return do # type: ignore[no-any-return]
|
|
71
|
-
|
|
72
|
-
@classmethod
|
|
73
|
-
def _wrap_action_func(cls, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
|
|
74
|
-
if _utils.is_coroutine_callable(fn):
|
|
75
|
-
return fn
|
|
76
|
-
|
|
77
|
-
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
78
|
-
return fn(*args, **kwargs)
|
|
79
|
-
|
|
80
|
-
return inner
|
|
81
|
-
|
|
82
|
-
def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
|
|
83
|
-
self.iter_state.actions.append(self._wrap_action_func(fn))
|
|
84
|
-
|
|
85
|
-
async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
86
|
-
self.iter_state.retry_run_result = await self._wrap_action_func(self.retry)(
|
|
87
|
-
retry_state
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
91
|
-
if self.wait:
|
|
92
|
-
sleep = await self._wrap_action_func(self.wait)(retry_state)
|
|
93
|
-
else:
|
|
94
|
-
sleep = 0.0
|
|
95
|
-
|
|
96
|
-
retry_state.upcoming_sleep = sleep
|
|
97
|
-
|
|
98
|
-
async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
99
|
-
self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
|
|
100
|
-
self.iter_state.stop_run_result = await self._wrap_action_func(self.stop)(
|
|
101
|
-
retry_state
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
async def iter(
|
|
105
|
-
self, retry_state: "RetryCallState"
|
|
106
|
-
) -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa: A003
|
|
107
|
-
self._begin_iter(retry_state)
|
|
108
|
-
result = None
|
|
109
|
-
for action in self.iter_state.actions:
|
|
110
|
-
result = await action(retry_state)
|
|
111
|
-
return result
|
|
112
|
-
|
|
113
|
-
def __iter__(self) -> t.Generator[AttemptManager, None, None]:
|
|
114
|
-
raise TypeError("AsyncRetrying object is not iterable")
|
|
115
|
-
|
|
116
|
-
def __aiter__(self) -> "AsyncRetrying":
|
|
117
|
-
self.begin()
|
|
118
|
-
self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
|
|
119
|
-
return self
|
|
120
|
-
|
|
121
|
-
async def __anext__(self) -> AttemptManager:
|
|
122
|
-
while True:
|
|
123
|
-
do = await self.iter(retry_state=self._retry_state)
|
|
124
|
-
if do is None:
|
|
125
|
-
raise StopAsyncIteration
|
|
126
|
-
elif isinstance(do, DoAttempt):
|
|
127
|
-
return AttemptManager(retry_state=self._retry_state)
|
|
128
|
-
elif isinstance(do, DoSleep):
|
|
129
|
-
self._retry_state.prepare_for_next_attempt()
|
|
130
|
-
await self.sleep(do)
|
|
131
|
-
else:
|
|
132
|
-
raise StopAsyncIteration
|
|
133
|
-
|
|
134
|
-
def wraps(self, fn: WrappedFn) -> WrappedFn:
|
|
135
|
-
fn = super().wraps(fn)
|
|
136
|
-
# Ensure wrapper is recognized as a coroutine function.
|
|
137
|
-
|
|
138
|
-
@functools.wraps(
|
|
139
|
-
fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
|
|
140
|
-
)
|
|
141
|
-
async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
142
|
-
return await fn(*args, **kwargs)
|
|
143
|
-
|
|
144
|
-
# Preserve attributes
|
|
145
|
-
async_wrapped.retry = fn.retry # type: ignore[attr-defined]
|
|
146
|
-
async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
|
|
147
|
-
|
|
148
|
-
return async_wrapped # type: ignore[return-value]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|