code-sandboxes 1.9.26__tar.gz → 1.9.28__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 (265) hide show
  1. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/datalayer.py +4 -2
  4. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/daytona.py +2 -3
  5. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/conformance.py +8 -0
  6. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/resolve_conda.py +146 -1
  7. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/jupyter_server_sandbox.py +11 -7
  8. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_datalayer_builder.py +8 -1
  9. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_daytona_builder.py +4 -0
  10. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_resolve_conda.py +93 -0
  11. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_jupyter_server.py +28 -0
  12. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/build.yml +0 -0
  13. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/environments-live.yml +0 -0
  14. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/py-code-style.yml +0 -0
  15. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/py-tests.yml +0 -0
  16. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/py-typing.yml +0 -0
  17. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.github/workflows/reusable-python.yml +0 -0
  18. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.gitignore +0 -0
  19. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.licenserc.yaml +0 -0
  20. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.pre-commit-config.yaml +0 -0
  21. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/.vscode/settings.json +0 -0
  22. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/CHANGELOG.md +0 -0
  23. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/Dockerfile +0 -0
  24. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/LICENSE +0 -0
  25. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/Makefile +0 -0
  26. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/README.md +0 -0
  27. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/__init__.py +0 -0
  28. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/base.py +0 -0
  29. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/bridge_mount.py +0 -0
  30. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/builds.py +0 -0
  31. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/cli.py +0 -0
  32. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/client.py +0 -0
  33. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/cloudflare_sandbox.py +0 -0
  34. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/commands.py +0 -0
  35. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/console.py +0 -0
  36. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/contents.py +0 -0
  37. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/coreweave_sandbox.py +0 -0
  38. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/datalayer_sandbox.py +0 -0
  39. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/daytona_sandbox.py +0 -0
  40. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/docker_sandbox.py +0 -0
  41. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/e2b_sandbox.py +0 -0
  42. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/__init__.py +0 -0
  43. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/__main__.py +0 -0
  44. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/accounts.py +0 -0
  45. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/__init__.py +0 -0
  46. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/e2b.py +0 -0
  47. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/managed.py +0 -0
  48. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/adapters/modal.py +0 -0
  49. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/attest.py +0 -0
  50. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/bases.py +0 -0
  51. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/breaker.py +0 -0
  52. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/build_secrets.py +0 -0
  53. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/builders.py +0 -0
  54. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/canonical.py +0 -0
  55. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  56. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  57. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  58. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/contract.py +0 -0
  59. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/doctor/__init__.py +0 -0
  60. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/doctor/build.py +0 -0
  61. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  62. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  63. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/errors.py +0 -0
  64. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/files.py +0 -0
  65. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/image_import.py +0 -0
  66. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/lifecycle.py +0 -0
  67. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/policy.py +0 -0
  68. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/redact.py +0 -0
  69. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/resolve.py +0 -0
  70. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/schema.py +0 -0
  71. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/environments/spec.py +0 -0
  72. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/eval_sandbox.py +0 -0
  73. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/exceptions.py +0 -0
  74. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/filesystem.py +0 -0
  75. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/google_colab.py +0 -0
  76. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/google_colab_sandbox.py +0 -0
  77. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/interfaces.py +0 -0
  78. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/jupyter_ingress.py +0 -0
  79. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/kaggle.py +0 -0
  80. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/kaggle_execute.py +0 -0
  81. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/kaggle_live.py +0 -0
  82. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/kaggle_sandbox.py +0 -0
  83. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/lifecycle.py +0 -0
  84. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/manage.py +0 -0
  85. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/modal_sandbox.py +0 -0
  86. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/models.py +0 -0
  87. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/monty_sandbox.py +0 -0
  88. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/provider_ingress.py +0 -0
  89. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/code_sandboxes/providers.py +0 -0
  90. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docker/Dockerfile +0 -0
  91. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/.gitignore +0 -0
  92. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/LICENSE +0 -0
  93. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/Makefile +0 -0
  94. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/README.md +0 -0
  95. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/babel.config.js +0 -0
  96. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/api-reference/_category_.yml +0 -0
  97. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/api-reference/index.mdx +0 -0
  98. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/cli/_category_.yml +0 -0
  99. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/cli/index.mdx +0 -0
  100. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/contribute/_category_.yml +0 -0
  101. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/contribute/index.mdx +0 -0
  102. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/environments/_category_.yml +0 -0
  103. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/environments/contract.mdx +0 -0
  104. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/environments/index.mdx +0 -0
  105. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/environments/specification.mdx +0 -0
  106. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/environments/spike-2026-09.mdx +0 -0
  107. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/examples/_category_.yml +0 -0
  108. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/examples/index.mdx +0 -0
  109. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/guide/_category_.yml +0 -0
  110. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/guide/index.mdx +0 -0
  111. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/index.mdx +0 -0
  112. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/install/_category_.yml +0 -0
  113. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/install/index.mdx +0 -0
  114. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/provider-ingress.mdx +0 -0
  115. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/_category_.yml +0 -0
  116. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/cloudflare.mdx +0 -0
  117. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/coreweave.mdx +0 -0
  118. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/datalayer.mdx +0 -0
  119. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/daytona.mdx +0 -0
  120. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/docker.mdx +0 -0
  121. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/e2b.mdx +0 -0
  122. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/eval.mdx +0 -0
  123. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/google-colab.mdx +0 -0
  124. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/index.mdx +0 -0
  125. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/jupyter-server.mdx +0 -0
  126. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/kaggle.mdx +0 -0
  127. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/modal.mdx +0 -0
  128. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docs/providers/monty.mdx +0 -0
  129. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/docusaurus.config.js +0 -0
  130. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/package.json +0 -0
  131. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/sidebars.js +0 -0
  132. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow1.js +0 -0
  133. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow1.module.css +0 -0
  134. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow2.js +0 -0
  135. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow2.module.css +0 -0
  136. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow3.js +0 -0
  137. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/components/HomepageRow3.module.css +0 -0
  138. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/css/custom.css +0 -0
  139. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/pages/index.module.css +0 -0
  140. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/pages/markdown-page.md +0 -0
  141. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/theme/CustomDocItem.tsx +0 -0
  142. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/src/theme/IconsTitle.tsx +0 -0
  143. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/bricks.svg +0 -0
  144. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/building-construction.svg +0 -0
  145. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/cloud-sun.svg +0 -0
  146. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/cloud.svg +0 -0
  147. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/collaboration.svg +0 -0
  148. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/copy-left.svg +0 -0
  149. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/logo.png +0 -0
  150. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/logo.svg +0 -0
  151. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/personas/business.svg +0 -0
  152. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  153. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/personas/devops.svg +0 -0
  154. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/datalayer/project/project-line.svg +0 -0
  155. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/favicon.ico +0 -0
  156. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/jupyter.svg +0 -0
  157. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/open-source.svg +0 -0
  158. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/react-js.svg +0 -0
  159. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/react-js2.svg +0 -0
  160. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/ringed-planet.svg +0 -0
  161. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/robot.svg +0 -0
  162. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/rocket.svg +0 -0
  163. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/shield.svg +0 -0
  164. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/sparkles.svg +0 -0
  165. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/streamlit.gif +0 -0
  166. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/super-hero.svg +0 -0
  167. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/target.svg +0 -0
  168. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/docs/static/img/thumbs-up.svg +0 -0
  169. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/README.md +0 -0
  170. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/Makefile +0 -0
  171. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/cloudflare_sandbox_example.py +0 -0
  172. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/coreweave_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/datalayer_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/daytona_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/docker_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/e2b_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/eval_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/google_colab_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/kaggle_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/modal_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/exec/monty_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/Makefile +0 -0
  184. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/cloudflare_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/coreweave_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/datalayer_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/daytona_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/docker_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/e2b_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/eval_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/google_colab_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/kaggle_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/modal_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/examples/repl/monty_sandbox_example.py +0 -0
  196. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/pyproject.toml +0 -0
  197. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/schemas/environment-v1alpha1.json +0 -0
  198. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/__init__.py +0 -0
  199. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/conftest.py +0 -0
  200. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  201. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_bridge_mount.py +0 -0
  202. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_builds.py +0 -0
  203. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_cli_exec.py +0 -0
  204. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_cli_repl.py +0 -0
  205. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_client.py +0 -0
  206. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_client_contents.py +0 -0
  207. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_client_files.py +0 -0
  208. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_client_outcome_error_output.py +0 -0
  209. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_cloudflare.py +0 -0
  210. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_commands_envelope.py +0 -0
  211. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_console.py +0 -0
  212. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_contents_contract.py +0 -0
  213. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_contents_live_matrix.py +0 -0
  214. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_coreweave.py +0 -0
  215. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_datalayer_sandbox.py +0 -0
  216. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_daytona.py +0 -0
  217. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_e2b.py +0 -0
  218. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_accounts.py +0 -0
  219. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_attest.py +0 -0
  220. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_bases.py +0 -0
  221. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_breaker.py +0 -0
  222. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_build_secrets.py +0 -0
  223. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_builders.py +0 -0
  224. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_canonical.py +0 -0
  225. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_conformance.py +0 -0
  226. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_constraints.py +0 -0
  227. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_contract.py +0 -0
  228. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_cross_variant_report.py +0 -0
  229. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_doctor.py +0 -0
  230. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_e2b_builder.py +0 -0
  231. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_errors.py +0 -0
  232. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_image_import.py +0 -0
  233. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_lifecycle.py +0 -0
  234. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_live_matrix.py +0 -0
  235. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_managed_builders.py +0 -0
  236. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_modal_builder.py +0 -0
  237. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_organization_policy.py +0 -0
  238. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_redact.py +0 -0
  239. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_resolve.py +0 -0
  240. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_spec.py +0 -0
  241. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_environment_spec_organization_policy.py +0 -0
  242. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_eval.py +0 -0
  243. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  244. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_factory.py +0 -0
  245. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_google_colab.py +0 -0
  246. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_integration.py +0 -0
  247. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_jupyter_ingress.py +0 -0
  248. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_jupyter_server_streaming.py +0 -0
  249. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_kaggle.py +0 -0
  250. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_kaggle_execute.py +0 -0
  251. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_kaggle_live.py +0 -0
  252. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_kaggle_session.py +0 -0
  253. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_kernel_client_compatibility.py +0 -0
  254. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_lifecycle_vocabulary.py +0 -0
  255. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_manage.py +0 -0
  256. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_manage_datalayer.py +0 -0
  257. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_modal_google_colab_sandbox.py +0 -0
  258. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_modal_session.py +0 -0
  259. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_models.py +0 -0
  260. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_output_line_fidelity.py +0 -0
  261. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_provider_catalog_names.py +0 -0
  262. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_provider_ingress.py +0 -0
  263. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_providers.py +0 -0
  264. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/tests/test_sandboxes.py +0 -0
  265. {code_sandboxes-1.9.26 → code_sandboxes-1.9.28}/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.26
