diff --git a/api.md b/api.md
index a36b2fa0b..47223f898 100644
--- a/api.md
+++ b/api.md
@@ -57,13 +57,13 @@ from runloop_api_client.types import (
Methods:
- client.blueprints.create(\*\*params) -> BlueprintView
+- client.blueprints.create_and_await_build_complete(\*\*params) -> BlueprintView
- client.blueprints.retrieve(id) -> BlueprintView
- client.blueprints.list(\*\*params) -> SyncBlueprintsCursorIDPage[BlueprintView]
- client.blueprints.delete(id) -> object
- client.blueprints.list_public(\*\*params) -> SyncBlueprintsCursorIDPage[BlueprintView]
- client.blueprints.logs(id) -> BlueprintBuildLogsListView
- client.blueprints.preview(\*\*params) -> BlueprintPreviewView
-- client.blueprints.create_and_await_build_complete(create_args, request_args=None) -> BlueprintView
# Devboxes
@@ -86,6 +86,7 @@ from runloop_api_client.types import (
Methods:
- client.devboxes.create(\*\*params) -> DevboxView
+- client.devboxes.create_and_await_running(\*\*params) -> DevboxView
- client.devboxes.retrieve(id) -> DevboxView
- client.devboxes.update(id, \*\*params) -> DevboxView
- client.devboxes.list(\*\*params) -> SyncDevboxesCursorIDPage[DevboxView]
@@ -106,7 +107,7 @@ Methods:
- client.devboxes.suspend(id) -> DevboxView
- client.devboxes.upload_file(id, \*\*params) -> object
- client.devboxes.write_file_contents(id, \*\*params) -> DevboxExecutionDetailView
-- client.devboxes.create_and_await_running(create_args, request_args=None) -> DevboxView
+
## DiskSnapshots
diff --git a/src/runloop_api_client/lib/polling_async.py b/src/runloop_api_client/lib/polling_async.py
index 1f52dae07..7ba192e86 100644
--- a/src/runloop_api_client/lib/polling_async.py
+++ b/src/runloop_api_client/lib/polling_async.py
@@ -4,7 +4,8 @@
from .polling import PollingConfig, PollingTimeout
-T = TypeVar('T')
+T = TypeVar("T")
+
async def async_poll_until(
retriever: Callable[[], Awaitable[T]],
@@ -14,27 +15,27 @@ async def async_poll_until(
) -> T:
"""
Poll until a condition is met or timeout/max attempts are reached.
-
+
Args:
retriever: Async or sync callable that returns the object to check
is_terminal: Callable that returns True when polling should stop
config: Optional polling configuration
on_error: Optional error handler that can return a value to continue polling
or re-raise the exception to stop polling
-
+
Returns:
The final state of the polled object
-
+
Raises:
PollingTimeout: When max attempts or timeout is reached
"""
if config is None:
config = PollingConfig()
-
+
attempts = 0
start_time = time.time()
last_result: Union[T, None] = None
-
+
while True:
try:
last_result = await retriever()
@@ -43,23 +44,17 @@ async def async_poll_until(
last_result = on_error(e)
else:
raise
-
+
if is_terminal(last_result):
return last_result
-
+
attempts += 1
if attempts >= config.max_attempts:
- raise PollingTimeout(
- f"Exceeded maximum attempts ({config.max_attempts})",
- last_result
- )
-
+ raise PollingTimeout(f"Exceeded maximum attempts ({config.max_attempts})", last_result)
+
if config.timeout_seconds is not None:
elapsed = time.time() - start_time
if elapsed >= config.timeout_seconds:
- raise PollingTimeout(
- f"Exceeded timeout of {config.timeout_seconds} seconds",
- last_result
- )
-
+ raise PollingTimeout(f"Exceeded timeout of {config.timeout_seconds} seconds", last_result)
+
await asyncio.sleep(config.interval_seconds)
diff --git a/src/runloop_api_client/resources/blueprints.py b/src/runloop_api_client/resources/blueprints.py
index 039dbeca5..8d49769f2 100644
--- a/src/runloop_api_client/resources/blueprints.py
+++ b/src/runloop_api_client/resources/blueprints.py
@@ -230,16 +230,30 @@ def is_done_building(blueprint: BlueprintView) -> bool:
def create_and_await_build_complete(
self,
*,
- create_args: blueprint_create_params.BlueprintCreateParams,
- request_args: BlueprintRequestArgs | None = None,
+ name: str,
+ base_blueprint_id: Optional[str] | NotGiven = NOT_GIVEN,
+ code_mounts: Optional[Iterable[CodeMountParameters]] | NotGiven = NOT_GIVEN,
+ dockerfile: Optional[str] | NotGiven = NOT_GIVEN,
+ file_mounts: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ launch_parameters: Optional[LaunchParameters] | NotGiven = NOT_GIVEN,
+ polling_config: PollingConfig | None = None,
+ services: Optional[Iterable[blueprint_create_params.Service]] | NotGiven = NOT_GIVEN,
+ system_setup_commands: Optional[List[str]] | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ idempotency_key: str | None = None,
) -> BlueprintView:
"""Create a new Blueprint and wait for it to finish building.
This is a wrapper around the `create` method that waits for the blueprint to finish building.
Args:
- create_args: Arguments to pass to the `create` method. See the `create` method for detailed documentation.
- request_args: Optional request arguments including polling configuration and additional request options
+ See the `create` method for detailed documentation.
+ polling_config: Optional polling configuration
Returns:
The built blueprint
@@ -249,18 +263,29 @@ def create_and_await_build_complete(
RunloopError: If blueprint enters a non-built terminal state
"""
# Pass all create_args to the underlying create method
- blueprint = self.create(**create_args)
-
- if request_args is None:
- request_args = {}
+ blueprint = self.create(
+ name=name,
+ base_blueprint_id=base_blueprint_id,
+ code_mounts=code_mounts,
+ dockerfile=dockerfile,
+ file_mounts=file_mounts,
+ launch_parameters=launch_parameters,
+ services=services,
+ system_setup_commands=system_setup_commands,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ )
return self.await_build_complete(
blueprint.id,
- polling_config=request_args.get("polling_config", None),
- extra_headers=request_args.get("extra_headers", None),
- extra_query=request_args.get("extra_query", None),
- extra_body=request_args.get("extra_body", None),
- timeout=request_args.get("timeout", None),
+ polling_config=polling_config,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
)
def list(
@@ -700,16 +725,30 @@ def is_done_building(blueprint: BlueprintView) -> bool:
async def create_and_await_build_complete(
self,
*,
- create_args: blueprint_create_params.BlueprintCreateParams,
- request_args: BlueprintRequestArgs | None = None,
+ name: str,
+ base_blueprint_id: Optional[str] | NotGiven = NOT_GIVEN,
+ code_mounts: Optional[Iterable[CodeMountParameters]] | NotGiven = NOT_GIVEN,
+ dockerfile: Optional[str] | NotGiven = NOT_GIVEN,
+ file_mounts: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ launch_parameters: Optional[LaunchParameters] | NotGiven = NOT_GIVEN,
+ polling_config: PollingConfig | None = None,
+ services: Optional[Iterable[blueprint_create_params.Service]] | NotGiven = NOT_GIVEN,
+ system_setup_commands: Optional[List[str]] | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ idempotency_key: str | None = None,
) -> BlueprintView:
"""Create a new Blueprint and wait for it to finish building.
This is a wrapper around the `create` method that waits for the blueprint to finish building.
Args:
- create_args: Arguments to pass to the `create` method. See the `create` method for detailed documentation.
- request_args: Optional request arguments including polling configuration and additional request options
+ See the `create` method for detailed documentation.
+ polling_config: Optional polling configuration
Returns:
The built blueprint
@@ -719,19 +758,29 @@ async def create_and_await_build_complete(
RunloopError: If blueprint enters a non-built terminal state
"""
# Pass all create_args to the underlying create method
- blueprint = await self.create(**create_args)
-
- # Extract polling config and other request args
- if request_args is None:
- request_args = {}
+ blueprint = await self.create(
+ name=name,
+ base_blueprint_id=base_blueprint_id,
+ code_mounts=code_mounts,
+ dockerfile=dockerfile,
+ file_mounts=file_mounts,
+ launch_parameters=launch_parameters,
+ services=services,
+ system_setup_commands=system_setup_commands,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ )
return await self.await_build_complete(
blueprint.id,
- polling_config=request_args.get("polling_config", None),
- extra_headers=request_args.get("extra_headers", None),
- extra_query=request_args.get("extra_query", None),
- extra_body=request_args.get("extra_body", None),
- timeout=request_args.get("timeout", None),
+ polling_config=polling_config,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
)
def list(
diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py
index c59157367..0d6b11d20 100644
--- a/src/runloop_api_client/resources/devboxes/devboxes.py
+++ b/src/runloop_api_client/resources/devboxes/devboxes.py
@@ -430,8 +430,26 @@ def is_done_booting(devbox: DevboxView) -> bool:
def create_and_await_running(
self,
*,
- create_args: devbox_create_params.DevboxCreateParams | None = None,
- request_args: DevboxRequestArgs | None = None,
+ blueprint_id: Optional[str] | NotGiven = NOT_GIVEN,
+ blueprint_name: Optional[str] | NotGiven = NOT_GIVEN,
+ code_mounts: Optional[Iterable[CodeMountParameters]] | NotGiven = NOT_GIVEN,
+ entrypoint: Optional[str] | NotGiven = NOT_GIVEN,
+ environment_variables: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ file_mounts: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ launch_parameters: Optional[LaunchParameters] | NotGiven = NOT_GIVEN,
+ metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ name: Optional[str] | NotGiven = NOT_GIVEN,
+ polling_config: PollingConfig | None = None,
+ repo_connection_id: Optional[str] | NotGiven = NOT_GIVEN,
+ secrets: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ snapshot_id: Optional[str] | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ idempotency_key: str | None = None,
) -> DevboxView:
"""Create a new devbox and wait for it to be in running state.
@@ -448,22 +466,30 @@ def create_and_await_running(
PollingTimeout: If polling times out before devbox is running
RunloopError: If devbox enters a non-running terminal state
"""
- # Extract polling config and other request args
- if request_args is None:
- request_args = {}
-
# Pass all create_args to the underlying create method
devbox = self.create(
- **(create_args or {}),
- extra_headers=request_args.get("extra_headers", None),
- extra_query=request_args.get("extra_query", None),
- extra_body=request_args.get("extra_body", None),
- timeout=request_args.get("timeout", None),
+ blueprint_id=blueprint_id,
+ blueprint_name=blueprint_name,
+ code_mounts=code_mounts,
+ entrypoint=entrypoint,
+ environment_variables=environment_variables,
+ file_mounts=file_mounts,
+ launch_parameters=launch_parameters,
+ metadata=metadata,
+ name=name,
+ repo_connection_id=repo_connection_id,
+ secrets=secrets,
+ snapshot_id=snapshot_id,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
)
return self.await_running(
devbox.id,
- polling_config=request_args.get("polling_config", None),
+ polling_config=polling_config,
)
def list(
@@ -1558,16 +1584,34 @@ async def retrieve(
async def create_and_await_running(
self,
*,
- create_args: devbox_create_params.DevboxCreateParams | None = None,
- request_args: DevboxRequestArgs | None = None,
+ blueprint_id: Optional[str] | NotGiven = NOT_GIVEN,
+ blueprint_name: Optional[str] | NotGiven = NOT_GIVEN,
+ code_mounts: Optional[Iterable[CodeMountParameters]] | NotGiven = NOT_GIVEN,
+ entrypoint: Optional[str] | NotGiven = NOT_GIVEN,
+ environment_variables: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ file_mounts: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ launch_parameters: Optional[LaunchParameters] | NotGiven = NOT_GIVEN,
+ metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ name: Optional[str] | NotGiven = NOT_GIVEN,
+ polling_config: PollingConfig | None = None,
+ repo_connection_id: Optional[str] | NotGiven = NOT_GIVEN,
+ secrets: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN,
+ snapshot_id: Optional[str] | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ idempotency_key: str | None = None,
) -> DevboxView:
"""Create a devbox and wait for it to be in running state.
This is a wrapper around the `create` method that waits for the devbox to reach running state.
Args:
- create_args: Arguments to pass to the `create` method. See the `create` method for detailed documentation.
- request_args: Optional request arguments including polling configuration and additional request options
+ See the `create` method for detailed documentation.
+ polling_config: Optional polling configuration
Returns:
The devbox in running state
@@ -1576,16 +1620,31 @@ async def create_and_await_running(
PollingTimeout: If polling times out before devbox is running
RunloopError: If devbox enters a non-running terminal state
"""
- # Pass all create_args to the underlying create method
- devbox = await self.create(**(create_args or {}))
- # Extract polling config and other request args
- if request_args is None:
- request_args = {}
+ # Pass all create_args, relevant request args to the underlying create method
+ devbox = await self.create(
+ blueprint_id=blueprint_id,
+ blueprint_name=blueprint_name,
+ code_mounts=code_mounts,
+ entrypoint=entrypoint,
+ environment_variables=environment_variables,
+ file_mounts=file_mounts,
+ launch_parameters=launch_parameters,
+ metadata=metadata,
+ name=name,
+ repo_connection_id=repo_connection_id,
+ secrets=secrets,
+ snapshot_id=snapshot_id,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ )
return await self.await_running(
devbox.id,
- polling_config=request_args.get("polling_config", None),
+ polling_config=polling_config,
)
async def await_running(
diff --git a/src/runloop_api_client/resources/scenarios/runs.py b/src/runloop_api_client/resources/scenarios/runs.py
index 029c16c1b..8f759a367 100644
--- a/src/runloop_api_client/resources/scenarios/runs.py
+++ b/src/runloop_api_client/resources/scenarios/runs.py
@@ -316,7 +316,7 @@ def await_scored(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Wait for a scenario run to be scored.
-
+
Args:
id: The ID of the scenario run to wait for
polling_config: Optional polling configuration
@@ -332,13 +332,10 @@ def await_scored(
PollingTimeout: If polling times out before scenario run is scored
RunloopError: If scenario run enters a non-scored terminal state
"""
+
def retrieve_run() -> ScenarioRunView:
return self.retrieve(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout
+ id, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
)
def is_done_scoring(run: ScenarioRunView) -> bool:
@@ -347,9 +344,7 @@ def is_done_scoring(run: ScenarioRunView) -> bool:
run = poll_until(retrieve_run, is_done_scoring, polling_config)
if run.state != "scored":
- raise RunloopError(
- f"Scenario run entered non-scored state unexpectedly: {run.state}"
- )
+ raise RunloopError(f"Scenario run entered non-scored state unexpectedly: {run.state}")
return run
@@ -366,7 +361,7 @@ def score_and_await(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Score a scenario run and wait for it to be scored.
-
+
Args:
id: The ID of the scenario run to score and wait for
polling_config: Optional polling configuration
@@ -412,7 +407,7 @@ def score_and_complete(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Score a scenario run, wait for it to be scored, then complete it.
-
+
Args:
id: The ID of the scenario run to score, wait for, and complete
polling_config: Optional polling configuration
@@ -445,6 +440,7 @@ def score_and_complete(
timeout=timeout,
)
+
class AsyncRunsResource(AsyncAPIResource):
@cached_property
def with_raw_response(self) -> AsyncRunsResourceWithRawResponse:
@@ -728,7 +724,7 @@ async def await_scored(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Wait for a scenario run to be scored.
-
+
Args:
id: The ID of the scenario run to wait for
polling_config: Optional polling configuration
@@ -744,13 +740,10 @@ async def await_scored(
PollingTimeout: If polling times out before scenario run is scored
RunloopError: If scenario run enters a non-scored terminal state
"""
+
async def retrieve_run() -> ScenarioRunView:
return await self.retrieve(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout
+ id, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
)
def is_done_scoring(run: ScenarioRunView) -> bool:
@@ -759,9 +752,7 @@ def is_done_scoring(run: ScenarioRunView) -> bool:
run = await async_poll_until(retrieve_run, is_done_scoring, polling_config)
if run.state != "scored":
- raise RunloopError(
- f"Scenario run entered non-scored state unexpectedly: {run.state}"
- )
+ raise RunloopError(f"Scenario run entered non-scored state unexpectedly: {run.state}")
return run
@@ -778,7 +769,7 @@ async def score_and_await(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Score a scenario run and wait for it to be scored.
-
+
Args:
id: The ID of the scenario run to score and wait for
polling_config: Optional polling configuration
@@ -824,7 +815,7 @@ async def score_and_complete(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> ScenarioRunView:
"""Score a scenario run, wait for it to be scored, then complete it.
-
+
Args:
id: The ID of the scenario run to score, wait for, and complete
polling_config: Optional polling configuration
diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py
index 7e041d594..528b91bc5 100644
--- a/src/runloop_api_client/resources/scenarios/scenarios.py
+++ b/src/runloop_api_client/resources/scenarios/scenarios.py
@@ -926,6 +926,7 @@ async def start_run_and_await_env_ready(
return run
+
class ScenariosResourceWithRawResponse:
def __init__(self, scenarios: ScenariosResource) -> None:
self._scenarios = scenarios
diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py
index 337312112..dfe1f1cbb 100644
--- a/tests/api_resources/test_devboxes.py
+++ b/tests/api_resources/test_devboxes.py
@@ -1168,9 +1168,7 @@ def test_method_create_and_await_running_success(self, client: Runloop) -> None:
mock_await.return_value = mock_devbox_running
result = client.devboxes.create_and_await_running(
- create_args={
- "name": "test",
- }
+ name="test",
)
assert result.id == "test_id"
@@ -1210,12 +1208,8 @@ def test_method_create_and_await_running_with_config(self, client: Runloop) -> N
mock_await.return_value = mock_devbox_running
result = client.devboxes.create_and_await_running(
- create_args={
- "name": "test",
- },
- request_args={
- "polling_config": config,
- },
+ name="test",
+ polling_config=config,
)
assert result.id == "test_id"
@@ -1235,9 +1229,7 @@ def test_method_create_and_await_running_create_failure(self, client: Runloop) -
with pytest.raises(APIStatusError, match="Bad request"):
client.devboxes.create_and_await_running(
- create_args={
- "name": "test",
- }
+ name="test",
)
@parametrize
@@ -1261,9 +1253,7 @@ def test_method_create_and_await_running_await_failure(self, client: Runloop) ->
with pytest.raises(RunloopError, match="Devbox entered non-running terminal state: failed"):
client.devboxes.create_and_await_running(
- create_args={
- "name": "test",
- }
+ name="test",
)
diff --git a/uv.lock b/uv.lock
index 3e8708e13..6a4831cd2 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1242,7 +1242,7 @@ wheels = [
[[package]]
name = "runloop-api-client"
-version = "0.53.0"
+version = "0.55.1"
source = { editable = "." }
dependencies = [
{ name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },