code-sandboxes 1.9.35__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.
Files changed (266) hide show
  1. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/datalayer.py +11 -0
  4. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/modal.py +24 -1
  5. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/resolve.py +47 -0
  6. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/resolve_conda.py +6 -0
  7. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/modal_sandbox.py +10 -1
  8. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_datalayer_builder.py +21 -0
  9. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_modal_builder.py +18 -4
  10. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_resolve.py +28 -0
  11. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_resolve_conda.py +24 -0
  12. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_modal_google_colab_sandbox.py +51 -0
  13. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/build.yml +0 -0
  14. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/environments-live.yml +0 -0
  15. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/py-code-style.yml +0 -0
  16. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/py-tests.yml +0 -0
  17. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/py-typing.yml +0 -0
  18. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.github/workflows/reusable-python.yml +0 -0
  19. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.gitignore +0 -0
  20. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.licenserc.yaml +0 -0
  21. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.pre-commit-config.yaml +0 -0
  22. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/.vscode/settings.json +0 -0
  23. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/CHANGELOG.md +0 -0
  24. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/Dockerfile +0 -0
  25. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/LICENSE +0 -0
  26. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/Makefile +0 -0
  27. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/README.md +0 -0
  28. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/__init__.py +0 -0
  29. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/base.py +0 -0
  30. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/bridge_mount.py +0 -0
  31. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/builds.py +0 -0
  32. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/cli.py +0 -0
  33. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/client.py +0 -0
  34. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/cloudflare_sandbox.py +0 -0
  35. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/commands.py +0 -0
  36. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/console.py +0 -0
  37. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/contents.py +0 -0
  38. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/coreweave_sandbox.py +0 -0
  39. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/datalayer_sandbox.py +0 -0
  40. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/daytona_sandbox.py +0 -0
  41. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/docker_sandbox.py +0 -0
  42. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/e2b_sandbox.py +0 -0
  43. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/__init__.py +0 -0
  44. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/__main__.py +0 -0
  45. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/accounts.py +0 -0
  46. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/__init__.py +0 -0
  47. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/daytona.py +0 -0
  48. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/e2b.py +0 -0
  49. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/adapters/managed.py +0 -0
  50. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/attest.py +0 -0
  51. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/bases.py +0 -0
  52. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/breaker.py +0 -0
  53. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/build_secrets.py +0 -0
  54. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/builders.py +0 -0
  55. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/canonical.py +0 -0
  56. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/conformance.py +0 -0
  57. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  58. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  59. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  60. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/contract.py +0 -0
  61. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/__init__.py +0 -0
  62. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/build.py +0 -0
  63. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  64. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  65. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/errors.py +0 -0
  66. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/files.py +0 -0
  67. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/image_import.py +0 -0
  68. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/lifecycle.py +0 -0
  69. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/policy.py +0 -0
  70. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/redact.py +0 -0
  71. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/schema.py +0 -0
  72. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/environments/spec.py +0 -0
  73. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/eval_sandbox.py +0 -0
  74. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/exceptions.py +0 -0
  75. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/filesystem.py +0 -0
  76. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/google_colab.py +0 -0
  77. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/google_colab_sandbox.py +0 -0
  78. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/interfaces.py +0 -0
  79. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/jupyter_ingress.py +0 -0
  80. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  81. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/kaggle.py +0 -0
  82. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_execute.py +0 -0
  83. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_live.py +0 -0
  84. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/kaggle_sandbox.py +0 -0
  85. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/lifecycle.py +0 -0
  86. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/manage.py +0 -0
  87. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/models.py +0 -0
  88. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/monty_sandbox.py +0 -0
  89. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/provider_ingress.py +0 -0
  90. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/code_sandboxes/providers.py +0 -0
  91. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docker/Dockerfile +0 -0
  92. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/.gitignore +0 -0
  93. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/LICENSE +0 -0
  94. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/Makefile +0 -0
  95. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/README.md +0 -0
  96. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/babel.config.js +0 -0
  97. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/api-reference/_category_.yml +0 -0
  98. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/api-reference/index.mdx +0 -0
  99. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/cli/_category_.yml +0 -0
  100. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/cli/index.mdx +0 -0
  101. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/contribute/_category_.yml +0 -0
  102. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/contribute/index.mdx +0 -0
  103. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/environments/_category_.yml +0 -0
  104. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/environments/contract.mdx +0 -0
  105. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/environments/index.mdx +0 -0
  106. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/environments/specification.mdx +0 -0
  107. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/environments/spike-2026-09.mdx +0 -0
  108. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/examples/_category_.yml +0 -0
  109. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/examples/index.mdx +0 -0
  110. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/guide/_category_.yml +0 -0
  111. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/guide/index.mdx +0 -0
  112. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/index.mdx +0 -0
  113. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/install/_category_.yml +0 -0
  114. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/install/index.mdx +0 -0
  115. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/provider-ingress.mdx +0 -0
  116. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/_category_.yml +0 -0
  117. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/cloudflare.mdx +0 -0
  118. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/coreweave.mdx +0 -0
  119. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/datalayer.mdx +0 -0
  120. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/daytona.mdx +0 -0
  121. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/docker.mdx +0 -0
  122. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/e2b.mdx +0 -0
  123. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/eval.mdx +0 -0
  124. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/google-colab.mdx +0 -0
  125. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/index.mdx +0 -0
  126. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/jupyter-server.mdx +0 -0
  127. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/kaggle.mdx +0 -0
  128. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/modal.mdx +0 -0
  129. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docs/providers/monty.mdx +0 -0
  130. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/docusaurus.config.js +0 -0
  131. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/package.json +0 -0
  132. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/sidebars.js +0 -0
  133. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow1.js +0 -0
  134. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow1.module.css +0 -0
  135. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow2.js +0 -0
  136. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow2.module.css +0 -0
  137. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow3.js +0 -0
  138. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/components/HomepageRow3.module.css +0 -0
  139. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/css/custom.css +0 -0
  140. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/pages/index.module.css +0 -0
  141. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/pages/markdown-page.md +0 -0
  142. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/theme/CustomDocItem.tsx +0 -0
  143. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/src/theme/IconsTitle.tsx +0 -0
  144. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/bricks.svg +0 -0
  145. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/building-construction.svg +0 -0
  146. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/cloud-sun.svg +0 -0
  147. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/cloud.svg +0 -0
  148. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/collaboration.svg +0 -0
  149. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/copy-left.svg +0 -0
  150. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/logo.png +0 -0
  151. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/logo.svg +0 -0
  152. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/business.svg +0 -0
  153. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  154. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/personas/devops.svg +0 -0
  155. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/datalayer/project/project-line.svg +0 -0
  156. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/favicon.ico +0 -0
  157. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/jupyter.svg +0 -0
  158. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/open-source.svg +0 -0
  159. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/react-js.svg +0 -0
  160. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/react-js2.svg +0 -0
  161. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/ringed-planet.svg +0 -0
  162. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/robot.svg +0 -0
  163. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/rocket.svg +0 -0
  164. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/shield.svg +0 -0
  165. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/sparkles.svg +0 -0
  166. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/streamlit.gif +0 -0
  167. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/super-hero.svg +0 -0
  168. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/target.svg +0 -0
  169. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/docs/static/img/thumbs-up.svg +0 -0
  170. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/README.md +0 -0
  171. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/Makefile +0 -0
  172. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/cloudflare_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/coreweave_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/datalayer_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/daytona_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/docker_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/e2b_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/eval_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/google_colab_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/kaggle_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/modal_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/exec/monty_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/Makefile +0 -0
  185. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/cloudflare_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/coreweave_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/datalayer_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/daytona_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/docker_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/e2b_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/eval_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/google_colab_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/kaggle_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/modal_sandbox_example.py +0 -0
  196. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/examples/repl/monty_sandbox_example.py +0 -0
  197. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/pyproject.toml +0 -0
  198. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/schemas/environment-v1alpha1.json +0 -0
  199. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/__init__.py +0 -0
  200. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/conftest.py +0 -0
  201. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  202. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_bridge_mount.py +0 -0
  203. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_builds.py +0 -0
  204. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_cli_exec.py +0 -0
  205. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_cli_repl.py +0 -0
  206. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_client.py +0 -0
  207. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_client_contents.py +0 -0
  208. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_client_files.py +0 -0
  209. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_client_outcome_error_output.py +0 -0
  210. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_cloudflare.py +0 -0
  211. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_commands_envelope.py +0 -0
  212. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_console.py +0 -0
  213. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_contents_contract.py +0 -0
  214. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_contents_live_matrix.py +0 -0
  215. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_coreweave.py +0 -0
  216. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_datalayer_sandbox.py +0 -0
  217. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_daytona.py +0 -0
  218. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_e2b.py +0 -0
  219. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_accounts.py +0 -0
  220. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_attest.py +0 -0
  221. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_bases.py +0 -0
  222. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_breaker.py +0 -0
  223. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_build_secrets.py +0 -0
  224. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_builders.py +0 -0
  225. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_canonical.py +0 -0
  226. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_conformance.py +0 -0
  227. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_constraints.py +0 -0
  228. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_contract.py +0 -0
  229. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_cross_variant_report.py +0 -0
  230. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_daytona_builder.py +0 -0
  231. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_dockerfile_source.py +0 -0
  232. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_doctor.py +0 -0
  233. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_e2b_builder.py +0 -0
  234. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_errors.py +0 -0
  235. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_image_import.py +0 -0
  236. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_lifecycle.py +0 -0
  237. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_live_matrix.py +0 -0
  238. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_managed_builders.py +0 -0
  239. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_organization_policy.py +0 -0
  240. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_redact.py +0 -0
  241. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_spec.py +0 -0
  242. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_environment_spec_organization_policy.py +0 -0
  243. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_eval.py +0 -0
  244. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  245. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_factory.py +0 -0
  246. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_google_colab.py +0 -0
  247. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_integration.py +0 -0
  248. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_jupyter_ingress.py +0 -0
  249. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_jupyter_server.py +0 -0
  250. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_jupyter_server_streaming.py +0 -0
  251. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_kaggle.py +0 -0
  252. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_kaggle_execute.py +0 -0
  253. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_kaggle_live.py +0 -0
  254. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_kaggle_session.py +0 -0
  255. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_kernel_client_compatibility.py +0 -0
  256. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_lifecycle_vocabulary.py +0 -0
  257. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_manage.py +0 -0
  258. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_manage_datalayer.py +0 -0
  259. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_modal_session.py +0 -0
  260. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_models.py +0 -0
  261. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_output_line_fidelity.py +0 -0
  262. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_provider_catalog_names.py +0 -0
  263. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_provider_ingress.py +0 -0
  264. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_providers.py +0 -0
  265. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/tests/test_sandboxes.py +0 -0
  266. {code_sandboxes-1.9.35 → code_sandboxes-1.9.36}/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.35