3
+ Version: 1.9.28
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.26"
6
+ __version__ = "1.9.28"
@@ -83,6 +83,7 @@ from ..resolve_conda import (
83
83
  conda_lock_pip_requirements,
84
84
  is_conda_lock,
85
85
  micromamba_bootstrap_dockerfile_line,
86
+ micromamba_install_command,
86
87
  )
87
88
  from ..spec import BuildSecret, Environment, command_names_secret
88
89
 
@@ -364,8 +365,9 @@ class Builder:
364
365
  micromamba_bootstrap_dockerfile_line(),
365
366
  "COPY lock.txt /opt/datalayer/lock.txt",
366
367
  "RUN --mount=type=cache,target=/opt/conda/pkgs "
367
- f"{MICROMAMBA_BINARY} install --yes --name base "
368
- "--file /opt/datalayer/lock.txt",
368
+ + micromamba_install_command(
369
+ "/opt/datalayer/lock.txt", micromamba=MICROMAMBA_BINARY
370
+ ),
369
371
  ]
370
372
  )
371
373
  if pip_requirements:
@@ -116,6 +116,7 @@ from ..resolve_conda import (
116
116
  conda_lock_pip_requirements,
117
117
  is_conda_lock,
118
118
  micromamba_bootstrap_command,
119
+ micromamba_install_command,
119
120
  )
