Skip to content

Commit 34f2dd0

Browse files
authored
cp dines (#697)
1 parent 80c9f2b commit 34f2dd0

6 files changed

Lines changed: 174 additions & 14 deletions

File tree

src/runloop_api_client/sdk/async_devbox.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,36 @@ async def suspend(
172172

173173
async def resume(
174174
self,
175+
*,
176+
polling_config: PollingConfig | None = None,
175177
**options: Unpack[LongRequestOptions],
176178
) -> DevboxView:
177-
"""Resume a suspended devbox.
179+
"""Resume a suspended devbox, restoring it to running state.
178180
179-
Returns immediately after issuing the resume request. Call
180-
:meth:`await_running` if you need to wait for the devbox to reach the
181-
``running`` state (contrast with the synchronous SDK, which blocks).
181+
Waits for the devbox to reach running state before returning.
182182
183+
:param polling_config: Optional polling behavior overrides, defaults to None
184+
:type polling_config: PollingConfig | None, optional
183185
:param options: Optional long-running request configuration
184186
:return: Resumed devbox state info
185187
:rtype: DevboxView
186188
"""
189+
await self.resume_async(**options)
190+
return await self.await_running(polling_config=polling_config)
191+
192+
async def resume_async(
193+
self,
194+
**options: Unpack[LongRequestOptions],
195+
) -> DevboxView:
196+
"""Resume a suspended devbox without waiting for it to reach running state.
197+
198+
Initiates the resume operation and returns immediately. Use :meth:`await_running`
199+
to wait for the devbox to reach running state if needed.
200+
201+
:param options: Optional long-running request configuration
202+
:return: Devbox state info immediately after resume request
203+
:rtype: DevboxView
204+
"""
187205
return await self._client.devboxes.resume(
188206
self._id,
189207
**options,

src/runloop_api_client/sdk/devbox.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,26 @@ def resume(
185185
:return: Resumed devbox state info
186186
:rtype: :class:`~runloop_api_client.types.devbox_view.DevboxView`
187187
"""
188-
self._client.devboxes.resume(
188+
self.resume_async(**filter_params(options, LongRequestOptions))
189+
return self._client.devboxes.await_running(self._id, polling_config=options.get("polling_config"))
190+
191+
def resume_async(
192+
self,
193+
**options: Unpack[LongRequestOptions],
194+
) -> DevboxView:
195+
"""Resume a suspended devbox without waiting for it to reach running state.
196+
197+
Initiates the resume operation and returns immediately. Use :meth:`await_running`
198+
to wait for the devbox to reach running state if needed.
199+
200+
:param options: Optional long-running request configuration
201+
:return: Devbox state info immediately after resume request
202+
:rtype: :class:`~runloop_api_client.types.devbox_view.DevboxView`
203+
"""
204+
return self._client.devboxes.resume(
189205
self._id,
190-
**filter_params(options, LongRequestOptions),
206+
**options,
191207
)
192-
return self._client.devboxes.await_running(self._id, polling_config=options.get("polling_config"))
193208

194209
def keep_alive(
195210
self,

tests/sdk/async_devbox/test_core.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,40 @@ async def test_suspend(self, mock_async_client: AsyncMock, devbox_view: MockDevb
161161
async def test_resume(self, mock_async_client: AsyncMock, devbox_view: MockDevboxView) -> None:
162162
"""Test resume method."""
163163
mock_async_client.devboxes.resume = AsyncMock(return_value=devbox_view)
164+
mock_async_client.devboxes.await_running = AsyncMock(return_value=devbox_view)
165+
polling_config = PollingConfig(timeout_seconds=60.0)
164166

165167
devbox = AsyncDevbox(mock_async_client, "dbx_123")
166168
result = await devbox.resume(
169+
polling_config=polling_config,
170+
extra_headers={"X-Custom": "value"},
171+
extra_query={"param": "value"},
172+
extra_body={"key": "value"},
173+
timeout=30.0,
174+
idempotency_key="key-123",
175+
)
176+
177+
assert result == devbox_view
178+
mock_async_client.devboxes.resume.assert_called_once_with(
179+
"dev_123",
180+
extra_headers={"X-Custom": "value"},
181+
extra_query={"param": "value"},
182+
extra_body={"key": "value"},
183+
timeout=30.0,
184+
idempotency_key="key-123",
185+
)
186+
mock_async_client.devboxes.await_running.assert_called_once_with(
187+
"dev_123",
188+
polling_config=polling_config,
189+
)
190+
191+
@pytest.mark.asyncio
192+
async def test_resume_async(self, mock_async_client: AsyncMock, devbox_view: MockDevboxView) -> None:
193+
"""Test resume_async method."""
194+
mock_async_client.devboxes.resume = AsyncMock(return_value=devbox_view)
195+
196+
devbox = AsyncDevbox(mock_async_client, "dev_123")
197+
result = await devbox.resume_async(
167198
extra_headers={"X-Custom": "value"},
168199
extra_query={"param": "value"},
169200
extra_body={"key": "value"},

tests/sdk/devbox/test_core.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,32 @@ def test_resume(self, mock_client: Mock, devbox_view: MockDevboxView) -> None:
190190
polling_config=polling_config,
191191
)
192192

193+
def test_resume_async(self, mock_client: Mock, devbox_view: MockDevboxView) -> None:
194+
"""Test resume_async method."""
195+
mock_client.devboxes.resume.return_value = devbox_view
196+
mock_client.devboxes.await_running = Mock()
197+
198+
devbox = Devbox(mock_client, "dev_123")
199+
result = devbox.resume_async(
200+
extra_headers={"X-Custom": "value"},
201+
extra_query={"param": "value"},
202+
extra_body={"key": "value"},
203+
timeout=30.0,
204+
idempotency_key="key-123",
205+
)
206+
207+
assert result == devbox_view
208+
mock_client.devboxes.resume.assert_called_once_with(
209+
"dev_123",
210+
extra_headers={"X-Custom": "value"},
211+
extra_query={"param": "value"},
212+
extra_body={"key": "value"},
213+
timeout=30.0,
214+
idempotency_key="key-123",
215+
)
216+
# Should not call await_running
217+
mock_client.devboxes.await_running.assert_not_called()
218+
193219
def test_keep_alive(self, mock_client: Mock) -> None:
194220
"""Test keep_alive method."""
195221
mock_client.devboxes.keep_alive.return_value = object()

tests/smoketests/sdk/test_async_devbox.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,10 @@ async def test_suspend_and_resume(self, async_sdk_client: AsyncRunloopSDK) -> No
346346
info = await devbox.get_info()
347347
assert info.status == "suspended"
348348

349-
# Resume the devbox
350-
resumed_info = await devbox.resume()
351-
if resumed_info.status != "running":
352-
resumed_info = await devbox.await_running(
353-
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0)
354-
)
349+
# Resume the devbox - resume() automatically waits for running state
350+
resumed_info = await devbox.resume(
351+
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0)
352+
)
355353
assert resumed_info.status == "running"
356354

357355
# Verify running state
@@ -360,6 +358,43 @@ async def test_suspend_and_resume(self, async_sdk_client: AsyncRunloopSDK) -> No
360358
finally:
361359
await devbox.shutdown()
362360

361+
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
362+
async def test_resume_async(self, async_sdk_client: AsyncRunloopSDK) -> None:
363+
"""Test resuming a devbox asynchronously without waiting."""
364+
devbox = await async_sdk_client.devbox.create(
365+
name=unique_name("sdk-async-devbox-resume-async"),
366+
launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
367+
)
368+
369+
try:
370+
# Suspend the devbox
371+
suspended_info = await devbox.suspend()
372+
if suspended_info.status != "suspended":
373+
suspended_info = await devbox.await_suspended(
374+
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0)
375+
)
376+
assert suspended_info.status == "suspended"
377+
378+
# Verify suspended state
379+
info = await devbox.get_info()
380+
assert info.status == "suspended"
381+
382+
# Resume the devbox asynchronously - doesn't wait automatically
383+
resume_response = await devbox.resume_async()
384+
assert resume_response is not None
385+
386+
# Status might still be suspended or transitioning
387+
info_after_resume = await devbox.get_info()
388+
assert info_after_resume.status in ["suspended", "running", "starting"]
389+
390+
# Now wait for running state explicitly
391+
running_info = await devbox.await_running(
392+
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0)
393+
)
394+
assert running_info.status == "running"
395+
finally:
396+
await devbox.shutdown()
397+
363398
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
364399
async def test_await_running(self, async_sdk_client: AsyncRunloopSDK) -> None:
365400
"""Test await_running method."""

tests/smoketests/sdk/test_devbox.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def test_suspend_and_resume(self, sdk_client: RunloopSDK) -> None:
345345
info = devbox.get_info()
346346
assert info.status == "suspended"
347347

348-
# Resume the devbox
348+
# Resume the devbox - resume() automatically waits for running state
349349
resumed_info = devbox.resume(
350350
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0),
351351
)
@@ -357,6 +357,41 @@ def test_suspend_and_resume(self, sdk_client: RunloopSDK) -> None:
357357
finally:
358358
devbox.shutdown()
359359

