@@ -426,6 +426,50 @@ def is_done_booting(devbox: DevboxView) -> bool:
426426
427427 return devbox
428428
429+ def await_suspended (
430+ self ,
431+ id : str ,
432+ * ,
433+ polling_config : PollingConfig | None = None ,
434+ ) -> DevboxView :
435+ """Wait for a devbox to reach the suspended state.
436+
437+ Args:
438+ id: The ID of the devbox to wait for.
439+ polling_config: Optional polling configuration.
440+
441+ Returns:
442+ The devbox in the suspended state.
443+
444+ Raises:
445+ PollingTimeout: If polling times out before the devbox is suspended.
446+ RunloopError: If the devbox enters a non-suspended terminal state.
447+ """
448+
449+ def wait_for_devbox_status () -> DevboxView :
450+ return self ._post (
451+ f"/v1/devboxes/{ id } /wait_for_status" ,
452+ body = {"statuses" : ["suspended" , "failure" , "shutdown" ]},
453+ cast_to = DevboxView ,
454+ )
455+
456+ def handle_timeout_error (error : Exception ) -> DevboxView :
457+ if isinstance (error , APITimeoutError ) or (
458+ isinstance (error , APIStatusError ) and error .response .status_code == 408
459+ ):
460+ return placeholder_devbox_view (id )
461+ raise error
462+
463+ def is_terminal_state (devbox : DevboxView ) -> bool :
464+ return devbox .status in {"suspended" , "failure" , "shutdown" }
465+
466+ devbox = poll_until (wait_for_devbox_status , is_terminal_state , polling_config , handle_timeout_error )
467+
468+ if devbox .status != "suspended" :
469+ raise RunloopError (f"Devbox entered non-suspended terminal state: { devbox .status } " )
470+
471+ return devbox
472+
429473 def create_and_await_running (
430474 self ,
431475 * ,
@@ -1917,6 +1961,48 @@ def is_done_booting(devbox: DevboxView) -> bool:
19171961
19181962 return devbox
19191963
1964+ async def await_suspended (
1965+ self ,
1966+ id : str ,
1967+ * ,
1968+ polling_config : PollingConfig | None = None ,
1969+ ) -> DevboxView :
1970+ """Wait for a devbox to reach the suspended state.
1971+
1972+ Args:
1973+ id: The ID of the devbox to wait for.
1974+ polling_config: Optional polling configuration.
1975+
1976+ Returns:
1977+ The devbox in the suspended state.
1978+
1979+ Raises:
1980+ PollingTimeout: If polling times out before the devbox is suspended.
1981+ RunloopError: If the devbox enters a non-suspended terminal state.
1982+ """
1983+
1984+ async def wait_for_devbox_status () -> DevboxView :
1985+ try :
1986+ return await self ._post (
1987+ f"/v1/devboxes/{ id } /wait_for_status" ,
1988+ body = {"statuses" : ["suspended" , "failure" , "shutdown" ]},
1989+ cast_to = DevboxView ,
1990+ )
1991+ except (APITimeoutError , APIStatusError ) as error :
1992+ if isinstance (error , APITimeoutError ) or error .response .status_code == 408 :
1993+ return placeholder_devbox_view (id )
1994+ raise
1995+
1996+ def is_terminal_state (devbox : DevboxView ) -> bool :
1997+ return devbox .status in {"suspended" , "failure" , "shutdown" }
1998+
1999+ devbox = await async_poll_until (wait_for_devbox_status , is_terminal_state , polling_config )
2000+
2001+ if devbox .status != "suspended" :
2002+ raise RunloopError (f"Devbox entered non-suspended terminal state: { devbox .status } " )
2003+
2004+ return devbox
2005+
19202006 async def update (
19212007 self ,
19222008 id : str ,
0 commit comments