3
+ Version: 1.9.36
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.35"
6
+ __version__ = "1.9.36"
@@ -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}")
@@ -96,6 +96,15 @@ 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
+ **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
+
99
108
  **A GPU is a launch option on Modal, not part of the image** (section 11.4
100
109
  item 10, E2-17). A GPU version builds the same image any version does, on the
101
110
  CUDA base; the spec's `accelerator` names one of Modal's GPUs, checked at
@@ -206,6 +215,8 @@ _ENTRYPOINT_PATH = "/opt/datalayer/bin/entrypoint.sh"
206
215
  #: is given keeps it alive for that; `exec "$@"` still wins when something
207
216
  #: is, for a caller that does supply a command directly.
208
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")
209
220
 
210
221
 
211
222
  def _modal_sdk() -> Any:
@@ -498,7 +509,19 @@ class Builder(ManagedBuilder):
498
509
  for command in files_step(request.environment, variant=self.variant):
499
510
  image = image.run_commands(command)
500
511
  image = _post_install(image, spec.commands.post_install, declared, step_secrets)
501
- image = image.workdir(_CONTENT_DIR).entrypoint([_ENTRYPOINT_PATH])
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
+ )
502
525
 
503
526
  logged: list[str] = []
504
527
  buffer = io.StringIO()
@@ -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:
@@ -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
- self._sandbox = modal.Sandbox.create(**create_kwargs)
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
 
@@ -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
@@ -166,6 +166,10 @@ class FakeImage:
166
166
  self.calls.append(Call("entrypoint", (commands,)))
167
167
  return self
168
168
 
169
+ def cmd(self, command: list[str]) -> FakeImage:
170
+ self.calls.append(Call("cmd", (command,)))
171
+ return self
172
+
169
173
  def build(self, app: Any) -> FakeImage:
170
174
  self.calls.append(Call("build", (app,)))
171
175
  # Simulates Modal's own `enable_output()` printing straight to
@@ -601,14 +605,24 @@ class TestBuildingAnImage:
601
605
  [image] = modal.Image.created
602
606
  assert not any("doctor" in call.args[0] for call in calls_named(image, "run_commands"))
603
607
 
604
- def test_the_chain_ends_with_workdir_then_entrypoint(self) -> None:
608
+ def test_the_chain_ends_with_workdir_entrypoint_and_cmd(self) -> None:
605
609
  modal = FakeModalModule()
606
610
  a_builder(modal=modal).build(a_request())
607
611
  [image] = modal.Image.created
608
- # `build` is appended by `FakeImage.build` itself; the two before it
609
- # are the chain's own last words.
612
+ # `build` is appended by `FakeImage.build` itself; the three before
613
+ # it are the chain's own last words.
610
614
  names = [call.name for call in image.calls]
611
- assert names[-3:] == ["workdir", "entrypoint", "build"]
615
+ assert names[-4:] == ["workdir", "entrypoint", "cmd", "build"]
616
+
617
+ def test_the_bases_jupyter_cmd_is_replaced_by_sleep(self) -> None:
618
+ """Modal keeps the base's `start-jupyter.sh` under a new ENTRYPOINT,
619
+ and a Jupyter server as the main process exits and ends the sandbox
620
+ (found live on 2026-09-18). The artifact says what holds it."""
621
+ modal = FakeModalModule()
622
+ a_builder(modal=modal).build(a_request())
623
+ [image] = modal.Image.created
624
+ [cmd_call] = calls_named(image, "cmd")
625
+ assert cmd_call.args[0] == ["sleep", "infinity"]
612
626
 
613
627
  def test_the_entrypoint_execs_its_arguments(self) -> None:
614
628
  """A bare script path, nothing for `entrypoint()`'s own Dockerfile
@@ -1315,6 +1315,34 @@ class TestTheBuildkitRunner:
1315
1315
  )
1316
1316
  assert not any(arg.startswith("--tls") for arg in seen["argv"])
1317
1317
 
1318
+ def test_the_solve_goes_through_the_pools_proxy(self, monkeypatch) -> None:
1319
+ """The solve reaches the index the way a build does, through the
1320
+ build pool's proxy (E1-06)."""
1321
+ import subprocess as subprocess_module
1322
+
1323
+ from code_sandboxes.environments import resolve as resolve_module
1324
+ from code_sandboxes.environments.resolve import BuildkitResolveRunner
1325
+
1326
+ seen: dict[str, list[str]] = {}
1327
+
1328
+ def fake_run(command, **kwargs):
1329
+ seen["argv"] = list(command)
1330
+ return subprocess_module.CompletedProcess(command, 1, "", "boom")
1331
+
1332
+ monkeypatch.setattr(resolve_module.subprocess, "run", fake_run)
1333
+ with pytest.raises(EnvironmentsError):
1334
+ BuildkitResolveRunner(buildctl="/usr/bin/true", proxy="http://127.0.0.1:3128").solve(
1335
+ ResolveRequest(
1336
+ python_version="3.13",
1337
+ requirements=("ipykernel==7.3.0",),
1338
+ constraints=(),
1339
+ indexes=(),
1340
+ base_reference="environments/base/python-cpu@sha256:" + "11" * 32,
1341
+ )
1342
+ )
1343
+ assert "build-arg:HTTPS_PROXY=http://127.0.0.1:3128" in seen["argv"]
1344
+ assert "build-arg:https_proxy=http://127.0.0.1:3128" in seen["argv"]
1345
+
1318
1346
  def test_it_refuses_a_base_that_is_not_pinned_by_digest(self) -> None:
1319
1347
  from code_sandboxes.environments.resolve import BuildkitResolveRunner
1320
1348
 
@@ -370,6 +370,30 @@ class TestTheRunners:
370
370
  )
371
371
  assert caught.value.code.code == "DL_ENV_CAPABILITY_UNSUPPORTED"
372
372
 
373
+ def test_the_buildkit_runner_goes_through_the_pools_proxy(
374
+ self, monkeypatch: pytest.MonkeyPatch
375
+ ) -> None:
376
+ # The conda solve reaches its channels the way a build does, through
377
+ # the build pool's proxy (E1-06).
378
+ seen: dict[str, list[str]] = {}
379
+
380
+ def fake_run(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
381
+ seen["argv"] = list(argv)
382
+ return subprocess.CompletedProcess(argv, 1, stdout="", stderr="boom")
383
+
384
+ monkeypatch.setenv("DATALAYER_BUILDKIT_PROXY", "http://127.0.0.1:3128")
385
+ monkeypatch.setattr("code_sandboxes.environments.resolve_conda.subprocess.run", fake_run)
386
+ runner = BuildkitCondaResolveRunner(buildctl="/usr/bin/buildctl")
387
+ with pytest.raises(EnvironmentsError):
388
+ runner.solve(
389
+ CondaResolveRequest(
390
+ environment_yml=A_YAML,
391
+ python_version="3.13",
392
+ base_reference="base@sha256:" + "11" * 32,
393
+ )
394
+ )
395
+ assert "build-arg:https_proxy=http://127.0.0.1:3128" in seen["argv"]
396
+
373
397
  def test_the_buildkit_runner_refuses_an_unpinned_base(self) -> None:
374
398
  runner = BuildkitCondaResolveRunner(buildctl="/usr/bin/buildctl")
375
399
  with pytest.raises(EnvironmentsError) as caught:
@@ -493,6 +493,57 @@ def test_modal_start_forwards_gpu_flavor(monkeypatch):
493
493
  sandbox.stop()
494
494
 
495
495
 
496
+ def test_an_environments_artifact_is_held_by_sleep_not_its_cmd(monkeypatch):
497
+ """An artifact's CMD may be its base's `start-jupyter.sh`, which exits and
498
+ ends the sandbox (found live on 2026-09-18): the launch names the command
499
+ that holds the container, and Jupyter is started by exec when asked."""
500
+
501
+ class _FakeApp:
502
+ pass
503
+
504
+ class _FakeSandboxObj:
505
+ object_id = "modal-object-id"
506
+
507
+ def exec(self, *_args, **_kwargs):
508
+ raise RuntimeError("no driver in this test")
509
+
510
+ def terminate(self):
511
+ return None
512
+
513
+ def detach(self):
514
+ return None
515
+
516
+ captured: dict = {}
517
+
518
+ class _FakeModal:
519
+ class App:
520
+ @staticmethod
521
+ def lookup(_name, create_if_missing=False):
522
+ return _FakeApp()
523
+
524
+ class Image:
525
+ @staticmethod
526
+ def from_id(image_id):
527
+ captured["image_id"] = image_id
528
+ return object()
529
+
530
+ class Sandbox:
531
+ @staticmethod
532
+ def create(*args, **kwargs):
533
+ captured["args"] = args
534
+ return _FakeSandboxObj()
535
+
536
+ monkeypatch.setitem(sys.modules, "modal", _FakeModal)
537
+
538
+ sandbox = ModalSandbox(config=SandboxConfig(timeout=10.0), image_id="im-artifact")
539
+ sandbox.start()
540
+
541
+ assert captured["image_id"] == "im-artifact"
542
+ assert captured["args"] == ("sleep", "infinity")
543
+
544
+ sandbox.stop()
545
+
546
+
496
547
  class _FakeExecSandbox:
497
548
  """Records what `_start_driver` asks `sandbox.exec` for, nothing more."""
498
549
 
File without changes