code-sandboxes 1.9.34__tar.gz → 1.9.36__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.36}/.github/workflows/environments-live.yml +98 -20
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/PKG-INFO +1 -1
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/datalayer.py +11 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/modal.py +88 -26
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/resolve.py +47 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/resolve_conda.py +6 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/modal_sandbox.py +10 -1
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/pyproject.toml +4 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_builders.py +4 -4
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_contract.py +1 -3
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_datalayer_builder.py +21 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_live_matrix.py +5 -18
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_modal_builder.py +106 -14
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_resolve.py +28 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_resolve_conda.py +24 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_modal_google_colab_sandbox.py +51 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.github/workflows/build.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.github/workflows/py-tests.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.gitignore +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.licenserc.yaml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.pre-commit-config.yaml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/.vscode/settings.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/CHANGELOG.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/Dockerfile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/LICENSE +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/base.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/client.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/commands.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/daytona_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/docker_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/e2b_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/accounts.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/daytona.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/e2b.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/managed.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/attest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/bases.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/breaker.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/build_secrets.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/builders.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/conformance.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/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.36}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/errors.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/image_import.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/spec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/jupyter_server_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/manage.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docker/Dockerfile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/.gitignore +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/LICENSE +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/babel.config.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/environments/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/environments/specification.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/package.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/sidebars.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/README.md +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/schemas/environment-v1alpha1.json +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/__init__.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/conftest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_bridge_mount.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_builds.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_client.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_commands_envelope.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_console.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_contents_live_matrix.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_daytona.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_e2b.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_accounts.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_attest.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_bases.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_breaker.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_build_secrets.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_conformance.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_cross_variant_report.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_daytona_builder.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_dockerfile_source.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_e2b_builder.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_errors.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_image_import.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_managed_builders.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_organization_policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_redact.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_spec.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_environment_spec_organization_policy.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_eval.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_factory.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_integration.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_jupyter_server.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_manage.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_manage_datalayer.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_modal_session.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_models.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_providers.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/tests/test_sandboxes.py +0 -0
- {code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/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.36}/code_sandboxes/environments/adapters/datalayer.py
RENAMED
|
@@ -76,6 +76,8 @@ from ..resolve import (
|
|
|
76
76
|
WHEELHOUSE_PATH,
|
|
77
77
|
apt_pins_in,
|
|
78
78
|
apt_snapshot_in,
|
|
79
|
+
buildkit_proxy,
|
|
80
|
+
buildkit_proxy_options,
|
|
79
81
|
locked_versions,
|
|
80
82
|
)
|
|
81
83
|
from ..resolve_conda import (
|
|
@@ -214,6 +216,11 @@ class Builder:
|
|
|
214
216
|
importing this module needs no credentials.
|
|
215
217
|
run
|
|
216
218
|
How a subprocess is run, so a test can watch the argv without a daemon.
|
|
219
|
+
proxy
|
|
220
|
+
The build pool's egress proxy, as ``buildkitd`` reaches it (E1-06):
|
|
221
|
+
every step of the build goes through it. ``DATALAYER_BUILDKIT_PROXY``
|
|
222
|
+
by default; empty is a ``buildkitd`` whose steps reach the network
|
|
223
|
+
directly.
|
|
217
224
|
"""
|
|
218
225
|
|
|
219
226
|
variant = "datalayer"
|
|
@@ -233,6 +240,7 @@ class Builder:
|
|
|
233
240
|
run: Callable[..., subprocess.CompletedProcess[str]] | None = None,
|
|
234
241
|
max_build_seconds: int = DEFAULT_MAX_BUILD_SECONDS,
|
|
235
242
|
resolve_secret: Callable[..., str] = resolve_build_secret,
|
|
243
|
+
proxy: str | None = None,
|
|
236
244
|
) -> None:
|
|
237
245
|
self._log = log or (lambda _line: None)
|
|
238
246
|
self._credential = credential
|
|
@@ -241,6 +249,8 @@ class Builder:
|
|
|
241
249
|
self._tlscert = tlscert or os.environ.get("DATALAYER_BUILDKIT_TLSCERT", "").strip()
|
|
242
250
|
self._tlskey = tlskey or os.environ.get("DATALAYER_BUILDKIT_TLSKEY", "").strip()
|
|
243
251
|
self._tlscacert = tlscacert or os.environ.get("DATALAYER_BUILDKIT_TLSCACERT", "").strip()
|
|
252
|
+
#: The build pool's egress proxy (E1-06), or `DATALAYER_BUILDKIT_PROXY`.
|
|
253
|
+
self._proxy = buildkit_proxy(proxy)
|
|
244
254
|
self._region = region or os.environ.get("AWS_REGION", "us-east-1")
|
|
245
255
|
self._ecr = ecr
|
|
246
256
|
self._run = run or subprocess.run
|
|
@@ -530,6 +540,7 @@ class Builder:
|
|
|
530
540
|
"--metadata-file",
|
|
531
541
|
str(metadata),
|
|
532
542
|
*self._cache_options(request),
|
|
543
|
+
*buildkit_proxy_options(self._proxy),
|
|
533
544
|
*secret_args,
|
|
534
545
|
]
|
|
535
546
|
self._log(f"Building {reference} from {request.resolved_base}")
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/modal.py
RENAMED
|
@@ -96,13 +96,20 @@ 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
|
-
**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
**And a sandbox with no command is not given no arguments** (found live on
|
|
100
|
+
2026-09-18): Modal keeps the base's own CMD, `start-jupyter.sh`, under the
|
|
101
|
+
new ENTRYPOINT where Docker would reset it, so the main process was a
|
|
102
|
+
Jupyter server, which exits within a minute and ends the sandbox. A smoke
|
|
103
|
+
test's restarted sandbox, started warm, died between checks 8 and 9. The
|
|
104
|
+
artifact now sets its CMD to `sleep infinity`, and `ModalSandbox` names the
|
|
105
|
+
same command when it launches an artifact, which also covers the ones built
|
|
106
|
+
before.
|
|
107
|
+
|
|
108
|
+
**A GPU is a launch option on Modal, not part of the image** (section 11.4
|
|
109
|
+
item 10, E2-17). A GPU version builds the same image any version does, on the
|
|
110
|
+
CUDA base; the spec's `accelerator` names one of Modal's GPUs, checked at
|
|
111
|
+
`validate`, and the smoke test launches the image on it (`gpu="T4"`,
|
|
112
|
+
`"H100:2"`) and adds check 11 to the core tier.
|
|
106
113
|
|
|
107
114
|
**A build secret is attached to the `postInstall` steps that name it (E3-05).**
|
|
108
115
|
`run_commands` takes a per-step `secrets=` collection, a mechanism E2B and
|
|
@@ -149,14 +156,42 @@ from ..files import files_step
|
|
|
149
156
|
from ..redact import redact
|
|
150
157
|
from ..resolve import WHEELHOUSE_IMAGE_PATH, apt_pins_in
|
|
151
158
|
from ..resolve_conda import conda_lock_pip_requirements, is_conda_lock
|
|
152
|
-
from ..spec import
|
|
159
|
+
from ..spec import BuildSecret, Environment, command_names_secret
|
|
153
160
|
from .managed import ManagedBuilder
|
|
154
161
|
|
|
155
|
-
__all__ = ["UNIMPLEMENTED_INSTRUCTIONS", "Builder"]
|
|
162
|
+
__all__ = ["MODAL_GPUS", "UNIMPLEMENTED_INSTRUCTIONS", "Builder", "modal_gpu"]
|
|
156
163
|
|
|
157
164
|
#: What Modal's own Dockerfile builder does not implement (§6).
|
|
158
165
|
UNIMPLEMENTED_INSTRUCTIONS = ("ONBUILD", "STOPSIGNAL", "VOLUME")
|
|
159
166
|
|
|
167
|
+
#: The GPUs a Modal sandbox takes by name (E2-17), as its `gpu=` spells them.
|
|
168
|
+
MODAL_GPUS: tuple[str, ...] = (
|
|
169
|
+
"T4",
|
|
170
|
+
"L4",
|
|
171
|
+
"A10G",
|
|
172
|
+
"L40S",
|
|
173
|
+
"A100",
|
|
174
|
+
"A100-40GB",
|
|
175
|
+
"A100-80GB",
|
|
176
|
+
"H100",
|
|
177
|
+
"H200",
|
|
178
|
+
"B200",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def modal_gpu(accelerator_type: str, count: int = 1) -> str | None:
|
|
183
|
+
"""Modal's `gpu=` for an accelerator, or `None` when Modal has no such GPU.
|
|
184
|
+
|
|
185
|
+
`t4` and `a100_80gb` name what Modal calls `T4` and `A100-80GB`, and a
|
|
186
|
+
count above one is Modal's `"H100:2"`. An `RTX-4090` is Daytona's
|
|
187
|
+
vocabulary, not Modal's.
|
|
188
|
+
"""
|
|
189
|
+
name = accelerator_type.strip().upper().replace("_", "-")
|
|
190
|
+
if name not in MODAL_GPUS:
|
|
191
|
+
return None
|
|
192
|
+
return f"{name}:{count}" if count > 1 else name
|
|
193
|
+
|
|
194
|
+
|
|
160
195
|
#: `uv`, pinned the same way every other builder's bootstrap is (E1-04/E3-04).
|
|
161
196
|
_UV_VERSION = "0.12.11"
|
|
162
197
|
|
|
@@ -180,6 +215,8 @@ _ENTRYPOINT_PATH = "/opt/datalayer/bin/entrypoint.sh"
|
|
|
180
215
|
#: is given keeps it alive for that; `exec "$@"` still wins when something
|
|
181
216
|
#: is, for a caller that does supply a command directly.
|
|
182
217
|
_ENTRYPOINT_SCRIPT = '#!/bin/sh\nif [ "$#" -eq 0 ]; then exec sleep infinity; fi\nexec "$@"\n'
|
|
218
|
+
#: The artifact's CMD: what a sandbox started with no command of its own runs.
|
|
219
|
+
_KEEP_ALIVE_COMMAND = ("sleep", "infinity")
|
|
183
220
|
|
|
184
221
|
|
|
185
222
|
def _modal_sdk() -> Any:
|
|
@@ -269,7 +306,10 @@ def _intermediates_of(built: Any) -> tuple[str, ...]:
|
|
|
269
306
|
if hasattr(dependency, "deps"):
|
|
270
307
|
walk(dependency)
|
|
271
308
|
object_id = getattr(image, "object_id", None)
|
|
272
|
-
|
|
309
|
+
# Only images: the build's ECR secret is in `deps()` too, and has an
|
|
310
|
+
# `st-` id `ImageDelete` refuses ("not a valid Image ID", found live
|
|
311
|
+
# on 2026-09-18). It is deleted by the build itself, as a secret.
|
|
312
|
+
if object_id and image is not built and str(object_id).startswith("im-"):
|
|
273
313
|
found.append(str(object_id))
|
|
274
314
|
|
|
275
315
|
try:
|
|
@@ -393,6 +433,18 @@ class Builder(ManagedBuilder):
|
|
|
393
433
|
field=f"spec.buildSecrets[{index}].mountAs",
|
|
394
434
|
)
|
|
395
435
|
)
|
|
436
|
+
accelerator = environment.spec.resources.accelerator
|
|
437
|
+
if accelerator != "none" and modal_gpu(accelerator.type) is None:
|
|
438
|
+
findings.append(
|
|
439
|
+
CapabilityFinding(
|
|
440
|
+
code="DL_ENV_CAPABILITY_UNSUPPORTED",
|
|
441
|
+
message=(
|
|
442
|
+
f"Modal has no GPU called `{accelerator.type}`; it offers "
|
|
443
|
+
+ ", ".join(MODAL_GPUS)
|
|
444
|
+
),
|
|
445
|
+
field="spec.resources.accelerator.type",
|
|
446
|
+
)
|
|
447
|
+
)
|
|
396
448
|
return findings
|
|
397
449
|
|
|
398
450
|
# -- Building -------------------------------------------------------------
|
|
@@ -406,18 +458,6 @@ class Builder(ManagedBuilder):
|
|
|
406
458
|
launch, not here, for the same reason.
|
|
407
459
|
"""
|
|
408
460
|
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
461
|
# Resolved before Modal is touched: a secret IAM will not give stops
|
|
422
462
|
# the build with nothing to clean up in the owner's workspace.
|
|
423
463
|
declared, values = self._resolved_secrets(request)
|
|
@@ -469,7 +509,19 @@ class Builder(ManagedBuilder):
|
|
|
469
509
|
for command in files_step(request.environment, variant=self.variant):
|
|
470
510
|
image = image.run_commands(command)
|
|
471
511
|
image = _post_install(image, spec.commands.post_install, declared, step_secrets)
|
|
472
|
-
|
|
512
|
+
# The base's own CMD is `start-jupyter.sh`, and Modal keeps
|
|
513
|
+
# it under a new ENTRYPOINT where Docker would reset it: a
|
|
514
|
+
# sandbox started with no command ran a Jupyter server as its
|
|
515
|
+
# main process, which exits within a minute and takes the
|
|
516
|
+
# sandbox with it (found live on 2026-09-18, the restarted
|
|
517
|
+
# sandbox of a smoke test dying mid-tier). What holds the
|
|
518
|
+
# container is `sleep`; Jupyter is started by exec, when
|
|
519
|
+
# asked (`ModalSandbox.prepare_jupyter_server`).
|
|
520
|
+
image = (
|
|
521
|
+
image.workdir(_CONTENT_DIR)
|
|
522
|
+
.entrypoint([_ENTRYPOINT_PATH])
|
|
523
|
+
.cmd(list(_KEEP_ALIVE_COMMAND))
|
|
524
|
+
)
|
|
473
525
|
|
|
474
526
|
logged: list[str] = []
|
|
475
527
|
buffer = io.StringIO()
|
|
@@ -717,11 +769,13 @@ class Builder(ManagedBuilder):
|
|
|
717
769
|
)
|
|
718
770
|
from ...modal_sandbox import ModalSandbox
|
|
719
771
|
from ...models import SandboxConfig
|
|
720
|
-
from ..conformance import expected_packages, run_core_tier
|
|
772
|
+
from ..conformance import expected_packages, run_accelerator_check, run_core_tier
|
|
721
773
|
|
|
722
774
|
sdk = self._modal_sdk()
|
|
775
|
+
accelerator = environment.spec.resources.accelerator
|
|
776
|
+
gpu = None if accelerator == "none" else modal_gpu(accelerator.type, accelerator.count)
|
|
723
777
|
sandbox = ModalSandbox(
|
|
724
|
-
config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}"),
|
|
778
|
+
config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}", gpu=gpu),
|
|
725
779
|
app_name=f"dl-{environment.metadata.name}",
|
|
726
780
|
image_id=artifact.provider_artifact_id,
|
|
727
781
|
client=self._client(sdk),
|
|
@@ -729,13 +783,21 @@ class Builder(ManagedBuilder):
|
|
|
729
783
|
self._log(f"Launching {artifact.provider_artifact_id} to smoke-test it")
|
|
730
784
|
try:
|
|
731
785
|
sandbox.start()
|
|
732
|
-
|
|
786
|
+
result = run_core_tier(
|
|
733
787
|
sandbox,
|
|
734
788
|
python_version=environment.spec.language.version,
|
|
735
789
|
expected_packages=expected_packages(environment, lock_text or ""),
|
|
736
790
|
secret_values=tuple(secret_values),
|
|
737
791
|
restart=lambda: self._restart(sandbox),
|
|
738
792
|
)
|
|
793
|
+
if gpu is not None:
|
|
794
|
+
# The core tier passes on a machine with no GPU: a GPU version
|
|
795
|
+
# is the version its spec describes only when its GPUs are
|
|
796
|
+
# visible and its CUDA is the spec's (check 11, E2-17).
|
|
797
|
+
result.checks.append(
|
|
798
|
+
run_accelerator_check(sandbox, cuda=accelerator.cuda, count=accelerator.count)
|
|
799
|
+
)
|
|
800
|
+
return result
|
|
739
801
|
except EnvironmentsError:
|
|
740
802
|
raise
|
|
741
803
|
except Exception as error:
|
|
@@ -83,6 +83,7 @@ if TYPE_CHECKING:
|
|
|
83
83
|
__all__ = [
|
|
84
84
|
"APT_PIN_PREFIX",
|
|
85
85
|
"APT_SNAPSHOT_PREFIX",
|
|
86
|
+
"BUILDKIT_PROXY_ENV",
|
|
86
87
|
"CONSTRAINTS_PATH",
|
|
87
88
|
"COVERAGE_PREFIX",
|
|
88
89
|
"DOCKERFILE_COVERAGE",
|
|
@@ -98,6 +99,8 @@ __all__ = [
|
|
|
98
99
|
"apt_pins",
|
|
99
100
|
"apt_pins_in",
|
|
100
101
|
"apt_snapshot_in",
|
|
102
|
+
"buildkit_proxy",
|
|
103
|
+
"buildkit_proxy_options",
|
|
101
104
|
"lock_document",
|
|
102
105
|
"locked_versions",
|
|
103
106
|
"merge_requirements",
|
|
@@ -145,6 +148,46 @@ DOCKERFILE_COVERAGE = (
|
|
|
145
148
|
APT_SNAPSHOT_PREFIX = "# datalayer-apt-snapshot: "
|
|
146
149
|
_SNAPSHOT_ID = re.compile(r"^\d{8}T\d{6}Z$")
|
|
147
150
|
|
|
151
|
+
#: Where a build's own steps reach the network through, when the build pool
|
|
152
|
+
#: gives them nowhere else to go (E1-06): an HTTP proxy that allows only the
|
|
153
|
+
#: package indexes, the snapshot mirrors and the registries. The address is
|
|
154
|
+
#: the one `buildkitd` itself sees, since a step runs in its network.
|
|
155
|
+
BUILDKIT_PROXY_ENV = "DATALAYER_BUILDKIT_PROXY"
|
|
156
|
+
_PROXY_URL = re.compile(r"^http://[A-Za-z0-9.\-]+:\d{1,5}$")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def buildkit_proxy(proxy: str | None = None) -> str:
|
|
160
|
+
"""The proxy a build's steps go through: ``proxy``, or the environment's.
|
|
161
|
+
|
|
162
|
+
Empty is no proxy, which is what a `buildkitd` with open egress needs
|
|
163
|
+
(`plane local`'s own). Anything that is not ``http://host:port`` is
|
|
164
|
+
refused rather than handed to every package manager of every build.
|
|
165
|
+
"""
|
|
166
|
+
value = (os.environ.get(BUILDKIT_PROXY_ENV, "") if proxy is None else proxy).strip()
|
|
167
|
+
if value and not _PROXY_URL.match(value):
|
|
168
|
+
raise ValueError(f"{BUILDKIT_PROXY_ENV} must be http://host:port, not {value!r}")
|
|
169
|
+
return value
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def buildkit_proxy_options(proxy: str) -> list[str]:
|
|
173
|
+
"""The ``buildctl`` options that send a build's steps through ``proxy``.
|
|
174
|
+
|
|
175
|
+
The Dockerfile frontend predefines these build args: a ``RUN`` step sees
|
|
176
|
+
them without an ``ARG``, they never reach the image's config or history,
|
|
177
|
+
and they do not change a cache key. Both spellings, since ``apt`` and
|
|
178
|
+
``curl`` read only the lower-case one for plain HTTP and ``uv``, ``pip``
|
|
179
|
+
and ``micromamba`` read either.
|
|
180
|
+
"""
|
|
181
|
+
if not proxy:
|
|
182
|
+
return []
|
|
183
|
+
options: list[str] = []
|
|
184
|
+
for name in ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"):
|
|
185
|
+
options += ["--opt", f"build-arg:{name}={proxy}"]
|
|
186
|
+
for name in ("NO_PROXY", "no_proxy"):
|
|
187
|
+
options += ["--opt", f"build-arg:{name}=127.0.0.1,localhost"]
|
|
188
|
+
return options
|
|
189
|
+
|
|
190
|
+
|
|
148
191
|
#: How a protected pin is recorded in the lock.
|
|
149
192
|
PROTECTED_PIN_PREFIX = "# datalayer-protected: "
|
|
150
193
|
|
|
@@ -623,8 +666,11 @@ class BuildkitResolveRunner:
|
|
|
623
666
|
tlscacert: str | None = None,
|
|
624
667
|
apt_snapshot: str = "",
|
|
625
668
|
timeout: float = 900.0,
|
|
669
|
+
proxy: str | None = None,
|
|
626
670
|
) -> None:
|
|
627
671
|
self._buildctl = (shutil.which("buildctl") or "") if buildctl is None else buildctl
|
|
672
|
+
#: The build pool's egress proxy (E1-06), or `DATALAYER_BUILDKIT_PROXY`.
|
|
673
|
+
self._proxy = buildkit_proxy(proxy)
|
|
628
674
|
#: The build pool's `buildkitd` takes mTLS connections only
|
|
629
675
|
#: (PLAN_ENV.md E1-06); a plain-socket one, such as `plane local`'s
|
|
630
676
|
#: own ephemeral daemon, needs none of these three. All or nothing,
|
|
@@ -761,6 +807,7 @@ class BuildkitResolveRunner:
|
|
|
761
807
|
f"dockerfile={root}",
|
|
762
808
|
"--output",
|
|
763
809
|
f"type=local,dest={out}",
|
|
810
|
+
*buildkit_proxy_options(self._proxy),
|
|
764
811
|
]
|
|
765
812
|
say(f"Solving the lock in {request.base_reference}")
|
|
766
813
|
try:
|
{code_sandboxes-1.9.34 → code_sandboxes-1.9.36}/code_sandboxes/environments/resolve_conda.py
RENAMED
|
@@ -68,6 +68,8 @@ from .resolve import (
|
|
|
68
68
|
WHEELHOUSE_PATH,
|
|
69
69
|
MergedRequirements,
|
|
70
70
|
ProtectedPin,
|
|
71
|
+
buildkit_proxy,
|
|
72
|
+
buildkit_proxy_options,
|
|
71
73
|
merge_requirements,
|
|
72
74
|
)
|
|
73
75
|
|
|
@@ -684,8 +686,11 @@ class BuildkitCondaResolveRunner:
|
|
|
684
686
|
tlskey: str | None = None,
|
|
685
687
|
tlscacert: str | None = None,
|
|
686
688
|
timeout: float = 1200.0,
|
|
689
|
+
proxy: str | None = None,
|
|
687
690
|
) -> None:
|
|
688
691
|
self._buildctl = (shutil.which("buildctl") or "") if buildctl is None else buildctl
|
|
692
|
+
#: The build pool's egress proxy (E1-06), or `DATALAYER_BUILDKIT_PROXY`.
|
|
693
|
+
self._proxy = buildkit_proxy(proxy)
|
|
689
694
|
self._tlscert = tlscert or ""
|
|
690
695
|
self._tlskey = tlskey or ""
|
|
691
696
|
self._tlscacert = tlscacert or ""
|
|
@@ -778,6 +783,7 @@ class BuildkitCondaResolveRunner:
|
|
|
778
783
|
f"dockerfile={root}",
|
|
779
784
|
"--output",
|
|
780
785
|
f"type=local,dest={out}",
|
|
786
|
+
*buildkit_proxy_options(self._proxy),
|
|
781
787
|
]
|
|
782
788
|
say(f"Solving the conda lock in {request.base_reference}")
|
|
783
789
|
try:
|
|
@@ -441,7 +441,16 @@ class ModalSandbox(Sandbox):
|
|
|
441
441
|
for mount_path, volume_id in self._volume_mounts.requested.items()
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
# An Environments artifact's main process only holds the container:
|
|
445
|
+
# everything this class runs in it is an exec, Jupyter included
|
|
446
|
+
# (`prepare_jupyter_server`). Named here rather than left to the
|
|
447
|
+
# image, since an artifact built before code-sandboxes 1.9.36 carries
|
|
448
|
+
# its base's `start-jupyter.sh` as its CMD, which Modal runs under the
|
|
449
|
+
# entrypoint; that server exits within a minute and ends the sandbox
|
|
450
|
+
# (found live on 2026-09-18: a smoke test's restarted sandbox died
|
|
451
|
+
# between checks 8 and 9).
|
|
452
|
+
command = ("sleep", "infinity") if self._image_id else ()
|
|
453
|
+
self._sandbox = modal.Sandbox.create(*command, **create_kwargs)
|
|
445
454
|
self._volume_mounts.created()
|
|
446
455
|
self._start_driver()
|
|
447
456
|
|
|
@@ -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:
|
|
@@ -726,6 +726,27 @@ class TestBuildingAndPushing:
|
|
|
726
726
|
a_builder(run=buildctl, tlscert="/certs/client/tls.crt").build(a_request())
|
|
727
727
|
assert not any(arg.startswith("--tls") for arg in buildctl.argv)
|
|
728
728
|
|
|
729
|
+
def test_the_steps_go_through_the_pools_proxy(self, monkeypatch) -> None:
|
|
730
|
+
"""The build pool lets a step reach the network only through its
|
|
731
|
+
proxy (E1-06), so every step is handed it, in both spellings."""
|
|
732
|
+
monkeypatch.setenv("DATALAYER_BUILDKIT_PROXY", "http://127.0.0.1:3128")
|
|
733
|
+
buildctl = Buildctl()
|
|
734
|
+
a_builder(run=buildctl).build(a_request())
|
|
735
|
+
for name in ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"):
|
|
736
|
+
assert f"build-arg:{name}=http://127.0.0.1:3128" in buildctl.argv
|
|
737
|
+
assert "build-arg:NO_PROXY=127.0.0.1,localhost" in buildctl.argv
|
|
738
|
+
|
|
739
|
+
def test_no_proxy_when_none_is_set(self, monkeypatch) -> None:
|
|
740
|
+
"""A `buildkitd` whose steps reach the network directly needs none."""
|
|
741
|
+
monkeypatch.delenv("DATALAYER_BUILDKIT_PROXY", raising=False)
|
|
742
|
+
buildctl = Buildctl()
|
|
743
|
+
a_builder(run=buildctl).build(a_request())
|
|
744
|
+
assert not any("proxy" in arg.lower() for arg in buildctl.argv)
|
|
745
|
+
|
|
746
|
+
def test_a_proxy_that_is_not_http_host_port_is_refused(self) -> None:
|
|
747
|
+
with pytest.raises(ValueError, match="DATALAYER_BUILDKIT_PROXY"):
|
|
748
|
+
a_builder(proxy="socks5://127.0.0.1:1080")
|
|
749
|
+
|
|
729
750
|
def test_the_docker_config_does_not_outlive_the_build(self) -> None:
|
|
730
751
|
"""A registry password base64'd into a file the worker keeps forever
|
|
731
752
|
is a credential leak on disk, whichever way the build ends (found on
|
|
@@ -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
|