|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import time |
| 6 | + |
5 | 7 | import httpx |
6 | 8 |
|
7 | 9 | from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given |
|
18 | 20 | from ..._base_client import AsyncPaginator, make_request_options |
19 | 21 | from ...types.config_registry import analysis_list_params |
20 | 22 | from ...types.analysis_summary import AnalysisSummary |
| 23 | +from ...lib.config_registry_wait import ( |
| 24 | + DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL, |
| 25 | + poll_delay, |
| 26 | + poll_headers, |
| 27 | + analysis_finished, |
| 28 | + wait_timeout_error, |
| 29 | + validate_wait_options, |
| 30 | +) |
21 | 31 | from ...types.config_registry_response import ConfigRegistryResponse |
22 | 32 |
|
23 | 33 | __all__ = ["AnalysesResource", "AsyncAnalysesResource"] |
@@ -79,6 +89,54 @@ def retrieve( |
79 | 89 | cast_to=ConfigRegistryResponse, |
80 | 90 | ) |
81 | 91 |
|
| 92 | + def wait_for_result( |
| 93 | + self, |
| 94 | + id: str, |
| 95 | + *, |
| 96 | + poll_interval: float = DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL, |
| 97 | + max_wait_seconds: float | None = None, |
| 98 | + extra_headers: Headers | None = None, |
| 99 | + extra_query: Query | None = None, |
| 100 | + extra_body: Body | None = None, |
| 101 | + timeout: float | httpx.Timeout | None | NotGiven = not_given, |
| 102 | + ) -> ConfigRegistryResponse: |
| 103 | + """Wait for an analysis to finish and return its complete result. |
| 104 | +
|
| 105 | + The first retrieval happens immediately. ``max_wait_seconds`` is a soft |
| 106 | + polling deadline: an in-flight request and its normal retries may finish |
| 107 | + after it. Timing out does not cancel the remote analysis. |
| 108 | + """ |
| 109 | + validate_wait_options(poll_interval, max_wait_seconds) |
| 110 | + started_at = time.monotonic() |
| 111 | + deadline = started_at + max_wait_seconds if max_wait_seconds is not None else None |
| 112 | + headers = poll_headers(extra_headers) |
| 113 | + polls = 0 |
| 114 | + last_status: str | None = None |
| 115 | + |
| 116 | + while True: |
| 117 | + if polls > 0 and deadline is not None and time.monotonic() >= deadline: |
| 118 | + raise wait_timeout_error(id, polls, last_status, started_at) |
| 119 | + |
| 120 | + response = self.retrieve( |
| 121 | + id, |
| 122 | + extra_headers=headers, |
| 123 | + extra_query=extra_query, |
| 124 | + extra_body=extra_body, |
| 125 | + timeout=timeout, |
| 126 | + ) |
| 127 | + polls += 1 |
| 128 | + finished, last_status = analysis_finished(response, id) |
| 129 | + if finished: |
| 130 | + return response |
| 131 | + |
| 132 | + delay = poll_delay(poll_interval) |
| 133 | + if deadline is not None: |
| 134 | + remaining = deadline - time.monotonic() |
| 135 | + if remaining <= 0: |
| 136 | + raise wait_timeout_error(id, polls, last_status, started_at) |
| 137 | + delay = min(delay, remaining) |
| 138 | + self._sleep(delay) |
| 139 | + |
82 | 140 | def list( |
83 | 141 | self, |
84 | 142 | *, |
@@ -220,6 +278,55 @@ async def retrieve( |
220 | 278 | cast_to=ConfigRegistryResponse, |
221 | 279 | ) |
222 | 280 |
|
| 281 | + async def wait_for_result( |
| 282 | + self, |
| 283 | + id: str, |
| 284 | + *, |
| 285 | + poll_interval: float = DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL, |
| 286 | + max_wait_seconds: float | None = None, |
| 287 | + extra_headers: Headers | None = None, |
| 288 | + extra_query: Query | None = None, |
| 289 | + extra_body: Body | None = None, |
| 290 | + timeout: float | httpx.Timeout | None | NotGiven = not_given, |
| 291 | + ) -> ConfigRegistryResponse: |
| 292 | + """Wait for an analysis to finish and return its complete result. |
| 293 | +
|
| 294 | + The first retrieval happens immediately. ``max_wait_seconds`` is a soft |
| 295 | + polling deadline: an in-flight request and its normal retries may finish |
| 296 | + after it. Timing out does not cancel the remote analysis. Cancelling the |
| 297 | + calling task stops the wait without cancelling the remote analysis. |
| 298 | + """ |
| 299 | + validate_wait_options(poll_interval, max_wait_seconds) |
| 300 | + started_at = time.monotonic() |
| 301 | + deadline = started_at + max_wait_seconds if max_wait_seconds is not None else None |
| 302 | + headers = poll_headers(extra_headers) |
| 303 | + polls = 0 |
| 304 | + last_status: str | None = None |
| 305 | + |
| 306 | + while True: |
| 307 | + if polls > 0 and deadline is not None and time.monotonic() >= deadline: |
| 308 | + raise wait_timeout_error(id, polls, last_status, started_at) |
| 309 | + |
| 310 | + response = await self.retrieve( |
| 311 | + id, |
| 312 | + extra_headers=headers, |
| 313 | + extra_query=extra_query, |
| 314 | + extra_body=extra_body, |
| 315 | + timeout=timeout, |
| 316 | + ) |
| 317 | + polls += 1 |
| 318 | + finished, last_status = analysis_finished(response, id) |
| 319 | + if finished: |
| 320 | + return response |
| 321 | + |
| 322 | + delay = poll_delay(poll_interval) |
| 323 | + if deadline is not None: |
| 324 | + remaining = deadline - time.monotonic() |
| 325 | + if remaining <= 0: |
| 326 | + raise wait_timeout_error(id, polls, last_status, started_at) |
| 327 | + delay = min(delay, remaining) |
| 328 | + await self._sleep(delay) |
| 329 | + |
223 | 330 | def list( |
224 | 331 | self, |
225 | 332 | *, |
|
0 commit comments