diff --git a/README-SDK.md b/README-SDK.md index 168aad093..b50d10b71 100644 --- a/README-SDK.md +++ b/README-SDK.md @@ -38,12 +38,12 @@ runloop = RunloopSDK() # Create a ready-to-use devbox with runloop.devbox.create(name="my-devbox") as devbox: - result = devbox.cmd.exec(command="echo 'Hello from Runloop!'") + result = devbox.cmd.exec("echo 'Hello from Runloop!'") print(result.stdout()) # Stream stdout in real time devbox.cmd.exec( - command="ls -la", + "ls -la", stdout=lambda line: print("stdout:", line), ) @@ -68,13 +68,13 @@ from runloop_api_client import AsyncRunloopSDK async def main(): runloop = AsyncRunloopSDK() async with await runloop.devbox.create(name="async-devbox") as devbox: - result = await devbox.cmd.exec(command="pwd") + result = await devbox.cmd.exec("pwd") print(await result.stdout()) def capture(line: str) -> None: print(">>", line) - await devbox.cmd.exec(command="ls", stdout=capture) + await devbox.cmd.exec("ls", stdout=capture) asyncio.run(main()) ``` @@ -147,13 +147,13 @@ Execute commands synchronously or asynchronously: ```python # Synchronous command execution (waits for completion) -result = devbox.cmd.exec(command="ls -la") +result = devbox.cmd.exec("ls -la") print("Output:", result.stdout()) print("Exit code:", result.exit_code) print("Success:", result.success) # Asynchronous command execution (returns immediately) -execution = devbox.cmd.exec_async(command="npm run dev") +execution = devbox.cmd.exec_async("npm run dev") # Check execution status state = execution.get_state() @@ -173,7 +173,7 @@ The `Execution` object provides fine-grained control over asynchronous command e ```python # Start a long-running process -execution = devbox.cmd.exec_async(command="python train_model.py") +execution = devbox.cmd.exec_async("python train_model.py") # Get the execution ID print("Execution ID:", execution.execution_id) @@ -208,10 +208,10 @@ The `ExecutionResult` object contains the output and exit status of a completed ```python # From synchronous execution -result = devbox.cmd.exec(command="ls -la /tmp") +result = devbox.cmd.exec("ls -la /tmp") # Or from asynchronous execution -execution = devbox.cmd.exec_async(command="echo 'test'") +execution = devbox.cmd.exec_async("echo 'test'") result = execution.result() # Access execution results @@ -250,7 +250,7 @@ def handle_output(line: str) -> None: print("LOG:", line) result = devbox.cmd.exec( - command="python train.py", + "python train.py", stdout=handle_output, stderr=lambda line: print("ERR:", line), output=lambda line: print("ANY:", line), @@ -267,7 +267,7 @@ def capture(line: str) -> None: log_queue.put_nowait(line) await devbox.cmd.exec( - command="tail -f /var/log/app.log", + "tail -f /var/log/app.log", stdout=capture, ) ``` @@ -365,13 +365,13 @@ Devboxes support context managers for automatic cleanup: ```python # Synchronous with runloop.devbox.create(name="temp-devbox") as devbox: - result = devbox.cmd.exec(command="echo 'Hello'") + result = devbox.cmd.exec("echo 'Hello'") print(result.stdout()) # devbox is automatically shutdown when exiting the context # Asynchronous async with await runloop.devbox.create(name="temp-devbox") as devbox: - result = await devbox.cmd.exec(command="echo 'Hello'") + result = await devbox.cmd.exec("echo 'Hello'") print(await result.stdout()) # devbox is automatically shutdown when exiting the context ``` @@ -586,7 +586,7 @@ devbox = runloop.devbox.create( ) # The storage object is now accessible at /home/user/data.txt in the devbox -result = devbox.cmd.exec(command="cat /home/user/data.txt") +result = devbox.cmd.exec("cat /home/user/data.txt") print(result.stdout()) # "Hello, World!" # Mount archived objects (tar, tgz, gzip) - they get extracted to a directory @@ -607,7 +607,7 @@ devbox_with_archive = runloop.devbox.create( ) # Access extracted archive contents -result = devbox_with_archive.cmd.exec(command="ls -la /home/user/project/") +result = devbox_with_archive.cmd.exec("ls -la /home/user/project/") print(result.stdout()) ``` @@ -634,7 +634,7 @@ runloop = RunloopSDK() try: devbox = runloop.devbox.create(name="example-devbox") - result = devbox.cmd.exec(command="invalid-command") + result = devbox.cmd.exec("invalid-command") except runloop_api_client.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -694,14 +694,14 @@ async def main(): # All the same operations, but with await async with await runloop.devbox.create(name="async-devbox") as devbox: - result = await devbox.cmd.exec(command="pwd") + result = await devbox.cmd.exec("pwd") print(await result.stdout()) # Streaming (note: callbacks must be synchronous) def capture(line: str) -> None: print(">>", line) - await devbox.cmd.exec(command="ls", stdout=capture) + await devbox.cmd.exec("ls", stdout=capture) # Async file operations await devbox.file.write(path="/tmp/test.txt", contents="Hello") diff --git a/docs/index.rst b/docs/index.rst index 30e7043bb..0a66c9802 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,7 @@ Synchronous Example # Create a ready-to-use devbox with runloop.devbox.create(name="my-devbox") as devbox: - result = devbox.cmd.exec(command="echo 'Hello from Runloop!'") + result = devbox.cmd.exec("echo 'Hello from Runloop!'") print(result.stdout()) Asynchronous Example @@ -49,7 +49,7 @@ Asynchronous Example runloop = AsyncRunloopSDK() async with await runloop.devbox.create(name="my-devbox") as devbox: - result = await devbox.cmd.exec(command="echo 'Hello from Runloop!'") + result = await devbox.cmd.exec("echo 'Hello from Runloop!'") print(await result.stdout()) asyncio.run(main()) diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 3bd3fc3cd..9cb0e21a9 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -14,7 +14,7 @@ from ..types.devbox_upload_file_params import DevboxUploadFileParams from ..types.devbox_create_tunnel_params import DevboxCreateTunnelParams from ..types.devbox_download_file_params import DevboxDownloadFileParams -from ..types.devbox_execute_async_params import DevboxExecuteAsyncParams +from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams from ..types.devbox_remove_tunnel_params import DevboxRemoveTunnelParams from ..types.devbox_snapshot_disk_params import DevboxSnapshotDiskParams from ..types.devbox_read_file_contents_params import DevboxReadFileContentsParams @@ -70,11 +70,11 @@ class SDKDevboxCreateFromImageParams(DevboxBaseCreateParams, LongPollingRequestO pass -class SDKDevboxExecuteParams(DevboxExecuteAsyncParams, ExecuteStreamingCallbacks, LongPollingRequestOptions): +class SDKDevboxExecuteParams(DevboxNiceExecuteAsyncParams, ExecuteStreamingCallbacks, LongPollingRequestOptions): pass -class SDKDevboxExecuteAsyncParams(DevboxExecuteAsyncParams, ExecuteStreamingCallbacks, LongRequestOptions): +class SDKDevboxExecuteAsyncParams(DevboxNiceExecuteAsyncParams, ExecuteStreamingCallbacks, LongRequestOptions): pass diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py index b32310be6..2dc4562c2 100644 --- a/src/runloop_api_client/sdk/async_.py +++ b/src/runloop_api_client/sdk/async_.py @@ -496,7 +496,7 @@ class AsyncRunloopSDK: Example: >>> runloop = AsyncRunloopSDK() # Uses RUNLOOP_API_KEY env var >>> devbox = await runloop.devbox.create(name="my-devbox") - >>> result = await devbox.cmd.exec(command="echo 'hello'") + >>> result = await devbox.cmd.exec("echo 'hello'") >>> print(await result.stdout()) >>> await devbox.shutdown() """ diff --git a/src/runloop_api_client/sdk/async_devbox.py b/src/runloop_api_client/sdk/async_devbox.py index 2dfac41f2..070c7590d 100644 --- a/src/runloop_api_client/sdk/async_devbox.py +++ b/src/runloop_api_client/sdk/async_devbox.py @@ -37,7 +37,7 @@ from ..types.devboxes import ExecutionUpdateChunk from .async_execution import AsyncExecution, _AsyncStreamingGroup from .async_execution_result import AsyncExecutionResult -from ..types.devbox_execute_async_params import DevboxExecuteAsyncParams +from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView StreamFactory = Callable[[], Awaitable[AsyncStream[ExecutionUpdateChunk]]] @@ -56,7 +56,7 @@ class AsyncDevbox: Example: >>> devbox = await sdk.devbox.create(name="my-devbox") >>> async with devbox: - ... result = await devbox.cmd.exec(command="echo 'hello'") + ... result = await devbox.cmd.exec("echo 'hello'") ... print(await result.stdout()) # Devbox is automatically shut down on exit """ @@ -368,6 +368,7 @@ def __init__(self, devbox: AsyncDevbox) -> None: async def exec( self, + command: str, **params: Unpack[SDKDevboxExecuteParams], ) -> AsyncExecutionResult: """Execute a command synchronously and wait for completion. @@ -377,7 +378,7 @@ async def exec( :rtype: AsyncExecutionResult Example: - >>> result = await devbox.cmd.exec(command="echo 'hello'") + >>> result = await devbox.cmd.exec("echo 'hello'") >>> print(await result.stdout()) >>> print(f"Exit code: {result.exit_code}") """ @@ -386,7 +387,8 @@ async def exec( execution: DevboxAsyncExecutionDetailView = await client.devboxes.execute_async( devbox.id, - **filter_params(params, DevboxExecuteAsyncParams), + command=command, + **filter_params(params, DevboxNiceExecuteAsyncParams), **filter_params(params, LongRequestOptions), ) streaming_group = devbox._start_streaming( @@ -421,6 +423,7 @@ async def command_coro() -> DevboxAsyncExecutionDetailView: async def exec_async( self, + command: str, **params: Unpack[SDKDevboxExecuteAsyncParams], ) -> AsyncExecution: """Execute a command asynchronously without waiting for completion. @@ -434,7 +437,7 @@ async def exec_async( :rtype: AsyncExecution Example: - >>> execution = await devbox.cmd.exec_async(command="sleep 10") + >>> execution = await devbox.cmd.exec_async("sleep 10") >>> state = await execution.get_state() >>> print(f"Status: {state.status}") >>> await execution.kill() # Terminate early if needed @@ -444,7 +447,8 @@ async def exec_async( execution: DevboxAsyncExecutionDetailView = await client.devboxes.execute_async( devbox.id, - **filter_params(params, DevboxExecuteAsyncParams), + command=command, + **filter_params(params, DevboxNiceExecuteAsyncParams), **filter_params(params, LongRequestOptions), ) diff --git a/src/runloop_api_client/sdk/async_execution.py b/src/runloop_api_client/sdk/async_execution.py index 242a869bd..073113bca 100644 --- a/src/runloop_api_client/sdk/async_execution.py +++ b/src/runloop_api_client/sdk/async_execution.py @@ -43,7 +43,7 @@ class AsyncExecution: terminate the running process. Created by ``await devbox.cmd.exec_async()``. Example: - >>> execution = await devbox.cmd.exec_async(command="python train.py") + >>> execution = await devbox.cmd.exec_async("python train.py") >>> state = await execution.get_state() >>> if state.status == "running": ... await execution.kill() diff --git a/src/runloop_api_client/sdk/devbox.py b/src/runloop_api_client/sdk/devbox.py index abd02d499..6e5497643 100644 --- a/src/runloop_api_client/sdk/devbox.py +++ b/src/runloop_api_client/sdk/devbox.py @@ -38,7 +38,7 @@ from ..lib.polling import PollingConfig from ..types.devboxes import ExecutionUpdateChunk from .execution_result import ExecutionResult -from ..types.devbox_execute_async_params import DevboxExecuteAsyncParams +from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView if TYPE_CHECKING: @@ -386,6 +386,7 @@ def __init__(self, devbox: Devbox) -> None: def exec( self, + command: str, **params: Unpack[SDKDevboxExecuteParams], ) -> ExecutionResult: """Execute a command synchronously and wait for completion. @@ -395,7 +396,7 @@ def exec( :rtype: ExecutionResult Example: - >>> result = devbox.cmd.exec(command="ls -la") + >>> result = devbox.cmd.exec("ls -la") >>> print(result.stdout()) >>> print(f"Exit code: {result.exit_code}") """ @@ -404,7 +405,8 @@ def exec( execution: DevboxAsyncExecutionDetailView = client.devboxes.execute_async( devbox.id, - **filter_params(params, DevboxExecuteAsyncParams), + command=command, + **filter_params(params, DevboxNiceExecuteAsyncParams), **filter_params(params, LongRequestOptions), ) streaming_group = devbox._start_streaming( @@ -429,6 +431,7 @@ def exec( def exec_async( self, + command: str, **params: Unpack[SDKDevboxExecuteAsyncParams], ) -> Execution: """Execute a command asynchronously without waiting for completion. @@ -442,7 +445,7 @@ def exec_async( :rtype: Execution Example: - >>> execution = devbox.cmd.exec_async(command="sleep 10") + >>> execution = devbox.cmd.exec_async("sleep 10") >>> state = execution.get_state() >>> print(f"Status: {state.status}") >>> execution.kill() # Terminate early if needed @@ -452,7 +455,8 @@ def exec_async( execution: DevboxAsyncExecutionDetailView = client.devboxes.execute_async( devbox.id, - **filter_params(params, DevboxExecuteAsyncParams), + command=command, + **filter_params(params, DevboxNiceExecuteAsyncParams), **filter_params(params, LongRequestOptions), ) diff --git a/src/runloop_api_client/sdk/execution.py b/src/runloop_api_client/sdk/execution.py index a17dff973..b37952df4 100644 --- a/src/runloop_api_client/sdk/execution.py +++ b/src/runloop_api_client/sdk/execution.py @@ -42,7 +42,7 @@ class Execution: the running process. Created by ``devbox.cmd.exec_async()``. Example: - >>> execution = devbox.cmd.exec_async(command="python train.py") + >>> execution = devbox.cmd.exec_async("python train.py") >>> state = execution.get_state() >>> if state.status == "running": ... execution.kill() diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py index 7368a0f7a..94715cca4 100644 --- a/src/runloop_api_client/sdk/sync.py +++ b/src/runloop_api_client/sdk/sync.py @@ -491,7 +491,7 @@ class RunloopSDK: Example: >>> runloop = RunloopSDK() # Uses RUNLOOP_API_KEY env var >>> devbox = runloop.devbox.create(name="my-devbox") - >>> result = devbox.cmd.exec(command="echo 'hello'") + >>> result = devbox.cmd.exec("echo 'hello'") >>> print(result.stdout()) >>> devbox.shutdown() """ diff --git a/src/runloop_api_client/types/devbox_execute_async_params.py b/src/runloop_api_client/types/devbox_execute_async_params.py index 9de00ccf0..466939809 100644 --- a/src/runloop_api_client/types/devbox_execute_async_params.py +++ b/src/runloop_api_client/types/devbox_execute_async_params.py @@ -8,15 +8,7 @@ __all__ = ["DevboxExecuteAsyncParams"] -class DevboxExecuteAsyncParams(TypedDict, total=False): - command: Required[str] - """The command to execute via the Devbox shell. - - By default, commands are run from the user home directory unless shell_name is - specified. If shell_name is specified the command is run from the directory - based on the recent state of the persistent shell. - """ - +class DevboxNiceExecuteAsyncParams(TypedDict, total=False): attach_stdin: Optional[bool] """Whether to attach stdin streaming for async commands. @@ -29,3 +21,13 @@ class DevboxExecuteAsyncParams(TypedDict, total=False): When using a persistent shell, the command will run from the directory at the end of the previous command and environment variables will be preserved. """ + + +class DevboxExecuteAsyncParams(DevboxNiceExecuteAsyncParams, total=False): + command: Required[str] + """The command to execute via the Devbox shell. + + By default, commands are run from the user home directory unless shell_name is + specified. If shell_name is specified the command is run from the directory + based on the recent state of the persistent shell. + """ diff --git a/tests/sdk/async_devbox/test_interfaces.py b/tests/sdk/async_devbox/test_interfaces.py index 672638fcf..bcb2a306b 100644 --- a/tests/sdk/async_devbox/test_interfaces.py +++ b/tests/sdk/async_devbox/test_interfaces.py @@ -28,7 +28,7 @@ async def test_exec_without_callbacks( mock_async_client.devboxes.executions.await_completed = AsyncMock(return_value=execution_view) devbox = AsyncDevbox(mock_async_client, "dev_123") - result = await devbox.cmd.exec(command="echo hello") + result = await devbox.cmd.exec("echo hello") assert result.exit_code == 0 assert await result.stdout(num_lines=10) == "output" @@ -62,7 +62,7 @@ async def test_exec_with_stdout_callback(self, mock_async_client: AsyncMock, moc stdout_calls: list[str] = [] devbox = AsyncDevbox(mock_async_client, "dev_123") - result = await devbox.cmd.exec(command="echo hello", stdout=stdout_calls.append) + result = await devbox.cmd.exec("echo hello", stdout=stdout_calls.append) assert result.exit_code == 0 mock_async_client.devboxes.execute_async.assert_called_once() @@ -82,7 +82,7 @@ async def test_exec_async_returns_execution( mock_async_client.devboxes.executions.stream_stdout_updates = AsyncMock(return_value=mock_async_stream) devbox = AsyncDevbox(mock_async_client, "dev_123") - execution = await devbox.cmd.exec_async(command="long-running command") + execution = await devbox.cmd.exec_async("long-running command") assert execution.execution_id == "exec_123" assert execution.devbox_id == "dev_123" diff --git a/tests/sdk/devbox/test_interfaces.py b/tests/sdk/devbox/test_interfaces.py index f1c3bc59c..a8ca574ba 100644 --- a/tests/sdk/devbox/test_interfaces.py +++ b/tests/sdk/devbox/test_interfaces.py @@ -25,7 +25,7 @@ def test_exec_without_callbacks(self, mock_client: Mock, execution_view: MockExe mock_client.devboxes.executions.await_completed.return_value = execution_view devbox = Devbox(mock_client, "dev_123") - result = devbox.cmd.exec(command="echo hello") + result = devbox.cmd.exec("echo hello") assert result.exit_code == 0 assert result.stdout(num_lines=10) == "output" @@ -58,7 +58,7 @@ def test_exec_with_stdout_callback(self, mock_client: Mock, mock_stream: Mock) - stdout_calls: list[str] = [] devbox = Devbox(mock_client, "dev_123") - result = devbox.cmd.exec(command="echo hello", stdout=stdout_calls.append) + result = devbox.cmd.exec("echo hello", stdout=stdout_calls.append) assert result.exit_code == 0 mock_client.devboxes.execute_async.assert_called_once() @@ -87,7 +87,7 @@ def test_exec_with_stderr_callback(self, mock_client: Mock, mock_stream: Mock) - stderr_calls: list[str] = [] devbox = Devbox(mock_client, "dev_123") - result = devbox.cmd.exec(command="echo hello", stderr=stderr_calls.append) + result = devbox.cmd.exec("echo hello", stderr=stderr_calls.append) assert result.exit_code == 0 mock_client.devboxes.execute_async.assert_called_once() @@ -116,7 +116,7 @@ def test_exec_with_output_callback(self, mock_client: Mock, mock_stream: Mock) - output_calls: list[str] = [] devbox = Devbox(mock_client, "dev_123") - result = devbox.cmd.exec(command="echo hello", output=output_calls.append) + result = devbox.cmd.exec("echo hello", output=output_calls.append) assert result.exit_code == 0 mock_client.devboxes.execute_async.assert_called_once() @@ -148,7 +148,7 @@ def test_exec_with_all_callbacks(self, mock_client: Mock, mock_stream: Mock) -> devbox = Devbox(mock_client, "dev_123") result = devbox.cmd.exec( - command="echo hello", + "echo hello", stdout=stdout_calls.append, stderr=stderr_calls.append, output=output_calls.append, @@ -169,7 +169,7 @@ def test_exec_async_returns_execution(self, mock_client: Mock, mock_stream: Mock mock_client.devboxes.executions.stream_stdout_updates.return_value = mock_stream devbox = Devbox(mock_client, "dev_123") - execution = devbox.cmd.exec_async(command="long-running command") + execution = devbox.cmd.exec_async("long-running command") assert execution.execution_id == "exec_123" assert execution.devbox_id == "dev_123" diff --git a/tests/smoketests/sdk/test_async_blueprint.py b/tests/smoketests/sdk/test_async_blueprint.py index 09d85a5d5..453e26f6e 100644 --- a/tests/smoketests/sdk/test_async_blueprint.py +++ b/tests/smoketests/sdk/test_async_blueprint.py @@ -234,7 +234,7 @@ async def test_create_devbox_from_blueprint(self, async_sdk_client: AsyncRunloop assert info.status == "running" # Verify the blueprint's software is installed - result = await devbox.cmd.exec(command="which python3") + result = await devbox.cmd.exec("which python3") assert result.exit_code == 0 assert result.success is True assert "python" in await result.stdout(num_lines=1) diff --git a/tests/smoketests/sdk/test_async_devbox.py b/tests/smoketests/sdk/test_async_devbox.py index 892aa1e9c..5eb1de6e2 100644 --- a/tests/smoketests/sdk/test_async_devbox.py +++ b/tests/smoketests/sdk/test_async_devbox.py @@ -106,7 +106,7 @@ class TestAsyncDevboxCommandExecution: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) async def test_exec_simple_command(self, shared_devbox: AsyncDevbox) -> None: """Test executing a simple command asynchronously.""" - result = await shared_devbox.cmd.exec(command="echo 'Hello from async SDK!'") + result = await shared_devbox.cmd.exec("echo 'Hello from async SDK!'") assert result is not None assert result.exit_code == 0 @@ -118,7 +118,7 @@ async def test_exec_simple_command(self, shared_devbox: AsyncDevbox) -> None: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) async def test_exec_with_exit_code(self, shared_devbox: AsyncDevbox) -> None: """Test command execution captures exit codes correctly.""" - result = await shared_devbox.cmd.exec(command="exit 42") + result = await shared_devbox.cmd.exec("exit 42") assert result.exit_code == 42 assert result.success is False @@ -127,7 +127,7 @@ async def test_exec_with_exit_code(self, shared_devbox: AsyncDevbox) -> None: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) async def test_exec_async_command(self, shared_devbox: AsyncDevbox) -> None: """Test executing a command asynchronously with exec_async.""" - execution = await shared_devbox.cmd.exec_async(command="echo 'Async command' && sleep 1") + execution = await shared_devbox.cmd.exec_async("echo 'Async command' && sleep 1") assert execution is not None assert execution.execution_id is not None @@ -149,7 +149,7 @@ def stdout_callback(line: str) -> None: stdout_lines.append(line) result = await shared_devbox.cmd.exec( - command='echo "line1" && echo "line2" && echo "line3"', + 'echo "line1" && echo "line2" && echo "line3"', stdout=stdout_callback, ) @@ -175,7 +175,7 @@ def stderr_callback(line: str) -> None: stderr_lines.append(line) result = await shared_devbox.cmd.exec( - command='echo "error1" >&2 && echo "error2" >&2', + 'echo "error1" >&2 && echo "error2" >&2', stderr=stderr_callback, ) @@ -195,7 +195,7 @@ def stderr_callback(line: str) -> None: async def test_exec_with_large_stdout(self, shared_devbox: AsyncDevbox) -> None: """Ensure we capture all stdout lines (similar to TS last_n coverage).""" result = await shared_devbox.cmd.exec( - command="; ".join([f"echo line {i}" for i in range(1, 7)]), + "; ".join([f"echo line {i}" for i in range(1, 7)]), ) assert result.exit_code == 0 @@ -214,7 +214,7 @@ def output_callback(line: str) -> None: output_lines.append(line) result = await shared_devbox.cmd.exec( - command='echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"', + 'echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"', output=output_callback, ) @@ -238,7 +238,7 @@ def stdout_callback(line: str) -> None: stdout_lines.append(line) execution = await shared_devbox.cmd.exec_async( - command='echo "async output"', + 'echo "async output"', stdout=stdout_callback, ) @@ -624,7 +624,7 @@ async def test_exec_with_large_stdout_streaming(self, shared_devbox: AsyncDevbox """Test that large stdout output is fully captured via streaming when truncated.""" # Generate 1000 lines of output result = await shared_devbox.cmd.exec( - command='for i in $(seq 1 1000); do echo "Line $i with some content to make it realistic"; done', + 'for i in $(seq 1 1000); do echo "Line $i with some content to make it realistic"; done', ) assert result.exit_code == 0 @@ -643,7 +643,7 @@ async def test_exec_with_large_stderr_streaming(self, shared_devbox: AsyncDevbox """Test that large stderr output is fully captured via streaming when truncated.""" # Generate 1000 lines of stderr output result = await shared_devbox.cmd.exec( - command='for i in $(seq 1 1000); do echo "Error line $i" >&2; done', + 'for i in $(seq 1 1000); do echo "Error line $i" >&2; done', ) assert result.exit_code == 0 @@ -662,7 +662,7 @@ async def test_exec_with_truncated_stdout_num_lines(self, shared_devbox: AsyncDe """Test num_lines parameter works correctly with potentially truncated output.""" # Generate 2000 lines of output result = await shared_devbox.cmd.exec( - command='for i in $(seq 1 2000); do echo "Line $i"; done', + 'for i in $(seq 1 2000); do echo "Line $i"; done', ) assert result.exit_code == 0 diff --git a/tests/smoketests/sdk/test_async_storage_object.py b/tests/smoketests/sdk/test_async_storage_object.py index cbc4e3e22..fcca2a928 100644 --- a/tests/smoketests/sdk/test_async_storage_object.py +++ b/tests/smoketests/sdk/test_async_storage_object.py @@ -365,7 +365,7 @@ async def test_access_mounted_storage_object(self, async_sdk_client: AsyncRunloo assert content == "Async content to mount and access" # Verify file exists via command - result = await devbox.cmd.exec(command="test -f /home/user/async-mounted-file && echo 'exists'") + result = await devbox.cmd.exec("test -f /home/user/async-mounted-file && echo 'exists'") stdout = await result.stdout(num_lines=1) assert "exists" in stdout finally: @@ -497,7 +497,7 @@ async def test_storage_object_in_devbox_workflow(self, async_sdk_client: AsyncRu assert content == "Async initial content" # Verify we can work with the file - result = await devbox.cmd.exec(command="cat /home/user/async-workflow-data") + result = await devbox.cmd.exec("cat /home/user/async-workflow-data") stdout = await result.stdout(num_lines=1) assert "Async initial content" in stdout finally: diff --git a/tests/smoketests/sdk/test_blueprint.py b/tests/smoketests/sdk/test_blueprint.py index cf9e4683b..3ff6adb9e 100644 --- a/tests/smoketests/sdk/test_blueprint.py +++ b/tests/smoketests/sdk/test_blueprint.py @@ -234,7 +234,7 @@ def test_create_devbox_from_blueprint(self, sdk_client: RunloopSDK) -> None: assert info.status == "running" # Verify the blueprint's software is installed - result = devbox.cmd.exec(command="which python3") + result = devbox.cmd.exec("which python3") assert result.exit_code == 0 assert result.success is True assert "python" in result.stdout(num_lines=1) diff --git a/tests/smoketests/sdk/test_devbox.py b/tests/smoketests/sdk/test_devbox.py index 69e605d79..737fd1e5d 100644 --- a/tests/smoketests/sdk/test_devbox.py +++ b/tests/smoketests/sdk/test_devbox.py @@ -107,7 +107,7 @@ class TestDevboxCommandExecution: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) def test_exec_simple_command(self, shared_devbox: Devbox) -> None: """Test executing a simple command synchronously.""" - result = shared_devbox.cmd.exec(command="echo 'Hello from SDK!'") + result = shared_devbox.cmd.exec("echo 'Hello from SDK!'") assert result is not None assert result.exit_code == 0 @@ -119,7 +119,7 @@ def test_exec_simple_command(self, shared_devbox: Devbox) -> None: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) def test_exec_with_exit_code(self, shared_devbox: Devbox) -> None: """Test command execution captures exit codes correctly.""" - result = shared_devbox.cmd.exec(command="exit 42") + result = shared_devbox.cmd.exec("exit 42") assert result.exit_code == 42 assert result.success is False @@ -128,7 +128,7 @@ def test_exec_with_exit_code(self, shared_devbox: Devbox) -> None: @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) def test_exec_async_command(self, shared_devbox: Devbox) -> None: """Test executing a command asynchronously.""" - execution = shared_devbox.cmd.exec_async(command="echo 'Async command' && sleep 1") + execution = shared_devbox.cmd.exec_async("echo 'Async command' && sleep 1") assert execution is not None assert execution.execution_id is not None @@ -150,7 +150,7 @@ def stdout_callback(line: str) -> None: stdout_lines.append(line) result = shared_devbox.cmd.exec( - command='echo "line1" && echo "line2" && echo "line3"', + 'echo "line1" && echo "line2" && echo "line3"', stdout=stdout_callback, ) @@ -176,7 +176,7 @@ def stderr_callback(line: str) -> None: stderr_lines.append(line) result = shared_devbox.cmd.exec( - command='echo "error1" >&2 && echo "error2" >&2', + 'echo "error1" >&2 && echo "error2" >&2', stderr=stderr_callback, ) @@ -196,7 +196,7 @@ def stderr_callback(line: str) -> None: def test_exec_with_large_stdout(self, shared_devbox: Devbox) -> None: """Ensure we capture all stdout lines (similar to TS last_n coverage).""" result = shared_devbox.cmd.exec( - command="; ".join([f"echo line {i}" for i in range(1, 7)]), + "; ".join([f"echo line {i}" for i in range(1, 7)]), ) assert result.exit_code == 0 @@ -215,7 +215,7 @@ def output_callback(line: str) -> None: output_lines.append(line) result = shared_devbox.cmd.exec( - command='echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"', + 'echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"', output=output_callback, ) @@ -239,7 +239,7 @@ def stdout_callback(line: str) -> None: stdout_lines.append(line) execution = shared_devbox.cmd.exec_async( - command='echo "async output"', + 'echo "async output"', stdout=stdout_callback, ) @@ -619,7 +619,7 @@ def test_exec_with_large_stdout_streaming(self, shared_devbox: Devbox) -> None: """Test that large stdout output is fully captured via streaming when truncated.""" # Generate 1000 lines of output result = shared_devbox.cmd.exec( - command='for i in $(seq 1 1000); do echo "Line $i with some content to make it realistic"; done', + 'for i in $(seq 1 1000); do echo "Line $i with some content to make it realistic"; done', ) assert result.exit_code == 0 @@ -638,7 +638,7 @@ def test_exec_with_large_stderr_streaming(self, shared_devbox: Devbox) -> None: """Test that large stderr output is fully captured via streaming when truncated.""" # Generate 1000 lines of stderr output result = shared_devbox.cmd.exec( - command='for i in $(seq 1 1000); do echo "Error line $i" >&2; done', + 'for i in $(seq 1 1000); do echo "Error line $i" >&2; done', ) assert result.exit_code == 0 @@ -657,7 +657,7 @@ def test_exec_with_truncated_stdout_num_lines(self, shared_devbox: Devbox) -> No """Test num_lines parameter works correctly with potentially truncated output.""" # Generate 2000 lines of output result = shared_devbox.cmd.exec( - command='for i in $(seq 1 2000); do echo "Line $i"; done', + 'for i in $(seq 1 2000); do echo "Line $i"; done', ) assert result.exit_code == 0 diff --git a/tests/smoketests/sdk/test_storage_object.py b/tests/smoketests/sdk/test_storage_object.py index 126d194ac..57c1f9d15 100644 --- a/tests/smoketests/sdk/test_storage_object.py +++ b/tests/smoketests/sdk/test_storage_object.py @@ -365,7 +365,7 @@ def test_access_mounted_storage_object(self, sdk_client: RunloopSDK) -> None: assert content == "Content to mount and access" # Verify file exists via command - result = devbox.cmd.exec(command="test -f /home/user/mounted-file && echo 'exists'") + result = devbox.cmd.exec("test -f /home/user/mounted-file && echo 'exists'") assert "exists" in result.stdout(num_lines=1) finally: devbox.shutdown() @@ -496,7 +496,7 @@ def test_storage_object_in_devbox_workflow(self, sdk_client: RunloopSDK) -> None assert content == "Initial content" # Verify we can work with the file - result = devbox.cmd.exec(command="cat /home/user/workflow-data") + result = devbox.cmd.exec("cat /home/user/workflow-data") assert "Initial content" in result.stdout(num_lines=1) finally: devbox.shutdown()