code-sandboxes 1.9.19__tar.gz → 1.9.20__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.
Files changed (263) hide show
  1. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/daytona.py +20 -2
  4. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/e2b.py +18 -1
  5. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/managed.py +37 -1
  6. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/modal.py +1 -1
  7. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/spec.py +49 -13
  8. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/schemas/environment-v1alpha1.json +24 -0
  9. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_daytona_builder.py +18 -2
  10. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_managed_builders.py +54 -15
  11. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_spec.py +35 -3
  12. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/build.yml +0 -0
  13. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/environments-live.yml +0 -0
  14. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/py-code-style.yml +0 -0
  15. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/py-tests.yml +0 -0
  16. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/py-typing.yml +0 -0
  17. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.github/workflows/reusable-python.yml +0 -0
  18. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.gitignore +0 -0
  19. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.licenserc.yaml +0 -0
  20. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.pre-commit-config.yaml +0 -0
  21. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/.vscode/settings.json +0 -0
  22. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/CHANGELOG.md +0 -0
  23. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/Dockerfile +0 -0
  24. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/LICENSE +0 -0
  25. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/Makefile +0 -0
  26. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/README.md +0 -0
  27. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/__init__.py +0 -0
  28. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/base.py +0 -0
  29. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/bridge_mount.py +0 -0
  30. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/builds.py +0 -0
  31. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/cli.py +0 -0
  32. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/client.py +0 -0
  33. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/cloudflare_sandbox.py +0 -0
  34. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/commands.py +0 -0
  35. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/console.py +0 -0
  36. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/contents.py +0 -0
  37. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/coreweave_sandbox.py +0 -0
  38. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/datalayer_sandbox.py +0 -0
  39. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/daytona_sandbox.py +0 -0
  40. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/docker_sandbox.py +0 -0
  41. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/e2b_sandbox.py +0 -0
  42. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/__init__.py +0 -0
  43. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/__main__.py +0 -0
  44. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/accounts.py +0 -0
  45. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/__init__.py +0 -0
  46. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/adapters/datalayer.py +0 -0
  47. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/attest.py +0 -0
  48. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/bases.py +0 -0
  49. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/breaker.py +0 -0
  50. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/build_secrets.py +0 -0
  51. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/builders.py +0 -0
  52. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/canonical.py +0 -0
  53. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/conformance.py +0 -0
  54. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  55. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  56. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  57. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/contract.py +0 -0
  58. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/doctor/__init__.py +0 -0
  59. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/doctor/build.py +0 -0
  60. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  61. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  62. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/errors.py +0 -0
  63. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/files.py +0 -0
  64. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/image_import.py +0 -0
  65. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/lifecycle.py +0 -0
  66. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/policy.py +0 -0
  67. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/redact.py +0 -0
  68. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/resolve.py +0 -0
  69. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/resolve_conda.py +0 -0
  70. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/environments/schema.py +0 -0
  71. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/eval_sandbox.py +0 -0
  72. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/exceptions.py +0 -0
  73. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/filesystem.py +0 -0
  74. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/google_colab.py +0 -0
  75. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/google_colab_sandbox.py +0 -0
  76. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/interfaces.py +0 -0
  77. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/jupyter_ingress.py +0 -0
  78. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  79. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/kaggle.py +0 -0
  80. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/kaggle_execute.py +0 -0
  81. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/kaggle_live.py +0 -0
  82. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/kaggle_sandbox.py +0 -0
  83. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/lifecycle.py +0 -0
  84. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/manage.py +0 -0
  85. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/modal_sandbox.py +0 -0
  86. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/models.py +0 -0
  87. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/monty_sandbox.py +0 -0
  88. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/provider_ingress.py +0 -0
  89. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/code_sandboxes/providers.py +0 -0
  90. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docker/Dockerfile +0 -0
  91. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/.gitignore +0 -0
  92. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/LICENSE +0 -0
  93. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/Makefile +0 -0
  94. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/README.md +0 -0
  95. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/babel.config.js +0 -0
  96. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/api-reference/_category_.yml +0 -0
  97. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/api-reference/index.mdx +0 -0
  98. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/cli/_category_.yml +0 -0
  99. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/cli/index.mdx +0 -0
  100. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/contribute/_category_.yml +0 -0
  101. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/contribute/index.mdx +0 -0
  102. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/environments/_category_.yml +0 -0
  103. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/environments/contract.mdx +0 -0
  104. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/environments/index.mdx +0 -0
  105. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/environments/specification.mdx +0 -0
  106. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/environments/spike-2026-09.mdx +0 -0
  107. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/examples/_category_.yml +0 -0
  108. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/examples/index.mdx +0 -0
  109. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/guide/_category_.yml +0 -0
  110. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/guide/index.mdx +0 -0
  111. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/index.mdx +0 -0
  112. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/install/_category_.yml +0 -0
  113. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/install/index.mdx +0 -0
  114. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/provider-ingress.mdx +0 -0
  115. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/_category_.yml +0 -0
  116. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/cloudflare.mdx +0 -0
  117. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/coreweave.mdx +0 -0
  118. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/datalayer.mdx +0 -0
  119. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/daytona.mdx +0 -0
  120. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/docker.mdx +0 -0
  121. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/e2b.mdx +0 -0
  122. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/eval.mdx +0 -0
  123. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/google-colab.mdx +0 -0
  124. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/index.mdx +0 -0
  125. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/jupyter-server.mdx +0 -0
  126. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/kaggle.mdx +0 -0
  127. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/modal.mdx +0 -0
  128. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docs/providers/monty.mdx +0 -0
  129. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/docusaurus.config.js +0 -0
  130. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/package.json +0 -0
  131. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/sidebars.js +0 -0
  132. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow1.js +0 -0
  133. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow1.module.css +0 -0
  134. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow2.js +0 -0
  135. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow2.module.css +0 -0
  136. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow3.js +0 -0
  137. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/components/HomepageRow3.module.css +0 -0
  138. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/css/custom.css +0 -0
  139. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/pages/index.module.css +0 -0
  140. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/pages/markdown-page.md +0 -0
  141. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/theme/CustomDocItem.tsx +0 -0
  142. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/src/theme/IconsTitle.tsx +0 -0
  143. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/bricks.svg +0 -0
  144. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/building-construction.svg +0 -0
  145. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/cloud-sun.svg +0 -0
  146. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/cloud.svg +0 -0
  147. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/collaboration.svg +0 -0
  148. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/copy-left.svg +0 -0
  149. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/logo.png +0 -0
  150. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/logo.svg +0 -0
  151. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/personas/business.svg +0 -0
  152. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  153. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/personas/devops.svg +0 -0
  154. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/datalayer/project/project-line.svg +0 -0
  155. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/favicon.ico +0 -0
  156. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/jupyter.svg +0 -0
  157. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/open-source.svg +0 -0
  158. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/react-js.svg +0 -0
  159. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/react-js2.svg +0 -0
  160. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/ringed-planet.svg +0 -0
  161. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/robot.svg +0 -0
  162. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/rocket.svg +0 -0
  163. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/shield.svg +0 -0
  164. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/sparkles.svg +0 -0
  165. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/streamlit.gif +0 -0
  166. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/super-hero.svg +0 -0
  167. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/target.svg +0 -0
  168. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/docs/static/img/thumbs-up.svg +0 -0
  169. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/README.md +0 -0
  170. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/Makefile +0 -0
  171. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/cloudflare_sandbox_example.py +0 -0
  172. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/coreweave_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/datalayer_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/daytona_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/docker_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/e2b_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/eval_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/google_colab_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/kaggle_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/modal_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/exec/monty_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/Makefile +0 -0
  184. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/cloudflare_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/coreweave_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/datalayer_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/daytona_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/docker_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/e2b_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/eval_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/google_colab_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/kaggle_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/modal_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/examples/repl/monty_sandbox_example.py +0 -0
  196. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/pyproject.toml +0 -0
  197. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/__init__.py +0 -0
  198. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/conftest.py +0 -0
  199. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  200. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_bridge_mount.py +0 -0
  201. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_builds.py +0 -0
  202. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_cli_exec.py +0 -0
  203. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_cli_repl.py +0 -0
  204. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_client.py +0 -0
  205. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_client_contents.py +0 -0
  206. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_client_files.py +0 -0
  207. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_client_outcome_error_output.py +0 -0
  208. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_cloudflare.py +0 -0
  209. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_commands_envelope.py +0 -0
  210. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_console.py +0 -0
  211. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_contents_contract.py +0 -0
  212. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_contents_live_matrix.py +0 -0
  213. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_coreweave.py +0 -0
  214. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_datalayer_sandbox.py +0 -0
  215. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_daytona.py +0 -0
  216. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_e2b.py +0 -0
  217. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_accounts.py +0 -0
  218. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_attest.py +0 -0
  219. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_bases.py +0 -0
  220. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_breaker.py +0 -0
  221. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_build_secrets.py +0 -0
  222. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_builders.py +0 -0
  223. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_canonical.py +0 -0
  224. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_conformance.py +0 -0
  225. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_constraints.py +0 -0
  226. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_contract.py +0 -0
  227. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_cross_variant_report.py +0 -0
  228. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_datalayer_builder.py +0 -0
  229. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_doctor.py +0 -0
  230. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_e2b_builder.py +0 -0
  231. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_errors.py +0 -0
  232. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_image_import.py +0 -0
  233. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_lifecycle.py +0 -0
  234. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_live_matrix.py +0 -0
  235. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_modal_builder.py +0 -0
  236. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_redact.py +0 -0
  237. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_resolve.py +0 -0
  238. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_environment_resolve_conda.py +0 -0
  239. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_eval.py +0 -0
  240. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  241. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_factory.py +0 -0
  242. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_google_colab.py +0 -0
  243. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_integration.py +0 -0
  244. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_jupyter_ingress.py +0 -0
  245. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_jupyter_server.py +0 -0
  246. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_jupyter_server_streaming.py +0 -0
  247. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_kaggle.py +0 -0
  248. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_kaggle_execute.py +0 -0
  249. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_kaggle_live.py +0 -0
  250. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_kaggle_session.py +0 -0
  251. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_kernel_client_compatibility.py +0 -0
  252. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_lifecycle_vocabulary.py +0 -0
  253. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_manage.py +0 -0
  254. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_manage_datalayer.py +0 -0
  255. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_modal_google_colab_sandbox.py +0 -0
  256. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_modal_session.py +0 -0
  257. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_models.py +0 -0
  258. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_output_line_fidelity.py +0 -0
  259. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_provider_catalog_names.py +0 -0
  260. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_provider_ingress.py +0 -0
  261. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_providers.py +0 -0
  262. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_sandboxes.py +0 -0
  263. {code_sandboxes-1.9.19 → code_sandboxes-1.9.20}/tests/test_the_kill_relaunch_drill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.9.19
