@@ -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 * ,
@@ -1921,6 +1965,48 @@ def is_done_booting(devbox: DevboxView) -> bool:
19211965
19221966 return devbox
19231967
1968+ async def await_suspended (
1969+ self ,
1970+ id : str ,
1971+ * ,
1972+ polling_config : PollingConfig | None = None ,
1973+ ) -> DevboxView :
1974+ """Wait for a devbox to reach the suspended state.
1975+
1976+ Args:
1977+ id: The ID of the devbox to wait for.
1978+ polling_config: Optional polling configuration.
1979+
1980+ Returns:
1981+ The devbox in the suspended state.
1982+
1983+ Raises:
1984+ PollingTimeout: If polling times out before the devbox is suspended.
1985+ RunloopError: If the devbox enters a non-suspended terminal state.
1986+ """
1987+
1988+ async def wait_for_devbox_status () -> DevboxView :
1989+ try :
1990+ return await self ._post (
1991+ f"/v1/devboxes/{ id } /wait_for_status" ,
1992+ body = {"statuses" : ["suspended" , "failure" , "shutdown" ]},
1993+ cast_to = DevboxView ,
1994+ )
1995+ except (APITimeoutError , APIStatusError ) as error :
1996+ if isinstance (error , APITimeoutError ) or error .response .status_code == 408 :
1997+ return placeholder_devbox_view (id )
1998+ raise
1999+
2000+ def is_terminal_state (devbox : DevboxView ) -> bool :
2001+ return devbox .status in {"suspended" , "failure" , "shutdown" }
2002+
2003+ devbox = await async_poll_until (wait_for_devbox_status , is_terminal_state , polling_config )
2004+
2005+ if devbox .status != "suspended" :
2006+ raise RunloopError (f"Devbox entered non-suspended terminal state: { devbox .status } " )
2007+
2008+ return devbox
2009+
19242010 async def update (
19252011 self ,
19262012 id : str ,
0 commit comments