120
121
  from ..spec import GPU_SIZE_CLASSES, Environment
121
122
  from .managed import ManagedBuilder
@@ -373,9 +374,7 @@ class Builder(ManagedBuilder):
373
374
  # present the same as for a pip source. micromamba is
374
375
  # installed first: the approved base bakes uv but not it.
375
376
  image = image.run_commands(micromamba_bootstrap_command())
376
- image = image.run_commands(
377
- f"micromamba install --yes --name base --file {_LOCK_PATH}"
378
- )
377
+ image = image.run_commands(micromamba_install_command(_LOCK_PATH))
379
378
  pip_requirements = conda_lock_pip_requirements(request.lock_text)
380
379
  if pip_requirements:
381
380
  requirements = " ".join(shlex.quote(req) for req in pip_requirements)
@@ -477,6 +477,14 @@ def expected_packages(environment: Any, lock_text: str) -> dict[str, str]:
477
477
 
478
478
  from .resolve import locked_versions
479
479
 
480
+ dependency_file = environment.spec.build.dependency_file
481
+ if dependency_file is not None and dependency_file.source_format == "conda":
482
+ # A conda source names its packages in the file, not in
483
+ # `packages.python`, and its lock is not a pip one (E3-02). Read the
484
+ # other way, check 5 was handed nothing and passed for it.
485
+ from .resolve_conda import conda_expected_packages
486
+
487
+ return conda_expected_packages(dependency_file.content, lock_text or "")
480
488
  pinned = locked_versions(lock_text) if lock_text else {}