3
+ Version: 1.9.20
4
4
  Project-URL: Home, https://github.com/datalayer/code-sandboxes
5
5
  Author-email: Datalayer <info@datalayer.io>
6
6
  License: BSD 3-Clause License
@@ -3,4 +3,4 @@
3
3
 
4
4
  """Code Sandboxes."""
5
5
 
6
- __version__ = "1.9.19"
6
+ __version__ = "1.9.20"
@@ -179,7 +179,12 @@ class Builder(ManagedBuilder):
179
179
  title = "Daytona"
180
180
  #: A `packages` list and, for conda (E3-02), an `environment.yml`
181
181
  #: dependency file installed with `micromamba`.
182
- build_sources = ("packages", "dependencyFile")
182
+ build_sources = ("packages", "dependencyFile", "dockerfile")
183
+ #: None beyond the contract's own (E3-03): `Image.from_dockerfile` keeps
184
+ #: the Dockerfile text as it is and Daytona builds it on a real Docker
185
+ #: builder, so the grammar it accepts is Docker's. Checked in the SDK on
186
+ #: 2026-09-17.
187
+ forbidden_instructions = ()
183
188
  dependency_formats = ("conda",)
184
189
  #: Daytona runs GPUs, on its own hardware and the owner's account (E2-17).
185
190
  #: This builder does not build one yet: see `_own_findings`.
