code-sandboxes 1.7.0__tar.gz → 1.8.2__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.7.0 → code_sandboxes-1.8.2}/.github/workflows/build.yml +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.github/workflows/py-tests.yml +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.pre-commit-config.yaml +5 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/CHANGELOG.md +49 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/PKG-INFO +5 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/base.py +24 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/client.py +8 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/commands.py +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/datalayer_sandbox.py +10 -10
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/daytona_sandbox.py +30 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/docker_sandbox.py +2 -3
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/e2b_sandbox.py +10 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/__init__.py +19 -2
- code_sandboxes-1.8.2/code_sandboxes/environments/accounts.py +143 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/adapters/datalayer.py +751 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/adapters/daytona.py +90 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/adapters/e2b.py +74 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/adapters/managed.py +242 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/adapters/modal.py +75 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/attest.py +461 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/bases.py +13 -2
- code_sandboxes-1.8.2/code_sandboxes/environments/breaker.py +294 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/build_secrets.py +152 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/builders.py +85 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/conformance.py +94 -5
- code_sandboxes-1.8.2/code_sandboxes/environments/constraints/wheelhouse/README.md +40 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/doctor/datalayer_sandbox.py +3 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/errors.py +16 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/image_import.py +289 -0
- code_sandboxes-1.8.2/code_sandboxes/environments/policy.py +281 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/resolve.py +351 -13
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/spec.py +260 -25
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/jupyter_ingress.py +1 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/jupyter_server_sandbox.py +29 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/modal_sandbox.py +83 -22
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/environments/index.mdx +3 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/environments/specification.mdx +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/pyproject.toml +6 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/schemas/environment-v1alpha1.json +75 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +1 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_bridge_mount.py +30 -18
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_client.py +25 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_commands_envelope.py +5 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_contents_contract.py +7 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_contents_live_matrix.py +8 -9
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_datalayer_sandbox.py +3 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_daytona.py +38 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_e2b.py +52 -1
- code_sandboxes-1.8.2/tests/test_environment_accounts.py +131 -0
- code_sandboxes-1.8.2/tests/test_environment_attest.py +425 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_bases.py +15 -4
- code_sandboxes-1.8.2/tests/test_environment_breaker.py +251 -0
- code_sandboxes-1.8.2/tests/test_environment_build_secrets.py +217 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_builders.py +127 -2
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_conformance.py +31 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_contract.py +3 -3
- code_sandboxes-1.8.2/tests/test_environment_cross_variant_report.py +153 -0
- code_sandboxes-1.8.2/tests/test_environment_datalayer_builder.py +718 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_errors.py +2 -0
- code_sandboxes-1.8.2/tests/test_environment_image_import.py +202 -0
- code_sandboxes-1.8.2/tests/test_environment_managed_builders.py +350 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_redact.py +1 -3
- code_sandboxes-1.8.2/tests/test_environment_resolve.py +1068 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_spec.py +216 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_eval.py +10 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_jupyter_server.py +149 -0
- code_sandboxes-1.8.2/tests/test_modal_session.py +181 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_the_kill_relaunch_drill.py +9 -9
- code_sandboxes-1.7.0/tests/test_environment_resolve.py +0 -495
- code_sandboxes-1.7.0/tests/test_modal_session.py +0 -60
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.gitignore +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.licenserc.yaml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/.vscode/settings.json +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/Dockerfile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/LICENSE +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/Makefile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/README.md +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/google_colab_sandbox.py +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/kaggle_sandbox.py +1 -1
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/manage.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docker/Dockerfile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/.gitignore +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/LICENSE +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/Makefile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/README.md +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/babel.config.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/package.json +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/sidebars.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/README.md +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/__init__.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/conftest.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_builds.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_console.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_factory.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_integration.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_manage.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_manage_datalayer.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_modal_google_colab_sandbox.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_models.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/tests/test_providers.py +0 -0
- {code_sandboxes-1.7.0 → code_sandboxes-1.8.2}/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
|
|
@@ -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,6 +8,54 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 1.8.2
|
|
12
|
+
|
|
13
|
+
- **A sandbox can be asked whether it is still alive, and the answer no longer
|
|
14
|
+
comes from a flag we set ourselves.** `CodeSandboxClient.is_alive()` returned
|
|
15
|
+
`is_started`, which records that `start()` ran in this process and nothing
|
|
16
|
+
else, so it kept answering `True` after the backend was gone. `Sandbox` now
|
|
17
|
+
carries an `is_alive()` that variants override; the Jupyter Server variant
|
|
18
|
+
looks its kernel id up on the server the way `_find_existing_kernel` lists
|
|
19
|
+
them, and the client asks the sandbox instead of reading its own flag.
|
|
20
|
+
Variants that have no way to ask their provider inherit the base answer,
|
|
21
|
+
`is_started`, which is all they can honestly say.
|
|
22
|
+
- **Build secrets** (`environments.build_secrets`, PLAN_ENV.md E3-05).
|
|
23
|
+
`spec.buildSecrets` now resolves for real on the Datalayer variant: each
|
|
24
|
+
declared secret's value is fetched from IAM's own internal route at the
|
|
25
|
+
moment the `postInstall` step runs, mounted with BuildKit's
|
|
26
|
+
`--mount=type=secret` (an environment variable or a file under
|
|
27
|
+
`/run/secrets/`, per `mountAs`) in its own directory outside the build
|
|
28
|
+
context, and passed to `buildctl` by file path — never in argv, never in a
|
|
29
|
+
step result, never an `ARG`/`ENV` that would bake it into the image's
|
|
30
|
+
history. E2B and Daytona refuse a spec naming one outright: E0-04's spike
|
|
31
|
+
found only a registry login for the private base on either, never a
|
|
32
|
+
per-step arbitrary secret. A version with any build secret can never be
|
|
33
|
+
published to the Library (D-12). New codes `DL_ENV_BUILD_SECRET_UNAVAILABLE`
|
|
34
|
+
and `DL_ENV_PUBLICATION_BLOCKED`.
|
|
35
|
+
|
|
36
|
+
## 1.8.0
|
|
37
|
+
|
|
38
|
+
- **The Datalayer builder** (`environments.adapters.datalayer`, PLAN_ENV.md
|
|
39
|
+
E1-07). The Dockerfile is generated from the lock — `uv pip sync --require-hashes`, apt at the versions the lock recorded, `env` before
|
|
40
|
+
anything installs, `postInstall` as uid 1000 with no network — and the push
|
|
41
|
+
is by digest with an SBOM and provenance attestation, under the operability
|
|
42
|
+
tag `v<n>-<build_uid>` so a retried build cannot collide with the attempt
|
|
43
|
+
before it. `inspect`, `resolve`, `exists` and `delete` go through the ECR
|
|
44
|
+
API. Needs the `environments-builder` extra.
|
|
45
|
+
- **The scan and the signature** (`environments.policy`,
|
|
46
|
+
`environments.attest`, E1-08, E1-09). A critical finding with a fixed
|
|
47
|
+
version blocks and one nothing fixes is recorded; the decision record keeps
|
|
48
|
+
the threshold it was decided under, so what stopped a build reads a month
|
|
49
|
+
later. cosign signs only once the scan passed, and a replay finds the
|
|
50
|
+
signature rather than pushing a second.
|
|
51
|
+
- **A sandbox launches from an artifact** (E2-02): `Sandbox.create(artifact=…)`
|
|
52
|
+
hands each variant its own argument — an E2B template build, a Daytona
|
|
53
|
+
snapshot, a Modal image id through `Image.from_id`.
|
|
54
|
+
- **Fixed, and a live defect:** Daytona's adapter took the image branch
|
|
55
|
+
whenever resources were requested, so a snapshot asked for with `cpu=` came
|
|
56
|
+
up from a plain Debian image running none of the snapshot's content, with
|
|
57
|
+
nothing saying so. The combination is refused (correction 13).
|
|
58
|
+
|
|
11
59
|
## 1.7.0
|
|
12
60
|
|
|
13
61
|
- **A version is resolved into one lock** (`code_sandboxes.environments.resolve`,
|
|
@@ -56,8 +104,7 @@
|
|
|
56
104
|
pinned exceptions left.
|
|
57
105
|
|
|
58
106
|
- A Datalayer sandbox says when it is running code, so an interrupt can reach
|
|
59
|
-
it. `Sandbox.interrupt` refuses before it delegates — `if not
|
|
60
|
-
self._executing_event.is_set(): return False` — and `run_code` never set that
|
|
107
|
+
it. `Sandbox.interrupt` refuses before it delegates — `if not self._executing_event.is_set(): return False` — and `run_code` never set that
|
|
61
108
|
event, so `is_executing` was always False, every interrupt was refused at the
|
|
62
109
|
door, and the refusal was reported as "no code was running" to a caller
|
|
63
110
|
watching a cell run.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: code_sandboxes
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.2
|
|
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
|
|
@@ -73,6 +73,9 @@ Provides-Extra: docker
|
|
|
73
73
|
Requires-Dist: docker>=6.0; extra == 'docker'
|
|
74
74
|
Provides-Extra: e2b
|
|
75
75
|
Requires-Dist: e2b-code-interpreter>=2.9.1; extra == 'e2b'
|
|
76
|
+
Provides-Extra: environments-builder
|
|
77
|
+
Requires-Dist: boto3>=1.34; extra == 'environments-builder'
|
|
78
|
+
Requires-Dist: httpx>=0.27; extra == 'environments-builder'
|
|
76
79
|
Provides-Extra: google-colab
|
|
77
80
|
Provides-Extra: kaggle
|
|
78
81
|
Requires-Dist: kaggle>=1.6; extra == 'kaggle'
|
|
@@ -85,6 +88,7 @@ Requires-Dist: modal>=1.5.2; extra == 'modal'
|
|
|
85
88
|
Provides-Extra: monty
|
|
86
89
|
Requires-Dist: pydantic-monty; extra == 'monty'
|
|
87
90
|
Provides-Extra: test
|
|
91
|
+
Requires-Dist: httpx>=0.27; extra == 'test'
|
|
88
92
|
Requires-Dist: ipykernel; extra == 'test'
|
|
89
93
|
Requires-Dist: jupyter-server<3,>=1.6; extra == 'test'
|
|
90
94
|
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
|
|
@@ -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."""
|
|
@@ -508,6 +519,7 @@ class Sandbox(ABC):
|
|
|
508
519
|
allowed_hosts: list[str] | None = None,
|
|
509
520
|
tags: dict[str, str] | None = None,
|
|
510
521
|
examples: list[tuple[str, str]] | None = None,
|
|
522
|
+
artifact: Any = None,
|
|
511
523
|
**kwargs,
|
|
512
524
|
) -> Sandbox:
|
|
513
525
|
"""Factory method to create a sandbox of the specified variant.
|
|
@@ -589,6 +601,17 @@ class Sandbox(ABC):
|
|
|
589
601
|
|
|
590
602
|
variant_value = normalize_variant(variant)
|
|
591
603
|
|
|
604
|
+
if artifact is not None:
|
|
605
|
+
# One neutral reference, translated into the argument its variant
|
|
606
|
+
# takes: a template build for E2B, a snapshot for Daytona, an
|
|
607
|
+
# image id for Modal (PLAN_ENV.md E2-02). A caller holding an
|
|
608
|
+
# Environment's artifact should not have to know which. What the
|
|
609
|
+
# caller passed itself wins, so naming both is not a surprise.
|
|
610
|
+
from .environments.builders import launch_arguments
|
|
611
|
+
|
|
612
|
+
for name_, value in launch_arguments(artifact, expected_variant=variant_value).items():
|
|
613
|
+
kwargs.setdefault(name_, value)
|
|
614
|
+
|
|
592
615
|
if variant_value == "eval":
|
|
593
616
|
sandbox = EvalSandbox(config=config, **kwargs)
|
|
594
617
|
elif variant_value == "docker":
|
|
@@ -1066,7 +1089,7 @@ class Sandbox(ABC):
|
|
|
1066
1089
|
from .exceptions import VariableNotFoundError
|
|
1067
1090
|
|
|
1068
1091
|
raise VariableNotFoundError(name)
|
|
1069
|
-
return _json.loads(printed[start + len(self._STDOUT_START):end])
|
|
1092
|
+
return _json.loads(printed[start + len(self._STDOUT_START) : end])
|
|
1070
1093
|
|
|
1071
1094
|
def set_variable(self, name: str, value: Any, context: Context | None = None) -> None:
|
|
1072
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)
|
|
@@ -510,8 +510,13 @@ class DaytonaSandbox(Sandbox):
|
|
|
510
510
|
given = {key: value for key, value in settings.items() if value}
|
|
511
511
|
return daytona.DaytonaConfig(**given) if given else None
|
|
512
512
|
|
|
513
|
-
def _create_params(self, daytona: Any) -> Any:
|
|
514
|
-
"""What to ask Daytona for, from the configuration of this sandbox.
|
|
513
|
+
def _create_params(self, daytona: Any) -> Any: # noqa: C901
|
|
514
|
+
"""What to ask Daytona for, from the configuration of this sandbox.
|
|
515
|
+
|
|
516
|
+
Read top to bottom: what it refuses — a snapshot with resources, a
|
|
517
|
+
snapshot with an image — is the rule, and splitting the refusals
|
|
518
|
+
across helpers would hide them from the one place a reader looks.
|
|
519
|
+
"""
|
|
515
520
|
common: dict[str, Any] = {"labels": self._labels()}
|
|
516
521
|
if self.config.env_vars:
|
|
517
522
|
common["env_vars"] = dict(self.config.env_vars)
|
|
@@ -538,6 +543,27 @@ class DaytonaSandbox(Sandbox):
|
|
|
538
543
|
common["auto_delete_interval"] = 0
|
|
539
544
|
if self._spot:
|
|
540
545
|
common.update(self._spot_params(resources))
|
|
546
|
+
if self._snapshot is not None:
|
|
547
|
+
# A snapshot carries the machine it was created for — Daytona bakes
|
|
548
|
+
# CPU, memory, disk and GPU into it, and it is region-scoped — so
|
|
549
|
+
# asking for resources beside one asks for two different machines.
|
|
550
|
+
# This used to take the image branch instead: the sandbox came up
|
|
551
|
+
# from a Debian image with the resources asked for, running none of
|
|
552
|
+
# the content the snapshot held, and nothing said so (PLAN_ENV.md
|
|
553
|
+
# correction 13, E2-02). A resource change is a new snapshot.
|
|
554
|
+
if resources is not None:
|
|
555
|
+
raise SandboxConfigurationError(
|
|
556
|
+
"A Daytona snapshot carries its own CPU, memory, disk and GPU, so "
|
|
557
|
+
"cpu=, memory= and gpu= cannot be passed with snapshot=: the sandbox "
|
|
558
|
+
"would have come up from a plain image instead, with none of the "
|
|
559
|
+
"snapshot's content. Create a snapshot for the machine you want, or "
|
|
560
|
+
"pass image= with the resources."
|
|
561
|
+
)
|
|
562
|
+
if self._image is not None:
|
|
563
|
+
raise SandboxConfigurationError(
|
|
564
|
+
"snapshot= and image= are two different things to start from; pass one. "
|
|
565
|
+
"A snapshot is what an Environment build records for this variant."
|
|
566
|
+
)
|
|
541
567
|
if self._image is not None or resources is not None:
|
|
542
568
|
image = self._image
|
|
543
569
|
if image is None:
|
|
@@ -547,6 +573,8 @@ class DaytonaSandbox(Sandbox):
|
|
|
547
573
|
else daytona.Image.debian_slim()
|
|
548
574
|
)
|
|
549
575
|
return daytona.CreateSandboxFromImageParams(image=image, resources=resources, **common)
|
|
576
|
+
# No snapshot named and nothing asked for: Daytona's own default, which
|
|
577
|
+
# is what `snapshot=None` means to it.
|
|
550
578
|
return daytona.CreateSandboxFromSnapshotParams(snapshot=self._snapshot, **common)
|
|
551
579
|
|
|
552
580
|
def _spot_params(self, resources: Any | None) -> dict[str, Any]:
|
|
@@ -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,
|
|
@@ -527,11 +527,19 @@ class E2BSandbox(Sandbox):
|
|
|
527
527
|
on_result(translated)
|
|
528
528
|
|
|
529
529
|
seconds = timeout if timeout is not None else self.config.timeout
|
|
530
|
+
# A language and a context are two ways of saying the same thing to
|
|
531
|
+
# E2B, and its SDK refuses both at once: "You can provide context or
|
|
532
|
+
# language, but not both at the same time." A context already carries
|
|
533
|
+
# the language it was created with, so the context wins when there is
|
|
534
|
+
# one — which is what check 14 of E0-04 found here (PLAN_ENV E2-02).
|
|
535
|
+
e2b_context = self._e2b_context(context)
|
|
536
|
+
chosen: dict[str, Any] = (
|
|
537
|
+
{"context": e2b_context} if e2b_context is not None else {"language": "python"}
|
|
538
|
+
)
|
|
530
539
|
try:
|
|
531
540
|
execution = self._sandbox.run_code(
|
|
532
541
|
code,
|
|
533
|
-
|
|
534
|
-
context=self._e2b_context(context),
|
|
542
|
+
**chosen,
|
|
535
543
|
on_stdout=take_stdout,
|
|
536
544
|
on_stderr=take_stderr,
|
|
537
545
|
on_result=take_result,
|
|
@@ -15,6 +15,9 @@ provider-specific:
|
|
|
15
15
|
|
|
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
|
+
- :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);
|
|
20
|
+
- :mod:`.attest` — the scan and the signature it needs before anything runs it;
|
|
18
21
|
- :mod:`.contract` — ``sandbox-contract/v1`` and the Dockerfile validator;
|
|
19
22
|
- :mod:`.lifecycle` — the version states and their legal moves;
|
|
20
23
|
- :mod:`.errors` — the error taxonomy every provider error maps into;
|
|
@@ -28,6 +31,8 @@ The builders themselves are in :mod:`.adapters`, loaded by variant.
|
|
|
28
31
|
|
|
29
32
|
from __future__ import annotations
|
|
30
33
|
|
|
34
|
+
from .accounts import account_fingerprints, fingerprint_matches, provider_account
|
|
35
|
+
from .attest import Attestor, attest_artifact, signature_tag
|
|
31
36
|
from .bases import APPROVED_BASES, ApprovedBase
|
|
32
37
|
from .builders import (
|
|
33
38
|
ArtifactReference,
|
|
@@ -50,6 +55,7 @@ from .contract import (
|
|
|
50
55
|
)
|
|
51
56
|
from .errors import ERROR_CODES, EnvironmentsError, ErrorCode, map_provider_error
|
|
52
57
|
from .lifecycle import VersionState, build_outcome, can_transition, transition
|
|
58
|
+
from .policy import DEFAULT_POLICY, PolicyDecision, ScanPolicy, decide, refuse_if_blocked
|
|
53
59
|
from .resolve import (
|
|
54
60
|
LOCK_FORMAT,
|
|
55
61
|
BuildkitResolveRunner,
|
|
@@ -76,6 +82,7 @@ __all__ = [
|
|
|
76
82
|
"API_VERSION",
|
|
77
83
|
"APPROVED_BASES",
|
|
78
84
|
"CONTRACT_V1",
|
|
85
|
+
"DEFAULT_POLICY",
|
|
79
86
|
"ERROR_CODES",
|
|
80
87
|
"LOCK_FORMAT",
|
|
81
88
|
"SANDBOX_CONTRACT_V1",
|
|
@@ -83,8 +90,9 @@ __all__ = [
|
|
|
83
90
|
"VARIANTS",
|
|
84
91
|
"ApprovedBase",
|
|
85
92
|
"ArtifactReference",
|
|
86
|
-
"
|
|
93
|
+
"Attestor",
|
|
87
94
|
"BuildRequest",
|
|
95
|
+
"BuildkitResolveRunner",
|
|
88
96
|
"CapabilityReport",
|
|
89
97
|
"CapabilitySet",
|
|
90
98
|
"CheckResult",
|
|
@@ -94,23 +102,32 @@ __all__ = [
|
|
|
94
102
|
"EnvironmentsError",
|
|
95
103
|
"ErrorCode",
|
|
96
104
|
"LocalResolveRunner",
|
|
105
|
+
"PolicyDecision",
|
|
97
106
|
"ResolveRunner",
|
|
98
107
|
"SandboxContract",
|
|
108
|
+
"ScanPolicy",
|
|
99
109
|
"ValidationResult",
|
|
100
110
|
"VersionState",
|
|
111
|
+
"account_fingerprints",
|
|
112
|
+
"attest_artifact",
|
|
101
113
|
"build_outcome",
|
|
102
114
|
"can_transition",
|
|
103
115
|
"canonical_digest",
|
|
104
116
|
"canonical_json",
|
|
105
117
|
"check_dockerfile",
|
|
118
|
+
"decide",
|
|
119
|
+
"fingerprint_matches",
|
|
106
120
|
"get_builder",
|
|
107
121
|
"locked_versions",
|
|
108
|
-
"merge_requirements",
|
|
109
122
|
"map_provider_error",
|
|
123
|
+
"merge_requirements",
|
|
110
124
|
"parse_environment",
|
|
111
125
|
"parse_resolver_failure",
|
|
112
126
|
"protected_pins",
|
|
127
|
+
"provider_account",
|
|
128
|
+
"refuse_if_blocked",
|
|
113
129
|
"resolve_environment",
|
|
130
|
+
"signature_tag",
|
|
114
131
|
"spec_digest",
|
|
115
132
|
"spec_findings",
|
|
116
133
|
"transition",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
2
|
+
#
|
|
3
|
+
# BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
"""Whose provider account an artifact lives in (PLAN_ENV.md, D-8, E2-01).
|
|
6
|
+
|
|
7
|
+
A managed-variant build runs with the **environment owner's** provider
|
|
8
|
+
secret — their E2B team, their Daytona organization, their Modal workspace —
|
|
9
|
+
and the artifact it produces exists only there. So a launch has to answer a
|
|
10
|
+
question the artifact's reference cannot: *are the credentials in this
|
|
11
|
+
caller's hands the ones that can see it?* A template id from another team is
|
|
12
|
+
not a missing template, it is somebody else's; launching it would either fail
|
|
13
|
+
obscurely or, worse, start something unrelated that happens to share a name.
|
|
14
|
+
|
|
15
|
+
This module answers it without holding a secret and without asking a provider
|
|
16
|
+
anything:
|
|
17
|
+
|
|
18
|
+
- **A fingerprint is derived from the credentials themselves.** The account's
|
|
19
|
+
own identifier when the caller's configuration names one — Daytona's
|
|
20
|
+
organization, Modal's workspace — and otherwise a digest of the credential
|
|
21
|
+
that opens the account. A digest identifies the account exactly (the same
|
|
22
|
+
key always answers the same thing) and reveals nothing.
|
|
23
|
+
- **An account answers to several names**, so both sides compute the whole set
|
|
24
|
+
and a match on any one is a match. That is what lets an artifact recorded
|
|
25
|
+
before the organization was configured still launch after it is.
|
|
26
|
+
- **No provider call.** Asking E2B who a key belongs to would make a launch
|
|
27
|
+
wait on a third party to decide whether it may proceed, and would need a
|
|
28
|
+
credential scope the launch path does not otherwise need.
|
|
29
|
+
|
|
30
|
+
The comparison is deliberately conservative: **no fingerprint on either side
|
|
31
|
+
is not a match.** An artifact recorded by an older build, or a caller with no
|
|
32
|
+
provider secret at all, is refused with `provider_account_mismatch` rather
|
|
33
|
+
than let through — the failure of a refusal is a launch somebody has to
|
|
34
|
+
retry, and the failure of a pass is a sandbox running in an account nobody
|
|
35
|
+
chose.
|
|
36
|
+
|
|
37
|
+
@module code_sandboxes.environments.accounts
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import hashlib
|
|
43
|
+
from collections.abc import Mapping, Sequence
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"ACCOUNT_VARIABLES",
|
|
47
|
+
"CREDENTIAL_VARIABLES",
|
|
48
|
+
"account_fingerprints",
|
|
49
|
+
"fingerprint_matches",
|
|
50
|
+
"provider_account",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
#: What names an account, per variant, when the caller's configuration says
|
|
54
|
+
#: so. Read from the same variables the SDKs read, so a deployment that can
|
|
55
|
+
#: reach a provider can also name its account there — nothing extra to set.
|
|
56
|
+
ACCOUNT_VARIABLES: dict[str, tuple[str, ...]] = {
|
|
57
|
+
# Daytona takes the organization explicitly, and a JWT credential is only
|
|
58
|
+
# usable with it.
|
|
59
|
+
"daytona": ("DAYTONA_ORGANIZATION_ID",),
|
|
60
|
+
# Modal names the workspace in its profile; `MODAL_WORKSPACE` is what a
|
|
61
|
+
# deployment sets when it holds several.
|
|
62
|
+
"modal": ("MODAL_WORKSPACE", "MODAL_PROFILE"),
|
|
63
|
+
# E2B's API key belongs to a team, and its API answers which only to an
|
|
64
|
+
# access token — which a launch does not hold. A deployment that wants the
|
|
65
|
+
# team in the record sets it; otherwise the credential's digest stands for
|
|
66
|
+
# it.
|
|
67
|
+
"e2b": ("E2B_TEAM_ID",),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#: The credential whose digest stands for the account when nothing names it.
|
|
71
|
+
#: The first variable present is used, so the pair of a token id and its
|
|
72
|
+
#: secret digests on the id — the half that identifies, not the half that
|
|
73
|
+
#: authenticates.
|
|
74
|
+
CREDENTIAL_VARIABLES: dict[str, tuple[str, ...]] = {
|
|
75
|
+
"daytona": ("DAYTONA_API_KEY", "DAYTONA_JWT_TOKEN"),
|
|
76
|
+
"modal": ("MODAL_TOKEN_ID",),
|
|
77
|
+
"e2b": ("E2B_API_KEY",),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _digest(value: str) -> str:
|
|
82
|
+
"""A short, stable, non-reversible name for a credential.
|
|
83
|
+
|
|
84
|
+
Sixteen hex characters of sha256: enough that two accounts do not collide,
|
|
85
|
+
short enough to read in a record, and nothing anybody can turn back into
|
|
86
|
+
the key.
|
|
87
|
+
"""
|
|
88
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()[:16]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def account_fingerprints(variant: str, secrets: Mapping[str, str] | None) -> tuple[str, ...]:
|
|
92
|
+
"""Every name the account these secrets open answers to, strongest first.
|
|
93
|
+
|
|
94
|
+
`("daytona:org-42", "daytona:cred-1f2e3d4c5b6a7980")` — the organization
|
|
95
|
+
the caller named, and the digest of the key that opens it. A build records
|
|
96
|
+
the first; a launch compares against all of them, so an artifact recorded
|
|
97
|
+
before the organization was configured still launches after it is.
|
|
98
|
+
|
|
99
|
+
Empty when the secrets open no account of this variant, which is a
|
|
100
|
+
refusal and not a pass: see the module docstring.
|
|
101
|
+
"""
|
|
102
|
+
name = str(variant or "").strip().lower()
|
|
103
|
+
held = {
|
|
104
|
+
str(key): str(value) for key, value in (secrets or {}).items() if str(value or "").strip()
|
|
105
|
+
}
|
|
106
|
+
if not held:
|
|
107
|
+
return ()
|
|
108
|
+
found: list[str] = []
|
|
109
|
+
for variable in ACCOUNT_VARIABLES.get(name, ()):
|
|
110
|
+
value = held.get(variable, "").strip()
|
|
111
|
+
if value:
|
|
112
|
+
found.append(f"{name}:{value}")
|
|
113
|
+
break
|
|
114
|
+
for variable in CREDENTIAL_VARIABLES.get(name, ()):
|
|
115
|
+
value = held.get(variable, "").strip()
|
|
116
|
+
if value:
|
|
117
|
+
found.append(f"{name}:cred-{_digest(value)}")
|
|
118
|
+
break
|
|
119
|
+
return tuple(found)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def provider_account(variant: str, secrets: Mapping[str, str] | None) -> str:
|
|
123
|
+
"""The one fingerprint an artifact records: the strongest name available."""
|
|
124
|
+
found = account_fingerprints(variant, secrets)
|
|
125
|
+
return found[0] if found else ""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def fingerprint_matches(recorded: str, held: Sequence[str] | Mapping[str, str] | None) -> bool:
|
|
129
|
+
"""Whether an artifact recorded for `recorded` may be launched by `held`.
|
|
130
|
+
|
|
131
|
+
`held` is what :func:`account_fingerprints` answered for the caller, or
|
|
132
|
+
the caller's secrets to work it out from. Nothing recorded, or nothing
|
|
133
|
+
held, is **not** a match.
|
|
134
|
+
"""
|
|
135
|
+
wanted = str(recorded or "").strip()
|
|
136
|
+
if not wanted:
|
|
137
|
+
return False
|
|
138
|
+
if isinstance(held, Mapping):
|
|
139
|
+
variant = wanted.split(":", 1)[0]
|
|
140
|
+
names: Sequence[str] = account_fingerprints(variant, held)
|
|
141
|
+
else:
|
|
142
|
+
names = list(held or ())
|
|
143
|
+
return wanted in {str(name).strip() for name in names if str(name).strip()}
|