481
489
  names: list[str] = []
482
490
  for text in environment.spec.packages.python.dependencies:
@@ -148,6 +148,30 @@ def micromamba_bootstrap_command() -> str:
148
148
  )
149
149
 
150
150
 
151
+ #: Where an approved Datalayer base keeps its interpreter: `python` on the
152
+ #: PATH is `/opt/conda/bin/python` (E1-05), and that is the environment a
153
+ #: sandbox's kernel runs in.
154
+ CONDA_PREFIX = "/opt/conda"
155
+
156
+
157
+ def micromamba_install_command(lock_path: str, *, micromamba: str = "micromamba") -> str:
158
+ """The command that installs an explicit lock into the base's own interpreter.
159
+
160
+ **Into `/opt/conda`, by name.** The base sets no `MAMBA_ROOT_PREFIX`, and
161
+ left to itself `micromamba install --name base` makes a *new* `base` under
162
+ `~/.local/share/mamba` — for a Datalayer base, inside the very home the
163
+ runtime mounts a person's content over, and nowhere `python` looks. The
164
+ first real conda build (2026-09-18) installed all 81 packages there, and
165
+ then failed its own `postInstall` with `No module named 'osgeo'`. Both the
166
+ root and the target prefix are named, so nothing depends on which user the
167
+ step happens to run as.
168
+ """
169
+ return (
170
+ f"{micromamba} install --yes --root-prefix {CONDA_PREFIX} "
171
+ f"--prefix {CONDA_PREFIX} --file {lock_path}"
172
+ )
173
+
174
+
151
175
  def _utcnow() -> datetime:
152
176
  return datetime.now(timezone.utc)
153
177
 
@@ -416,6 +440,46 @@ def pip_requirements_from_env_yaml(text: str) -> tuple[str, ...]:
416
440
  return tuple(requirements)
417
441
 
418
442
 
443
+ #: What the solved prefix's own interpreter is asked, to list the pip layer
444
+ #: whole. Standard library only, and it runs inside the prefix.
445
+ #:
446
+ #: ``micromamba env export`` names the pip packages the *file* asked for and
447
+ #: nothing they pulled in: the first real solve (2026-09-18) exported 7 pip
448
+ #: pins where pip had installed several dozen distributions, so every build
449
+ #: would have resolved ``tornado``, ``pyzmq``, ``traitlets`` and the rest
450
+ #: afresh — on each variant, on whichever day it ran — under a lock that
451
+ #: claimed to say the whole of what a build installs. A distribution records
452
+ #: who installed it in its own ``INSTALLER`` file, which is what tells the pip
453
+ #: layer from the conda packages that also carry Python metadata.
454
+ PIP_LAYER_SCRIPT = """\
455
+ import importlib.metadata as metadata
456
+
457
+ layer = {}
458
+ for distribution in metadata.distributions():
459
+ installer = (distribution.read_text("INSTALLER") or "").strip().lower()
460
+ name = distribution.metadata["Name"]
461
+ if installer in ("pip", "uv") and name:
462
+ layer[name.lower().replace("_", "-")] = distribution.version
463
+ for name in sorted(layer):
464
+ print(f"{name}=={layer[name]}")
465
+ """
466
+
467
+
468
+ def pip_requirements_from_listing(text: str) -> tuple[str, ...]:
469
+ """The pip layer :data:`PIP_LAYER_SCRIPT` printed: ``name==version`` lines.
470
+
471
+ Anything else on a line — a warning an interpreter wrote to the same
472
+ stream — is dropped rather than installed.
473
+ """
474
+ requirements: list[str] = []
475
+ for line in text.splitlines():
476
+ entry = line.strip()
477
+ name, separator, version = entry.partition("==")
478
+ if separator and name and version and " " not in entry:
479
+ requirements.append(entry)
480
+ return tuple(requirements)
481
+
482
+
419
483
  class CondaResolveRunner(Protocol):