@@ -419,6 +424,11 @@ class Builder(ManagedBuilder):
419
424
  self._log(line)
420
425
 
421
426
  resources = self._resources(sdk, request.size_class)
427
+ # A snapshot is region-scoped, and the region that scopes it is
428
+ # Daytona's, not this platform's. `validate` already refuses
429
+ # more than one, so the first is the only one.
430
+ declared = list(request.environment.spec.compatibility.regions)
431
+ region = declared[0] if declared else None
422
432
  try:
423
433
  snapshot = client.snapshot.create(
424
434
  sdk.CreateSnapshotParams(
@@ -426,7 +436,15 @@ class Builder(ManagedBuilder):
426
436
  image=image,
427
437
  resources=resources,
428
438
  entrypoint=_CONTRACT_ENTRYPOINT,
429
- region_id=request.region,
439
+ # Only a region Daytona knows. `request.region` is
440
+ # *Datalayer's* — `r1` — and sending it answered
441
+ # "Region not found" on the first real Daytona
442
+ # build (2026-09-17). The owner names a Daytona
443
+ # region in `compatibility.regions`; with none,
444
+ # the field is left out and the account's own
445
+ # default decides, which is what every snapshot
446
+ # in the owner's account already has.
447
+ **({"region_id": region} if region else {}),
430
448
  ),
431
449
  on_logs=on_logs,
432
450
  timeout=self.max_build_seconds,
@@ -199,7 +199,24 @@ class Builder(ManagedBuilder):
199
199
  title = "E2B"
200
200
  #: A `packages` list and, for conda (E3-02), an `environment.yml`
201
201
  #: dependency file installed with `micromamba`.
202
- build_sources = ("packages", "dependencyFile")
202
+ build_sources = ("packages", "dependencyFile", "dockerfile")
203
+ #: What E2B's own Dockerfile parser does not handle (E3-03). Read from the
204
+ #: SDK on 2026-09-17: `e2b.template.dockerfile_parser` branches on FROM,
205
+ #: RUN, COPY, ADD, WORKDIR, USER, ENV, ARG, CMD and ENTRYPOINT, and for
206
+ #: anything else **prints `Unsupported instruction` and carries on**. So a
207
+ #: template built from a Dockerfile naming one of these comes back without
208
+ #: it and reports success — which is exactly what a capability report
209
+ #: exists to prevent.
210
+ forbidden_instructions = (
211
+ "VOLUME",
212
+ "EXPOSE",
213
+ "HEALTHCHECK",
214
+ "SHELL",
215
+ "ONBUILD",
216
+ "STOPSIGNAL",
217
+ "LABEL",
218
+ "MAINTAINER",
219
+ )
203
220
  dependency_formats = ("conda",)
204
221
  #: Firecracker microVMs: no GPU passthrough.
205
222
  gpu = False
@@ -73,7 +73,11 @@ class ManagedBuilder:
73
73
  #: built for a managed variant yet (E3-01), so it is not listed here.
74
74
  dependency_formats: tuple[str, ...] = ()
75
75
  package_managers: tuple[str, ...] = ("uv", "pip")
76
- #: Dockerfile instructions its own builder does not implement (§6).
76
+ #: Dockerfile instructions its own builder does not implement (§6, E3-03).
77
+ #: Checked against a `dockerfile`-sourced spec at `validate`, before
78
+ #: anything is queued — the point being to refuse an instruction a variant
79
+ #: would otherwise drop, rather than hand back an image quietly missing
80
+ #: whatever it asked for.
77
81
  forbidden_instructions: tuple[str, ...] = ()
78
82
  #: Where its artifacts live; empty when the variant is regionless.
79
83
  regions: tuple[str, ...] = ()
@@ -128,6 +132,8 @@ class ManagedBuilder:
128
132
  )
