Skip to content

Commit cd8877c

Browse files
authored
Merge pull request #613 from runloopai/alb/add-long-poll-to-await-completed
Add long poll endpoint for querying async exec status
2 parents 9b125a7 + 314fd5a commit cd8877c

1 file changed

Lines changed: 57 additions & 24 deletions

File tree

src/runloop_api_client/resources/devboxes/executions.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
async_to_streamed_response_wrapper,
1818
)
1919
from ..._constants import DEFAULT_TIMEOUT
20+
from ..._exceptions import APIStatusError
2021
from ...lib.polling import PollingConfig, poll_until
2122
from ..._base_client import make_request_options
2223
from ...types.devboxes import execution_retrieve_params, execution_execute_sync_params, execution_execute_async_params
@@ -105,7 +106,7 @@ def await_completed(
105106
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
106107
) -> DevboxAsyncExecutionDetailView:
107108
"""Wait for an execution to complete.
108-
109+
109110
Args:
110111
execution_id: The ID of the execution to wait for
111112
id: The ID of the devbox
@@ -121,20 +122,37 @@ def await_completed(
121122
Raises:
122123
PollingTimeout: If polling times out before execution completes
123124
"""
124-
def retrieve_execution() -> DevboxAsyncExecutionDetailView:
125-
return self.retrieve(
126-
execution_id,
127-
devbox_id=devbox_id,
128-
extra_headers=extra_headers,
129-
extra_query=extra_query,
130-
extra_body=extra_body,
131-
timeout=timeout
125+
126+
def wait_for_execution_status() -> DevboxAsyncExecutionDetailView:
127+
# This wait_for_status endpoint polls the execution status for 60 seconds until it reaches either completed.
128+
return self._post(
129+
f"/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status",
130+
body={"statuses": ["completed"]},
131+
options=make_request_options(
132+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
133+
),
134+
cast_to=DevboxAsyncExecutionDetailView,
132135
)
133136

137+
def handle_timeout_error(error: Exception) -> DevboxAsyncExecutionDetailView:
138+
# Handle 408 timeout errors by returning current execution state to continue polling
139+
if isinstance(error, APIStatusError) and error.response.status_code == 408:
140+
# Return a placeholder result to continue polling
141+
return DevboxAsyncExecutionDetailView(
142+
devbox_id=devbox_id,
143+
execution_id=execution_id,
144+
status="queued",
145+
stdout="",
146+
stderr="",
147+
)
148+
else:
149+
# Re-raise other errors to stop polling
150+
raise error
151+
134152
def is_done(execution: DevboxAsyncExecutionDetailView) -> bool:
135-
return execution.status == 'completed'
153+
return execution.status == "completed"
136154

137-
return poll_until(retrieve_execution, is_done, config)
155+
return poll_until(wait_for_execution_status, is_done, config, handle_timeout_error)
138156

139157
def execute_async(
140158
self,
@@ -369,7 +387,7 @@ async def retrieve(
369387

370388
async def await_completed(
371389
self,
372-
execution_id: str,
390+
execution_id: str,
373391
*,
374392
devbox_id: str,
375393
polling_config: PollingConfig | None = None,
@@ -381,7 +399,7 @@ async def await_completed(
381399
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
382400
) -> DevboxAsyncExecutionDetailView:
383401
"""Wait for an execution to complete.
384-
402+
385403
Args:
386404
execution_id: The ID of the execution to wait for
387405
id: The ID of the devbox
@@ -397,20 +415,35 @@ async def await_completed(
397415
Raises:
398416
PollingTimeout: If polling times out before execution completes
399417
"""
400-
async def retrieve_execution() -> DevboxAsyncExecutionDetailView:
401-
return await self.retrieve(
402-
execution_id,
403-
devbox_id=devbox_id,
404-
extra_headers=extra_headers,
405-
extra_query=extra_query,
406-
extra_body=extra_body,
407-
timeout=timeout,
408-
)
418+
419+
async def wait_for_execution_status() -> DevboxAsyncExecutionDetailView:
420+
try:
421+
return await self._post(
422+
f"/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status",
423+
body={"statuses": ["completed"]},
424+
options=make_request_options(
425+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
426+
),
427+
cast_to=DevboxAsyncExecutionDetailView,
428+
)
429+
except APIStatusError as error:
430+
if error.response.status_code == 408:
431+
# Handle 408 timeout errors by returning current execution state to continue polling
432+
return DevboxAsyncExecutionDetailView(
433+
devbox_id=devbox_id,
434+
execution_id=execution_id,
435+
status="queued",
436+
stdout="",
437+
stderr="",
438+
)
439+
else:
440+
# Re-raise other errors to stop polling
441+
raise
409442

410443
def is_done(execution: DevboxAsyncExecutionDetailView) -> bool:
411-
return execution.status == 'completed'
444+
return execution.status == "completed"
412445

413-
return await async_poll_until(retrieve_execution, is_done, polling_config)
446+
return await async_poll_until(wait_for_execution_status, is_done, polling_config)
414447

415448
async def execute_async(
416449
self,

0 commit comments

Comments
 (0)