420
484
  """Where a conda solve runs."""
421
485
 
@@ -559,9 +623,16 @@ class MicromambaResolveRunner:
559
623
  [self._micromamba, "env", "export", "--prefix", str(prefix)],
560
624
  say,
561
625
  )
626
+ # Every distribution pip installed, the transitive ones included:
627
+ # the export above names only what the file asked for.
628
+ listing = self._export(
629
+ [str(prefix / "bin" / "python"), "-c", PIP_LAYER_SCRIPT],
630
+ say,
631
+ )
562
632
  return CondaResolveOutcome(
563
633
  lock_text=export.stdout,
564
- pip_lock=pip_requirements_from_env_yaml(pip_export.stdout),
634
+ pip_lock=pip_requirements_from_listing(listing.stdout)
635
+ or pip_requirements_from_env_yaml(pip_export.stdout),
565
636
  )
566
637
 
567
638
  def _export(
@@ -659,9 +730,14 @@ class BuildkitCondaResolveRunner:
659
730
  f"RUN {micromamba} env export --explicit "
660
731
  "--prefix /solve/prefix > /solve/lock.txt",
661
732
  f"RUN {micromamba} env export --prefix /solve/prefix > /solve/pip-env.yml",
733
+ # The pip layer whole, from the prefix's own interpreter:
734
+ # a file of this package's, never a spec field.
735
+ "COPY pip_layer.py ./pip_layer.py",
736
+ "RUN /solve/prefix/bin/python pip_layer.py > /solve/pip-lock.txt",
662
737
  "FROM scratch",
663
738
  "COPY --from=solve /solve/lock.txt /lock.txt",
664
739
  "COPY --from=solve /solve/pip-env.yml /pip-env.yml",
740
+ "COPY --from=solve /solve/pip-lock.txt /pip-lock.txt",
665
741
  ]
666
742
  )
667
743
  + "\n"
@@ -686,6 +762,7 @@ class BuildkitCondaResolveRunner:
686
762
  with tempfile.TemporaryDirectory(prefix="dl-conda-solve-") as directory:
687
763
  root = Path(directory)
688
764
  (root / "environment.yml").write_text(request.environment_yml, encoding="utf-8")
765
+ (root / "pip_layer.py").write_text(PIP_LAYER_SCRIPT, encoding="utf-8")
689
766
  (root / "Dockerfile").write_text(self.dockerfile(request), encoding="utf-8")
690
767
  out = root / "out"
