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
4 changes: 2 additions & 2 deletions examples/04_streaming_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ def main():

# Stream output in real-time
result = sandbox.exec(
'''python3 -c "
'''python3 -u -c "
import time
for i in range(5):
print(f'Line {i+1}')
time.sleep(0.5)
"''',
on_stdout=lambda data: print(data.strip(), end=" "),
on_stdout=lambda data: print(data.strip()),
on_stderr=lambda data: print(f"ERR: {data.strip()}"),
)
print(f"\nExit code: {result.exit_code}")
Expand Down
4 changes: 2 additions & 2 deletions examples/04_streaming_output_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ async def main():

# Stream output in real-time
result = await sandbox.exec(
'''python3 -c "
'''python3 -u -c "
import time
for i in range(5):
print(f'Line {i+1}')
time.sleep(0.5)
"''',
on_stdout=lambda data: print(data.strip(), end=" "),
on_stdout=lambda data: print(data.strip()),
on_stderr=lambda data: print(f"ERR: {data.strip()}"),
)
print(f"\nExit code: {result.exit_code}")
Expand Down
3 changes: 2 additions & 1 deletion koyeb/sandbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)
from .filesystem import FileInfo, SandboxFilesystem
from .sandbox import AsyncSandbox, ExposedPort, ProcessInfo, Sandbox
from .utils import SandboxDeploymentError, SandboxError, SandboxTimeoutError
from .utils import SandboxDeploymentError, SandboxError, SandboxServiceError, SandboxTimeoutError

__all__ = [
"Sandbox",
Expand All @@ -33,6 +33,7 @@
"SandboxStatus",
"SandboxDeploymentError",
"SandboxError",
"SandboxServiceError",
"SandboxTimeoutError",
"CommandResult",
"CommandStatus",
Expand Down
246 changes: 103 additions & 143 deletions koyeb/sandbox/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,86 +117,66 @@ def __call__(
stderr_buffer = []
exit_code = 0

try:
client = self._get_client()
for event in client.run_streaming(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
):
if "stream" in event:
stream_type = event["stream"]
data = event["data"]

if stream_type == "stdout":
stdout_buffer.append(data)
if on_stdout:
on_stdout(data)
elif stream_type == "stderr":
stderr_buffer.append(data)
if on_stderr:
on_stderr(data)
elif "code" in event:
exit_code = event["code"]
elif "error" in event and isinstance(event["error"], str):
# Error starting command
return CommandResult(
stdout="",
stderr=event["error"],
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

return CommandResult(
stdout="".join(stdout_buffer),
stderr="".join(stderr_buffer),
exit_code=exit_code,
status=(
CommandStatus.FINISHED
if exit_code == 0
else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)
except Exception as e:
return CommandResult(
stdout="",
stderr=f"Command execution failed: {str(e)}",
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

# Use regular run for non-streaming execution
try:
client = self._get_client()
response = client.run(cmd=command, cwd=cwd, env=env, timeout=float(timeout))

stdout = response.get("stdout", "")
stderr = response.get("stderr", "")
exit_code = response.get("code", 0)
for event in client.run_streaming(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
):
if "stream" in event:
stream_type = event["stream"]
data = event["data"]

if stream_type == "stdout":
stdout_buffer.append(data)
if on_stdout:
on_stdout(data)
elif stream_type == "stderr":
stderr_buffer.append(data)
if on_stderr:
on_stderr(data)
elif "code" in event:
exit_code = event["code"]
elif "error" in event and isinstance(event["error"], str):
# Error starting command
return CommandResult(
stdout="",
stderr=event["error"],
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

return CommandResult(
stdout=stdout,
stderr=stderr,
stdout="".join(stdout_buffer),
stderr="".join(stderr_buffer),
exit_code=exit_code,
status=(
CommandStatus.FINISHED if exit_code == 0 else CommandStatus.FAILED
CommandStatus.FINISHED
if exit_code == 0
else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)
except Exception as e:
return CommandResult(
stdout="",
stderr=f"Command execution failed: {str(e)}",
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

# Use regular run for non-streaming execution
client = self._get_client()
response = client.run(cmd=command, cwd=cwd, env=env, timeout=float(timeout))

stdout = response.get("stdout", "")
stderr = response.get("stderr", "")
exit_code = response.get("code", 0)

return CommandResult(
stdout=stdout,
stderr=stderr,
exit_code=exit_code,
status=(
CommandStatus.FINISHED if exit_code == 0 else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)


class AsyncSandboxExecutor(SandboxExecutor):
Expand Down Expand Up @@ -256,85 +236,65 @@ async def __call__(
stderr_buffer: List[str] = []
exit_code = 0

try:
client = self._get_async_client()

async for event in client.run_streaming(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
):
if "stream" in event:
stream_type = event["stream"]
data = event["data"]

if stream_type == "stdout":
stdout_buffer.append(data)
if on_stdout:
on_stdout(data)
elif stream_type == "stderr":
stderr_buffer.append(data)
if on_stderr:
on_stderr(data)
elif "code" in event:
exit_code = event["code"]
elif "error" in event and isinstance(event["error"], str):
return CommandResult(
stdout="",
stderr=event["error"],
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

return CommandResult(
stdout="".join(stdout_buffer),
stderr="".join(stderr_buffer),
exit_code=exit_code,
status=(
CommandStatus.FINISHED
if exit_code == 0
else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)
except Exception as e:
return CommandResult(
stdout="",
stderr=f"Command execution failed: {str(e)}",
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

# Use native async for non-streaming execution
try:
client = self._get_async_client()
response = await client.run(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
)

stdout = response.get("stdout", "")
stderr = response.get("stderr", "")
exit_code = response.get("code", 0)
async for event in client.run_streaming(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
):
if "stream" in event:
stream_type = event["stream"]
data = event["data"]

if stream_type == "stdout":
stdout_buffer.append(data)
if on_stdout:
on_stdout(data)
elif stream_type == "stderr":
stderr_buffer.append(data)
if on_stderr:
on_stderr(data)
elif "code" in event:
exit_code = event["code"]
elif "error" in event and isinstance(event["error"], str):
return CommandResult(
stdout="",
stderr=event["error"],
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

return CommandResult(
stdout=stdout,
stderr=stderr,
stdout="".join(stdout_buffer),
stderr="".join(stderr_buffer),
exit_code=exit_code,
status=(
CommandStatus.FINISHED if exit_code == 0 else CommandStatus.FAILED
CommandStatus.FINISHED
if exit_code == 0
else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)
except Exception as e:
return CommandResult(
stdout="",
stderr=f"Command execution failed: {str(e)}",
exit_code=1,
status=CommandStatus.FAILED,
duration=time.time() - start_time,
command=command,
)

# Use native async for non-streaming execution
client = self._get_async_client()
response = await client.run(
cmd=command, cwd=cwd, env=env, timeout=float(timeout)
)

stdout = response.get("stdout", "")
stderr = response.get("stderr", "")
exit_code = response.get("code", 0)

return CommandResult(
stdout=stdout,
stderr=stderr,
exit_code=exit_code,
status=(
CommandStatus.FINISHED if exit_code == 0 else CommandStatus.FAILED
),
duration=time.time() - start_time,
command=command,
)
Loading
Loading