360+
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
361+
def test_resume_async(self, sdk_client: RunloopSDK) -> None:
362+
"""Test resuming a devbox asynchronously without waiting."""
363+
devbox = sdk_client.devbox.create(
364+
name=unique_name("sdk-devbox-resume-async"),
365+
launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
366+
)
367+
368+
try:
369+
# Suspend the devbox
370+
suspended_info = devbox.suspend(
371+
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0),
372+
)
373+
assert suspended_info.status == "suspended"
374+
375+
# Verify suspended state
376+
info = devbox.get_info()
377+
assert info.status == "suspended"
378+
379+
# Resume the devbox asynchronously - doesn't wait automatically
380+
resume_response = devbox.resume_async()
381+
assert resume_response is not None
382+
383+
# Status might still be suspended or transitioning
384+
info_after_resume = devbox.get_info()
385+
assert info_after_resume.status in ["suspended", "running", "starting"]
386+
387+
# Now wait for running state explicitly
388+
running_info = devbox.await_running(
389+
polling_config=PollingConfig(timeout_seconds=120.0, interval_seconds=5.0)
390+
)
391+
assert running_info.status == "running"
392+
finally:
393+
devbox.shutdown()
394+
360395
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
361396
def test_await_running(self, sdk_client: RunloopSDK) -> None:
362397
"""Test await_running method."""

0 commit comments

Comments
 (0)