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

## Unreleased

## 1.10.0

- **One subpackage per provider.** Every sandbox moved under
`code_sandboxes.sandboxes.<provider>` — the implementation in
`<provider>/<provider>.py`, re-exported by the package's `__init__`, and the
modules only it needs beside it (Kaggle's kernel client,
executors and live session; Marimo's reactive graph and cells driver;
Google Colab's kernel client). The public names are unchanged: import them
from `code_sandboxes` as before. **Breaking:** the old flat module paths
(`code_sandboxes.datalayer_sandbox`, `code_sandboxes.marimo_sandbox`, …)
are gone, with no aliases; import from `code_sandboxes` or the new paths.
- **The Marimo kernel helper is real code.** `sandboxes/marimo/reactive_kernel.py`
is a typed, importable, self-contained module; what a sandbox sends to the
kernel is that file's own text (`reactive.KERNEL_HELPER_SOURCE` reads it
from the installed package). The type-checker and a new test suite
(`tests/test_marimo_reactive_kernel.py`) now read exactly what the kernel
runs: replacement, conflicts, cycles, plans, removal, re-execution keeping
the graph, and the stdout wire encoding.
- Docs: every provider page imports from its subpackage; the Marimo page
describes the real-code helper and points at the hosted `marimo` toolset.

## 1.9.39

- The GitHub workflow files are all `.yaml` now (`build`, `py-tests`,
Expand Down
44 changes: 24 additions & 20 deletions code_sandboxes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
installed_environment_contents,
)
from .client import CodeExecutionOutcome, CodeSandboxClient, execution_result_to_reply
from .cloudflare_sandbox import CloudflareSandbox
from .commands import CommandResult, ProcessHandle, SandboxCommands
from .console import (
EXIT_COMMANDS,
Expand All @@ -95,12 +94,6 @@
MaterializeEntry,
PreparedAttachment,
)
from .coreweave_sandbox import CoreWeaveSandbox
from .datalayer_sandbox import DatalayerSandbox
from .daytona_sandbox import DaytonaSandbox
from .docker_sandbox import DockerSandbox
from .e2b_sandbox import E2BSandbox
from .eval_sandbox import EvalSandbox
from .exceptions import (
ContextNotFoundError,
SandboxAuthenticationError,
Expand All @@ -124,16 +117,7 @@
SandboxFileHandle,
SandboxFilesystem,
)
from .google_colab import (
GoogleColabKernelClient,
parse_google_colab_channels_url,
)
from .google_colab_sandbox import GoogleColabSandbox
from .interfaces import ISandboxClient
from .jupyter_server_sandbox import JupyterServerSandbox
from .kaggle import KAGGLE_API_TOKEN_ENV, KaggleKernelClient, parse_kaggle_channels_url
from .kaggle_execute import KaggleExecutionResult, KaggleKernelExecutor
from .kaggle_sandbox import KaggleSandbox
from .lifecycle import (
INSTANCE_OPERATIONS,
LIFECYCLE_OPERATIONS,
Expand All @@ -157,9 +141,6 @@
get_manager,
manageable_variants,
)
from .marimo_cells import CellReply, CellsRun, MarimoCells
from .marimo_sandbox import CellRun, MarimoRun, MarimoSandbox
from .modal_sandbox import ModalSandbox
from .models import (
CodeError,
Context,
Expand All @@ -183,7 +164,6 @@
TunnelInfo,
normalize_variant,
)
from .monty_sandbox import MontySandbox
from .provider_ingress import provider_ingress_execution
from .providers import (
PROVIDERS,
Expand All @@ -192,6 +172,30 @@
available_providers,
get_provider,
)
from .sandboxes.cloudflare import CloudflareSandbox
from .sandboxes.coreweave import CoreWeaveSandbox
from .sandboxes.datalayer import DatalayerSandbox
from .sandboxes.daytona import DaytonaSandbox
from .sandboxes.docker import DockerSandbox
from .sandboxes.e2b import E2BSandbox
from .sandboxes.eval import EvalSandbox
from .sandboxes.google_colab import GoogleColabSandbox
from .sandboxes.google_colab.client import (
GoogleColabKernelClient,
parse_google_colab_channels_url,
)
from .sandboxes.jupyter_server import JupyterServerSandbox
from .sandboxes.kaggle import KaggleSandbox
from .sandboxes.kaggle.client import (
KAGGLE_API_TOKEN_ENV,
KaggleKernelClient,
parse_kaggle_channels_url,
)
from .sandboxes.kaggle.execute import KaggleExecutionResult, KaggleKernelExecutor
from .sandboxes.marimo import CellRun, MarimoRun, MarimoSandbox
from .sandboxes.marimo.cells import CellReply, CellsRun, MarimoCells
from .sandboxes.modal import ModalSandbox
from .sandboxes.monty import MontySandbox

