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
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,50 @@

## Unreleased

- Added GPU support to the `daytona` variant. `gpu=` takes Daytona's own
flavors, `gpu_count=` how many, and several names comma-separated are an
ordered list of preferences Daytona falls back along — `gpu="H100,H200"`
takes an H200 when no H100 is free. `spot=True` runs on preemptible
capacity, which is far cheaper and outside the GPU quota; it is GPU-only and
built from an image with `auto_delete_interval=0`, both checked before the
request rather than left to come back as an API error.
`DaytonaSandbox.preempted_at()` answers when a spot sandbox was reclaimed,
and `run_code` asks on your behalf so that an eviction is not reported as a
dropped connection. `code-sandboxes exec/repl --spot` reaches it from the
CLI.

- Renamed the `google_colab` variant to `google-colab`, so every canonical
variant name is spelled the one way (`jupyter-server` already was). Any
spelling is still accepted everywhere a variant is named — `normalize_variant`
now folds to the canonical dashed form rather than to underscores, which is
what a dispatcher compares against, so the two can no longer drift apart.

- Added `code-sandboxes exec`, which runs one snippet in a fresh sandbox of any
variant and exits with the status the code earned — `0` when it ran cleanly,
`1` when it raised — so it composes in a shell. The code comes from an
argument, from `--file`, or from standard input; `--quiet` prints only what
the code produced. `exec` and `repl` take the same options.

- Moved the machinery for showing a run — `show_code`, `show_result`,
`show_and_run`, `run_repl`, `repl_prompt` — into `code_sandboxes.console`,
exported from the package. It existed three times over: in the CLI, in the
REPL examples and in the exec examples, disagreeing about whether the value
of a trailing expression is shown, whether stderr is told apart from stdout,
and which words end a session. The examples now import it like any other
consumer, and `examples/*/[exec|repl]_common.py` are gone.

