Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions README-SDK.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)

Expand All @@ -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())
```
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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,
)
```
Expand Down Expand Up @@ -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
```
Expand Down Expand Up @@ -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
Expand All @@ -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())
```

Expand All @@ -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.
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand Down
6 changes: 3 additions & 3 deletions src/runloop_api_client/sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/sdk/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
"""
Expand Down
16 changes: 10 additions & 6 deletions src/runloop_api_client/sdk/async_devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]]
Expand All @@ -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
"""
Expand Down Expand Up @@ -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.
Expand All @@ -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}")
"""
Expand All @@ -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(
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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),
)

Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/sdk/async_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 9 additions & 5 deletions src/runloop_api_client/sdk/devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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}")
"""
Expand All @@ -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(
Expand All @@ -429,6 +431,7 @@ def exec(

def exec_async(
self,
command: str,
**params: Unpack[SDKDevboxExecuteAsyncParams],
) -> Execution:
"""Execute a command asynchronously without waiting for completion.
Expand All @@ -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
Expand All @@ -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),
)

Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/sdk/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/sdk/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
"""
Expand Down
20 changes: 11 additions & 9 deletions src/runloop_api_client/types/devbox_execute_async_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
"""
Loading