nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__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.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
nextmv/polling.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polling module containing logic to poll for a run result.
|
|
3
|
+
|
|
4
|
+
Polling can be used with both Cloud and local applications.
|
|
5
|
+
|
|
6
|
+
Classes
|
|
7
|
+
-------
|
|
8
|
+
PollingOptions
|
|
9
|
+
Options to use when polling for a run result.
|
|
10
|
+
|
|
11
|
+
Functions
|
|
12
|
+
---------
|
|
13
|
+
poll
|
|
14
|
+
Function to poll a function until it succeeds or the polling strategy is
|
|
15
|
+
exhausted.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import random
|
|
19
|
+
import time
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from nextmv.logger import log
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PollingOptions:
|
|
29
|
+
"""
|
|
30
|
+
Options to use when polling for a run result.
|
|
31
|
+
|
|
32
|
+
You can import the `PollingOptions` class directly from `nextmv`:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from nextmv import PollingOptions
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The Cloud API will be polled for the result. The polling stops if:
|
|
39
|
+
|
|
40
|
+
* The maximum number of polls (tries) are exhausted. This is specified by
|
|
41
|
+
the `max_tries` parameter.
|
|
42
|
+
* The maximum duration of the polling strategy is reached. This is
|
|
43
|
+
specified by the `max_duration` parameter.
|
|
44
|
+
|
|
45
|
+
Before conducting the first poll, the `initial_delay` is used to sleep.
|
|
46
|
+
After each poll, a sleep duration is calculated using the following
|
|
47
|
+
strategy, based on exponential backoff with jitter:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
sleep_duration = min(`max_delay`, `delay` + `backoff` * 2 ** i + Uniform(0, `jitter`))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Where:
|
|
54
|
+
* i is the retry (poll) number.
|
|
55
|
+
* Uniform is the uniform distribution.
|
|
56
|
+
|
|
57
|
+
Note that the sleep duration is capped by the `max_delay` parameter.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
backoff : float, default=0.9
|
|
62
|
+
Exponential backoff factor, in seconds, to use between polls.
|
|
63
|
+
delay : float, default=0.1
|
|
64
|
+
Base delay to use between polls, in seconds.
|
|
65
|
+
initial_delay : float, default=1.0
|
|
66
|
+
Initial delay to use before starting the polling strategy, in seconds.
|
|
67
|
+
max_delay : float, default=20.0
|
|
68
|
+
Maximum delay to use between polls, in seconds.
|
|
69
|
+
max_duration : float, default=300.0
|
|
70
|
+
Maximum duration of the polling strategy, in seconds.
|
|
71
|
+
max_tries : int, default=100
|
|
72
|
+
Maximum number of tries to use.
|
|
73
|
+
jitter : float, default=1.0
|
|
74
|
+
Jitter to use for the polling strategy. A uniform distribution is sampled
|
|
75
|
+
between 0 and this number. The resulting random number is added to the
|
|
76
|
+
delay for each poll, adding a random noise. Set this to 0 to avoid using
|
|
77
|
+
random jitter.
|
|
78
|
+
verbose : bool, default=False
|
|
79
|
+
Whether to log the polling strategy. This is useful for debugging.
|
|
80
|
+
stop : callable, default=None
|
|
81
|
+
Function to call to check if the polling should stop. This is useful for
|
|
82
|
+
stopping the polling based on external conditions. The function should
|
|
83
|
+
return True to stop the polling and False to continue. The function does
|
|
84
|
+
not receive any arguments. The function is called before each poll.
|
|
85
|
+
|
|
86
|
+
Examples
|
|
87
|
+
--------
|
|
88
|
+
>>> from nextmv.cloud import PollingOptions
|
|
89
|
+
>>> # Create polling options with custom settings
|
|
90
|
+
>>> polling_options = PollingOptions(
|
|
91
|
+
... max_tries=50,
|
|
92
|
+
... max_duration=600,
|
|
93
|
+
... verbose=True
|
|
94
|
+
... )
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
backoff: float = 0.9
|
|
98
|
+
"""
|
|
99
|
+
Exponential backoff factor, in seconds, to use between polls.
|
|
100
|
+
"""
|
|
101
|
+
delay: float = 0.1
|
|
102
|
+
"""Base delay to use between polls, in seconds."""
|
|
103
|
+
initial_delay: float = 1
|
|
104
|
+
"""
|
|
105
|
+
Initial delay to use before starting the polling strategy, in seconds.
|
|
106
|
+
"""
|
|
107
|
+
max_delay: float = 20
|
|
108
|
+
"""Maximum delay to use between polls, in seconds."""
|
|
109
|
+
max_duration: float = -1
|
|
110
|
+
"""
|
|
111
|
+
Maximum duration of the polling strategy, in seconds. A negative value means no limit.
|
|
112
|
+
"""
|
|
113
|
+
max_tries: int = -1
|
|
114
|
+
"""Maximum number of tries to use. A negative value means no limit."""
|
|
115
|
+
jitter: float = 1
|
|
116
|
+
"""
|
|
117
|
+
Jitter to use for the polling strategy. A uniform distribution is sampled
|
|
118
|
+
between 0 and this number. The resulting random number is added to the
|
|
119
|
+
delay for each poll, adding a random noise. Set this to 0 to avoid using
|
|
120
|
+
random jitter.
|
|
121
|
+
"""
|
|
122
|
+
verbose: bool = False
|
|
123
|
+
"""Whether to log the polling strategy. This is useful for debugging."""
|
|
124
|
+
stop: Callable[[], bool] | None = None
|
|
125
|
+
"""
|
|
126
|
+
Function to call to check if the polling should stop. This is useful for
|
|
127
|
+
stopping the polling based on external conditions. The function should
|
|
128
|
+
return True to stop the polling and False to continue. The function does
|
|
129
|
+
not receive any arguments. The function is called before each poll.
|
|
130
|
+
"""
|
|
131
|
+
sleep_duration_func: Callable[[], float] | None = None
|
|
132
|
+
"""
|
|
133
|
+
Optional function to calculate the sleep duration between polls. If provided,
|
|
134
|
+
this function will be called to determine how long to sleep instead of using
|
|
135
|
+
the default exponential backoff calculation. The function should return a
|
|
136
|
+
float representing the sleep duration in seconds. The function does not
|
|
137
|
+
receive any arguments and is called before each sleep.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
DEFAULT_POLLING_OPTIONS: PollingOptions = PollingOptions()
|
|
142
|
+
"""
|
|
143
|
+
!!! warning
|
|
144
|
+
`DEFAULT_POLLING_OPTIONS` is a mutable global variable. Use the `default_polling_options`
|
|
145
|
+
function to obtain a fresh instance of `PollingOptions` with default settings.
|
|
146
|
+
|
|
147
|
+
Default polling options to use when polling for a run result. This constant
|
|
148
|
+
provides the default values for `PollingOptions` used across the module.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def default_polling_options() -> PollingOptions:
|
|
153
|
+
"""
|
|
154
|
+
Returns a new instance of PollingOptions with default settings.
|
|
155
|
+
|
|
156
|
+
This function can be used to obtain a fresh set of default polling options
|
|
157
|
+
that can be modified as needed without affecting the global defaults.
|
|
158
|
+
|
|
159
|
+
You can import the `default_polling_options` function directly from `nextmv`:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from nextmv import default_polling_options
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
PollingOptions
|
|
168
|
+
A new instance of PollingOptions with default values.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
return PollingOptions()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def poll( # noqa: C901
|
|
175
|
+
polling_options: PollingOptions,
|
|
176
|
+
polling_func: Callable[[], tuple[Any, bool]],
|
|
177
|
+
__sleep_func: Callable[[float], None] = time.sleep,
|
|
178
|
+
) -> Any:
|
|
179
|
+
"""
|
|
180
|
+
Poll a function until it succeeds or the polling strategy is exhausted.
|
|
181
|
+
|
|
182
|
+
You can import the `poll` function directly from `nextmv`:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from nextmv import poll
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This function implements a flexible polling strategy with exponential backoff
|
|
189
|
+
and jitter. It calls the provided polling function repeatedly until it indicates
|
|
190
|
+
success, the maximum number of tries is reached, or the maximum duration is exceeded.
|
|
191
|
+
|
|
192
|
+
The `polling_func` is a callable that must return a `tuple[Any, bool]`
|
|
193
|
+
where the first element is the result of the polling and the second
|
|
194
|
+
element is a boolean indicating if the polling was successful or should be
|
|
195
|
+
retried.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
polling_options : PollingOptions
|
|
200
|
+
Options for configuring the polling behavior, including retry counts,
|
|
201
|
+
delays, timeouts, and verbosity settings.
|
|
202
|
+
polling_func : callable
|
|
203
|
+
Function to call to check if the polling was successful. Must return a tuple
|
|
204
|
+
where the first element is the result value and the second is a boolean
|
|
205
|
+
indicating success (True) or need to retry (False).
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
Any
|
|
210
|
+
Result value from the polling function when successful.
|
|
211
|
+
|
|
212
|
+
Raises
|
|
213
|
+
------
|
|
214
|
+
TimeoutError
|
|
215
|
+
If the polling exceeds the maximum duration specified in polling_options.
|
|
216
|
+
RuntimeError
|
|
217
|
+
If the maximum number of tries is exhausted without success.
|
|
218
|
+
|
|
219
|
+
Examples
|
|
220
|
+
--------
|
|
221
|
+
>>> from nextmv.cloud import PollingOptions, poll
|
|
222
|
+
>>> import time
|
|
223
|
+
>>>
|
|
224
|
+
>>> # Define a polling function that succeeds after 3 tries
|
|
225
|
+
>>> counter = 0
|
|
226
|
+
>>> def check_completion() -> tuple[str, bool]:
|
|
227
|
+
... global counter
|
|
228
|
+
... counter += 1
|
|
229
|
+
... if counter >= 3:
|
|
230
|
+
... return "Success", True
|
|
231
|
+
... return None, False
|
|
232
|
+
...
|
|
233
|
+
>>> # Configure polling options
|
|
234
|
+
>>> options = PollingOptions(
|
|
235
|
+
... max_tries=5,
|
|
236
|
+
... delay=0.1,
|
|
237
|
+
... backoff=0.2,
|
|
238
|
+
... verbose=True
|
|
239
|
+
... )
|
|
240
|
+
>>>
|
|
241
|
+
>>> # Poll until the function succeeds
|
|
242
|
+
>>> result = poll(options, check_completion)
|
|
243
|
+
>>> print(result)
|
|
244
|
+
'Success'
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
# Start by sleeping for the duration specified as initial delay.
|
|
248
|
+
if polling_options.verbose:
|
|
249
|
+
log(f"polling | sleeping for initial delay: {polling_options.initial_delay}")
|
|
250
|
+
|
|
251
|
+
__sleep_func(polling_options.initial_delay)
|
|
252
|
+
|
|
253
|
+
start_time = time.time()
|
|
254
|
+
stopped = False
|
|
255
|
+
|
|
256
|
+
# Begin the polling process.
|
|
257
|
+
max_reached = False
|
|
258
|
+
ix = 0
|
|
259
|
+
while True:
|
|
260
|
+
# Check if we reached the maximum number of tries. Break if so.
|
|
261
|
+
if ix >= polling_options.max_tries and polling_options.max_tries >= 0:
|
|
262
|
+
break
|
|
263
|
+
ix += 1
|
|
264
|
+
|
|
265
|
+
# Check is we should stop polling according to the stop callback.
|
|
266
|
+
if polling_options.stop is not None and polling_options.stop():
|
|
267
|
+
stopped = True
|
|
268
|
+
|
|
269
|
+
break
|
|
270
|
+
|
|
271
|
+
# We check if we can stop polling.
|
|
272
|
+
result, ok = polling_func()
|
|
273
|
+
if polling_options.verbose:
|
|
274
|
+
log(f"polling | try # {ix + 1}, ok: {ok}")
|
|
275
|
+
|
|
276
|
+
if ok:
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
# An exit condition happens if we exceed the allowed duration.
|
|
280
|
+
passed = time.time() - start_time
|
|
281
|
+
if polling_options.verbose:
|
|
282
|
+
log(f"polling | elapsed time: {passed}")
|
|
283
|
+
|
|
284
|
+
if passed >= polling_options.max_duration and polling_options.max_duration >= 0:
|
|
285
|
+
raise TimeoutError(
|
|
286
|
+
f"polling did not succeed after {passed} seconds, exceeds max duration: {polling_options.max_duration}",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Calculate the delay.
|
|
290
|
+
if polling_options.sleep_duration_func is not None:
|
|
291
|
+
# Use the custom sleep duration function if provided.
|
|
292
|
+
sleep_duration = polling_options.sleep_duration_func()
|
|
293
|
+
else:
|
|
294
|
+
# Calculate delay using exponential backoff with jitter.
|
|
295
|
+
if max_reached:
|
|
296
|
+
# If we already reached the maximum, we don't want to further calculate the
|
|
297
|
+
# delay to avoid overflows.
|
|
298
|
+
delay = polling_options.max_delay
|
|
299
|
+
else:
|
|
300
|
+
delay = polling_options.delay # Base
|
|
301
|
+
delay += polling_options.backoff * (2**ix) # Add exponential backoff.
|
|
302
|
+
|
|
303
|
+
# We cannot exceed the max delay.
|
|
304
|
+
if delay >= polling_options.max_delay:
|
|
305
|
+
max_reached = True
|
|
306
|
+
delay = polling_options.max_delay
|
|
307
|
+
|
|
308
|
+
# Add jitter.
|
|
309
|
+
delay += random.uniform(0, polling_options.jitter)
|
|
310
|
+
|
|
311
|
+
sleep_duration = delay
|
|
312
|
+
|
|
313
|
+
if polling_options.verbose:
|
|
314
|
+
log(f"polling | sleeping for duration: {sleep_duration}")
|
|
315
|
+
|
|
316
|
+
__sleep_func(sleep_duration)
|
|
317
|
+
|
|
318
|
+
if stopped:
|
|
319
|
+
log("polling | stop condition met, stopping polling")
|
|
320
|
+
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
raise RuntimeError(
|
|
324
|
+
f"polling did not succeed after {polling_options.max_tries} tries",
|
|
325
|
+
)
|