code-sandboxes 1.9.34__tar.gz → 1.9.35__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.9.34 → code_sandboxes-1.9.35}/.github/workflows/environments-live.yml +98 -20
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/PKG-INFO +1 -1
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/modal.py +64 -25
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/pyproject.toml +4 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_builders.py +4 -4
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_contract.py +1 -3
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_live_matrix.py +5 -18
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_modal_builder.py +88 -10
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/build.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-tests.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.gitignore +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.licenserc.yaml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.pre-commit-config.yaml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.vscode/settings.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/CHANGELOG.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/Dockerfile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/LICENSE +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/base.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/client.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/commands.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/daytona_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/docker_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/e2b_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/accounts.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/datalayer.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/daytona.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/e2b.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/managed.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/attest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/bases.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/breaker.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/build_secrets.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/builders.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/conformance.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/errors.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/image_import.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/resolve.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/resolve_conda.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/spec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/jupyter_server_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/manage.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/modal_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docker/Dockerfile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/.gitignore +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/LICENSE +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/babel.config.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/specification.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/package.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/sidebars.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/schemas/environment-v1alpha1.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/conftest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_bridge_mount.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_builds.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_commands_envelope.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_console.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_contents_live_matrix.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_daytona.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_e2b.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_accounts.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_attest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_bases.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_breaker.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_build_secrets.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_conformance.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_cross_variant_report.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_datalayer_builder.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_daytona_builder.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_dockerfile_source.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_e2b_builder.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_errors.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_image_import.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_managed_builders.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_organization_policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_redact.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_resolve.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_resolve_conda.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_spec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_spec_organization_policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_eval.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_factory.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_integration.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_server.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_manage.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_manage_datalayer.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_modal_google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_modal_session.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_models.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_providers.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_sandboxes.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_the_kill_relaunch_drill.py +0 -0
|
@@ -4,18 +4,19 @@ name: Environments Live Matrix
|
|
|
4
4
|
# providers, nightly (PLAN_ENV.md E2-11): a real, hash-verified lock, a real
|
|
5
5
|
# build, a real launch, and the formal core tier — tests/test_environment_live_matrix.py.
|
|
6
6
|
#
|
|
7
|
-
# E2B
|
|
8
|
-
# (
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# comments on) an issue naming it.
|
|
7
|
+
# E2B carries its own documented, unticked gap there (E2-03: its services run
|
|
8
|
+
# as root) and is `xfail(strict=False)` in that file, so an ordinary failure
|
|
9
|
+
# there is expected and does not fail this workflow. Daytona and Modal carry
|
|
10
|
+
# no such marker: a failure there is a real regression, and this workflow
|
|
11
|
+
# opens (or comments on) an issue naming it.
|
|
13
12
|
#
|
|
14
|
-
# Needs
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
13
|
+
# Needs one repository secret, DATALAYER_API_KEY. The providers' keys are
|
|
14
|
+
# Datalayer secrets of the account it belongs to, read at run time: E2B_API_KEY,
|
|
15
|
+
# DAYTONA_API_KEY, MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, and the AWS pair under
|
|
16
|
+
# CODE_SANDBOXES_CI_AWS_ACCESS_KEY_ID and CODE_SANDBOXES_CI_AWS_SECRET_ACCESS_KEY,
|
|
17
|
+
# a key of the base reader that can only pull the approved base from ECR (D-18).
|
|
18
|
+
# A key that is not there skips the test it belongs to, by name, and the job
|
|
19
|
+
# summary lists each leg's outcome; a Daytona leg that did not run fails the job.
|
|
19
20
|
|
|
20
21
|
on:
|
|
21
22
|
schedule:
|
|
@@ -55,6 +56,48 @@ jobs:
|
|
|
55
56
|
uv pip install ".[e2b,daytona,modal,test]"
|
|
56
57
|
uv pip install boto3
|
|
57
58
|
|
|
59
|
+
# Each value is masked before it reaches the job's environment, and none
|
|
60
|
+
# is set on a later step's own `env:`, which would override it with an
|
|
61
|
+
# empty string.
|
|
62
|
+
- name: Read the providers' keys from Datalayer
|
|
63
|
+
env:
|
|
64
|
+
DATALAYER_API_KEY: ${{ secrets.DATALAYER_API_KEY }}
|
|
65
|
+
DATALAYER_IAM_URL: https://prod1.datalayer.run
|
|
66
|
+
run: |
|
|
67
|
+
python - <<'PY'
|
|
68
|
+
import json
|
|
69
|
+
import os
|
|
70
|
+
import urllib.request
|
|
71
|
+
|
|
72
|
+
key = os.environ.get("DATALAYER_API_KEY", "")
|
|
73
|
+
if not key:
|
|
74
|
+
print("DATALAYER_API_KEY is not a secret of this repository: every leg will skip")
|
|
75
|
+
raise SystemExit(0)
|
|
76
|
+
request = urllib.request.Request(
|
|
77
|
+
os.environ["DATALAYER_IAM_URL"] + "/api/iam/v1/secrets/values",
|
|
78
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
79
|
+
)
|
|
80
|
+
with urllib.request.urlopen(request, timeout=60) as response:
|
|
81
|
+
secrets = json.load(response).get("secrets") or {}
|
|
82
|
+
wanted = {
|
|
83
|
+
"DAYTONA_API_KEY": "DAYTONA_API_KEY",
|
|
84
|
+
"E2B_API_KEY": "E2B_API_KEY",
|
|
85
|
+
"MODAL_TOKEN_ID": "MODAL_TOKEN_ID",
|
|
86
|
+
"MODAL_TOKEN_SECRET": "MODAL_TOKEN_SECRET",
|
|
87
|
+
"AWS_ACCESS_KEY_ID": "CODE_SANDBOXES_CI_AWS_ACCESS_KEY_ID",
|
|
88
|
+
"AWS_SECRET_ACCESS_KEY": "CODE_SANDBOXES_CI_AWS_SECRET_ACCESS_KEY",
|
|
89
|
+
}
|
|
90
|
+
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as environment:
|
|
91
|
+
for variable, name in wanted.items():
|
|
92
|
+
value = str(secrets.get(name) or "")
|
|
93
|
+
if not value or "\n" in value:
|
|
94
|
+
print(f"{variable}: no Datalayer secret {name} for this account")
|
|
95
|
+
continue
|
|
96
|
+
print(f"::add-mask::{value}")
|
|
97
|
+
environment.write(f"{variable}={value}\n")
|
|
98
|
+
print(f"{variable}: read from the Datalayer secret {name}")
|
|
99
|
+
PY
|
|
100
|
+
|
|
58
101
|
- name: Configure AWS credentials
|
|
59
102
|
run: |
|
|
60
103
|
mkdir -p ~/.aws
|
|
@@ -63,9 +106,6 @@ jobs:
|
|
|
63
106
|
echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
|
|
64
107
|
echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY}"
|
|
65
108
|
} > ~/.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
109
|
|
|
70
110
|
- name: Run the live matrix
|
|
71
111
|
id: live
|
|
@@ -75,13 +115,51 @@ jobs:
|
|
|
75
115
|
tests/test_environment_live_matrix.py \
|
|
76
116
|
--junitxml=live-matrix-results.xml
|
|
77
117
|
env:
|
|
78
|
-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
79
|
-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
80
118
|
AWS_REGION: us-east-1
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
|
|
120
|
+
# A leg whose secret is not set skips, and a run where every leg skipped
|
|
121
|
+
# reported success in under a second: the nightly was green every night
|
|
122
|
+
# while it tested nothing (found on 2026-09-18, "3 skipped in 0.61s").
|
|
123
|
+
# Each leg's outcome goes in the job summary, and a Daytona leg that did
|
|
124
|
+
# not run fails the job: it is the one this matrix is required to prove
|
|
125
|
+
# (PLAN_ENV.md, the Datalayer and Daytona target, E2-11).
|
|
126
|
+
- name: Say which legs ran, and refuse a Daytona leg that did not
|
|
127
|
+
if: always()
|
|
128
|
+
run: |
|
|
129
|
+
python - <<'PY'
|
|
130
|
+
import os
|
|
131
|
+
import sys
|
|
132
|
+
import xml.etree.ElementTree as ET
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
cases = list(ET.parse("live-matrix-results.xml").getroot().iter("testcase"))
|
|
136
|
+
except (OSError, ET.ParseError) as error:
|
|
137
|
+
sys.exit(f"no JUnit report to read: {error}")
|
|
138
|
+
rows, daytona = [], None
|
|
139
|
+
for case in cases:
|
|
140
|
+
leg = (case.get("classname") or "").rsplit(".", 1)[-1]
|
|
141
|
+
skipped = case.find("skipped")
|
|
142
|
+
if skipped is not None:
|
|
143
|
+
outcome = "skipped: " + (skipped.get("message") or "").strip()
|
|
144
|
+
elif case.find("failure") is not None or case.find("error") is not None:
|
|
145
|
+
outcome = "failed"
|
|
146
|
+
else:
|
|
147
|
+
outcome = "passed"
|
|
148
|
+
rows.append(f"| {leg} | {outcome} |")
|
|
149
|
+
if leg == "TestDaytona":
|
|
150
|
+
daytona = outcome
|
|
151
|
+
summary = "| Leg | Outcome |\n|---|---|\n" + "\n".join(rows) + "\n"
|
|
152
|
+
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as handle:
|
|
153
|
+
handle.write(summary)
|
|
154
|
+
print(summary)
|
|
155
|
+
if daytona is None or daytona.startswith("skipped"):
|
|
156
|
+
sys.exit(
|
|
157
|
+
f"The Daytona leg did not run ({daytona or 'absent'}): it is the one this "
|
|
158
|
+
"matrix must prove, so a run without it is not a green run. Set the "
|
|
159
|
+
"DATALAYER_API_KEY repository secret, whose account holds the "
|
|
160
|
+
"DAYTONA_API_KEY and CODE_SANDBOXES_CI_AWS_* Datalayer secrets."
|
|
161
|
+
)
|
|
162
|
+
PY
|
|
85
163
|
|
|
86
164
|
- name: Upload the JUnit report
|
|
87
165
|
if: always()
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/modal.py
RENAMED
|
@@ -96,13 +96,11 @@ end and the container exited before that first real exec ever reached it.
|
|
|
96
96
|
The entrypoint now execs `sleep infinity` when it is given no arguments,
|
|
97
97
|
and `"$@"` when it is — correct either way a caller invokes it.
|
|
98
98
|
|
|
99
|
-
**A GPU
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
either (D-20 leaves that to a later item; a GPU is Modal's own launch
|
|
105
|
-
option, not an image property, per section 11.4 item 10).
|
|
99
|
+
**A GPU is a launch option on Modal, not part of the image** (section 11.4
|
|
100
|
+
item 10, E2-17). A GPU version builds the same image any version does, on the
|
|
101
|
+
CUDA base; the spec's `accelerator` names one of Modal's GPUs, checked at
|
|
102
|
+
`validate`, and the smoke test launches the image on it (`gpu="T4"`,
|
|
103
|
+
`"H100:2"`) and adds check 11 to the core tier.
|
|
106
104
|
|
|
107
105
|
**A build secret is attached to the `postInstall` steps that name it (E3-05).**
|
|
108
106
|
`run_commands` takes a per-step `secrets=` collection, a mechanism E2B and
|
|
@@ -149,14 +147,42 @@ from ..files import files_step
|
|
|
149
147
|
from ..redact import redact
|
|
150
148
|
from ..resolve import WHEELHOUSE_IMAGE_PATH, apt_pins_in
|
|
151
149
|
from ..resolve_conda import conda_lock_pip_requirements, is_conda_lock
|
|
152
|
-
from ..spec import
|
|
150
|
+
from ..spec import BuildSecret, Environment, command_names_secret
|
|
153
151
|
from .managed import ManagedBuilder
|
|
154
152
|
|
|
155
|
-
__all__ = ["UNIMPLEMENTED_INSTRUCTIONS", "Builder"]
|
|
153
|
+
__all__ = ["MODAL_GPUS", "UNIMPLEMENTED_INSTRUCTIONS", "Builder", "modal_gpu"]
|
|
156
154
|
|
|
157
155
|
#: What Modal's own Dockerfile builder does not implement (§6).
|
|
158
156
|
UNIMPLEMENTED_INSTRUCTIONS = ("ONBUILD", "STOPSIGNAL", "VOLUME")
|
|
159
157
|
|
|
158
|
+
#: The GPUs a Modal sandbox takes by name (E2-17), as its `gpu=` spells them.
|
|
159
|
+
MODAL_GPUS: tuple[str, ...] = (
|
|
160
|
+
"T4",
|
|
161
|
+
"L4",
|
|
162
|
+
"A10G",
|
|
163
|
+
"L40S",
|
|
164
|
+
"A100",
|
|
165
|
+
"A100-40GB",
|
|
166
|
+
"A100-80GB",
|
|
167
|
+
"H100",
|
|
168
|
+
"H200",
|
|
169
|
+
"B200",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def modal_gpu(accelerator_type: str, count: int = 1) -> str | None:
|
|
174
|
+
"""Modal's `gpu=` for an accelerator, or `None` when Modal has no such GPU.
|
|
175
|
+
|
|
176
|
+
`t4` and `a100_80gb` name what Modal calls `T4` and `A100-80GB`, and a
|
|
177
|
+
count above one is Modal's `"H100:2"`. An `RTX-4090` is Daytona's
|
|
178
|
+
vocabulary, not Modal's.
|
|
179
|
+
"""
|
|
180
|
+
name = accelerator_type.strip().upper().replace("_", "-")
|
|
181
|
+
if name not in MODAL_GPUS:
|
|
182
|
+
return None
|
|
183
|
+
return f"{name}:{count}" if count > 1 else name
|
|
184
|
+
|
|
185
|
+
|
|
160
186
|
#: `uv`, pinned the same way every other builder's bootstrap is (E1-04/E3-04).
|
|
161
187
|
_UV_VERSION = "0.12.11"
|
|
162
188
|
|
|
@@ -269,7 +295,10 @@ def _intermediates_of(built: Any) -> tuple[str, ...]:
|
|
|
269
295
|
if hasattr(dependency, "deps"):
|
|
270
296
|
walk(dependency)
|
|
271
297
|
object_id = getattr(image, "object_id", None)
|
|
272
|
-
|
|
298
|
+
# Only images: the build's ECR secret is in `deps()` too, and has an
|
|
299
|
+
# `st-` id `ImageDelete` refuses ("not a valid Image ID", found live
|
|
300
|
+
# on 2026-09-18). It is deleted by the build itself, as a secret.
|
|
301
|
+
if object_id and image is not built and str(object_id).startswith("im-"):
|
|
273
302
|
found.append(str(object_id))
|
|
274
303
|
|
|
275
304
|
try:
|
|
@@ -393,6 +422,18 @@ class Builder(ManagedBuilder):
|
|
|
393
422
|
field=f"spec.buildSecrets[{index}].mountAs",
|
|
394
423
|
)
|
|
395
424
|
)
|
|
425
|
+
accelerator = environment.spec.resources.accelerator
|
|
426
|
+
if accelerator != "none" and modal_gpu(accelerator.type) is None:
|
|
427
|
+
findings.append(
|
|
428
|
+
CapabilityFinding(
|
|
429
|
+
code="DL_ENV_CAPABILITY_UNSUPPORTED",
|
|
430
|
+
message=(
|
|
431
|
+
f"Modal has no GPU called `{accelerator.type}`; it offers "
|
|
432
|
+
+ ", ".join(MODAL_GPUS)
|
|
433
|
+
),
|
|
434
|
+
field="spec.resources.accelerator.type",
|
|
435
|
+
)
|
|
436
|
+
)
|
|
396
437
|
return findings
|
|
397
438
|
|
|
398
439
|
# -- Building -------------------------------------------------------------
|
|
@@ -406,18 +447,6 @@ class Builder(ManagedBuilder):
|
|
|
406
447
|
launch, not here, for the same reason.
|
|
407
448
|
"""
|
|
408
449
|
spec = request.environment.spec
|
|
409
|
-
if request.size_class in GPU_SIZE_CLASSES:
|
|
410
|
-
# `validate` leaves a GPU class buildable (`gpu = True`, D-20):
|
|
411
|
-
# the CUDA base E2-17 has not published yet, so this cannot be
|
|
412
|
-
# reached in practice — refused plainly here rather than
|
|
413
|
-
# guessing at the launch-time `gpu=` argument this class needs
|
|
414
|
-
# (section 11.4 item 10 is a launch concern, not a build one).
|
|
415
|
-
raise EnvironmentsError(
|
|
416
|
-
CAPABILITY_UNSUPPORTED,
|
|
417
|
-
f"Modal runs `{request.size_class}` on its own GPUs, but the CUDA base this "
|
|
418
|
-
"needs is E2-17's, not built yet",
|
|
419
|
-
detail={"variant": self.variant, "missing": "E2-17"},
|
|
420
|
-
)
|
|
421
450
|
# Resolved before Modal is touched: a secret IAM will not give stops
|
|
422
451
|
# the build with nothing to clean up in the owner's workspace.
|
|
423
452
|
declared, values = self._resolved_secrets(request)
|
|
@@ -717,11 +746,13 @@ class Builder(ManagedBuilder):
|
|
|
717
746
|
)
|
|
718
747
|
from ...modal_sandbox import ModalSandbox
|
|
719
748
|
from ...models import SandboxConfig
|
|
720
|
-
from ..conformance import expected_packages, run_core_tier
|
|
749
|
+
from ..conformance import expected_packages, run_accelerator_check, run_core_tier
|
|
721
750
|
|
|
722
751
|
sdk = self._modal_sdk()
|
|
752
|
+
accelerator = environment.spec.resources.accelerator
|
|
753
|
+
gpu = None if accelerator == "none" else modal_gpu(accelerator.type, accelerator.count)
|
|
723
754
|
sandbox = ModalSandbox(
|
|
724
|
-
config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}"),
|
|
755
|
+
config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}", gpu=gpu),
|
|
725
756
|
app_name=f"dl-{environment.metadata.name}",
|
|
726
757
|
image_id=artifact.provider_artifact_id,
|
|
727
758
|
client=self._client(sdk),
|
|
@@ -729,13 +760,21 @@ class Builder(ManagedBuilder):
|
|
|
729
760
|
self._log(f"Launching {artifact.provider_artifact_id} to smoke-test it")
|
|
730
761
|
try:
|
|
731
762
|
sandbox.start()
|
|
732
|
-
|
|
763
|
+
result = run_core_tier(
|
|
733
764
|
sandbox,
|
|
734
765
|
python_version=environment.spec.language.version,
|
|
735
766
|
expected_packages=expected_packages(environment, lock_text or ""),
|
|
736
767
|
secret_values=tuple(secret_values),
|
|
737
768
|
restart=lambda: self._restart(sandbox),
|
|
738
769
|
)
|
|
770
|
+
if gpu is not None:
|
|
771
|
+
# The core tier passes on a machine with no GPU: a GPU version
|
|
772
|
+
# is the version its spec describes only when its GPUs are
|
|
773
|
+
# visible and its CUDA is the spec's (check 11, E2-17).
|
|
774
|
+
result.checks.append(
|
|
775
|
+
run_accelerator_check(sandbox, cuda=accelerator.cuda, count=accelerator.count)
|
|
776
|
+
)
|
|
777
|
+
return result
|
|
739
778
|
except EnvironmentsError:
|
|
740
779
|
raise
|
|
741
780
|
except Exception as error:
|
|
@@ -105,6 +105,10 @@ filterwarnings = [
|
|
|
105
105
|
# use of the client in a test became an exception. It took the live matrix
|
|
106
106
|
# against a real Datalayer sandbox down before the sandbox was reached.
|
|
107
107
|
"module:Datalayer is migrating its paths to use standard platformdirs:DeprecationWarning",
|
|
108
|
+
# The Daytona SDK (0.205) reads its own deprecated `DaytonaConfig.server_url`
|
|
109
|
+
# inside `Daytona(config)`: with `error` in force, every live Daytona test
|
|
110
|
+
# failed before reaching Daytona, and a nightly that skipped hid it.
|
|
111
|
+
"ignore:`server_url` is deprecated:DeprecationWarning",
|
|
108
112
|
]
|
|
109
113
|
|
|
110
114
|
[tool.mypy]
|
|
@@ -326,10 +326,10 @@ def test_files_and_contents_build_are_baked_together() -> None:
|
|
|
326
326
|
|
|
327
327
|
|
|
328
328
|
def test_a_contents_build_entry_without_its_digest_will_not_parse() -> None:
|
|
329
|
-
with pytest.raises(
|
|
330
|
-
environment(
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
with pytest.raises(EnvironmentsError) as raised:
|
|
330
|
+
environment(contents_build=[{"source": "https://data.example/x", "path": "/opt/x"}])
|
|
331
|
+
assert raised.value.code.code == "DL_ENV_SPEC_INVALID"
|
|
332
|
+
assert "sha256" in raised.value.message
|
|
333
333
|
|
|
334
334
|
|
|
335
335
|
def test_the_neutral_modules_import_no_provider_sdk() -> None:
|
|
@@ -163,9 +163,7 @@ def test_a_plain_build_context_passes() -> None:
|
|
|
163
163
|
),
|
|
164
164
|
],
|
|
165
165
|
)
|
|
166
|
-
def test_a_forbidden_context_member_is_refused(
|
|
167
|
-
entry: BuildContextEntry, message: str
|
|
168
|
-
) -> None:
|
|
166
|
+
def test_a_forbidden_context_member_is_refused(entry: BuildContextEntry, message: str) -> None:
|
|
169
167
|
findings = validate_build_context([entry])
|
|
170
168
|
assert any(finding.message == message for finding in findings), findings
|
|
171
169
|
with pytest.raises(EnvironmentsError) as refused:
|
|
@@ -243,25 +243,12 @@ class TestDaytona:
|
|
|
243
243
|
|
|
244
244
|
|
|
245
245
|
class TestModal:
|
|
246
|
-
"""`xfail
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
contract-built artifact's `ModalSandbox` now drops its driver to
|
|
252
|
-
`1000:100` (`_start_driver`, gated on `image_id` so a plain
|
|
253
|
-
`ModalSandbox` is unaffected), and checks 1/2 pass with it. 5/6 turned
|
|
254
|
-
out to be a separate, still-unexplained issue: a plain, non-Environments
|
|
255
|
-
`ModalSandbox` runs several sequential snippets with no trouble at all
|
|
256
|
-
over the same span, so this is specific to a contract-built artifact's
|
|
257
|
-
image under repeated `exec`. An `XPASS` here means that's closed too."""
|
|
246
|
+
"""No `xfail` since 2026-09-18: a real artifact, built by the production
|
|
247
|
+
builder and launched by image id, passes all nine checks, check 9 included
|
|
248
|
+
with a secret to scan for, right after check 8. The break recorded under
|
|
249
|
+
E2-05 (the exec channel gone after check 8) no longer reproduces; the
|
|
250
|
+
kernel-restart fix of 1.9.28, check 7's own path, is the likeliest cause."""
|
|
258
251
|
|
|
259
|
-
@pytest.mark.xfail(
|
|
260
|
-
reason="checks 5 and 6 fail on a session driver restart the sandbox refuses, "
|
|
261
|
-
"specific to a contract-built artifact and not yet root-caused (identity, "
|
|
262
|
-
"checks 1 and 2, is fixed)",
|
|
263
|
-
strict=False,
|
|
264
|
-
)
|
|
265
252
|
def test_build_launch_and_the_core_tier(self, real_lock: tuple[str, str]) -> None:
|
|
266
253
|
_skip_unless_available("modal")
|
|
267
254
|
import modal
|
|
@@ -23,7 +23,7 @@ from typing import Any, ClassVar
|
|
|
23
23
|
|
|
24
24
|
import pytest
|
|
25
25
|
|
|
26
|
-
from code_sandboxes.environments.adapters.modal import Builder
|
|
26
|
+
from code_sandboxes.environments.adapters.modal import MODAL_GPUS, Builder, modal_gpu
|
|
27
27
|
from code_sandboxes.environments.builders import ArtifactReference, BuildRequest
|
|
28
28
|
from code_sandboxes.environments.errors import (
|
|
29
29
|
ARTIFACT_MISSING,
|
|
@@ -748,16 +748,84 @@ class TestTheEcrSecretIsCleanedUp:
|
|
|
748
748
|
assert deleted.kwargs["secret_id"] == secret.object_id
|
|
749
749
|
|
|
750
750
|
|
|
751
|
-
class
|
|
752
|
-
|
|
751
|
+
class TestAGpuVersion:
|
|
752
|
+
"""E2-17: on Modal a GPU is a launch option, not part of the image."""
|
|
753
|
+
|
|
754
|
+
GPU: ClassVar[dict[str, Any]] = {
|
|
755
|
+
"base": {"ref": "datalayer/python-cuda", "channel": "2026.09"},
|
|
756
|
+
"resources": {
|
|
757
|
+
"sizeClass": "gpu-small",
|
|
758
|
+
"accelerator": {"type": "l4", "count": 2, "cuda": "12.8"},
|
|
759
|
+
},
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
def test_it_builds_the_image_any_version_builds(self) -> None:
|
|
753
763
|
modal = FakeModalModule()
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
assert
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
764
|
+
artifact = a_builder(modal=modal).build(a_request(spec=self.GPU, size_class="gpu-small"))
|
|
765
|
+
assert artifact.provider_artifact_id.startswith("im-")
|
|
766
|
+
assert modal.Image.from_aws_ecr_calls, "built from the CUDA base like any image"
|
|
767
|
+
|
|
768
|
+
def test_a_gpu_modal_does_not_offer_is_refused_before_any_build(self) -> None:
|
|
769
|
+
spec = {
|
|
770
|
+
**self.GPU,
|
|
771
|
+
"resources": {"sizeClass": "gpu-small", "accelerator": {"type": "RTX-4090"}},
|
|
772
|
+
}
|
|
773
|
+
report = a_builder().validate(a_request(spec=spec).environment)
|
|
774
|
+
assert report.supported is False
|
|
775
|
+
[finding] = [item for item in report.findings if "RTX-4090" in item.message]
|
|
776
|
+
assert finding.field == "spec.resources.accelerator.type"
|
|
777
|
+
assert all(name in finding.message for name in MODAL_GPUS)
|
|
778
|
+
|
|
779
|
+
def test_a_name_is_modal_s_gpu_argument(self) -> None:
|
|
780
|
+
assert [
|
|
781
|
+
modal_gpu("t4"),
|
|
782
|
+
modal_gpu("a100_80gb"),
|
|
783
|
+
modal_gpu("H100", 2),
|
|
784
|
+
modal_gpu("RTX-4090"),
|
|
785
|
+
] == [
|
|
786
|
+
"T4",
|
|
787
|
+
"A100-80GB",
|
|
788
|
+
"H100:2",
|
|
789
|
+
None,
|
|
790
|
+
]
|
|
791
|
+
|
|
792
|
+
def test_the_smoke_test_runs_on_the_gpu_and_adds_check_eleven(self, monkeypatch) -> None:
|
|
793
|
+
from code_sandboxes.environments.builders import CheckResult, ValidationResult
|
|
794
|
+
|
|
795
|
+
made: dict = {}
|
|
796
|
+
asked: dict = {}
|
|
797
|
+
|
|
798
|
+
class FakeSandbox:
|
|
799
|
+
def __init__(self, **kwargs):
|
|
800
|
+
made.update(kwargs)
|
|
801
|
+
|
|
802
|
+
def start(self):
|
|
803
|
+
pass
|
|
804
|
+
|
|
805
|
+
def stop(self):
|
|
806
|
+
pass
|
|
807
|
+
|
|
808
|
+
monkeypatch.setattr("code_sandboxes.modal_sandbox.ModalSandbox", FakeSandbox)
|
|
809
|
+
monkeypatch.setattr(
|
|
810
|
+
"code_sandboxes.environments.conformance.run_core_tier",
|
|
811
|
+
lambda sandbox, **kwargs: ValidationResult(contract_version="sandbox-contract/v1"),
|
|
812
|
+
)
|
|
813
|
+
monkeypatch.setattr(
|
|
814
|
+
"code_sandboxes.environments.conformance.run_accelerator_check",
|
|
815
|
+
lambda sandbox, **kwargs: asked.update(kwargs)
|
|
816
|
+
or CheckResult(id="conformance:11", name="gpu", passed=True, gating=True),
|
|
817
|
+
)
|
|
818
|
+
environment = a_request(spec=self.GPU, size_class="gpu-small").environment
|
|
819
|
+
artifact = ArtifactReference(
|
|
820
|
+
variant="modal",
|
|
821
|
+
immutable_reference="im-1",
|
|
822
|
+
provider_artifact_id="im-1",
|
|
823
|
+
contract_version="sandbox-contract/v1",
|
|
824
|
+
)
|
|
825
|
+
answer = a_builder().smoke_test(artifact, environment=environment, lock_text=LOCK)
|
|
826
|
+
assert made["config"].gpu == "L4:2"
|
|
827
|
+
assert asked == {"cuda": "12.8", "count": 2}
|
|
828
|
+
assert [check.id for check in answer.checks] == ["conformance:11"]
|
|
761
829
|
|
|
762
830
|
|
|
763
831
|
class TestABuildSecret:
|
|
@@ -958,6 +1026,16 @@ class TestTheIntermediateLayers:
|
|
|
958
1026
|
# The built image is the artifact, not an intermediate.
|
|
959
1027
|
assert _intermediates_of(built) == ("im-base", "im-middle")
|
|
960
1028
|
|
|
1029
|
+
def test_the_builds_ecr_secret_is_not_an_intermediate(self) -> None:
|
|
1030
|
+
"""It sits in `deps()` beside the layers, with an `st-` id `ImageDelete`
|
|
1031
|
+
refuses as "not a valid Image ID" (found live, 2026-09-18)."""
|
|
1032
|
+
from code_sandboxes.environments.adapters.modal import _intermediates_of
|
|
1033
|
+
|
|
1034
|
+
secret = _Layer(object_id="st-ecr", deps=lambda: ())
|
|
1035
|
+
base = _Layer(object_id="im-base", deps=lambda: (secret,))
|
|
1036
|
+
built = _Layer(object_id="im-built", deps=lambda: (base,))
|
|
1037
|
+
assert _intermediates_of(built) == ("im-base",)
|
|
1038
|
+
|
|
961
1039
|
def test_a_layer_with_no_id_is_not_recorded(self) -> None:
|
|
962
1040
|
"""Only a hydrated layer has an id worth writing down."""
|
|
963
1041
|
from code_sandboxes.environments.adapters.modal import _intermediates_of
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/__init__.py
RENAMED
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/datalayer.py
RENAMED
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/daytona.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/managed.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/build_secrets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/__init__.py
RENAMED
|
File without changes
|