691
768
  command = [
@@ -724,7 +801,12 @@ class BuildkitCondaResolveRunner:
724
801
  raise parse_conda_failure(finished.stderr or finished.stdout or "")
725
802
  lock = (out / "lock.txt").read_text(encoding="utf-8")
726
803
  pip_env = out / "pip-env.yml"
804
+ pip_listing = out / "pip-lock.txt"
727
805
  pip_lock = (
806
+ pip_requirements_from_listing(pip_listing.read_text(encoding="utf-8"))
807
+ if pip_listing.exists()
808
+ else ()
809
+ ) or (
728
810
  pip_requirements_from_env_yaml(pip_env.read_text(encoding="utf-8"))
729
811
  if pip_env.exists()
730
812
  else ()
@@ -763,6 +845,69 @@ def explicit_lock_packages(lock_text: str) -> list[str]:
763
845
  return packages
764
846
 
765
847
 
848
+ #: `name-version-build.conda` (or `.tar.bz2`), as the last segment of an
849
+ #: explicit lock's URL. A conda name may itself contain `-`, so the version and
850
+ #: the build are the last two dash-separated fields, never the first two.
851
+ _EXPLICIT_PACKAGE = re.compile(
852
+ r"/(?P<name>[^/]+)-(?P<version>[^-/]+)-(?P<build>[^-/]+)\.(?:conda|tar\.bz2)(?:#.*)?$"
853
+ )
854
+
855
+
856
+ def conda_lock_python_packages(lock_text: str) -> dict[str, str]:
857
+ """The Python distributions an explicit lock installs, by name, with their versions.
858
+
859
+ Only the ones a build string marks as Python packages — `py313h…` for a
860
+ compiled one, `pyh…`/`pyhd8ed…` for a noarch one. A conda lock is mostly
861
+ libraries with no Python metadata at all (`libgdal-core`, `proj`, `openssl`),
862
+ and Appendix B check 5 asks the interpreter for a distribution's version:
863
+ handing it `proj` would fail a check with nothing wrong to report.
864
+ """
865
+ found: dict[str, str] = {}
866
+ for url in explicit_lock_packages(lock_text):
867
+ match = _EXPLICIT_PACKAGE.search(url)
868
+ if match and match["build"].startswith("py"):
869
+ found[match["name"].lower()] = match["version"]
870
+ return found
871
+
872
+
873
+ def conda_expected_packages(environment_yml: str, lock_text: str) -> dict[str, str]:
874
+ """What an `environment.yml` names at its top level, pinned to what its lock resolved.
875
+
876
+ Check 5's question, for a conda source (E3-02): the file's own `pip:`
877
+ requirements, and the conda packages it names that are Python
878
+ distributions. The interpreter is left out — check 3 asks about it, and
879
+ `python` is not a distribution the interpreter reports about itself.
880
+ Empty for a file that cannot be read: the spec's own validation refuses
881
+ one long before a build, and a smoke test is not where to say so again.
882
+ """
883
+ from packaging.requirements import InvalidRequirement, Requirement
884
+ from packaging.utils import canonicalize_name
885
+
886
+ try:
887
+ environment = parse_conda_environment(environment_yml)
888
+ except EnvironmentsError:
889
+ return {}
890
+ expected: dict[str, str] = {}
891
+ pythons = conda_lock_python_packages(lock_text)
892
+ for spec in environment.conda_dependencies:
893
+ name = _conda_package_name(spec)
894
+ if name != "python" and name in pythons:
895
+ expected[canonicalize_name(name)] = pythons[name]
896
+ pinned: dict[str, str] = {}
897
+ for requirement in conda_lock_pip_requirements(lock_text):
898
+ name, separator, version = requirement.partition("==")
899
+ if separator:
900
+ pinned[canonicalize_name(name.strip())] = version.strip()
901
+ for text in environment.pip_dependencies:
902
+ try:
903
+ name = canonicalize_name(Requirement(text).name)
904
+ except InvalidRequirement:
905
+ continue
906
+ if name in pinned:
907
+ expected[name] = pinned[name]
908
+ return expected
909
+
910
+
766
911
  def is_conda_lock(lock_text: str | None) -> bool:
767
912
  """Whether a lock is a conda explicit lock, and not the pip one.
768
913
 
@@ -650,13 +650,17 @@ class JupyterServerSandbox(Sandbox):
650
650
  "Failed to restart Jupyter kernel: the server answered %s", response.status_code
651
651
  )
652
652
  return False
653
- # The websocket the client holds is to the kernel that has just been
654
- # replaced; reconnecting is what makes the next execution land in the
655
- # new interpreter rather than on a channel nobody is reading.
656
- with contextlib.suppress(Exception):
657
- self._client.stop()
658
- with contextlib.suppress(Exception):
659
- self._client.start()
653
+ # **The websocket is kept, never reconnected.** jupyter-server binds a
654
+ # kernel's websocket to the kernel id, not to its process, and moves
655
+ # it onto the restarted kernel itself — which is how JupyterLab's own
656
+ # restart works. Reconnecting here broke every restart instead:
657
+ # `jupyter_kernel_client` caches its shell and IOPub channels bound to
658
+ # the socket they were built with, so after `stop()` and `start()` the
659
+ # client held a new socket while its channels still wrote to the
660
+ # closed one. The next execution answered `ok` with no output — found
661
+ # on r1, 2026-09-18, as checks 7, 8 and 9 all failing together, and
662
+ # reproduced against the approved base itself, with the deployed
663
+ # client. Swallowing both calls' errors is what hid it.
660
664
  return True
661
665
 
662
666
  @marks_execution
@@ -277,7 +277,14 @@ class TestTheDockerfileItGenerates:
277
277
  follows, so the kernel stack (E1-04) is present the same."""
278
278
  request = a_request(lock_text=CONDA_LOCK, spec=CONDA_SPEC)
279
279
  dockerfile = a_builder().dockerfile(request)
280
- assert "micromamba install --yes --name base --file /opt/datalayer/lock.txt" in dockerfile
280
+ # Into the base's own interpreter, by name: left to itself micromamba
281
+ # made a new `base` under the content home, where `python` never looks
282
+ # (found on the first real conda build, 2026-09-18).
283
+ assert (
284
+ "micromamba install --yes --root-prefix /opt/conda --prefix /opt/conda "
285
+ "--file /opt/datalayer/lock.txt"
286
+ ) in dockerfile
287
+ assert "--name base" not in dockerfile
281
288
  # micromamba is copied in from its pinned image before it is invoked.
282
289
  bootstrap = dockerfile.index("COPY --from=mambaorg/micromamba")
283
290
  micromamba = dockerfile.index("micromamba install")
@@ -503,6 +503,10 @@ class TestBuildingASnapshot:
503
503
  pip = next(i for i, call in enumerate(runs) if "ipykernel==7.3.0" in call.args[0])
504
504
  assert bootstrap < micromamba < pip
505
505
  assert not any("uv pip sync" in call.args[0] for call in runs)
506
+ # The interpreter a sandbox runs, not a new `base` under the content
507
+ # home (2026-09-18): the same command the Datalayer builder emits.
508
+ assert "--root-prefix /opt/conda --prefix /opt/conda" in runs[micromamba].args[0]
509
+ assert "--name base" not in runs[micromamba].args[0]
506
510
 
507
511
  def test_user_root_brackets_the_install_steps(self) -> None:
508
512
  """Daytona honours the base's `USER`, unlike E2B (E0-04): no synthetic
@@ -20,16 +20,20 @@ from code_sandboxes.environments.errors import EnvironmentsError
20
20
  from code_sandboxes.environments.resolve import protected_pins
21
21
  from code_sandboxes.environments.resolve_conda import (
22
22
  CONDA_LOCK_FORMAT,
23
+ PIP_LAYER_SCRIPT,
23
24
  BuildkitCondaResolveRunner,
24
25
  CondaResolveOutcome,
25
26
  CondaResolveRequest,
26
27
  MicromambaResolveRunner,
28
+ conda_expected_packages,
27
29
  conda_lock_document,
30
+ conda_lock_python_packages,
28
31
  explicit_lock_packages,
29
32
  merge_conda_pip,
30
33
  parse_conda_environment,
31
34
  parse_conda_failure,
32
35
  pip_requirements_from_env_yaml,
36
+ pip_requirements_from_listing,
33
37
  rendered_environment,
34
38
  resolve_conda_environment,
35
39
  )
@@ -393,6 +397,95 @@ class TestTheRunners:
393
397
  assert "COPY --from=mambaorg/micromamba" in dockerfile
394
398
  assert "env export --prefix /solve/prefix > /solve/pip-env.yml" in dockerfile
395
399
  assert "COPY --from=solve /solve/pip-env.yml /pip-env.yml" in dockerfile
400
+ # And the pip layer whole, asked of the prefix's own interpreter: the
401
+ # export names what the file asked for and nothing it pulled in.
402
+ assert "COPY pip_layer.py ./pip_layer.py" in dockerfile
403
+ assert "RUN /solve/prefix/bin/python pip_layer.py > /solve/pip-lock.txt" in dockerfile
404
+ assert "COPY --from=solve /solve/pip-lock.txt /pip-lock.txt" in dockerfile
405
+
406
+
407
+ class TestThePipLayerIsWhole:
408
+ """`micromamba env export` named 7 pip pins where pip had installed 310
409
+ distributions (the first real solve, 2026-09-18): everything they pulled
410
+ in would have been resolved afresh by each build, on each variant."""
411
+
412
+ def test_the_script_lists_what_pip_installed_and_not_what_conda_did(self, tmp_path) -> None:
413
+ import subprocess as process
414
+ import sys
415
+
416
+ site = tmp_path / "site"
417
+ for name, version, installer in (
418
+ ("Tornado", "6.5.10", "pip"),
419
+ ("jupyter_server", "2.21.0+datalayer.1", "uv"),
420
+ ("GDAL", "3.11.5", "conda"),
421
+ ("unknown", "1.0", ""),
422
+ ):
423
+ info = site / f"{name}-{version}.dist-info"
424
+ info.mkdir(parents=True)
425
+ (info / "METADATA").write_text(
426
+ f"Metadata-Version: 2.1\nName: {name}\nVersion: {version}\n"
427
+ )
428
+ (info / "INSTALLER").write_text(installer + "\n")
429
+ listed = process.run( # noqa: S603 - this interpreter, this package's script
430
+ [
431
+ sys.executable,
432
+ "-S",
433
+ "-c",
434
+ f"import sys; sys.path[:] = [{str(site)!r}] + sys.path\n" + PIP_LAYER_SCRIPT,
435
+ ],
436
+ capture_output=True,
437
+ text=True,
438
+ check=True,
439
+ ).stdout
440
+ assert pip_requirements_from_listing(listed) == (
441
+ "jupyter-server==2.21.0+datalayer.1",
442
+ "tornado==6.5.10",
443
+ )
444
+
445
+ def test_a_line_that_is_not_a_pin_is_never_installed(self) -> None:
446
+ listing = "a==1\nWARNING: something was said\n\nb-c==2.0\n== \n"
447
+ assert pip_requirements_from_listing(listing) == ("a==1", "b-c==2.0")
448
+
449
+
450
+ class TestWhatCheckFiveExpectsOfACondaLock:
451
+ LOCK = (
452
+ "# datalayer-pip: shapely==2.1.2\n# datalayer-pip: tornado==6.5.10\n@EXPLICIT\n"
453
+ "https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.11.5-h4f65170_7.conda#aa\n"
454
+ "https://conda.anaconda.org/conda-forge/linux-64/gdal-3.11.5-py313h1ee8c46_7.conda#bb\n"
455
+ "https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-pyhcf101f3_0.conda#cc\n"
456
+ "https://conda.anaconda.org/conda-forge/linux-64/python-3.13.15-h2b335a9_0_cp313.conda#dd\n"
457
+ )
458
+ FILE = (
459
+ "channels: [conda-forge]\n"
460
+ "dependencies:\n"
461
+ " - python=3.13\n"
462
+ " - gdal=3.11\n"
463
+ " - libgdal-core\n"
464
+ " - pip:\n"
465
+ " - shapely==2.1.2\n"
466
+ )
467
+
468
+ def test_only_python_distributions_are_read_out_of_the_lock(self) -> None:
469
+ """`libgdal-core` has no Python metadata: asking the interpreter for
470
+ its version would fail a check with nothing wrong to report."""
471
+ assert conda_lock_python_packages(self.LOCK) == {
472
+ "gdal": "3.11.5",
473
+ "typing-extensions": "4.15.0",
474
+ }
475
+
476
+ def test_it_is_what_the_file_names_pinned_to_what_the_lock_resolved(self) -> None:
477
+ assert conda_expected_packages(self.FILE, self.LOCK) == {
478
+ "gdal": "3.11.5",
479
+ "shapely": "2.1.2",
480
+ }
481
+
482
+ def test_check_five_is_handed_them_for_a_conda_version(self) -> None:
483
+ """Read from `packages.python`, as a pip source is, it was handed
484
+ nothing, and check 5 passed for having nothing to check."""
485
+ from code_sandboxes.environments.conformance import expected_packages
486
+
487
+ environment = validate_environment(a_conda_spec(content=self.FILE))
488
+ assert expected_packages(environment, self.LOCK) == {"gdal": "3.11.5", "shapely": "2.1.2"}
396
489
 
397
490
 
398
491
  # -- The whole resolve, through the recorded runner --------------------------
@@ -559,6 +559,34 @@ class TestRestartingTheKernelRatherThanTheConnection:
559
559
  finally:
560
560
  sandbox.stop()
561
561
 
562
+ def test_the_connection_is_kept_across_the_restart(self, monkeypatch):
563
+ """jupyter-server moves a kernel's websocket onto the restarted kernel.
564
+
565
+ Reconnecting broke it: `jupyter_kernel_client`'s channels stay bound
566
+ to the socket they were built with, so after a `stop()` and `start()`
567
+ every execution answered `ok` with no output (r1, 2026-09-18).
568
+ """
569
+ sandbox = _started_sandbox(monkeypatch, kernel_id="kernel-1")
570
+ lifecycle: list[str] = []
571
+ client = sandbox._client
572
+ monkeypatch.setattr(client, "stop", lambda *a, **k: lifecycle.append("stop"))
573
+ monkeypatch.setattr(client, "start", lambda *a, **k: lifecycle.append("start"))
574
+
575
+ class _Response:
576
+ ok = True
577
+ status_code = 200
578
+
579
+ monkeypatch.setattr(
580
+ "code_sandboxes.jupyter_server_sandbox.requests.post",
581
+ lambda *args, **kwargs: _Response(),
582
+ )
583
+ try:
584
+ assert sandbox.restart_kernel() is True
585
+ assert lifecycle == []
586
+ assert sandbox._client is client
587
+ finally:
588
+ sandbox.stop()
589
+
562
590
  def test_a_server_that_refuses_the_restart_is_reported_not_raised(self, monkeypatch):
563
591
  sandbox = _started_sandbox(monkeypatch, kernel_id="kernel-1")
564
592
 
File without changes