code-sandboxes 1.4.6__tar.gz → 1.7.0__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.4.6 → code_sandboxes-1.7.0}/CHANGELOG.md +18 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/Makefile +18 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/PKG-INFO +1 -1
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/base.py +8 -1
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/datalayer_sandbox.py +9 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/__init__.py +119 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/__main__.py +31 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/adapters/__init__.py +11 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/bases.py +194 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/builders.py +272 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/canonical.py +121 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/conformance.py +655 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +27 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/contract.py +559 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/doctor/__init__.py +13 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/doctor/build.py +47 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/doctor/datalayer_sandbox.py +441 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/doctor/report.schema.json +112 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/errors.py +316 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/files.py +73 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/lifecycle.py +237 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/redact.py +424 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/resolve.py +887 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/schema.py +68 -0
- code_sandboxes-1.7.0/code_sandboxes/environments/spec.py +598 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/jupyter_server_sandbox.py +11 -5
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/manage.py +43 -6
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/models.py +8 -1
- code_sandboxes-1.7.0/docs/docs/environments/_category_.yml +2 -0
- code_sandboxes-1.7.0/docs/docs/environments/contract.mdx +57 -0
- code_sandboxes-1.7.0/docs/docs/environments/index.mdx +167 -0
- code_sandboxes-1.7.0/docs/docs/environments/specification.mdx +53 -0
- code_sandboxes-1.7.0/docs/docs/environments/spike-2026-09.mdx +114 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/pyproject.toml +9 -0
- code_sandboxes-1.7.0/schemas/environment-v1alpha1.json +497 -0
- code_sandboxes-1.7.0/tests/test_environment_bases.py +100 -0
- code_sandboxes-1.7.0/tests/test_environment_builders.py +281 -0
- code_sandboxes-1.7.0/tests/test_environment_canonical.py +121 -0
- code_sandboxes-1.7.0/tests/test_environment_conformance.py +357 -0
- code_sandboxes-1.7.0/tests/test_environment_constraints.py +64 -0
- code_sandboxes-1.7.0/tests/test_environment_contract.py +159 -0
- code_sandboxes-1.7.0/tests/test_environment_doctor.py +259 -0
- code_sandboxes-1.7.0/tests/test_environment_errors.py +184 -0
- code_sandboxes-1.7.0/tests/test_environment_lifecycle.py +170 -0
- code_sandboxes-1.7.0/tests/test_environment_redact.py +269 -0
- code_sandboxes-1.7.0/tests/test_environment_resolve.py +495 -0
- code_sandboxes-1.7.0/tests/test_environment_spec.py +345 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_jupyter_server.py +24 -0
- code_sandboxes-1.7.0/tests/test_manage_datalayer.py +102 -0
- code_sandboxes-1.7.0/tests/test_the_kill_relaunch_drill.py +131 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.github/workflows/build.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.github/workflows/py-tests.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.gitignore +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.licenserc.yaml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.pre-commit-config.yaml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/.vscode/settings.json +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/Dockerfile +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/LICENSE +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/README.md +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/client.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/commands.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/daytona_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/docker_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/e2b_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/google_colab_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/jupyter_ingress.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/kaggle_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/modal_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docker/Dockerfile +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/.gitignore +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/LICENSE +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/Makefile +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/README.md +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/babel.config.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/package.json +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/sidebars.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/README.md +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/__init__.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/conftest.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_bridge_mount.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_builds.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_client.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_commands_envelope.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_console.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_contents_live_matrix.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_datalayer_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_daytona.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_e2b.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_eval.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_factory.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_integration.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_manage.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_modal_google_colab_sandbox.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_modal_session.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_models.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_providers.py +0 -0
- {code_sandboxes-1.4.6 → code_sandboxes-1.7.0}/tests/test_sandboxes.py +0 -0
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 1.7.0
|
|
12
|
+
|
|
13
|
+
- **A version is resolved into one lock** (`code_sandboxes.environments.resolve`,
|
|
14
|
+
PLAN_ENV.md E1-04, D-9). Datalayer's protected constraints are merged over the
|
|
15
|
+
user's requirements — a requirement that agrees with a pin is dropped for it,
|
|
16
|
+
one that contradicts it is `DL_ENV_PROTECTED_PACKAGE` with the supported range
|
|
17
|
+
— the base is resolved to a digest per requested variant, and uv's refusals
|
|
18
|
+
are read into the error taxonomy: a conflict with the pair that cannot hold, a
|
|
19
|
+
package no index has, a protected pin, and `DL_ENV_PROVIDER_ERROR`, which is
|
|
20
|
+
retryable, for a failure that is not about the version.
|
|
21
|
+
|
|
22
|
+
The lock is uv's hashed output with the apt pins and the protected pins above
|
|
23
|
+
it as comments: one document that says everything a build installs, and still
|
|
24
|
+
a requirements file. `BuildkitResolveRunner` is D-9's solve, `FROM` the
|
|
25
|
+
resolved base digest; `LocalResolveRunner` runs `uv` where it is called, for
|
|
26
|
+
`plane local`, and refuses to pin apt rather than lock another
|
|
27
|
+
distribution's versions.
|
|
28
|
+
|
|
11
29
|
- **Every variant has a real interrupt now, and can be reached to give it.**
|
|
12
30
|
`Sandbox.interrupt` is two gates — `_executing_event` must be set, then
|
|
13
31
|
`_do_interrupt` must answer — and seven variants failed one of them, each in
|
|
@@ -32,10 +32,28 @@ build:
|
|
|
32
32
|
test:
|
|
33
33
|
pytest -q
|
|
34
34
|
|
|
35
|
+
## Build the datalayer-sandbox doctor as a single-file zipapp.
|
|
36
|
+
doctor-zipapp:
|
|
37
|
+
python -m code_sandboxes.environments.doctor.build dist/datalayer-sandbox
|
|
38
|
+
|
|
39
|
+
## Regenerate the Environment JSON Schema and the sandbox contract page.
|
|
40
|
+
environments-generate:
|
|
41
|
+
python -m code_sandboxes.environments schema --write
|
|
42
|
+
python -m code_sandboxes.environments contract --markdown docs/docs/environments/contract.mdx
|
|
43
|
+
|
|
44
|
+
## Fail when the Environment JSON Schema or the sandbox contract page has drifted.
|
|
45
|
+
environments-check:
|
|
46
|
+
python -m code_sandboxes.environments schema --check
|
|
47
|
+
python -m code_sandboxes.environments contract --check docs/docs/environments/contract.mdx
|
|
48
|
+
|
|
35
49
|
## The Contents provider matrix against the real providers (skips those without credentials).
|
|
36
50
|
live-matrix:
|
|
37
51
|
CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_contents_live_matrix.py
|
|
38
52
|
|
|
53
|
+
## Lose a sandbox on each live provider and check the harness says so.
|
|
54
|
+
kill-relaunch:
|
|
55
|
+
CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_the_kill_relaunch_drill.py
|
|
56
|
+
|
|
39
57
|
clean: ## clean
|
|
40
58
|
git clean -fdx
|
|
41
59
|
|
|
@@ -499,6 +499,7 @@ class Sandbox(ABC):
|
|
|
499
499
|
timeout: float | None = None,
|
|
500
500
|
name: str | None = None,
|
|
501
501
|
environment: str | None = None,
|
|
502
|
+
environment_version: str | int | None = None,
|
|
502
503
|
gpu: str | None = None,
|
|
503
504
|
cpu: float | None = None,
|
|
504
505
|
memory: int | None = None,
|
|
@@ -525,7 +526,12 @@ class Sandbox(ABC):
|
|
|
525
526
|
config: Optional full configuration object (overrides individual params).
|
|
526
527
|
timeout: Default timeout for code execution in seconds.
|
|
527
528
|
name: Optional name for the sandbox.
|
|
528
|
-
environment: Runtime environment (e.g., "ai-agents-env")
|
|
529
|
+
environment: Runtime environment (e.g., "ai-agents-env"), or a user
|
|
530
|
+
environment as "<account-handle>/<name>" or by its uid.
|
|
531
|
+
environment_version: Which version of a user environment to launch:
|
|
532
|
+
its number, or a version uid. Without one the promoted version
|
|
533
|
+
launches, which is what a platform environment always does.
|
|
534
|
+
Only the datalayer variant has versions (PLAN_ENV.md, E1-19).
|
|
529
535
|
gpu: GPU type to use (e.g., "T4", "A100", "H100"). Only for datalayer.
|
|
530
536
|
cpu: CPU cores to allocate.
|
|
531
537
|
memory: Memory limit in MB.
|
|
@@ -559,6 +565,7 @@ class Sandbox(ABC):
|
|
|
559
565
|
config = SandboxConfig(
|
|
560
566
|
timeout=timeout or 30.0,
|
|
561
567
|
environment=environment or DEFAULT_ENVIRONMENT,
|
|
568
|
+
environment_version=environment_version,
|
|
562
569
|
memory_limit=memory * 1024 * 1024 if memory else None,
|
|
563
570
|
cpu_limit=cpu,
|
|
564
571
|
env_vars=env or {},
|
|
@@ -475,11 +475,19 @@ class DatalayerSandbox(Sandbox):
|
|
|
475
475
|
# Build sandbox name
|
|
476
476
|
sandbox_name = self.config.name or f"sandbox-{self._sandbox_id[:8]}"
|
|
477
477
|
|
|
478
|
+
# Which VERSION of that environment, passed through as the
|
|
479
|
+
# environment is (PLAN_ENV.md, E1-19). `None` means the promoted
|
|
480
|
+
# version, which is what every platform environment launches — and
|
|
481
|
+
# what every sandbox launched before there were versions asked for.
|
|
482
|
+
# The GPU rewrite above is left exactly as it is until E4-11 (D-20).
|
|
483
|
+
environment_version = self.config.environment_version
|
|
484
|
+
|
|
478
485
|
# Create the runtime (optionally from snapshot)
|
|
479
486
|
if self._snapshot_name:
|
|
480
487
|
self._runtime = self._client.create_runtime(
|
|
481
488
|
name=sandbox_name,
|
|
482
489
|
environment=environment,
|
|
490
|
+
version=environment_version,
|
|
483
491
|
time_reservation=time_reservation,
|
|
484
492
|
snapshot_name=self._snapshot_name,
|
|
485
493
|
)
|
|
@@ -487,6 +495,7 @@ class DatalayerSandbox(Sandbox):
|
|
|
487
495
|
self._runtime = self._client.create_runtime(
|
|
488
496
|
name=sandbox_name,
|
|
489
497
|
environment=environment,
|
|
498
|
+
version=environment_version,
|
|
490
499
|
time_reservation=time_reservation,
|
|
491
500
|
)
|
|
492
501
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
2
|
+
#
|
|
3
|
+
# BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
"""Environments: define once, resolve once, build an immutable artifact per variant.
|
|
6
|
+
|
|
7
|
+
An **Environment** is the stable object a person names. An **Environment
|
|
8
|
+
Version** is immutable once it leaves draft: its spec, the lock resolved from
|
|
9
|
+
it, and its build outcome. An **Environment Artifact** is the build output of
|
|
10
|
+
one version on one variant — an OCI digest for Datalayer, a build id for E2B,
|
|
11
|
+
a snapshot id for Daytona, an image id for Modal.
|
|
12
|
+
|
|
13
|
+
This package holds what every party to that life shares, and nothing
|
|
14
|
+
provider-specific:
|
|
15
|
+
|
|
16
|
+
- :mod:`.spec` — the canonical specification and its rules;
|
|
17
|
+
- :mod:`.resolve` — the lock a version is resolved into, once (D-9);
|
|
18
|
+
- :mod:`.contract` — ``sandbox-contract/v1`` and the Dockerfile validator;
|
|
19
|
+
- :mod:`.lifecycle` — the version states and their legal moves;
|
|
20
|
+
- :mod:`.errors` — the error taxonomy every provider error maps into;
|
|
21
|
+
- :mod:`.builders` — the builder interface and its neutral types;
|
|
22
|
+
- :mod:`.conformance` — the checks an artifact passes before it is ready;
|
|
23
|
+
- :mod:`.redact` — a build's log with its secrets taken out before it is stored;
|
|
24
|
+
- :mod:`.doctor` — the in-sandbox contract checker.
|
|
25
|
+
|
|
26
|
+
The builders themselves are in :mod:`.adapters`, loaded by variant.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from .bases import APPROVED_BASES, ApprovedBase
|
|
32
|
+
from .builders import (
|
|
33
|
+
ArtifactReference,
|
|
34
|
+
BuildRequest,
|
|
35
|
+
CapabilityReport,
|
|
36
|
+
CapabilitySet,
|
|
37
|
+
CheckResult,
|
|
38
|
+
EnvironmentBuilder,
|
|
39
|
+
ValidationResult,
|
|
40
|
+
get_builder,
|
|
41
|
+
)
|
|
42
|
+
from .canonical import canonical_digest, canonical_json
|
|
43
|
+
from .contract import (
|
|
44
|
+
CONTRACT_V1,
|
|
45
|
+
SANDBOX_CONTRACT_V1,
|
|
46
|
+
SUPPORTED_CONTRACTS,
|
|
47
|
+
SandboxContract,
|
|
48
|
+
check_dockerfile,
|
|
49
|
+
validate_dockerfile,
|
|
50
|
+
)
|
|
51
|
+
from .errors import ERROR_CODES, EnvironmentsError, ErrorCode, map_provider_error
|
|
52
|
+
from .lifecycle import VersionState, build_outcome, can_transition, transition
|
|
53
|
+
from .resolve import (
|
|
54
|
+
LOCK_FORMAT,
|
|
55
|
+
BuildkitResolveRunner,
|
|
56
|
+
LocalResolveRunner,
|
|
57
|
+
ResolveRunner,
|
|
58
|
+
locked_versions,
|
|
59
|
+
merge_requirements,
|
|
60
|
+
parse_resolver_failure,
|
|
61
|
+
protected_pins,
|
|
62
|
+
resolve_environment,
|
|
63
|
+
)
|
|
64
|
+
from .spec import (
|
|
65
|
+
API_VERSION,
|
|
66
|
+
VARIANTS,
|
|
67
|
+
Environment,
|
|
68
|
+
EnvironmentSpec,
|
|
69
|
+
parse_environment,
|
|
70
|
+
spec_digest,
|
|
71
|
+
spec_findings,
|
|
72
|
+
validate_environment,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"API_VERSION",
|
|
77
|
+
"APPROVED_BASES",
|
|
78
|
+
"CONTRACT_V1",
|
|
79
|
+
"ERROR_CODES",
|
|
80
|
+
"LOCK_FORMAT",
|
|
81
|
+
"SANDBOX_CONTRACT_V1",
|
|
82
|
+
"SUPPORTED_CONTRACTS",
|
|
83
|
+
"VARIANTS",
|
|
84
|
+
"ApprovedBase",
|
|
85
|
+
"ArtifactReference",
|
|
86
|
+
"BuildkitResolveRunner",
|
|
87
|
+
"BuildRequest",
|
|
88
|
+
"CapabilityReport",
|
|
89
|
+
"CapabilitySet",
|
|
90
|
+
"CheckResult",
|
|
91
|
+
"Environment",
|
|
92
|
+
"EnvironmentBuilder",
|
|
93
|
+
"EnvironmentSpec",
|
|
94
|
+
"EnvironmentsError",
|
|
95
|
+
"ErrorCode",
|
|
96
|
+
"LocalResolveRunner",
|
|
97
|
+
"ResolveRunner",
|
|
98
|
+
"SandboxContract",
|
|
99
|
+
"ValidationResult",
|
|
100
|
+
"VersionState",
|
|
101
|
+
"build_outcome",
|
|
102
|
+
"can_transition",
|
|
103
|
+
"canonical_digest",
|
|
104
|
+
"canonical_json",
|
|
105
|
+
"check_dockerfile",
|
|
106
|
+
"get_builder",
|
|
107
|
+
"locked_versions",
|
|
108
|
+
"merge_requirements",
|
|
109
|
+
"map_provider_error",
|
|
110
|
+
"parse_environment",
|
|
111
|
+
"parse_resolver_failure",
|
|
112
|
+
"protected_pins",
|
|
113
|
+
"resolve_environment",
|
|
114
|
+
"spec_digest",
|
|
115
|
+
"spec_findings",
|
|
116
|
+
"transition",
|
|
117
|
+
"validate_dockerfile",
|
|
118
|
+
"validate_environment",
|
|
119
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
2
|
+
#
|
|
3
|
+
# BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
"""Generate what is generated from the Environment models.
|
|
6
|
+
|
|
7
|
+
python -m code_sandboxes.environments schema --write [PATH]
|
|
8
|
+
python -m code_sandboxes.environments schema --check [PATH]
|
|
9
|
+
python -m code_sandboxes.environments contract --markdown docs/docs/environments/contract.mdx
|
|
10
|
+
python -m code_sandboxes.environments contract --check docs/docs/environments/contract.mdx
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from . import contract, schema
|
|
18
|
+
|
|
19
|
+
_COMMANDS = {"schema": schema.main, "contract": contract.main}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(argv: list[str] | None = None) -> int:
|
|
23
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
24
|
+
if not arguments or arguments[0] not in _COMMANDS:
|
|
25
|
+
print(__doc__.strip(), file=sys.stderr) # noqa: T201 - a command's own output
|
|
26
|
+
return 2
|
|
27
|
+
return _COMMANDS[arguments[0]](arguments[1:])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
2
|
+
#
|
|
3
|
+
# BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
"""The provider-specific Environment builders, one module per variant.
|
|
6
|
+
|
|
7
|
+
Not neutral: each module speaks its provider's SDK. They are loaded only by
|
|
8
|
+
:func:`code_sandboxes.environments.builders.get_builder`, and only where the
|
|
9
|
+
build runs — the durable worker — never by a service that merely describes
|
|
10
|
+
or launches an Environment.
|
|
11
|
+
"""
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
2
|
+
#
|
|
3
|
+
# BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
"""The approved bases an Environment may start from.
|
|
6
|
+
|
|
7
|
+
An Environment names a base by reference and channel — ``datalayer/python-cpu``
|
|
8
|
+
at ``2026.09`` — and resolution turns the pair into a digest per variant. Only
|
|
9
|
+
the bases below are approved while Environments build from packages; importing
|
|
10
|
+
arbitrary images comes later, with the contract injected and verified.
|
|
11
|
+
|
|
12
|
+
The Python versions are what each base's interpreter is: 3.13, measured by
|
|
13
|
+
``datalayer-sandbox doctor`` inside ``jupyter-python:0.1.1`` (3.13.14 on
|
|
14
|
+
2026-09-11) and again inside ``jupyter-python:0.2.0``, the image the ``2026.09``
|
|
15
|
+
channel is built on (PLAN_ENV.md, E1-05).
|
|
16
|
+
|
|
17
|
+
A channel's digests are written here only once its release has pushed the
|
|
18
|
+
channel to ECR and printed them. Until then the channel is known and has none,
|
|
19
|
+
and resolving it raises :class:`BaseChannelUnpublishedError` rather than answer
|
|
20
|
+
with a digest nobody pushed.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
28
|
+
|
|
29
|
+
from ..models import normalize_variant
|
|
30
|
+
from .errors import ARTIFACT_MISSING, SPEC_INVALID, EnvironmentsError
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"APPROVED_BASES",
|
|
34
|
+
"ECR_BASE_PREFIX",
|
|
35
|
+
"ApprovedBase",
|
|
36
|
+
"BaseChannelUnpublishedError",
|
|
37
|
+
"approved_base",
|
|
38
|
+
"approved_repositories",
|
|
39
|
+
"is_approved_repository",
|
|
40
|
+
"resolve_base",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
#: Where the bases are published in ECR (PLAN_ENV.md, D-18).
|
|
44
|
+
ECR_BASE_PREFIX = "environments/base/"
|
|
45
|
+
|
|
46
|
+
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ApprovedBase(BaseModel):
|
|
50
|
+
"""One approved base."""
|
|
51
|
+
|
|
52
|
+
model_config = ConfigDict(frozen=True)
|
|
53
|
+
|
|
54
|
+
ref: str
|
|
55
|
+
#: The ``major.minor`` Python versions an Environment on this base may ask for.
|
|
56
|
+
python_versions: tuple[str, ...]
|
|
57
|
+
#: Whether the base carries CUDA, which a GPU size class requires.
|
|
58
|
+
accelerator: bool = False
|
|
59
|
+
#: Channel, then variant, to the digest resolution pins. A channel with no
|
|
60
|
+
#: variant is approved and not yet published.
|
|
61
|
+
channels: dict[str, dict[str, str]] = Field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
@field_validator("channels")
|
|
64
|
+
@classmethod
|
|
65
|
+
def _digests_only(cls, channels: dict[str, dict[str, str]]) -> dict[str, dict[str, str]]:
|
|
66
|
+
for channel, digests in channels.items():
|
|
67
|
+
for variant, digest in digests.items():
|
|
68
|
+
if not _DIGEST.fullmatch(digest):
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"channel {channel!r} pins {variant!r} to {digest!r}, "
|
|
71
|
+
"which is not a sha256 digest"
|
|
72
|
+
)
|
|
73
|
+
return {
|
|
74
|
+
channel: {normalize_variant(variant): digest for variant, digest in digests.items()}
|
|
75
|
+
for channel, digests in channels.items()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def name(self) -> str:
|
|
80
|
+
"""The base's name without its namespace: ``python-cpu``."""
|
|
81
|
+
return self.ref.rsplit("/", 1)[-1]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def repository(self) -> str:
|
|
85
|
+
"""The ECR repository the base is published to: ``environments/base/python-cpu``."""
|
|
86
|
+
return ECR_BASE_PREFIX + self.name
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
APPROVED_BASES: dict[str, ApprovedBase] = {
|
|
90
|
+
base.ref: base
|
|
91
|
+
for base in (
|
|
92
|
+
# E1-05: jupyter-python:0.2.0 plus the contract layer. No digest until released.
|
|
93
|
+
ApprovedBase(
|
|
94
|
+
ref="datalayer/python-cpu", python_versions=("3.13",), channels={"2026.09": {}}
|
|
95
|
+
),
|
|
96
|
+
# E2-17: jupyter-python-cuda plus the same layer.
|
|
97
|
+
ApprovedBase(
|
|
98
|
+
ref="datalayer/python-cuda",
|
|
99
|
+
python_versions=("3.13",),
|
|
100
|
+
accelerator=True,
|
|
101
|
+
channels={"2026.09": {}},
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class BaseChannelUnpublishedError(EnvironmentsError):
|
|
108
|
+
"""An approved channel that has no digest yet for the variant asked.
|
|
109
|
+
|
|
110
|
+
Raised under ``DL_ENV_ARTIFACT_MISSING`` with the reason
|
|
111
|
+
``base_channel_unpublished``, the way D-8 gives its own reason under that
|
|
112
|
+
code: the base the variant builds from is not in the registry. A retry
|
|
113
|
+
succeeds once the channel's release has pushed it and its digest is
|
|
114
|
+
written into :data:`APPROVED_BASES`.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, base: ApprovedBase, channel: str, variant: str) -> None:
|
|
118
|
+
super().__init__(
|
|
119
|
+
ARTIFACT_MISSING,
|
|
120
|
+
f"`{base.ref}:{channel}` has no published digest for the {variant} variant; "
|
|
121
|
+
f"it resolves once the channel is pushed to `{base.repository}`",
|
|
122
|
+
detail={
|
|
123
|
+
"reason": "base_channel_unpublished",
|
|
124
|
+
"base": base.ref,
|
|
125
|
+
"channel": channel,
|
|
126
|
+
"variant": variant,
|
|
127
|
+
"repository": base.repository,
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def approved_base(ref: str, bases: dict[str, ApprovedBase] = APPROVED_BASES) -> ApprovedBase | None:
|
|
133
|
+
return bases.get(ref)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def resolve_base(
|
|
137
|
+
ref: str, channel: str, variant: str, bases: dict[str, ApprovedBase] = APPROVED_BASES
|
|
138
|
+
) -> str:
|
|
139
|
+
"""The digest ``ref`` at ``channel`` pins for ``variant``.
|
|
140
|
+
|
|
141
|
+
A base that is not approved, or a channel it does not have, is the spec's
|
|
142
|
+
fault (``DL_ENV_SPEC_INVALID``). A channel it has, with no digest for the
|
|
143
|
+
variant, raises :class:`BaseChannelUnpublishedError`.
|
|
144
|
+
"""
|
|
145
|
+
base = bases.get(ref)
|
|
146
|
+
if base is None:
|
|
147
|
+
raise EnvironmentsError(
|
|
148
|
+
SPEC_INVALID,
|
|
149
|
+
f"`{ref}` is not an approved base; approved: " + ", ".join(bases),
|
|
150
|
+
detail={"field": "spec.base.ref", "base": ref, "approved": list(bases)},
|
|
151
|
+
)
|
|
152
|
+
digests = base.channels.get(channel)
|
|
153
|
+
if digests is None:
|
|
154
|
+
raise EnvironmentsError(
|
|
155
|
+
SPEC_INVALID,
|
|
156
|
+
f"`{ref}` has no channel `{channel}`; channels: "
|
|
157
|
+
+ (", ".join(base.channels) or "none"),
|
|
158
|
+
detail={
|
|
159
|
+
"field": "spec.base.channel",
|
|
160
|
+
"base": ref,
|
|
161
|
+
"channel": channel,
|
|
162
|
+
"channels": list(base.channels),
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
normalized = normalize_variant(variant)
|
|
166
|
+
digest = digests.get(normalized)
|
|
167
|
+
if digest is None:
|
|
168
|
+
raise BaseChannelUnpublishedError(base, channel, normalized)
|
|
169
|
+
return digest
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def approved_repositories(bases: dict[str, ApprovedBase] = APPROVED_BASES) -> tuple[str, ...]:
|
|
173
|
+
"""Every repository name an approved base is published under."""
|
|
174
|
+
names: list[str] = []
|
|
175
|
+
for base in bases.values():
|
|
176
|
+
names.extend((base.ref, base.repository))
|
|
177
|
+
return tuple(names)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def is_approved_repository(image: str, bases: dict[str, ApprovedBase] = APPROVED_BASES) -> bool:
|
|
181
|
+
"""Whether an image reference names an approved base, in any registry.
|
|
182
|
+
|
|
183
|
+
``ghcr.io/datalayer/python-cpu@sha256:…`` and
|
|
184
|
+
``<account>.dkr.ecr.<region>.amazonaws.com/environments/base/python-cpu:2026.09``
|
|
185
|
+
both name ``datalayer/python-cpu``; ``python:3.12`` names nothing approved.
|
|
186
|
+
"""
|
|
187
|
+
repository = image.split("@", 1)[0]
|
|
188
|
+
last = repository.rsplit("/", 1)[-1]
|
|
189
|
+
if ":" in last:
|
|
190
|
+
repository = repository[: len(repository) - len(last)] + last.split(":", 1)[0]
|
|
191
|
+
return any(
|
|
192
|
+
repository == name or repository.endswith("/" + name)
|
|
193
|
+
for name in approved_repositories(bases)
|
|
194
|
+
)
|