129
133
  elif spec.build.source == "dependencyFile":
130
134
  findings.extend(self._dependency_file_findings(spec))
135
+ elif spec.build.source == "dockerfile":
136
+ findings.extend(self._dockerfile_findings(spec))
131
137
  if spec.packages.python.manager not in self.package_managers:
132
138
  findings.append(
133
139
  CapabilityFinding(
@@ -269,6 +275,36 @@ class ManagedBuilder:
269
275
 
270
276
  # -- helpers for the subclasses -------------------------------------------
271
277
 
278
+ def _dockerfile_findings(self, spec: Any) -> list[CapabilityFinding]:
279
+ """Every instruction in the spec's Dockerfile this variant would not honour.
280
+
281
+ The contract's own refusals are `spec.py`'s to make and apply to every
282
+ variant alike; this is the narrower question of what *this* builder
283
+ does with a Dockerfile it accepts. Each is named with its line, since
284
+ a Dockerfile is somebody's file.
285
+ """
286
+ if not self.forbidden_instructions:
287
+ return []
288
+ dockerfile = getattr(spec.build, "dockerfile", None)
289
+ content = getattr(dockerfile, "content", "") or ""
290
+ if not content.strip():
291
+ return []
292
+ from ..contract import parse_dockerfile
293
+
294
+ refused = set(self.forbidden_instructions)
295
+ return [
296
+ CapabilityFinding(
297
+ code=CAPABILITY_UNSUPPORTED.code,
298
+ message=(
299
+ f"line {instruction.line}: {self.title} does not implement "
300
+ f"`{instruction.keyword}`"
301
+ ),
302
+ field="spec.build.dockerfile.content",
303
+ )
304
+ for instruction in parse_dockerfile(content)
305
+ if instruction.keyword in refused
306
+ ]
307
+
272
308
  @staticmethod
273
309
  def _spec_finding(message: str, field: str) -> CapabilityFinding:
274
310
  return CapabilityFinding(code=SPEC_INVALID.code, message=message, field=field)
@@ -286,7 +286,7 @@ class Builder(ManagedBuilder):
286
286
  title = "Modal"
287
287
  #: A `packages` list and, for conda (E3-02), an `environment.yml`
288
288
  #: dependency file installed with `micromamba_install`.
289
- build_sources = ("packages", "dependencyFile")
289
+ build_sources = ("packages", "dependencyFile", "dockerfile")
290
290
  dependency_formats = ("conda",)
291
291
  #: Modal runs GPUs, in the owner's workspace (E2-17). This builder does
292
292
  #: not build one yet: see `build`'s own guard.
@@ -55,10 +55,10 @@ __all__ = [
55
55
  "BUILD_SOURCES",
56
56
  "GPU_SIZE_CLASSES",
57
57
  "KIND",
58
+ "PUBLIC_PACKAGE_INDEX_HOSTS",
58
59
  "SIZE_CLASSES",
59
60
  "SUPPORTED_BUILD_SOURCES",
60
61
  "VARIANTS",
61
- "PUBLIC_PACKAGE_INDEX_HOSTS",
62
62
  "Accelerator",
63
63
  "ArtifactStatus",
64
64
  "Base",
@@ -174,14 +174,12 @@ class Platform(_Model):
174
174
  #: reached with a credential the public does not hold. Matched on host, so the
175
175
  #: trailing `/simple` or its absence never decides it. `pypi.org` is the index;
176
176
  #: `files.pythonhosted.org` is where its wheels are served from.
177
- PUBLIC_PACKAGE_INDEX_HOSTS = frozenset(
178
- {"pypi.org", "files.pythonhosted.org"}
179
- )
177
+ PUBLIC_PACKAGE_INDEX_HOSTS = frozenset({"pypi.org", "files.pythonhosted.org"})
180
178
 
181
179
 
182
180
  def _package_index_host(url: str) -> str:
183
181
  """The host an index URL names, lower-cased and without its port, or `""`."""
184
- from urllib.parse import urlsplit # noqa: PLC0415
182
+ from urllib.parse import urlsplit
185
183
 
186
184
  try:
187
185
  return (urlsplit(url).hostname or "").lower()
@@ -214,9 +212,7 @@ PUBLIC_CONDA_CHANNELS = frozenset(
214
212
  "msys2",
215
213
  }
216
214
  )
217
- PUBLIC_CONDA_CHANNEL_HOSTS = frozenset(
218
- {"conda.anaconda.org", "repo.anaconda.com", "anaconda.org"}
219
- )
215
+ PUBLIC_CONDA_CHANNEL_HOSTS = frozenset({"conda.anaconda.org", "repo.anaconda.com", "anaconda.org"})
220
216
 
221
217
 
222
218
  def channel_is_public(channel: str) -> bool:
@@ -344,6 +340,25 @@ class DependencyFileSpec(_Model):
344
340
  lock_content: str = ""
345
341
 
346
342
 
343
+ class DockerfileSpec(_Model):
344
+ """The Dockerfile a `dockerfile`-sourced version builds from (E3-03).
345
+
346
+ Inline, the way ``DependencyFileSpec`` carries a ``requirements.txt``: a
347
+ file the author brings lives in the spec, so ``check_dockerfile`` and
348
+ every variant's capability report can read it **before** anything is
349
+ queued — which is the whole point of refusing an instruction a variant
350
+ does not implement at ``validate`` rather than halfway through a build.
351
+
352
+ The build *context* — the extra files a ``COPY`` needs — is the separate
353
+ upload E3-03 describes, bounded and checked by ``validate_build_context``.
354
+ A Dockerfile with no context is the common case and needs no upload at
355
+ all.
356
+ """
357
+
358
+ #: The Dockerfile text.
359
+ content: str = ""
360
+
361
+
347
362
  class ImageSourceSpec(_Model):
348
363
  """An existing OCI image, imported as the build's base (E3-04).
349
364
 
@@ -370,6 +385,7 @@ class ImageSourceSpec(_Model):
370
385
  class BuildSpec(_Model):
371
386
  source: Literal["packages", "dependencyFile", "dockerfile", "image"] = "packages"
372
387
  dependency_file: DependencyFileSpec | None = None
388
+ dockerfile: DockerfileSpec | None = None
373
389
  image: ImageSourceSpec | None = None
374
390
 
375
391
 
@@ -486,6 +502,28 @@ def parse_requirements_txt(text: str) -> list[str]:
486
502
  return lines
487
503
 
488
504
 
505
+ def _dockerfile_findings(dockerfile: DockerfileSpec | None) -> list[SpecFinding]:
506
+ """What a `dockerfile`-sourced version must carry, and what the contract refuses.
507
+
508
+ The contract's own refusals are reported here, at `validate`, rather than
509
+ at build time: a `FROM` that is not an approved base, a privileged build,
510
+ the host network, a Docker socket mount, a host bind mount, and every
511
+ instruction §6 does not allow. Each is named with its line, because a
512
+ Dockerfile is somebody's file and "it was refused" is not a reason.
513
+ """
514
+ field = "spec.build.dockerfile"
515
+ if dockerfile is None:
516
+ return [SpecFinding(field, "is required when `spec.build.source` is `dockerfile`")]
517
+ if not dockerfile.content.strip():
518
+ return [SpecFinding(f"{field}.content", "is empty; it is the Dockerfile text")]
519
+ from .contract import validate_dockerfile
520
+
521
+ return [
522
+ SpecFinding(f"{field}.content", f"line {finding.line}: {finding.message}")
523
+ for finding in validate_dockerfile(dockerfile.content)
524
+ ]
525
+
526
+
489
527
  def _dependency_file_findings(dependency_file: DependencyFileSpec | None) -> list[SpecFinding]:
490
528
  field = "spec.build.dependencyFile"
491
529
  if dependency_file is None:
@@ -644,6 +682,8 @@ def spec_findings(
644
682
  )
645
683
  if spec.build.source == "dependencyFile":
646
684
  findings.extend(_dependency_file_findings(spec.build.dependency_file))
685
+ elif spec.build.source == "dockerfile":
686
+ findings.extend(_dockerfile_findings(spec.build.dockerfile))
647
687
  elif spec.build.source == "image":
648
688
  findings.extend(_image_findings(spec.build.image))
649
689
 
@@ -951,11 +991,7 @@ def publication_findings(environment: Environment) -> list[SpecFinding]:
951
991
  PUBLICATION_BLOCKED,
952
992
  )
953
993
  )
954
- private = [
955
- url
956
- for url in environment.spec.packages.python.indexes
957
- if not index_is_public(url)
958
- ]
994
+ private = [url for url in environment.spec.packages.python.indexes if not index_is_public(url)]
959
995
  if private:
960
996
  findings.append(
961
997
  SpecFinding(
@@ -95,6 +95,17 @@
95
95
  ],
96
96
  "default": null
97
97
  },
98
+ "dockerfile": {
99
+ "anyOf": [
100
+ {
101
+ "$ref": "#/$defs/DockerfileSpec"
102
+ },
103
+ {
104
+ "type": "null"
105
+ }
106
+ ],
107
+ "default": null
108
+ },
98
109
  "image": {
99
110
  "anyOf": [
100
111
  {
@@ -219,6 +230,19 @@
219
230
  "title": "DependencyFileSpec",
220
231
  "type": "object"
221
232
  },
233
+ "DockerfileSpec": {
234
+ "additionalProperties": false,
235
+ "description": "The Dockerfile a `dockerfile`-sourced version builds from (E3-03).\n\nInline, the way ``DependencyFileSpec`` carries a ``requirements.txt``: a\nfile the author brings lives in the spec, so ``check_dockerfile`` and\nevery variant's capability report can read it **before** anything is\nqueued \u2014 which is the whole point of refusing an instruction a variant\ndoes not implement at ``validate`` rather than halfway through a build.\n\nThe build *context* \u2014 the extra files a ``COPY`` needs \u2014 is the separate\nupload E3-03 describes, bounded and checked by ``validate_build_context``.\nA Dockerfile with no context is the common case and needs no upload at\nall.",
236
+ "properties": {
237
+ "content": {
238
+ "default": "",
239
+ "title": "Content",
240
+ "type": "string"
241
+ }
242
+ },
243
+ "title": "DockerfileSpec",
244
+ "type": "object"
245
+ },
222
246
  "EnvironmentSpec": {
223
247
  "additionalProperties": false,
224
248
  "properties": {
@@ -568,12 +568,28 @@ class TestBuildingASnapshot:
568
568
  resources = daytona.client.snapshot.create_calls[0].args[0].resources
569
569
  assert (resources.cpu, resources.memory, resources.disk) == (cpu, memory, disk)
570
570
 
571
- def test_the_region_is_passed_through(self) -> None:
571
+ def test_the_region_sent_is_daytonas_own_not_this_platforms(self) -> None:
572
+ """`request.region` is Datalayer's — `r1` — and Daytona answered
573
+ "Region not found" for it on the first real build (2026-09-17).
574
+
575
+ The owner names a Daytona region in `compatibility.regions`; that is
576
+ the one that scopes the snapshot.
577
+ """
572
578
  daytona = FakeDaytonaModule()
573
- a_builder(daytona=daytona).build(a_request(region="eu"))
579
+ a_builder(daytona=daytona).build(
580
+ a_request(region="r1", spec={"compatibility": {"regions": ["eu"]}})
581
+ )
574
582
  params = daytona.client.snapshot.create_calls[0].args[0]
575
583
  assert params.region_id == "eu"
576
584
 
585
+ def test_with_no_region_named_the_account_default_decides(self) -> None:
586
+ """The field is left out rather than filled with something Daytona
587
+ does not know — which is what every snapshot in a real account has."""
588
+ daytona = FakeDaytonaModule()
589
+ a_builder(daytona=daytona).build(a_request(region="r1"))
590
+ params = daytona.client.snapshot.create_calls[0].args[0]
591
+ assert params.region_id is None
592
+
577
593
  def test_the_snapshot_is_named_after_the_environment_and_version(self) -> None:
578
594
  daytona = FakeDaytonaModule()
579
595
  a_builder(daytona=daytona).build(a_request())
@@ -55,11 +55,7 @@ def environment(**spec: Any) -> Environment:
55
55
  #: A `dependencyFile` conda source: an `environment.yml` a managed variant
56
56
  #: builds with `micromamba` (E3-02).
57
57
  CONDA_ENVIRONMENT_YML = (
58
- "name: geo\n"
59
- "channels: [conda-forge]\n"
60
- "dependencies:\n"
61
- " - python=3.13\n"
62
- " - gdal\n"
58
+ "name: geo\nchannels: [conda-forge]\ndependencies:\n - python=3.13\n - gdal\n"
63
59
  )
64
60
 
65
61
 
@@ -102,15 +98,33 @@ class TestTheCapabilitySets:
102
98
  assert get_builder("modal").capabilities().supports_gpu is True
103
99
  assert get_builder("daytona").capabilities().supports_gpu is True
104
100
 
105
- def test_only_modal_forbids_instructions_its_builder_never_implemented(self) -> None:
106
- """Modal implements its own Dockerfile builder; the others hand a
107
- Dockerfile to BuildKit, which implements all of them."""
101
+ def test_each_variant_forbids_what_its_own_builder_will_not_honour(self) -> None:
102
+ """Modal implements its own Dockerfile builder, and E2B parses a
103
+ Dockerfile into Template SDK calls; Daytona hands the text to a real
104
+ Docker builder, which implements all of them.
105
+
106
+ E2B's list is read from its SDK (E3-03, 2026-09-17):
107
+ `e2b.template.dockerfile_parser` branches on FROM, RUN, COPY, ADD,
108
+ WORKDIR, USER, ENV, ARG, CMD and ENTRYPOINT, and for anything else
109
+ **prints `Unsupported instruction` and carries on** — so a template
110
+ built from a Dockerfile naming one of these comes back without it and
111
+ reports success. That is the case a capability report exists for.
112
+ """
108
113
  assert set(get_builder("modal").capabilities().forbidden_instructions) == {
109
114
  "ONBUILD",
110
115
  "STOPSIGNAL",
111
116
  "VOLUME",
112
117
  }
113
- assert get_builder("e2b").capabilities().forbidden_instructions == ()
118
+ assert set(get_builder("e2b").capabilities().forbidden_instructions) == {
119
+ "VOLUME",
120
+ "EXPOSE",
121
+ "HEALTHCHECK",
122
+ "SHELL",
123
+ "ONBUILD",
124
+ "STOPSIGNAL",
125
+ "LABEL",
126
+ "MAINTAINER",
127
+ }
114
128
  assert get_builder("daytona").capabilities().forbidden_instructions == ()
115
129
 
116
130
  def test_each_one_bounds_how_long_a_build_may_take(self) -> None:
@@ -120,10 +134,13 @@ class TestTheCapabilitySets:
120
134
  seconds = get_builder(variant).capabilities().max_build_seconds
121
135
  assert seconds and 0 < seconds <= 60 * 60, variant
122
136
 
123
- def test_this_phase_builds_a_package_list_and_a_conda_file(self) -> None:
137
+ def test_this_phase_builds_a_package_list_a_conda_file_and_a_dockerfile(self) -> None:
138
+ """All three take a Dockerfile (E3-03), each through its own door:
139
+ E2B parses one into Template SDK calls, Daytona hands the text to a
140
+ real Docker builder, Modal builds it with its own frontend."""
124
141
  for variant in MANAGED:
125
142
  sources = get_builder(variant).capabilities().build_sources
126
- assert sources == ("packages", "dependencyFile"), variant
143
+ assert sources == ("packages", "dependencyFile", "dockerfile"), variant
127
144
 
128
145
 
129
146
  # -- what each one refuses ------------------------------------------------------
@@ -230,10 +247,29 @@ class TestWhatIsSaidBeforeAnythingIsQueued:
230
247
 
231
248
  def test_a_source_this_phase_does_not_build_is_refused_by_name(self) -> None:
232
249
  for variant in MANAGED:
233
- report = get_builder(variant).validate(environment(build={"source": "dockerfile"}))
250
+ report = get_builder(variant).validate(environment(build={"source": "image"}))
234
251
  assert report.supported is False, variant
235
- assert "`dockerfile` is not built for" in messages(report)
236
- assert "it builds packages, dependencyFile" in messages(report)
252
+ assert "`image` is not built for" in messages(report)
253
+ assert "it builds packages, dependencyFile, dockerfile" in messages(report)
254
+
255
+ def test_an_instruction_a_variant_would_drop_is_refused_with_its_line(self) -> None:
256
+ """The case this exists for: E2B's parser prints `Unsupported
257
+ instruction` and carries on, so without this the template comes back
258
+ missing what the Dockerfile asked for and the build reports success."""
259
+ dockerfile = "FROM datalayer/python-cpu:2026.09\nRUN true\nVOLUME /data\n"
260
+ report = get_builder("e2b").validate(
261
+ environment(build={"source": "dockerfile", "dockerfile": {"content": dockerfile}})
262
+ )
263
+ assert report.supported is False
264
+ assert "line 3: E2B does not implement `VOLUME`" in messages(report)
265
+
266
+ def test_a_dockerfile_a_variant_can_honour_is_accepted(self) -> None:
267
+ """Daytona builds the text as it is, so Docker's own grammar is the limit."""
268
+ dockerfile = "FROM datalayer/python-cpu:2026.09\nVOLUME /data\nEXPOSE 8888\n"
269
+ report = get_builder("daytona").validate(
270
+ environment(build={"source": "dockerfile", "dockerfile": {"content": dockerfile}})
271
+ )
272
+ assert report.supported is True, messages(report)
237
273
 
238
274
  def test_a_conda_dependency_file_is_buildable_on_every_managed_variant(self) -> None:
239
275
  for variant in MANAGED:
@@ -415,8 +451,11 @@ def test_every_variant_validates_with_no_provider_sdk_installed() -> None:
415
451
  assert builder.capabilities().variant == variant
416
452
  report = builder.validate(environment())
417
453
  assert report.supported is True, f"{variant}: {messages(report)}"
454
+ # A refusal each variant still makes, and makes without an SDK.
455
+ # `dockerfile` stopped being one when E3-03 gave all three a door
456
+ # into it, so a managed variant is asked about `image` instead.
418
457
  refused = get_builder(variant).validate(
419
- environment(build={"source": "dockerfile"})
458
+ environment(build={"source": "image"})
420
459
  if variant != "datalayer"
421
460
  else environment(
422
461
  resources={"sizeClass": "gpu-small", "accelerator": {"type": "A10G"}}
@@ -322,14 +322,46 @@ def test_an_invalid_field_outranks_something_unsupported() -> None:
322
322
  }
323
323
 
324
324
 
325
- def test_a_dockerfile_source_is_accepted_and_keeps_its_own_base() -> None:
326
- # E3-03: the base is the `FROM` its uploaded Dockerfile names, so
327
- # `spec.base` is not checked against the approved table (as for `image`).
325
+ def a_dockerfile_document(content: str) -> dict[str, Any]:
328
326
  data = mutated("spec.build.source", "dockerfile")
327
+ data["spec"]["build"]["dockerfile"] = {"content": content}
328
+ return data
329
+
330
+
331
+ def test_a_dockerfile_source_is_accepted_and_keeps_its_own_base() -> None:
332
+ # E3-03: the base is the `FROM` the Dockerfile names, so `spec.base` is
333
+ # not checked against the approved table (as for `image`).
334
+ data = a_dockerfile_document("FROM datalayer/python-cpu:2026.09\nRUN true\n")
329
335
  data["spec"]["base"]["ref"] = "python"
330
336
  assert _codes(data) == {}
331
337
 
332
338
 
339
+ def test_a_dockerfile_source_without_a_dockerfile_is_refused() -> None:
340
+ """`source: dockerfile` with nowhere to read the file from is not a spec."""
341
+ data = mutated("spec.build.source", "dockerfile")
342
+ assert _codes(data) == {"spec.build.dockerfile": "DL_ENV_SPEC_INVALID"}
343
+
344
+
345
+ def test_an_empty_dockerfile_is_refused() -> None:
346
+ assert _codes(a_dockerfile_document(" \n")) == {
347
+ "spec.build.dockerfile.content": "DL_ENV_SPEC_INVALID"
348
+ }
349
+
350
+
351
+ def test_the_contract_refuses_a_dockerfile_at_validate_naming_the_line() -> None:
352
+ """A Dockerfile is somebody's file: "it was refused" is not a reason.
353
+
354
+ The refusals are the contract's own — an unapproved base, the host
355
+ network, a privileged build, a Docker socket mount — and they are made
356
+ here, before anything is queued, rather than partway through a build.
357
+ """
358
+ data = a_dockerfile_document("FROM ubuntu:22.04\nRUN --network=host apt-get update\n")
359
+ assert _codes(data) == {"spec.build.dockerfile.content": "DL_ENV_SPEC_INVALID"}
360
+ messages = [finding.message for finding in spec_findings(parse_environment(data))]
361
+ assert any("line 1" in message and "approved" in message for message in messages)
362
+ assert any("line 2" in message and "host network" in message for message in messages)
363
+
364
+
333
365
  # -- Dependency files (E3-01) --------------------------------------------------
334
366
 
335
367
 
File without changes