code-sandboxes 1.8.0__tar.gz → 1.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/build.yml +1 -1
- code_sandboxes-1.9.1/.github/workflows/environments-live.yml +181 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-tests.yml +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.pre-commit-config.yaml +5 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/CHANGELOG.md +100 -4
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/Makefile +5 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/PKG-INFO +4 -2
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/README.md +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/base.py +13 -2
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/client.py +8 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/commands.py +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/datalayer_sandbox.py +10 -10
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/daytona_sandbox.py +14 -10
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/docker_sandbox.py +2 -3
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/e2b_sandbox.py +21 -9
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/__init__.py +5 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/accounts.py +143 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/adapters/datalayer.py +232 -67
- code_sandboxes-1.9.1/code_sandboxes/environments/adapters/daytona.py +532 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/adapters/e2b.py +533 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/adapters/managed.py +242 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/adapters/modal.py +565 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/attest.py +18 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/bases.py +13 -2
- code_sandboxes-1.9.1/code_sandboxes/environments/breaker.py +294 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/build_secrets.py +152 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/builders.py +15 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/conformance.py +94 -5
- code_sandboxes-1.9.1/code_sandboxes/environments/constraints/wheelhouse/README.md +40 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/datalayer_sandbox.py +3 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/errors.py +16 -0
- code_sandboxes-1.9.1/code_sandboxes/environments/image_import.py +289 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/resolve.py +357 -13
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/spec.py +260 -25
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/jupyter_ingress.py +1 -2
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/jupyter_server_sandbox.py +29 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/modal_sandbox.py +150 -35
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/index.mdx +3 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/specification.mdx +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/index.mdx +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/daytona.mdx +32 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/e2b.mdx +31 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/modal.mdx +30 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docusaurus.config.js +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/README.md +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/pyproject.toml +4 -2
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/schemas/environment-v1alpha1.json +75 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +1 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_bridge_mount.py +30 -18
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client.py +25 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_commands_envelope.py +5 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_contents_live_matrix.py +8 -9
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_datalayer_sandbox.py +3 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_daytona.py +20 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_e2b.py +72 -1
- code_sandboxes-1.9.1/tests/test_environment_accounts.py +131 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_attest.py +28 -7
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_bases.py +15 -4
- code_sandboxes-1.9.1/tests/test_environment_breaker.py +251 -0
- code_sandboxes-1.9.1/tests/test_environment_build_secrets.py +217 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_builders.py +40 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_conformance.py +31 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_contract.py +3 -3
- code_sandboxes-1.9.1/tests/test_environment_cross_variant_report.py +153 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_datalayer_builder.py +312 -18
- code_sandboxes-1.9.1/tests/test_environment_daytona_builder.py +786 -0
- code_sandboxes-1.9.1/tests/test_environment_e2b_builder.py +498 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_errors.py +2 -0
- code_sandboxes-1.9.1/tests/test_environment_image_import.py +202 -0
- code_sandboxes-1.9.1/tests/test_environment_live_matrix.py +312 -0
- code_sandboxes-1.9.1/tests/test_environment_managed_builders.py +384 -0
- code_sandboxes-1.9.1/tests/test_environment_modal_builder.py +746 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_redact.py +1 -3
- code_sandboxes-1.9.1/tests/test_environment_resolve.py +1131 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_spec.py +216 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_eval.py +10 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_server.py +149 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_server_streaming.py +14 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_modal_google_colab_sandbox.py +88 -0
- code_sandboxes-1.9.1/tests/test_modal_session.py +181 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_the_kill_relaunch_drill.py +9 -9
- code_sandboxes-1.8.0/tests/test_environment_resolve.py +0 -495
- code_sandboxes-1.8.0/tests/test_modal_session.py +0 -60
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.gitignore +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.licenserc.yaml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.vscode/settings.json +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/Dockerfile +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/LICENSE +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/policy.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/google_colab_sandbox.py +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_sandbox.py +1 -1
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/manage.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docker/Dockerfile +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/.gitignore +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/LICENSE +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/Makefile +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/README.md +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/babel.config.js +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/package.json +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/sidebars.js +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/__init__.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/conftest.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_builds.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_console.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_factory.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_integration.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_manage.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_manage_datalayer.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_models.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_providers.py +0 -0
- {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_sandboxes.py +0 -0
|
@@ -16,7 +16,7 @@ jobs:
|
|
|
16
16
|
uses: ./.github/workflows/reusable-python.yml
|
|
17
17
|
with:
|
|
18
18
|
python-version: ${{ matrix.python-version }}
|
|
19
|
-
install-extras: test,typing
|
|
19
|
+
install-extras: test,typing,datalayer
|
|
20
20
|
extra-packages: dbus-python keyring secretstorage
|
|
21
21
|
run-tests: true
|
|
22
22
|
test-command: pytest tests/ --cov=code_sandboxes --cov-report term-missing
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
name: Environments Live Matrix
|
|
2
|
+
|
|
3
|
+
# The managed Environment builders (E2-03, E2-04, E2-05) against the real
|
|
4
|
+
# providers, nightly (PLAN_ENV.md E2-11): a real, hash-verified lock, a real
|
|
5
|
+
# build, a real launch, and the formal core tier — tests/test_environment_live_matrix.py.
|
|
6
|
+
#
|
|
7
|
+
# E2B and Modal carry their own already-documented, unticked gaps there
|
|
8
|
+
# (E2-03's uid/gid mismatch; modal_sandbox.py's missing setpriv) and are
|
|
9
|
+
# `xfail(strict=False)` in that file, so an ordinary failure there is
|
|
10
|
+
# expected and does not fail this workflow. Daytona carries no such marker:
|
|
11
|
+
# a failure there is a real regression, and this workflow opens (or
|
|
12
|
+
# comments on) an issue naming it.
|
|
13
|
+
#
|
|
14
|
+
# Needs, as repository secrets: E2B_API_KEY, DAYTONA_API_KEY, MODAL_TOKEN_ID,
|
|
15
|
+
# MODAL_TOKEN_SECRET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY — the last two
|
|
16
|
+
# scoped to read the approved base from ECR only (D-18). A secret that is
|
|
17
|
+
# not set is not a workflow failure: the test each secret belongs to skips,
|
|
18
|
+
# by name, and says so in the job summary.
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
schedule:
|
|
22
|
+
# 04:17 UTC: off-peak for every provider this reaches, and away from the
|
|
23
|
+
# hour boundary every other scheduled workflow in this org tends to pick.
|
|
24
|
+
- cron: '17 4 * * *'
|
|
25
|
+
workflow_dispatch: {}
|
|
26
|
+
|
|
27
|
+
concurrency:
|
|
28
|
+
group: ${{ github.workflow }}
|
|
29
|
+
cancel-in-progress: false
|
|
30
|
+
|
|
31
|
+
# The default token on this repository is read-only: without this, a genuine
|
|
32
|
+
# matrix failure would still 403 trying to label and open the issue that is
|
|
33
|
+
# meant to report it (found in review).
|
|
34
|
+
permissions:
|
|
35
|
+
contents: read
|
|
36
|
+
issues: write
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
live-matrix:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
timeout-minutes: 45
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout
|
|
44
|
+
uses: actions/checkout@v6
|
|
45
|
+
|
|
46
|
+
- name: Setup UV
|
|
47
|
+
uses: astral-sh/setup-uv@v7
|
|
48
|
+
with:
|
|
49
|
+
version: latest
|
|
50
|
+
python-version: '3.13'
|
|
51
|
+
activate-environment: true
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: |
|
|
55
|
+
uv pip install ".[e2b,daytona,modal,test]"
|
|
56
|
+
uv pip install boto3
|
|
57
|
+
|
|
58
|
+
- name: Configure AWS credentials
|
|
59
|
+
run: |
|
|
60
|
+
mkdir -p ~/.aws
|
|
61
|
+
{
|
|
62
|
+
echo "[default]"
|
|
63
|
+
echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
|
|
64
|
+
echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY}"
|
|
65
|
+
} > ~/.aws/credentials
|
|
66
|
+
env:
|
|
67
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
68
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
69
|
+
|
|
70
|
+
- name: Run the live matrix
|
|
71
|
+
id: live
|
|
72
|
+
continue-on-error: true
|
|
73
|
+
run: |
|
|
74
|
+
CODE_SANDBOXES_LIVE=1 pytest -q -m live \
|
|
75
|
+
tests/test_environment_live_matrix.py \
|
|
76
|
+
--junitxml=live-matrix-results.xml
|
|
77
|
+
env:
|
|
78
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
79
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
80
|
+
AWS_REGION: us-east-1
|
|
81
|
+
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
|
|
82
|
+
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
|
|
83
|
+
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
|
|
84
|
+
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
|
|
85
|
+
|
|
86
|
+
- name: Upload the JUnit report
|
|
87
|
+
if: always()
|
|
88
|
+
uses: actions/upload-artifact@v4
|
|
89
|
+
with:
|
|
90
|
+
name: environments-live-results
|
|
91
|
+
path: live-matrix-results.xml
|
|
92
|
+
retention-days: 14
|
|
93
|
+
|
|
94
|
+
- name: Summarize genuine failures (not xfail) naming provider and check
|
|
95
|
+
if: steps.live.outcome == 'failure'
|
|
96
|
+
run: |
|
|
97
|
+
python - <<'PY'
|
|
98
|
+
import xml.etree.ElementTree as ET
|
|
99
|
+
|
|
100
|
+
tree = ET.parse("live-matrix-results.xml")
|
|
101
|
+
lines = []
|
|
102
|
+
for testcase in tree.getroot().iter("testcase"):
|
|
103
|
+
# An expected-fail (E2B/Modal's own documented gaps) is reported
|
|
104
|
+
# by pytest's junit writer as its own case with neither
|
|
105
|
+
# <failure> nor <error> — only a genuine, un-marked failure
|
|
106
|
+
# (Daytona, or a real regression anywhere else) has one.
|
|
107
|
+
node = testcase.find("failure")
|
|
108
|
+
if node is None:
|
|
109
|
+
node = testcase.find("error")
|
|
110
|
+
if node is None:
|
|
111
|
+
continue
|
|
112
|
+
name = f"{testcase.get('classname')}::{testcase.get('name')}"
|
|
113
|
+
detail = (node.get("message") or node.text or "").strip()
|
|
114
|
+
short = "\n".join(detail.splitlines()[:20])
|
|
115
|
+
lines.append(f"### `{name}`\n\n```\n{short}\n```")
|
|
116
|
+
|
|
117
|
+
with open("failures.md", "w") as handle:
|
|
118
|
+
handle.write("\n\n".join(lines))
|
|
119
|
+
print(f"{len(lines)} genuine failure(s) found")
|
|
120
|
+
PY
|
|
121
|
+
|
|
122
|
+
- name: Ensure the environments-live label exists
|
|
123
|
+
if: steps.live.outcome == 'failure'
|
|
124
|
+
continue-on-error: true # already existing is not an error
|
|
125
|
+
uses: actions/github-script@v7
|
|
126
|
+
with:
|
|
127
|
+
script: |
|
|
128
|
+
await github.rest.issues.createLabel({
|
|
129
|
+
owner: context.repo.owner,
|
|
130
|
+
repo: context.repo.repo,
|
|
131
|
+
name: 'environments-live',
|
|
132
|
+
color: 'b60205',
|
|
133
|
+
description: 'The nightly Environments live matrix (E2-11)',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
- name: Open or update an issue naming the provider and the check
|
|
137
|
+
if: steps.live.outcome == 'failure'
|
|
138
|
+
uses: actions/github-script@v7
|
|
139
|
+
with:
|
|
140
|
+
script: |
|
|
141
|
+
const fs = require('fs');
|
|
142
|
+
const detail = fs.readFileSync('failures.md', 'utf8').trim();
|
|
143
|
+
if (!detail) {
|
|
144
|
+
// Every failure was inside an `xfail` — nothing genuinely
|
|
145
|
+
// broke (see the module docstring's own reasoning).
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
149
|
+
const body = [
|
|
150
|
+
`The nightly [environments live matrix](${runUrl}) failed for real — not the two already-documented \`xfail\` gaps (E2-03's uid/gid, Modal's missing \`setpriv\`).`,
|
|
151
|
+
'',
|
|
152
|
+
detail,
|
|
153
|
+
].join('\n');
|
|
154
|
+
const title = 'environments-live: nightly failure';
|
|
155
|
+
const existing = await github.rest.issues.listForRepo({
|
|
156
|
+
owner: context.repo.owner,
|
|
157
|
+
repo: context.repo.repo,
|
|
158
|
+
state: 'open',
|
|
159
|
+
labels: 'environments-live',
|
|
160
|
+
});
|
|
161
|
+
const found = existing.data.find((issue) => issue.title === title);
|
|
162
|
+
if (found) {
|
|
163
|
+
await github.rest.issues.createComment({
|
|
164
|
+
owner: context.repo.owner,
|
|
165
|
+
repo: context.repo.repo,
|
|
166
|
+
issue_number: found.number,
|
|
167
|
+
body,
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
await github.rest.issues.create({
|
|
171
|
+
owner: context.repo.owner,
|
|
172
|
+
repo: context.repo.repo,
|
|
173
|
+
title,
|
|
174
|
+
body,
|
|
175
|
+
labels: ['environments-live'],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- name: Fail the job if something genuinely broke
|
|
180
|
+
if: steps.live.outcome == 'failure'
|
|
181
|
+
run: exit 1
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
uses: ./.github/workflows/reusable-python.yml
|
|
38
38
|
with:
|
|
39
39
|
python-version: ${{ matrix.python-version }}
|
|
40
|
-
install-extras: test
|
|
40
|
+
install-extras: test,datalayer
|
|
41
41
|
extra-packages: dbus-python keyring secretstorage
|
|
42
42
|
run-tests: true
|
|
43
43
|
test-command: pytest tests/ --cov=code_sandboxes --cov-report term-missing
|
|
@@ -9,6 +9,11 @@ repos:
|
|
|
9
9
|
- id: check-case-conflict
|
|
10
10
|
- id: check-executables-have-shebangs
|
|
11
11
|
- id: requirements-txt-fixer
|
|
12
|
+
# Datalayer's protected constraints (PLAN_ENV.md, E1-04): the header
|
|
13
|
+
# explains why it is ordered the way it is, and the fixer's sort
|
|
14
|
+
# would push that header below a package line rather than order the
|
|
15
|
+
# packages beneath it.
|
|
16
|
+
exclude: ^code_sandboxes/environments/constraints/sandbox-contract-v1\.txt$
|
|
12
17
|
- id: check-added-large-files
|
|
13
18
|
- id: check-case-conflict
|
|
14
19
|
- id: check-toml
|
|
@@ -8,11 +8,108 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 1.9.1
|
|
12
|
+
|
|
13
|
+
- **mTLS to the build pool's `buildkitd`** (`environments.adapters.datalayer.Builder`,
|
|
14
|
+
`environments.resolve.BuildkitResolveRunner`; PLAN_ENV.md E1-06/E1-07). Both
|
|
15
|
+
drivers of `buildctl` took only `--addr`, which is what every prior live
|
|
16
|
+
drill needed against a plain-socket, ephemeral `buildkitd` — never a real
|
|
17
|
+
one. The build pool's real daemon on r1 takes mTLS connections only, so
|
|
18
|
+
reaching it needed three more flags neither driver had: `--tlscert`,
|
|
19
|
+
`--tlskey` and `--tlscacert`, each read from `DATALAYER_BUILDKIT_TLSCERT`,
|
|
20
|
+
`DATALAYER_BUILDKIT_TLSKEY` and `DATALAYER_BUILDKIT_TLSCACERT` when not
|
|
21
|
+
passed explicitly (`Builder`, matching how it already reads
|
|
22
|
+
`DATALAYER_BUILDKIT_ADDR`) or passed in by the caller (`BuildkitResolveRunner`,
|
|
23
|
+
constructed by durable's own `activities_environments.py`, which now reads
|
|
24
|
+
and forwards the same three). All three or none — a partial set is treated
|
|
25
|
+
as none, since a `buildkitd` requiring mTLS refuses a client carrying only
|
|
26
|
+
some of them at the daemon, with a less useful error than refusing here.
|
|
27
|
+
|
|
28
|
+
## 1.9.0
|
|
29
|
+
|
|
30
|
+
- **The Daytona builder** (`environments.adapters.daytona`, PLAN_ENV.md
|
|
31
|
+
E2-04). A snapshot built from the approved Datalayer base — `uv pip sync --require-hashes` against the resolved lock, your files, `postInstall`
|
|
32
|
+
commands, and an explicit `tini -- sleep infinity` entrypoint — with the
|
|
33
|
+
owner's own Daytona organization, never this package's ambient
|
|
34
|
+
credentials. Confirmed live against a real, hash-verified lock: a real
|
|
35
|
+
snapshot reaches `Active` and a sandbox launched from it passes the full
|
|
36
|
+
sandbox contract, doctor included — the one managed variant, of the
|
|
37
|
+
three landing here, whose build-time state and launched state fully
|
|
38
|
+
agree.
|
|
39
|
+
- **The E2B builder** (`environments.adapters.e2b`, PLAN_ENV.md E2-03). A
|
|
40
|
+
template built from `code-interpreter-v1` — E2B's own proprietary
|
|
41
|
+
code-interpreter server ships baked into it only, so the build starts
|
|
42
|
+
there rather than from the Datalayer base and reconciles whatever it
|
|
43
|
+
shipped to exactly what the lock pins. Two live-found identity bugs are
|
|
44
|
+
fixed: `code-interpreter-v1` already holds an account at uid 1000 and a
|
|
45
|
+
group at gid 100 of its own, so the old `useradd` was silently landing
|
|
46
|
+
elsewhere — the build now renames the existing account instead — and the
|
|
47
|
+
base sets no locale at all, now set explicitly. A real build passes the
|
|
48
|
+
contract's doctor check in full. **Still open:** a launched sandbox
|
|
49
|
+
currently runs as root, not the contract's `1000:100` — the two
|
|
50
|
+
`systemd` services that actually execute a launched sandbox's code have
|
|
51
|
+
no `User=` of their own, and E2B's private server hardcodes `/home/user`
|
|
52
|
+
as the working directory, independent of anything the build sets.
|
|
53
|
+
- **The Modal builder** (`environments.adapters.modal`, PLAN_ENV.md E2-05).
|
|
54
|
+
An image built `from_aws_ecr` off the approved base, with a per-build
|
|
55
|
+
Secret made and torn down around it. The contract's own identity is put
|
|
56
|
+
back at launch: `modal_sandbox.py`'s session driver now drops itself to
|
|
57
|
+
`1000:100` for a sandbox launched from a built Environments artifact
|
|
58
|
+
specifically (gated on `image_id`, so general Modal sandbox usage
|
|
59
|
+
elsewhere is unaffected) — Modal ignores the image's own `USER`, so this
|
|
60
|
+
is the only place it can be restored. A separate, previously
|
|
61
|
+
silently-fatal bug is also fixed: the real launcher creates the sandbox
|
|
62
|
+
with no command arguments at all, so an entrypoint relying on `exec "$@"` alone was a no-op and every launched environment exited within
|
|
63
|
+
seconds; the entrypoint now falls back to `sleep infinity` when given
|
|
64
|
+
none. **Still open:** two of the core tier's checks (imports, filesystem)
|
|
65
|
+
still fail on a launched sandbox, for a reason not yet root-caused —
|
|
66
|
+
confirmed specific to a contract-built image under repeated `exec`, not
|
|
67
|
+
the identity fix.
|
|
68
|
+
- **A real, shared bug in all three managed launchers, found in review**:
|
|
69
|
+
`start()` creates the remote sandbox well before it marks itself
|
|
70
|
+
started, while `stop()` was guarded on that flag rather than on the
|
|
71
|
+
resource itself — a failure in between left a real, running sandbox
|
|
72
|
+
that `stop()` skipped entirely, orphaned for good. Fixed identically in
|
|
73
|
+
`daytona_sandbox.py`, `modal_sandbox.py` and `e2b_sandbox.py`.
|
|
74
|
+
- **A nightly live matrix for the three managed builders**
|
|
75
|
+
(`.github/workflows/environments-live.yml`, PLAN_ENV.md E2-11). Builds a
|
|
76
|
+
real artifact on each real provider from a real, hash-verified lock,
|
|
77
|
+
launches a sandbox from it, and runs the formal core tier — opening (or
|
|
78
|
+
commenting on) an issue naming the provider and the check on a genuine
|
|
79
|
+
failure. E2B and Modal run `xfail(strict=False)` for their own
|
|
80
|
+
already-documented gaps above; Daytona carries no such marker. Needs
|
|
81
|
+
provider secrets added to the repository before it does anything for
|
|
82
|
+
real.
|
|
83
|
+
|
|
84
|
+
## 1.8.2
|
|
85
|
+
|
|
86
|
+
- **A sandbox can be asked whether it is still alive, and the answer no longer
|
|
87
|
+
comes from a flag we set ourselves.** `CodeSandboxClient.is_alive()` returned
|
|
88
|
+
`is_started`, which records that `start()` ran in this process and nothing
|
|
89
|
+
else, so it kept answering `True` after the backend was gone. `Sandbox` now
|
|
90
|
+
carries an `is_alive()` that variants override; the Jupyter Server variant
|
|
91
|
+
looks its kernel id up on the server the way `_find_existing_kernel` lists
|
|
92
|
+
them, and the client asks the sandbox instead of reading its own flag.
|
|
93
|
+
Variants that have no way to ask their provider inherit the base answer,
|
|
94
|
+
`is_started`, which is all they can honestly say.
|
|
95
|
+
- **Build secrets** (`environments.build_secrets`, PLAN_ENV.md E3-05).
|
|
96
|
+
`spec.buildSecrets` now resolves for real on the Datalayer variant: each
|
|
97
|
+
declared secret's value is fetched from IAM's own internal route at the
|
|
98
|
+
moment the `postInstall` step runs, mounted with BuildKit's
|
|
99
|
+
`--mount=type=secret` (an environment variable or a file under
|
|
100
|
+
`/run/secrets/`, per `mountAs`) in its own directory outside the build
|
|
101
|
+
context, and passed to `buildctl` by file path — never in argv, never in a
|
|
102
|
+
step result, never an `ARG`/`ENV` that would bake it into the image's
|
|
103
|
+
history. E2B and Daytona refuse a spec naming one outright: E0-04's spike
|
|
104
|
+
found only a registry login for the private base on either, never a
|
|
105
|
+
per-step arbitrary secret. A version with any build secret can never be
|
|
106
|
+
published to the Library (D-12). New codes `DL_ENV_BUILD_SECRET_UNAVAILABLE`
|
|
107
|
+
and `DL_ENV_PUBLICATION_BLOCKED`.
|
|
108
|
+
|
|
11
109
|
## 1.8.0
|
|
12
110
|
|
|
13
111
|
- **The Datalayer builder** (`environments.adapters.datalayer`, PLAN_ENV.md
|
|
14
|
-
E1-07). The Dockerfile is generated from the lock — `uv pip sync
|
|
15
|
-
--require-hashes`, apt at the versions the lock recorded, `env` before
|
|
112
|
+
E1-07). The Dockerfile is generated from the lock — `uv pip sync --require-hashes`, apt at the versions the lock recorded, `env` before
|
|
16
113
|
anything installs, `postInstall` as uid 1000 with no network — and the push
|
|
17
114
|
is by digest with an SBOM and provenance attestation, under the operability
|
|
18
115
|
tag `v<n>-<build_uid>` so a retried build cannot collide with the attempt
|
|
@@ -80,8 +177,7 @@
|
|
|
80
177
|
pinned exceptions left.
|
|
81
178
|
|
|
82
179
|
- A Datalayer sandbox says when it is running code, so an interrupt can reach
|
|
83
|
-
it. `Sandbox.interrupt` refuses before it delegates — `if not
|
|
84
|
-
self._executing_event.is_set(): return False` — and `run_code` never set that
|
|
180
|
+
it. `Sandbox.interrupt` refuses before it delegates — `if not self._executing_event.is_set(): return False` — and `run_code` never set that
|
|
85
181
|
event, so `is_executing` was always False, every interrupt was refused at the
|
|
86
182
|
door, and the refusal was reported as "no code was running" to a caller
|
|
87
183
|
watching a cell run.
|
|
@@ -54,6 +54,11 @@ live-matrix:
|
|
|
54
54
|
kill-relaunch:
|
|
55
55
|
CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_the_kill_relaunch_drill.py
|
|
56
56
|
|
|
57
|
+
## The managed Environment builders against the real providers: a real
|
|
58
|
+
## resolved lock, build, launch, and the formal core tier (E2-11).
|
|
59
|
+
environments-live:
|
|
60
|
+
CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_environment_live_matrix.py
|
|
61
|
+
|
|
57
62
|
clean: ## clean
|
|
58
63
|
git clean -fdx
|
|
59
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: code_sandboxes
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.1
|
|
4
4
|
Project-URL: Home, https://github.com/datalayer/code-sandboxes
|
|
5
5
|
Author-email: Datalayer <info@datalayer.io>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -75,6 +75,7 @@ Provides-Extra: e2b
|
|
|
75
75
|
Requires-Dist: e2b-code-interpreter>=2.9.1; extra == 'e2b'
|
|
76
76
|
Provides-Extra: environments-builder
|
|
77
77
|
Requires-Dist: boto3>=1.34; extra == 'environments-builder'
|
|
78
|
+
Requires-Dist: httpx>=0.27; extra == 'environments-builder'
|
|
78
79
|
Provides-Extra: google-colab
|
|
79
80
|
Provides-Extra: kaggle
|
|
80
81
|
Requires-Dist: kaggle>=1.6; extra == 'kaggle'
|
|
@@ -87,6 +88,7 @@ Requires-Dist: modal>=1.5.2; extra == 'modal'
|
|
|
87
88
|
Provides-Extra: monty
|
|
88
89
|
Requires-Dist: pydantic-monty; extra == 'monty'
|
|
89
90
|
Provides-Extra: test
|
|
91
|
+
Requires-Dist: httpx>=0.27; extra == 'test'
|
|
90
92
|
Requires-Dist: ipykernel; extra == 'test'
|
|
91
93
|
Requires-Dist: jupyter-server<3,>=1.6; extra == 'test'
|
|
92
94
|
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
|
|
@@ -107,7 +109,7 @@ Description-Content-Type: text/markdown
|
|
|
107
109
|
[](https://github.com/sponsors/datalayer)
|
|
108
110
|
[](https://pypi.org/project/code-sandboxes)
|
|
109
111
|
|
|
110
|
-
#
|
|
112
|
+
# 📦 Code Sandboxes
|
|
111
113
|
|
|
112
114
|
Code Sandboxes (`code_sandboxes`) is a Python package for running code in isolated sandbox variants through a unified API.
|
|
113
115
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://github.com/sponsors/datalayer)
|
|
10
10
|
[](https://pypi.org/project/code-sandboxes)
|
|
11
11
|
|
|
12
|
-
#
|
|
12
|
+
# 📦 Code Sandboxes
|
|
13
13
|
|
|
14
14
|
Code Sandboxes (`code_sandboxes`) is a Python package for running code in isolated sandbox variants through a unified API.
|
|
15
15
|
|
|
@@ -198,6 +198,17 @@ class Sandbox(ABC):
|
|
|
198
198
|
"""Check if sandbox has been started."""
|
|
199
199
|
return self._started
|
|
200
200
|
|
|
201
|
+
def is_alive(self) -> bool:
|
|
202
|
+
"""Check whether the sandbox is still usable.
|
|
203
|
+
|
|
204
|
+
Distinct from :attr:`is_started`, which only records that `start` ran
|
|
205
|
+
in this process: a sandbox can be culled, evicted or shut down by
|
|
206
|
+
whoever else holds it, and nothing tells us. The base answer is
|
|
207
|
+
:attr:`is_started`, all a variant with no way to ask its provider can
|
|
208
|
+
honestly say; variants that can ask override this.
|
|
209
|
+
"""
|
|
210
|
+
return self.is_started
|
|
211
|
+
|
|
201
212
|
@property
|
|
202
213
|
def is_executing(self) -> bool:
|
|
203
214
|
"""Check if the sandbox is currently executing code."""
|
|
@@ -598,7 +609,7 @@ class Sandbox(ABC):
|
|
|
598
609
|
# caller passed itself wins, so naming both is not a surprise.
|
|
599
610
|
from .environments.builders import launch_arguments
|
|
600
611
|
|
|
601
|
-
for name_, value in launch_arguments(artifact).items():
|
|
612
|
+
for name_, value in launch_arguments(artifact, expected_variant=variant_value).items():
|
|
602
613
|
kwargs.setdefault(name_, value)
|
|
603
614
|
|
|
604
615
|
if variant_value == "eval":
|
|
@@ -1078,7 +1089,7 @@ class Sandbox(ABC):
|
|
|
1078
1089
|
from .exceptions import VariableNotFoundError
|
|
1079
1090
|
|
|
1080
1091
|
raise VariableNotFoundError(name)
|
|
1081
|
-
return _json.loads(printed[start + len(self._STDOUT_START):end])
|
|
1092
|
+
return _json.loads(printed[start + len(self._STDOUT_START) : end])
|
|
1082
1093
|
|
|
1083
1094
|
def set_variable(self, name: str, value: Any, context: Context | None = None) -> None:
|
|
1084
1095
|
"""Set a variable in the sandbox.
|
|
@@ -497,7 +497,14 @@ class CodeSandboxClient:
|
|
|
497
497
|
return self._sandbox.interrupt()
|
|
498
498
|
|
|
499
499
|
def is_alive(self) -> bool:
|
|
500
|
-
"""Whether the sandbox is
|
|
500
|
+
"""Whether the wrapped sandbox is still available for execution.
|
|
501
|
+
|
|
502
|
+
Asks the sandbox, which may check its provider. Sandboxes that expose
|
|
503
|
+
no liveness hook fall back to whether they report themselves started.
|
|
504
|
+
"""
|
|
505
|
+
is_alive_fn = getattr(self._sandbox, "is_alive", None)
|
|
506
|
+
if callable(is_alive_fn):
|
|
507
|
+
return bool(is_alive_fn())
|
|
501
508
|
return self.is_started
|
|
502
509
|
|
|
503
510
|
def restart(self) -> None:
|
|
@@ -284,7 +284,7 @@ print({_ENVELOPE_START!r} + __cmd_json__.dumps(__cmd_output__) + {_ENVELOPE_END!
|
|
|
284
284
|
duration=time.time() - start_time,
|
|
285
285
|
)
|
|
286
286
|
try:
|
|
287
|
-
result = json.loads(printed[start + len(_ENVELOPE_START):end])
|
|
287
|
+
result = json.loads(printed[start + len(_ENVELOPE_START) : end])
|
|
288
288
|
return CommandResult(
|
|
289
289
|
exit_code=int(result["exit_code"]),
|
|
290
290
|
stdout=str(result["stdout"]),
|
|
@@ -55,7 +55,6 @@ from .models import (
|
|
|
55
55
|
SnapshotInfo,
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
|
|
59
58
|
logger = logging.getLogger(__name__)
|
|
60
59
|
|
|
61
60
|
|
|
@@ -580,10 +579,11 @@ class DatalayerSandbox(Sandbox):
|
|
|
580
579
|
**This variant neither implemented it nor read the flag**, so the
|
|
581
580
|
base class's default ran instead: it sets a flag, returns `True`, and
|
|
582
581
|
stops nothing. (`google_colab` and `kaggle` are entitled to that
|
|
583
|
-
default — they poll `_interrupt_requested` and stop cooperatively.)
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
on a runtime that goes on
|
|
582
|
+
default — they poll `_interrupt_requested` and stop cooperatively.)
|
|
583
|
+
Everything above believed it. `tasks/cancel` marks a task `cancelled`
|
|
584
|
+
and calls the interrupt registered by `execute_cell`; the interrupt
|
|
585
|
+
answered yes; the cell went on running on a runtime that goes on
|
|
586
|
+
being billed.
|
|
587
587
|
|
|
588
588
|
Measured on prod1 on 2026-09-07: a cell looping for two minutes,
|
|
589
589
|
cancelled ten seconds in, `tasks/cancel` and `tasks/get` both
|
|
@@ -603,13 +603,11 @@ class DatalayerSandbox(Sandbox):
|
|
|
603
603
|
"""
|
|
604
604
|
client = getattr(self._runtime, "sandbox_client", None)
|
|
605
605
|
if client is None:
|
|
606
|
-
logger.warning(
|
|
607
|
-
"This sandbox has no runtime client, so nothing could be interrupted"
|
|
608
|
-
)
|
|
606
|
+
logger.warning("This sandbox has no runtime client, so nothing could be interrupted")
|
|
609
607
|
return False
|
|
610
608
|
try:
|
|
611
609
|
return bool(client.interrupt())
|
|
612
|
-
except Exception as error:
|
|
610
|
+
except Exception as error:
|
|
613
611
|
logger.warning("The runtime's kernel could not be interrupted: %s", error)
|
|
614
612
|
return False
|
|
615
613
|
|
|
@@ -817,7 +815,9 @@ class DatalayerSandbox(Sandbox):
|
|
|
817
815
|
try:
|
|
818
816
|
# Set environment variables if provided
|
|
819
817
|
if envs:
|
|
820
|
-
env_code = "\n".join(
|
|
818
|
+
env_code = "\n".join(
|
|
819
|
+
f"import os; os.environ[{k!r}] = {v!r}" for k, v in envs.items()
|
|
820
|
+
)
|
|
821
821
|
self._runtime.execute(env_code)
|
|
822
822
|
|
|
823
823
|
response = self._runtime.execute(code, timeout=execution_timeout)
|
|
@@ -655,17 +655,21 @@ class DaytonaSandbox(Sandbox):
|
|
|
655
655
|
)
|
|
656
656
|
|
|
657
657
|
def stop(self) -> None:
|
|
658
|
-
|
|
658
|
+
# Guarded on the resource itself, not `_started` (found in review):
|
|
659
|
+
# `start()` creates the remote sandbox well before it marks itself
|
|
660
|
+
# started, so a failure in between — `create_context`, building
|
|
661
|
+
# `SandboxInfo` — would otherwise leave a real, running sandbox that
|
|
662
|
+
# `stop()` skips entirely and this object can never clean up again.
|
|
663
|
+
if self._sandbox is None:
|
|
659
664
|
return
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
self._sandbox = None
|
|
665
|
+
try:
|
|
666
|
+
if self._delete_on_stop:
|
|
667
|
+
self._sandbox.delete()
|
|
668
|
+
else:
|
|
669
|
+
self._sandbox.stop()
|
|
670
|
+
except Exception:
|
|
671
|
+
logger.debug("Ignoring error while stopping the Daytona sandbox", exc_info=True)
|
|
672
|
+
self._sandbox = None
|
|
669
673
|
self._daytona = None
|
|
670
674
|
self._jupyter_endpoint = None
|
|
671
675
|
self._contexts.clear()
|
|
@@ -10,19 +10,18 @@ through `jupyter-kernel-client` to execute code.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import logging
|
|
13
14
|
import os
|
|
14
15
|
import tempfile
|
|
15
16
|
import time
|
|
16
17
|
import uuid
|
|
17
18
|
|
|
18
|
-
import logging
|
|
19
|
-
|
|
20
19
|
import requests
|
|
21
20
|
|
|
22
21
|
from .base import Sandbox, marks_execution
|
|
23
|
-
from .jupyter_ingress import interrupt_kernel_client
|
|
24
22
|
from .exceptions import SandboxConfigurationError, SandboxNotStartedError
|
|
25
23
|
from .interfaces import ISandboxClient
|
|
24
|
+
from .jupyter_ingress import interrupt_kernel_client
|
|
26
25
|
from .models import (
|
|
27
26
|
CodeError,
|
|
28
27
|
Context,
|
|
@@ -419,14 +419,18 @@ class E2BSandbox(Sandbox):
|
|
|
419
419
|
return self._allow_internet_access
|
|
420
420
|
|
|
421
421
|
def stop(self) -> None:
|
|
422
|
-
|
|
422
|
+
# Guarded on the resource itself, not `_started` (found in review):
|
|
423
|
+
# `start()` creates the remote sandbox well before it marks itself
|
|
424
|
+
# started, so a failure in between — `create_context`, building
|
|
425
|
+
# `SandboxInfo` — would otherwise leave a real, running sandbox that
|
|
426
|
+
# `stop()` skips entirely and this object can never clean up again.
|
|
427
|
+
if self._sandbox is None:
|
|
423
428
|
return
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
self._sandbox = None
|
|
429
|
+
try:
|
|
430
|
+
self._sandbox.kill()
|
|
431
|
+
except Exception:
|
|
432
|
+
logger.debug("Ignoring error while killing the E2B sandbox", exc_info=True)
|
|
433
|
+
self._sandbox = None
|
|
430
434
|
self._contexts.clear()
|
|
431
435
|
self._jupyter_endpoint = None
|
|
432
436
|
self._volume_mounts.stopped()
|
|
@@ -527,11 +531,19 @@ class E2BSandbox(Sandbox):
|
|
|
527
531
|
on_result(translated)
|
|
528
532
|
|
|
529
533
|
seconds = timeout if timeout is not None else self.config.timeout
|
|
534
|
+
# A language and a context are two ways of saying the same thing to
|
|
535
|
+
# E2B, and its SDK refuses both at once: "You can provide context or
|
|
536
|
+
# language, but not both at the same time." A context already carries
|
|
537
|
+
# the language it was created with, so the context wins when there is
|
|
538
|
+
# one — which is what check 14 of E0-04 found here (PLAN_ENV E2-02).
|
|
539
|
+
e2b_context = self._e2b_context(context)
|
|
540
|
+
chosen: dict[str, Any] = (
|
|
541
|
+
{"context": e2b_context} if e2b_context is not None else {"language": "python"}
|
|
542
|
+
)
|
|
530
543
|
try:
|
|
531
544
|
execution = self._sandbox.run_code(
|
|
532
545
|
code,
|
|
533
|
-
|
|
534
|
-
context=self._e2b_context(context),
|
|
546
|
+
**chosen,
|
|
535
547
|
on_stdout=take_stdout,
|
|
536
548
|
on_stderr=take_stderr,
|
|
537
549
|
on_result=take_result,
|
|
@@ -16,6 +16,7 @@ provider-specific:
|
|
|
16
16
|
- :mod:`.spec` — the canonical specification and its rules;
|
|
17
17
|
- :mod:`.resolve` — the lock a version is resolved into, once (D-9);
|
|
18
18
|
- :mod:`.policy` — whether an artifact's scan lets it be used (D-11);
|
|
19
|
+
- :mod:`.accounts` — whose provider account a managed artifact lives in (D-8);
|
|
19
20
|
- :mod:`.attest` — the scan and the signature it needs before anything runs it;
|
|
20
21
|
- :mod:`.contract` — ``sandbox-contract/v1`` and the Dockerfile validator;
|
|
21
22
|
- :mod:`.lifecycle` — the version states and their legal moves;
|
|
@@ -30,6 +31,7 @@ The builders themselves are in :mod:`.adapters`, loaded by variant.
|
|
|
30
31
|
|
|
31
32
|
from __future__ import annotations
|
|
32
33
|
|
|
34
|
+
from .accounts import account_fingerprints, fingerprint_matches, provider_account
|
|
33
35
|
from .attest import Attestor, attest_artifact, signature_tag
|
|
34
36
|
from .bases import APPROVED_BASES, ApprovedBase
|
|
35
37
|
from .builders import (
|
|
@@ -106,6 +108,7 @@ __all__ = [
|
|
|
106
108
|
"ScanPolicy",
|
|
107
109
|
"ValidationResult",
|
|
108
110
|
"VersionState",
|
|
111
|
+
"account_fingerprints",
|
|
109
112
|
"attest_artifact",
|
|
110
113
|
"build_outcome",
|
|
111
114
|
"can_transition",
|
|
@@ -113,6 +116,7 @@ __all__ = [
|
|
|
113
116
|
"canonical_json",
|
|
114
117
|
"check_dockerfile",
|
|
115
118
|
"decide",
|
|
119
|
+
"fingerprint_matches",
|
|
116
120
|
"get_builder",
|
|
117
121
|
"locked_versions",
|
|
118
122
|
"map_provider_error",
|
|
@@ -120,6 +124,7 @@ __all__ = [
|
|
|
120
124
|
"parse_environment",
|
|
121
125
|
"parse_resolver_failure",
|
|
122
126
|
"protected_pins",
|
|
127
|
+
"provider_account",
|
|
123
128
|
"refuse_if_blocked",
|
|
124
129
|
"resolve_environment",
|
|
125
130
|
"signature_tag",
|