python-backoff 2.3.0__py3-none-any.whl → 2.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.
- backoff/__init__.py +15 -13
- backoff/_async.py +228 -115
- backoff/_common.py +202 -31
- backoff/_decorator.py +220 -44
- backoff/_jitter.py +0 -2
- backoff/_sync.py +200 -92
- backoff/_typing.py +48 -25
- backoff/_wait_gen.py +36 -42
- backoff/types.py +1 -2
- {python_backoff-2.3.0.dist-info → python_backoff-2.4.0.dist-info}/METADATA +108 -59
- python_backoff-2.4.0.dist-info/RECORD +14 -0
- {python_backoff-2.3.0.dist-info → python_backoff-2.4.0.dist-info}/WHEEL +1 -1
- python_backoff-2.3.0.dist-info/RECORD +0 -14
- {python_backoff-2.3.0.dist-info → python_backoff-2.4.0.dist-info}/licenses/LICENSE +0 -0
backoff/_sync.py
CHANGED
|
@@ -1,130 +1,238 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import functools
|
|
3
4
|
import time
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
|
6
|
+
|
|
7
|
+
from backoff._common import _Attempt, _dispatch_handlers, _RetryState
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Generator, Iterable
|
|
12
|
+
|
|
13
|
+
from backoff._typing import (
|
|
14
|
+
ContextDetails,
|
|
15
|
+
Details,
|
|
16
|
+
_BaseDetails,
|
|
17
|
+
_CallDetails,
|
|
18
|
+
_ContextHandler,
|
|
19
|
+
_Handler,
|
|
20
|
+
_Jitterer,
|
|
21
|
+
_MaybeCallable,
|
|
22
|
+
_MaybeTuple,
|
|
23
|
+
_Predicate,
|
|
24
|
+
_WaitGenerator,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if sys.version_info >= (3, 10):
|
|
28
|
+
from typing import ParamSpec
|
|
29
|
+
else:
|
|
30
|
+
from typing_extensions import ParamSpec
|
|
31
|
+
|
|
32
|
+
if sys.version_info >= (3, 11):
|
|
33
|
+
from typing import Unpack
|
|
34
|
+
else:
|
|
35
|
+
from typing_extensions import Unpack
|
|
36
|
+
|
|
37
|
+
T = TypeVar("T")
|
|
38
|
+
P = ParamSpec("P")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _call_handlers(
|
|
42
|
+
hdlrs: Iterable[_Handler],
|
|
43
|
+
target: Callable[..., Any],
|
|
44
|
+
args: tuple[Any, ...],
|
|
45
|
+
kwargs: dict[str, Any],
|
|
46
|
+
tries: int,
|
|
47
|
+
elapsed: float,
|
|
48
|
+
**extra: Unpack[_CallDetails],
|
|
49
|
+
) -> None:
|
|
50
|
+
details: Details = {
|
|
51
|
+
"target": target,
|
|
52
|
+
"args": args,
|
|
53
|
+
"kwargs": kwargs,
|
|
54
|
+
"tries": tries,
|
|
55
|
+
"elapsed": elapsed,
|
|
15
56
|
}
|
|
57
|
+
# pyrefly: ignore [no-matching-overload]
|
|
16
58
|
details.update(extra)
|
|
17
59
|
for hdlr in hdlrs:
|
|
18
60
|
hdlr(details)
|
|
19
61
|
|
|
20
62
|
|
|
21
|
-
def retry_predicate(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
63
|
+
def retry_predicate(
|
|
64
|
+
target: Callable[P, T],
|
|
65
|
+
wait_gen: _WaitGenerator,
|
|
66
|
+
predicate: _Predicate[T],
|
|
67
|
+
*,
|
|
68
|
+
max_tries: _MaybeCallable[int] | None,
|
|
69
|
+
max_time: _MaybeCallable[float] | None,
|
|
70
|
+
jitter: _Jitterer | None,
|
|
71
|
+
on_success: Iterable[_Handler],
|
|
72
|
+
on_backoff: Iterable[_Handler],
|
|
73
|
+
on_giveup: Iterable[_Handler],
|
|
74
|
+
wait_gen_kwargs: dict[str, Any],
|
|
75
|
+
) -> Callable[P, T]:
|
|
27
76
|
@functools.wraps(target)
|
|
28
|
-
def retry(*args, **kwargs):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
def retry(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
78
|
+
state = _RetryState(
|
|
79
|
+
wait_gen,
|
|
80
|
+
wait_gen_kwargs,
|
|
81
|
+
max_tries=max_tries,
|
|
82
|
+
max_time=max_time,
|
|
83
|
+
)
|
|
35
84
|
while True:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
details = {
|
|
85
|
+
state.start_attempt()
|
|
86
|
+
ret = target(*args, **kwargs)
|
|
87
|
+
details: _BaseDetails = {
|
|
39
88
|
"target": target,
|
|
40
89
|
"args": args,
|
|
41
90
|
"kwargs": kwargs,
|
|
42
|
-
"tries": tries,
|
|
43
|
-
"elapsed":
|
|
91
|
+
"tries": state.tries,
|
|
92
|
+
"elapsed": state.record_elapsed(),
|
|
44
93
|
}
|
|
45
94
|
|
|
46
|
-
ret = target(*args, **kwargs)
|
|
47
95
|
if predicate(ret):
|
|
48
|
-
|
|
49
|
-
max_time_exceeded = (max_time_value is not None and
|
|
50
|
-
elapsed >= max_time_value)
|
|
51
|
-
|
|
52
|
-
if max_tries_exceeded or max_time_exceeded:
|
|
96
|
+
if state.exhausted():
|
|
53
97
|
_call_handlers(on_giveup, **details, value=ret)
|
|
54
98
|
break
|
|
55
99
|
|
|
56
100
|
try:
|
|
57
|
-
seconds =
|
|
58
|
-
max_time_value)
|
|
101
|
+
seconds = state.next_wait(ret, jitter)
|
|
59
102
|
except StopIteration:
|
|
60
|
-
_call_handlers(on_giveup, **details)
|
|
103
|
+
_call_handlers(on_giveup, **details, value=ret)
|
|
61
104
|
break
|
|
62
105
|
|
|
63
|
-
_call_handlers(on_backoff, **details,
|
|
64
|
-
value=ret, wait=seconds)
|
|
106
|
+
_call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
65
107
|
|
|
66
108
|
time.sleep(seconds)
|
|
67
109
|
continue
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
break
|
|
110
|
+
_call_handlers(on_success, **details, value=ret)
|
|
111
|
+
break
|
|
71
112
|
|
|
72
113
|
return ret
|
|
73
114
|
|
|
74
115
|
return retry
|
|
75
116
|
|
|
76
117
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
def _adapt_context_handlers(
|
|
119
|
+
handlers: Iterable[_Handler],
|
|
120
|
+
target: Callable[..., Any],
|
|
121
|
+
args: tuple[Any, ...],
|
|
122
|
+
kwargs: dict[str, Any],
|
|
123
|
+
) -> list[_ContextHandler]:
|
|
124
|
+
def adapted(details: ContextDetails) -> None:
|
|
125
|
+
full_details: Details = {
|
|
126
|
+
"target": target,
|
|
127
|
+
"args": args,
|
|
128
|
+
"kwargs": kwargs,
|
|
129
|
+
**details,
|
|
130
|
+
}
|
|
131
|
+
for hdlr in handlers:
|
|
132
|
+
hdlr(full_details)
|
|
133
|
+
|
|
134
|
+
return [adapted]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def retry_exception(
|
|
138
|
+
target: Callable[P, T],
|
|
139
|
+
wait_gen: _WaitGenerator,
|
|
140
|
+
exception: _MaybeTuple[type[Exception]],
|
|
141
|
+
*,
|
|
142
|
+
max_tries: _MaybeCallable[int] | None,
|
|
143
|
+
max_time: _MaybeCallable[float] | None,
|
|
144
|
+
jitter: _Jitterer | None,
|
|
145
|
+
giveup: _Predicate[Exception],
|
|
146
|
+
on_success: Iterable[_Handler],
|
|
147
|
+
on_backoff: Iterable[_Handler],
|
|
148
|
+
on_giveup: Iterable[_Handler],
|
|
149
|
+
raise_on_giveup: bool,
|
|
150
|
+
wait_gen_kwargs: dict[str, Any],
|
|
151
|
+
) -> Callable[P, T]:
|
|
83
152
|
@functools.wraps(target)
|
|
84
|
-
def retry(*args, **kwargs):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
try:
|
|
153
|
+
def retry(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
154
|
+
ret: T = None # type: ignore[assignment] # ty:ignore[invalid-assignment]
|
|
155
|
+
|
|
156
|
+
for attempt in retry_context(
|
|
157
|
+
exception,
|
|
158
|
+
wait_gen,
|
|
159
|
+
max_tries=max_tries,
|
|
160
|
+
max_time=max_time,
|
|
161
|
+
jitter=jitter,
|
|
162
|
+
giveup=giveup, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
|
|
163
|
+
on_success=_adapt_context_handlers(on_success, target, args, kwargs),
|
|
164
|
+
on_backoff=_adapt_context_handlers(on_backoff, target, args, kwargs),
|
|
165
|
+
on_giveup=_adapt_context_handlers(on_giveup, target, args, kwargs),
|
|
166
|
+
raise_on_giveup=raise_on_giveup,
|
|
167
|
+
wait_gen_kwargs=wait_gen_kwargs,
|
|
168
|
+
):
|
|
169
|
+
with attempt:
|
|
103
170
|
ret = target(*args, **kwargs)
|
|
104
|
-
except exception as e:
|
|
105
|
-
max_tries_exceeded = (tries == max_tries_value)
|
|
106
|
-
max_time_exceeded = (max_time_value is not None and
|
|
107
|
-
elapsed >= max_time_value)
|
|
108
|
-
|
|
109
|
-
if giveup(e) or max_tries_exceeded or max_time_exceeded:
|
|
110
|
-
_call_handlers(on_giveup, **details, exception=e)
|
|
111
|
-
if raise_on_giveup:
|
|
112
|
-
raise
|
|
113
|
-
return None
|
|
114
171
|
|
|
115
|
-
|
|
116
|
-
seconds = _next_wait(wait, e, jitter, elapsed,
|
|
117
|
-
max_time_value)
|
|
118
|
-
except StopIteration:
|
|
119
|
-
_call_handlers(on_giveup, **details, exception=e)
|
|
120
|
-
raise e
|
|
172
|
+
return ret
|
|
121
173
|
|
|
122
|
-
|
|
123
|
-
exception=e)
|
|
174
|
+
return retry
|
|
124
175
|
|
|
125
|
-
time.sleep(seconds)
|
|
126
|
-
else:
|
|
127
|
-
_call_handlers(on_success, **details)
|
|
128
176
|
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
def retry_context(
|
|
178
|
+
exception: _MaybeTuple[type[Exception]],
|
|
179
|
+
wait_gen: _WaitGenerator,
|
|
180
|
+
*,
|
|
181
|
+
max_tries: _MaybeCallable[int] | None,
|
|
182
|
+
max_time: _MaybeCallable[float] | None,
|
|
183
|
+
jitter: _Jitterer | None,
|
|
184
|
+
giveup: _Predicate[BaseException],
|
|
185
|
+
on_success: Iterable[_ContextHandler],
|
|
186
|
+
on_backoff: Iterable[_ContextHandler],
|
|
187
|
+
on_giveup: Iterable[_ContextHandler],
|
|
188
|
+
raise_on_giveup: bool,
|
|
189
|
+
wait_gen_kwargs: dict[str, Any],
|
|
190
|
+
) -> Generator[_Attempt, None, None]:
|
|
191
|
+
state = _RetryState(
|
|
192
|
+
wait_gen,
|
|
193
|
+
wait_gen_kwargs,
|
|
194
|
+
max_tries=max_tries,
|
|
195
|
+
max_time=max_time,
|
|
196
|
+
)
|
|
197
|
+
while True:
|
|
198
|
+
state.start_attempt()
|
|
199
|
+
attempt = _Attempt(exception)
|
|
200
|
+
yield attempt
|
|
201
|
+
elapsed = state.record_elapsed()
|
|
202
|
+
|
|
203
|
+
exc = attempt.exception
|
|
204
|
+
if exc is None:
|
|
205
|
+
_dispatch_handlers(on_success, tries=state.tries, elapsed=elapsed)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if giveup(exc) or state.exhausted():
|
|
209
|
+
_dispatch_handlers(
|
|
210
|
+
on_giveup,
|
|
211
|
+
tries=state.tries,
|
|
212
|
+
elapsed=elapsed,
|
|
213
|
+
exception=exc,
|
|
214
|
+
)
|
|
215
|
+
if raise_on_giveup:
|
|
216
|
+
raise exc
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
seconds = state.next_wait(exc, jitter)
|
|
221
|
+
except StopIteration:
|
|
222
|
+
_dispatch_handlers(
|
|
223
|
+
on_giveup,
|
|
224
|
+
tries=state.tries,
|
|
225
|
+
elapsed=elapsed,
|
|
226
|
+
exception=exc,
|
|
227
|
+
)
|
|
228
|
+
raise exc from None
|
|
229
|
+
|
|
230
|
+
_dispatch_handlers(
|
|
231
|
+
on_backoff,
|
|
232
|
+
tries=state.tries,
|
|
233
|
+
elapsed=elapsed,
|
|
234
|
+
wait=seconds,
|
|
235
|
+
exception=exc,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
time.sleep(seconds)
|
backoff/_typing.py
CHANGED
|
@@ -1,45 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
|
-
import
|
|
4
|
-
from typing import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TypedDict(dict):
|
|
15
|
-
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
16
|
-
return super().__init_subclass__()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class _Details(TypedDict):
|
|
4
|
+
from collections.abc import Coroutine, Generator
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
TypedDict,
|
|
9
|
+
TypeVar,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _BaseDetails(TypedDict):
|
|
20
15
|
target: Callable[..., Any]
|
|
21
|
-
args:
|
|
22
|
-
kwargs:
|
|
16
|
+
args: tuple[Any, ...]
|
|
17
|
+
kwargs: dict[str, Any]
|
|
23
18
|
tries: int
|
|
24
19
|
elapsed: float
|
|
25
20
|
|
|
26
21
|
|
|
27
|
-
class
|
|
22
|
+
class _CallDetails(TypedDict, total=False):
|
|
28
23
|
wait: float # present in the on_backoff handler case for either decorator
|
|
29
24
|
value: Any # present in the on_predicate decorator case
|
|
30
25
|
exception: Exception # present in the on_exception decorator case
|
|
31
26
|
|
|
32
27
|
|
|
28
|
+
class Details(_BaseDetails, _CallDetails, total=False):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _BaseContextDetails(TypedDict):
|
|
33
|
+
tries: int
|
|
34
|
+
elapsed: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _ContextCallDetails(TypedDict, total=False):
|
|
38
|
+
wait: float # present in the on_backoff handler case
|
|
39
|
+
exception: Exception # present in the on_giveup handler case
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ContextDetails(_BaseContextDetails, _ContextCallDetails, total=False):
|
|
43
|
+
"""Details passed to handlers registered with `retry_context`/`aretry_context`.
|
|
44
|
+
|
|
45
|
+
Unlike `Details`, there's no wrapped callable, so no `target`/`args`/`kwargs`.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
T = TypeVar("T")
|
|
34
50
|
|
|
35
|
-
_CallableT = TypeVar(
|
|
51
|
+
_CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) # ruff:ignore[unused-private-type-var]
|
|
36
52
|
_Handler = Union[
|
|
37
53
|
Callable[[Details], None],
|
|
38
54
|
Callable[[Details], Coroutine[Any, Any, None]],
|
|
39
55
|
]
|
|
56
|
+
_ContextHandler = Union[
|
|
57
|
+
Callable[[ContextDetails], None],
|
|
58
|
+
Callable[[ContextDetails], Coroutine[Any, Any, None]],
|
|
59
|
+
]
|
|
40
60
|
_Jitterer = Callable[[float], float]
|
|
41
61
|
_MaybeCallable = Union[T, Callable[[], T]]
|
|
42
62
|
_MaybeLogger = Union[str, logging.Logger, logging.LoggerAdapter, None]
|
|
43
|
-
|
|
44
|
-
_Predicate =
|
|
45
|
-
|
|
63
|
+
_MaybeTuple = Union[T, tuple[T, ...]]
|
|
64
|
+
_Predicate = Union[
|
|
65
|
+
Callable[[T], bool],
|
|
66
|
+
Callable[[T], Coroutine[Any, Any, bool]],
|
|
67
|
+
]
|
|
68
|
+
_WaitGenerator = Callable[..., Generator[float, Any, None]]
|
backoff/_wait_gen.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
import math
|
|
5
|
-
from typing import Any, Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import Generator, Iterable
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def expo(
|
|
9
12
|
base: float = 2,
|
|
10
13
|
factor: float = 1,
|
|
11
|
-
max_value:
|
|
14
|
+
max_value: float | None = None,
|
|
12
15
|
) -> Generator[float, Any, None]:
|
|
13
|
-
|
|
14
16
|
"""Generator for exponential decay.
|
|
15
17
|
|
|
16
18
|
Args:
|
|
@@ -21,23 +23,21 @@ def expo(
|
|
|
21
23
|
of max_value will forever after be yielded.
|
|
22
24
|
"""
|
|
23
25
|
# Advance past initial .send() call
|
|
24
|
-
yield
|
|
25
|
-
|
|
26
|
+
yield 0
|
|
27
|
+
|
|
28
|
+
a = factor
|
|
29
|
+
while max_value is None or a < max_value:
|
|
30
|
+
yield a
|
|
31
|
+
a *= base
|
|
26
32
|
while True:
|
|
27
|
-
|
|
28
|
-
if max_value is None or a < max_value:
|
|
29
|
-
yield a
|
|
30
|
-
base_n *= base
|
|
31
|
-
else:
|
|
32
|
-
yield max_value
|
|
33
|
+
yield max_value
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def decay(
|
|
36
37
|
initial_value: float = 1,
|
|
37
38
|
decay_factor: float = 1,
|
|
38
|
-
min_value:
|
|
39
|
+
min_value: float | None = None,
|
|
39
40
|
) -> Generator[float, Any, None]:
|
|
40
|
-
|
|
41
41
|
"""Generator for exponential decay[1]:
|
|
42
42
|
|
|
43
43
|
Args:
|
|
@@ -50,18 +50,17 @@ def decay(
|
|
|
50
50
|
[1] https://en.wikipedia.org/wiki/Exponential_decay
|
|
51
51
|
"""
|
|
52
52
|
# Advance past initial .send() call
|
|
53
|
-
yield
|
|
54
|
-
|
|
53
|
+
yield 0
|
|
54
|
+
a = initial_value
|
|
55
|
+
min_value = min_value or 0.0
|
|
56
|
+
while a > min_value:
|
|
57
|
+
yield a
|
|
58
|
+
a *= math.exp(-decay_factor)
|
|
55
59
|
while True:
|
|
56
|
-
|
|
57
|
-
if min_value is None or a > min_value:
|
|
58
|
-
yield a
|
|
59
|
-
t += 1
|
|
60
|
-
else:
|
|
61
|
-
yield min_value
|
|
60
|
+
yield min_value
|
|
62
61
|
|
|
63
62
|
|
|
64
|
-
def fibo(max_value:
|
|
63
|
+
def fibo(max_value: int | None = None) -> Generator[int, Any, None]:
|
|
65
64
|
"""Generator for fibonaccial decay.
|
|
66
65
|
|
|
67
66
|
Args:
|
|
@@ -70,42 +69,37 @@ def fibo(max_value: Optional[int] = None) -> Generator[int, None, None]:
|
|
|
70
69
|
of max_value will forever after be yielded.
|
|
71
70
|
"""
|
|
72
71
|
# Advance past initial .send() call
|
|
73
|
-
yield
|
|
72
|
+
yield 0
|
|
74
73
|
|
|
75
74
|
a = 1
|
|
76
75
|
b = 1
|
|
76
|
+
while max_value is None or a < max_value:
|
|
77
|
+
yield a
|
|
78
|
+
a, b = b, a + b
|
|
77
79
|
while True:
|
|
78
|
-
|
|
79
|
-
yield a
|
|
80
|
-
a, b = b, a + b
|
|
81
|
-
else:
|
|
82
|
-
yield max_value
|
|
80
|
+
yield max_value
|
|
83
81
|
|
|
84
82
|
|
|
85
|
-
def constant(
|
|
86
|
-
interval: Union[int, Iterable[float]] = 1,
|
|
87
|
-
) -> Generator[float, None, None]:
|
|
83
|
+
def constant(interval: float | Iterable[float] = 1) -> Generator[float, Any, None]:
|
|
88
84
|
"""Generator for constant intervals.
|
|
89
85
|
|
|
90
86
|
Args:
|
|
91
87
|
interval: A constant value to yield or an iterable of such values.
|
|
92
88
|
"""
|
|
93
89
|
# Advance past initial .send() call
|
|
94
|
-
yield
|
|
90
|
+
yield 0
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
itr = (
|
|
93
|
+
itertools.repeat(interval)
|
|
94
|
+
if isinstance(interval, (int, float))
|
|
95
|
+
else iter(interval)
|
|
96
|
+
)
|
|
100
97
|
|
|
101
98
|
for val in itr:
|
|
102
99
|
yield val
|
|
103
100
|
|
|
104
101
|
|
|
105
|
-
def runtime(
|
|
106
|
-
*,
|
|
107
|
-
value: Callable[[Any], float],
|
|
108
|
-
) -> Generator[float, None, None]:
|
|
102
|
+
def runtime(*, value: Callable[[Any], float]) -> Generator[float, Any, None]:
|
|
109
103
|
"""Generator that is based on parsing the return value or thrown
|
|
110
104
|
exception of the decorated method
|
|
111
105
|
|
|
@@ -114,6 +108,6 @@ def runtime(
|
|
|
114
108
|
function's return value or thrown exception and
|
|
115
109
|
determines how long to wait
|
|
116
110
|
"""
|
|
117
|
-
ret_or_exc = yield
|
|
111
|
+
ret_or_exc = yield 0
|
|
118
112
|
while True:
|
|
119
113
|
ret_or_exc = yield value(ret_or_exc)
|