#: Everything this package exports, in one sorted list — the groups it
#: used to be split into stopped matching what they sat above.
Expand Down
7 changes: 1 addition & 6 deletions code_sandboxes/__version__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
#
# BSD 3-Clause License

"""Code Sandboxes."""

__version__ = "1.9.39"
__version__ = "1.10.0"
56 changes: 28 additions & 28 deletions code_sandboxes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ def create( # noqa: C901
# it, and silently dropping them would be the surprising reading.
config = config.model_copy(update={"examples": list(examples)})

from .eval_sandbox import EvalSandbox
from .sandboxes.eval import EvalSandbox

variant_value = normalize_variant(variant)

Expand All @@ -617,51 +617,51 @@ def create( # noqa: C901
sandbox = EvalSandbox(config=config, **kwargs)
elif variant_value == "docker":
# Import here to avoid circular imports
from .docker_sandbox import DockerSandbox
from .sandboxes.docker import DockerSandbox

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

sandbox = JupyterServerSandbox(config=config, **kwargs)
elif variant_value == "marimo":
from .marimo_sandbox import MarimoSandbox
from .sandboxes.marimo import MarimoSandbox

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

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

sandbox = GoogleColabSandbox(config=config, **kwargs)
elif variant_value == "kaggle":
from .kaggle_sandbox import KaggleSandbox
from .sandboxes.kaggle import KaggleSandbox

sandbox = KaggleSandbox(config=config, **kwargs)
elif variant_value == "monty":
from .monty_sandbox import MontySandbox
from .sandboxes.monty import MontySandbox

sandbox = MontySandbox(config=config, **kwargs)
elif variant_value == "modal":
from .modal_sandbox import ModalSandbox
from .sandboxes.modal import ModalSandbox

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

sandbox = DaytonaSandbox(config=config, **kwargs)
elif variant_value == "e2b":
from .e2b_sandbox import E2BSandbox
from .sandboxes.e2b import E2BSandbox

sandbox = E2BSandbox(config=config, **kwargs)
elif variant_value == "coreweave":
from .coreweave_sandbox import CoreWeaveSandbox
from .sandboxes.coreweave import CoreWeaveSandbox

sandbox = CoreWeaveSandbox(config=config, **kwargs)
elif variant_value == "cloudflare":
from .cloudflare_sandbox import CloudflareSandbox
from .sandboxes.cloudflare import CloudflareSandbox

sandbox = CloudflareSandbox(config=config, **kwargs)
else:
Expand Down Expand Up @@ -693,7 +693,7 @@ def from_id(cls, sandbox_id: str, **kwargs) -> Sandbox:
SandboxNotFoundError: If no sandbox with the given ID exists.
"""
# This is primarily for datalayer
from .datalayer_sandbox import DatalayerSandbox
from .sandboxes.datalayer import DatalayerSandbox

return DatalayerSandbox.from_id(sandbox_id, **kwargs)

Expand All @@ -715,55 +715,55 @@ def list_environments( # noqa: C901
variant_value = normalize_variant(variant)

if variant_value == "eval":
from .eval_sandbox import EvalSandbox
from .sandboxes.eval import EvalSandbox

return EvalSandbox.list_environments()
if variant_value == "docker":
from .docker_sandbox import DockerSandbox
from .sandboxes.docker import DockerSandbox

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

return JupyterServerSandbox.list_environments()
if variant_value == "marimo":
from .marimo_sandbox import MarimoSandbox
from .sandboxes.marimo import MarimoSandbox

return MarimoSandbox.list_environments()
if variant_value == "monty":
from .monty_sandbox import MontySandbox
from .sandboxes.monty import MontySandbox

return MontySandbox.list_environments()
if variant_value == "modal":
from .modal_sandbox import ModalSandbox
from .sandboxes.modal import ModalSandbox

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

return DaytonaSandbox.list_environments()
if variant_value == "e2b":
from .e2b_sandbox import E2BSandbox
from .sandboxes.e2b import E2BSandbox

return E2BSandbox.list_environments()
if variant_value == "coreweave":
from .coreweave_sandbox import CoreWeaveSandbox
from .sandboxes.coreweave import CoreWeaveSandbox

return CoreWeaveSandbox.list_environments()
if variant_value == "cloudflare":
from .cloudflare_sandbox import CloudflareSandbox
from .sandboxes.cloudflare import CloudflareSandbox

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

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

return GoogleColabSandbox.list_environments()
if variant_value == "datalayer":
from .datalayer_sandbox import DatalayerSandbox
from .sandboxes.datalayer import DatalayerSandbox

return DatalayerSandbox.list_environments(**kwargs)
raise ValueError(
Expand All @@ -788,7 +788,7 @@ def list(
Yields:
Sandbox instances.
"""
from .datalayer_sandbox import DatalayerSandbox
from .sandboxes.datalayer import DatalayerSandbox

yield from DatalayerSandbox.list_all(tags=tags, **kwargs)

Expand Down
2 changes: 1 addition & 1 deletion code_sandboxes/environments/adapters/daytona.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ def smoke_test(

def _smoke_test_sandbox(self, snapshot: str) -> Any:
"""A sandbox of this build's own snapshot, deleted when it stops."""
from ...daytona_sandbox import DaytonaSandbox
from ...sandboxes.daytona import DaytonaSandbox

secrets = self._provider_secrets()
return DaytonaSandbox(
Expand Down
8 changes: 4 additions & 4 deletions code_sandboxes/environments/adapters/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
would report `uid: 0`, not the `1000:100` the contract asks for. Section
11.4 item 6 says a live launch is where this gets fixed — "every exec
re-asserts the user" — and, since 2026-09-13, it does:
`code_sandboxes.modal_sandbox.ModalSandbox._start_driver` sets
`code_sandboxes.sandboxes.modal.ModalSandbox._start_driver` sets
`os.setgid(100)`/`os.setuid(1000)` inside the driver it starts, in place of
the `setpriv` wrapper section 11.4 names, but only when `self._image_id` is
set — that is, only when the sandbox was launched from a built Environments
Expand Down Expand Up @@ -86,7 +86,7 @@
script file, not an inline one-liner: `/opt/datalayer/bin/entrypoint.sh`,
baked in with `exec "$@"` as real file *content*, needs no Dockerfile-string
escaping, and forwards correctly. But **the actual launcher,
`code_sandboxes.modal_sandbox.ModalSandbox.start()`, creates the sandbox
`code_sandboxes.sandboxes.modal.ModalSandbox.start()`, creates the sandbox
with no command args at all** — it execs into the running container
separately, after creation (found live, 2026-09-13, running this exact
builder's own artifact through it, not a hand-rolled `Sandbox.create` call
Expand Down Expand Up @@ -206,7 +206,7 @@ def modal_gpu(accelerator_type: str, count: int = 1) -> str | None:
#: module docstring for why. `entrypoint()` is given this one bare path,
#: nothing in it needing a Dockerfile-string escape.
_ENTRYPOINT_PATH = "/opt/datalayer/bin/entrypoint.sh"
#: `code_sandboxes.modal_sandbox.ModalSandbox.start()` — the actual launcher
#: `code_sandboxes.sandboxes.modal.ModalSandbox.start()` — the actual launcher
#: — creates the sandbox with no command args at all, and execs into it
#: separately afterward (found live, 2026-09-13: `exec "$@"` with nothing to
#: expand is a no-op in `sh`, so the container's own PID 1 fell straight
Expand Down Expand Up @@ -767,8 +767,8 @@ def smoke_test(
"lock pinned, and an artifact carries neither",
detail={"variant": self.variant},
)
from ...modal_sandbox import ModalSandbox
from ...models import SandboxConfig
from ...sandboxes.modal import ModalSandbox
from ..conformance import expected_packages, run_accelerator_check, run_core_tier

sdk = self._modal_sdk()
Expand Down
Loading
Loading