- Added the `daytona` sandbox variant (`DaytonaSandbox`), running code in a
[Daytona](https://www.daytona.io/docs/) cloud sandbox. It drives the
sandbox's code interpreter rather than `process.code_run`, so state persists
between calls and `create_context()` gives a namespace Daytona keeps apart.
The value of a trailing expression is captured and returned as
`ExecutionResult.text`, which the interpreter itself does not report. GPUs,
cpu/memory and the network policy map onto Daytona's own settings; binary
files go through its filesystem API. Authenticate with `DAYTONA_API_KEY` (or
`DAYTONA_JWT_TOKEN` with `DAYTONA_ORGANIZATION_ID`) and install with
`pip install code-sandboxes[daytona]`. `get_manager("daytona")` answers the
CRUD verbs over an organization's sandboxes.

- Added the `kaggle` sandbox variant (`KaggleSandbox`) to connect to a Kaggle
interactive notebook runtime via `jupyter-kernel-client`'s
`KaggleKernelClient`. Authenticate with a Kaggle API token (`token` argument or
Expand All @@ -16,18 +60,24 @@
`server_url`/`kernel_id` or a notebook session `channels_url` (the signed JWT in
the proxied URL provides the authentication). Install with
`pip install code-sandboxes[kaggle]`.

- Enhanced `KaggleSandbox` with a transparent batch primitive: when no runtime
connection details are provided, it automatically executes code through
`KaggleKernelExecutor` (submit/poll/download) so integrations like
`jupyter-mcp-server` can run on Kaggle without requiring interactive runtime
wiring.

- Added Kaggle accelerator forwarding in batch mode: `Sandbox.create(variant="kaggle", gpu=...)`
now passes the value to `KaggleKernelExecutor.execute(accelerator=...)`,
supporting both Kaggle API values (`NvidiaTeslaT4`, ...) and friendly aliases
(`T4`, `P100`, ...).

- Updated `ColabSandbox` to be reuse-only for existing Colab runtimes and added
`channels_url` parsing support for extracting `server_url` / `kernel_id` /
`proxy_token` directly from the Colab WebSocket channels URL.

- Breaking change: sandbox variant names are `eval`, `docker`, `jupyter`, and `datalayer`.

- Removed support for the older `local-*` variant names from the public API and documentation.

- Clarified in the documentation that `Sandbox.create()` defaults to `datalayer`.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ Code Sandboxes (`code_sandboxes`) is a Python package for running code in isolat
Canonical variant names:

- `datalayer`
- `daytona`
- `docker`
- `eval`
- `google_colab`
- `jupyter`
- `google-colab`
- `jupyter-server`
- `kaggle`
- `modal`
- `monty`
Expand Down Expand Up @@ -48,18 +49,18 @@ pip install code-sandboxes

For backend-specific extras and credentials, see [https://code-sandboxes.datalayer.tech/installation](https://code-sandboxes.datalayer.tech/installation) and [https://code-sandboxes.datalayer.tech/sandboxes](https://code-sandboxes.datalayer.tech/sandboxes).

### Jupyter Sandbox
### Jupyter Server Sandbox

```python
from code_sandboxes import Sandbox

# Option 1: manage a local Jupyter server automatically
with Sandbox.create(variant="jupyter") as sandbox:
with Sandbox.create(variant="jupyter-server") as sandbox:
print(sandbox.run_code("1 + 1").text) # 2

# Option 2: connect to an existing Jupyter server
with Sandbox.create(
variant="jupyter",
variant="jupyter-server",
server_url="http://localhost:8888",
token="MY_TOKEN",
) as sandbox:
Expand Down Expand Up @@ -89,7 +90,7 @@ pip install code-sandboxes[kaggle]
export KAGGLE_API_KEY="<your-kaggle-api-key>"

# Launch the REPL
sandbox repl --variant kaggle
code-sandboxes repl --variant kaggle
```

For batch execution, configure Kaggle credentials and create the sandbox
Expand Down Expand Up @@ -141,7 +142,7 @@ directly to the sandbox:
```python
from code_sandboxes import Sandbox

with Sandbox.create(variant="google_colab", channels_url=channels_url) as sandbox:
with Sandbox.create(variant="google-colab", channels_url=channels_url) as sandbox:
print(sandbox.run_code("x = 1 + 1; print(x)").stdout)
```

Expand Down
37 changes: 24 additions & 13 deletions code_sandboxes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

Cloud container sandboxes:
- ModalSandbox: Modal cloud containers, per-snippet process execution
- DaytonaSandbox: Daytona cloud sandboxes, stateful Python interpreter

Features:
- Code execution with streaming support
Expand Down Expand Up @@ -60,7 +61,16 @@
from .base import Sandbox
from .client import CodeExecutionOutcome, CodeSandboxClient, execution_result_to_reply
from .commands import CommandResult, ProcessHandle, SandboxCommands
from .console import (
EXIT_COMMANDS,
repl_prompt,
run_repl,
show_and_run,
show_code,
show_result,
)
from .datalayer_sandbox import DatalayerSandbox
from .daytona_sandbox import DaytonaSandbox
from .docker_sandbox import DockerSandbox
from .eval_sandbox import EvalSandbox
from .exceptions import (
Expand Down Expand Up @@ -121,33 +131,30 @@
SnapshotInfo,
TunnelInfo,
)
from .monty_sandbox import MontySandbox
from .providers import (
PROVIDERS,
ProviderRequirement,
SandboxProvider,
available_providers,
get_provider,
)
from .monty_sandbox import MontySandbox

#: Everything this package exports, in one sorted list — the groups it
#: used to be split into stopped matching what they sat above.
__all__ = [
"EXIT_COMMANDS",
"KAGGLE_API_TOKEN_ENV",
# Providers
"PROVIDERS",
"ProviderRequirement",
"SandboxProvider",
"available_providers",
"get_provider",
# Models
"CodeError",
"CodeExecutionOutcome",
"CodeSandboxClient",
"CommandResult",
"Context",
"ContextNotFoundError",
"DatalayerSandbox",
"DaytonaSandbox",
"DockerSandbox",
# Sandbox implementations
"EvalSandbox",
"ExecutionResult",
"FileInfo",
Expand All @@ -170,28 +177,25 @@
"OutputHandler",
"OutputMessage",
"ProcessHandle",
"ProviderRequirement",
"ResourceConfig",
"Result",
# Main sandbox class
"Sandbox",
"SandboxAuthenticationError",
# Commands
"SandboxCommands",
"SandboxConfig",
"SandboxConfigurationError",
"SandboxConnectionError",
"SandboxEnvironment",
# Exceptions
"SandboxError",
"SandboxExecutionError",
"SandboxFileHandle",
# Filesystem
"SandboxFilesystem",
"SandboxInfo",
# Management (CRUD)
"SandboxManagementError",
"SandboxManager",
"SandboxNotStartedError",
"SandboxProvider",
"SandboxQuotaExceededError",
"SandboxResourceError",
"SandboxSnapshotError",
Expand All @@ -201,9 +205,16 @@
"SnapshotInfo",
"TunnelInfo",
"VariableNotFoundError",
"available_providers",
"execution_result_to_reply",
"get_manager",
"get_provider",
"manageable_variants",
"parse_google_colab_channels_url",
"parse_kaggle_channels_url",
"repl_prompt",
"run_repl",
"show_and_run",
"show_code",
"show_result",
]
2 changes: 1 addition & 1 deletion code_sandboxes/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

"""Code Sandboxes."""

__version__ = "1.0.10"
__version__ = "1.1.0"
33 changes: 17 additions & 16 deletions code_sandboxes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
SandboxInfo,
SandboxStatus,
SandboxVariant,
normalize_variant,
)


Expand Down Expand Up @@ -286,12 +287,7 @@ def create( # noqa: C901

from .eval_sandbox import EvalSandbox

# In one normal form, as every dispatcher here reads it: the value of
# a variant may carry a dash — `jupyter-server` — and callers type
# either spelling.
variant_value = (
variant.value if isinstance(variant, SandboxVariant) else variant
).replace("-", "_")
variant_value = normalize_variant(variant)

if variant_value == "eval":
sandbox = EvalSandbox(config=config, **kwargs)
Expand All @@ -300,15 +296,15 @@ def create( # noqa: C901
from .docker_sandbox import DockerSandbox

sandbox = DockerSandbox(config=config, **kwargs)
elif variant_value == "jupyter_server":
elif variant_value == "jupyter-server":
from .jupyter_server_sandbox import JupyterServerSandbox

sandbox = JupyterServerSandbox(config=config, **kwargs)
elif variant_value == "datalayer":
from .datalayer_sandbox import DatalayerSandbox

sandbox = DatalayerSandbox(config=config, **kwargs)
elif variant_value == "google_colab":
elif variant_value == "google-colab":
from .google_colab_sandbox import GoogleColabSandbox

sandbox = GoogleColabSandbox(config=config, **kwargs)
Expand All @@ -324,11 +320,14 @@ def create( # noqa: C901
from .modal_sandbox import ModalSandbox

sandbox = ModalSandbox(config=config, **kwargs)
elif variant_value == "daytona":
from .daytona_sandbox import DaytonaSandbox

sandbox = DaytonaSandbox(config=config, **kwargs)
else:
raise ValueError(
f"Unknown sandbox variant: {variant}. "
"Supported variants: "
+ ", ".join(sorted(v.value for v in SandboxVariant))
"Supported variants: " + ", ".join(sorted(v.value for v in SandboxVariant))
)

# Set tags if provided
Expand Down Expand Up @@ -373,8 +372,7 @@ def list_environments(
Returns:
List of SandboxEnvironment entries.
"""
variant_value = variant.value if isinstance(variant, SandboxVariant) else variant
variant_value = variant_value.replace("-", "_")
variant_value = normalize_variant(variant)

if variant_value == "eval":
from .eval_sandbox import EvalSandbox
Expand All @@ -384,7 +382,7 @@ def list_environments(
from .docker_sandbox import DockerSandbox

return DockerSandbox.list_environments()
if variant_value == "jupyter_server":
if variant_value == "jupyter-server":
from .jupyter_server_sandbox import JupyterServerSandbox

return JupyterServerSandbox.list_environments()
Expand All @@ -396,11 +394,15 @@ def list_environments(
from .modal_sandbox import ModalSandbox

return ModalSandbox.list_environments()
if variant_value == "daytona":
from .daytona_sandbox import DaytonaSandbox

return DaytonaSandbox.list_environments()
if variant_value == "kaggle":
from .kaggle_sandbox import KaggleSandbox

return KaggleSandbox.list_environments()
if variant_value == "google_colab":
if variant_value == "google-colab":
from .google_colab_sandbox import GoogleColabSandbox

return GoogleColabSandbox.list_environments()
Expand All @@ -410,8 +412,7 @@ def list_environments(
return DatalayerSandbox.list_environments(**kwargs)
raise ValueError(
f"Unknown sandbox variant: {variant}. "
"Supported variants: eval, docker, jupyter-server, monty, modal, "
"kaggle, google_colab, datalayer"
"Supported variants: " + ", ".join(sorted(v.value for v in SandboxVariant))
)

@classmethod
Expand Down
Loading