code-sandboxes 1.9.30__tar.gz → 1.9.31__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.30 → code_sandboxes-1.9.31}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/conformance.py +37 -1
  4. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/resolve.py +50 -0
  5. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/environments/specification.mdx +1 -1
  6. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/daytona.mdx +27 -9
  7. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/e2b.mdx +1 -1
  8. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/modal.mdx +1 -1
  9. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_resolve.py +87 -1
  10. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/build.yml +0 -0
  11. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/environments-live.yml +0 -0
  12. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/py-code-style.yml +0 -0
  13. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/py-tests.yml +0 -0
  14. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/py-typing.yml +0 -0
  15. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.github/workflows/reusable-python.yml +0 -0
  16. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.gitignore +0 -0
  17. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.licenserc.yaml +0 -0
  18. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.pre-commit-config.yaml +0 -0
  19. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/.vscode/settings.json +0 -0
  20. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/CHANGELOG.md +0 -0
  21. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/Dockerfile +0 -0
  22. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/LICENSE +0 -0
  23. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/Makefile +0 -0
  24. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/README.md +0 -0
  25. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/__init__.py +0 -0
  26. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/base.py +0 -0
  27. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/bridge_mount.py +0 -0
  28. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/builds.py +0 -0
  29. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/cli.py +0 -0
  30. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/client.py +0 -0
  31. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/cloudflare_sandbox.py +0 -0
  32. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/commands.py +0 -0
  33. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/console.py +0 -0
  34. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/contents.py +0 -0
  35. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/coreweave_sandbox.py +0 -0
  36. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/datalayer_sandbox.py +0 -0
  37. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/daytona_sandbox.py +0 -0
  38. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/docker_sandbox.py +0 -0
  39. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/e2b_sandbox.py +0 -0
  40. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/__init__.py +0 -0
  41. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/__main__.py +0 -0
  42. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/accounts.py +0 -0
  43. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/__init__.py +0 -0
  44. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/datalayer.py +0 -0
  45. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/daytona.py +0 -0
  46. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/e2b.py +0 -0
  47. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/managed.py +0 -0
  48. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/adapters/modal.py +0 -0
  49. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/attest.py +0 -0
  50. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/bases.py +0 -0
  51. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/breaker.py +0 -0
  52. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/build_secrets.py +0 -0
  53. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/builders.py +0 -0
  54. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/canonical.py +0 -0
  55. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  56. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  57. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  58. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/contract.py +0 -0
  59. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/doctor/__init__.py +0 -0
  60. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/doctor/build.py +0 -0
  61. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  62. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  63. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/errors.py +0 -0
  64. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/files.py +0 -0
  65. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/image_import.py +0 -0
  66. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/lifecycle.py +0 -0
  67. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/policy.py +0 -0
  68. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/redact.py +0 -0
  69. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/resolve_conda.py +0 -0
  70. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/schema.py +0 -0
  71. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/environments/spec.py +0 -0
  72. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/eval_sandbox.py +0 -0
  73. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/exceptions.py +0 -0
  74. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/filesystem.py +0 -0
  75. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/google_colab.py +0 -0
  76. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/google_colab_sandbox.py +0 -0
  77. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/interfaces.py +0 -0
  78. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/jupyter_ingress.py +0 -0
  79. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  80. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/kaggle.py +0 -0
  81. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/kaggle_execute.py +0 -0
  82. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/kaggle_live.py +0 -0
  83. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/kaggle_sandbox.py +0 -0
  84. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/lifecycle.py +0 -0
  85. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/manage.py +0 -0
  86. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/modal_sandbox.py +0 -0
  87. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/models.py +0 -0
  88. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/monty_sandbox.py +0 -0
  89. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/provider_ingress.py +0 -0
  90. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/code_sandboxes/providers.py +0 -0
  91. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docker/Dockerfile +0 -0
  92. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/.gitignore +0 -0
  93. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/LICENSE +0 -0
  94. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/Makefile +0 -0
  95. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/README.md +0 -0
  96. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/babel.config.js +0 -0
  97. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/api-reference/_category_.yml +0 -0
  98. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/api-reference/index.mdx +0 -0
  99. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/cli/_category_.yml +0 -0
  100. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/cli/index.mdx +0 -0
  101. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/contribute/_category_.yml +0 -0
  102. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/contribute/index.mdx +0 -0
  103. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/environments/_category_.yml +0 -0
  104. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/environments/contract.mdx +0 -0
  105. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/environments/index.mdx +0 -0
  106. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/environments/spike-2026-09.mdx +0 -0
  107. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/examples/_category_.yml +0 -0
  108. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/examples/index.mdx +0 -0
  109. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/guide/_category_.yml +0 -0
  110. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/guide/index.mdx +0 -0
  111. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/index.mdx +0 -0
  112. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/install/_category_.yml +0 -0
  113. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/install/index.mdx +0 -0
  114. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/provider-ingress.mdx +0 -0
  115. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/_category_.yml +0 -0
  116. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/cloudflare.mdx +0 -0
  117. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/coreweave.mdx +0 -0
  118. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/datalayer.mdx +0 -0
  119. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/docker.mdx +0 -0
  120. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/eval.mdx +0 -0
  121. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/google-colab.mdx +0 -0
  122. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/index.mdx +0 -0
  123. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/jupyter-server.mdx +0 -0
  124. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/kaggle.mdx +0 -0
  125. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docs/providers/monty.mdx +0 -0
  126. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/docusaurus.config.js +0 -0
  127. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/package.json +0 -0
  128. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/sidebars.js +0 -0
  129. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow1.js +0 -0
  130. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow1.module.css +0 -0
  131. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow2.js +0 -0
  132. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow2.module.css +0 -0
  133. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow3.js +0 -0
  134. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/components/HomepageRow3.module.css +0 -0
  135. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/css/custom.css +0 -0
  136. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/pages/index.module.css +0 -0
  137. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/pages/markdown-page.md +0 -0
  138. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/theme/CustomDocItem.tsx +0 -0
  139. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/src/theme/IconsTitle.tsx +0 -0
  140. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/bricks.svg +0 -0
  141. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/building-construction.svg +0 -0
  142. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/cloud-sun.svg +0 -0
  143. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/cloud.svg +0 -0
  144. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/collaboration.svg +0 -0
  145. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/copy-left.svg +0 -0
  146. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/logo.png +0 -0
  147. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/logo.svg +0 -0
  148. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/personas/business.svg +0 -0
  149. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  150. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/personas/devops.svg +0 -0
  151. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/datalayer/project/project-line.svg +0 -0
  152. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/favicon.ico +0 -0
  153. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/jupyter.svg +0 -0
  154. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/open-source.svg +0 -0
  155. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/react-js.svg +0 -0
  156. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/react-js2.svg +0 -0
  157. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/ringed-planet.svg +0 -0
  158. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/robot.svg +0 -0
  159. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/rocket.svg +0 -0
  160. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/shield.svg +0 -0
  161. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/sparkles.svg +0 -0
  162. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/streamlit.gif +0 -0
  163. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/super-hero.svg +0 -0
  164. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/target.svg +0 -0
  165. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/docs/static/img/thumbs-up.svg +0 -0
  166. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/README.md +0 -0
  167. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/Makefile +0 -0
  168. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/cloudflare_sandbox_example.py +0 -0
  169. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/coreweave_sandbox_example.py +0 -0
  170. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/datalayer_sandbox_example.py +0 -0
  171. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/daytona_sandbox_example.py +0 -0
  172. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/docker_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/e2b_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/eval_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/google_colab_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/kaggle_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/modal_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/exec/monty_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/Makefile +0 -0
  181. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/cloudflare_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/coreweave_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/datalayer_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/daytona_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/docker_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/e2b_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/eval_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/google_colab_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/kaggle_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/modal_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/examples/repl/monty_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/pyproject.toml +0 -0
  194. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/schemas/environment-v1alpha1.json +0 -0
  195. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/__init__.py +0 -0
  196. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/conftest.py +0 -0
  197. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  198. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_bridge_mount.py +0 -0
  199. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_builds.py +0 -0
  200. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_cli_exec.py +0 -0
  201. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_cli_repl.py +0 -0
  202. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_client.py +0 -0
  203. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_client_contents.py +0 -0
  204. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_client_files.py +0 -0
  205. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_client_outcome_error_output.py +0 -0
  206. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_cloudflare.py +0 -0
  207. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_commands_envelope.py +0 -0
  208. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_console.py +0 -0
  209. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_contents_contract.py +0 -0
  210. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_contents_live_matrix.py +0 -0
  211. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_coreweave.py +0 -0
  212. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_datalayer_sandbox.py +0 -0
  213. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_daytona.py +0 -0
  214. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_e2b.py +0 -0
  215. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_accounts.py +0 -0
  216. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_attest.py +0 -0
  217. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_bases.py +0 -0
  218. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_breaker.py +0 -0
  219. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_build_secrets.py +0 -0
  220. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_builders.py +0 -0
  221. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_canonical.py +0 -0
  222. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_conformance.py +0 -0
  223. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_constraints.py +0 -0
  224. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_contract.py +0 -0
  225. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_cross_variant_report.py +0 -0
  226. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_datalayer_builder.py +0 -0
  227. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_daytona_builder.py +0 -0
  228. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_dockerfile_source.py +0 -0
  229. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_doctor.py +0 -0
  230. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_e2b_builder.py +0 -0
  231. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_errors.py +0 -0
  232. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_image_import.py +0 -0
  233. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_lifecycle.py +0 -0
  234. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_live_matrix.py +0 -0
  235. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_managed_builders.py +0 -0
  236. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_modal_builder.py +0 -0
  237. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_organization_policy.py +0 -0
  238. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_redact.py +0 -0
  239. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_resolve_conda.py +0 -0
  240. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_spec.py +0 -0
  241. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_environment_spec_organization_policy.py +0 -0
  242. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_eval.py +0 -0
  243. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  244. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_factory.py +0 -0
  245. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_google_colab.py +0 -0
  246. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_integration.py +0 -0
  247. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_jupyter_ingress.py +0 -0
  248. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_jupyter_server.py +0 -0
  249. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_jupyter_server_streaming.py +0 -0
  250. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_kaggle.py +0 -0
  251. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_kaggle_execute.py +0 -0
  252. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_kaggle_live.py +0 -0
  253. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_kaggle_session.py +0 -0
  254. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_kernel_client_compatibility.py +0 -0
  255. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_lifecycle_vocabulary.py +0 -0
  256. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_manage.py +0 -0
  257. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_manage_datalayer.py +0 -0
  258. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_modal_google_colab_sandbox.py +0 -0
  259. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_modal_session.py +0 -0
  260. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_models.py +0 -0
  261. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_output_line_fidelity.py +0 -0
  262. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_provider_catalog_names.py +0 -0
  263. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_provider_ingress.py +0 -0
  264. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_providers.py +0 -0
  265. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/tests/test_sandboxes.py +0 -0
  266. {code_sandboxes-1.9.30 → code_sandboxes-1.9.31}/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.30
3
+ Version: 1.9.31
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.30"
6
+ __version__ = "1.9.31"
@@ -487,7 +487,7 @@ def expected_packages(environment: Any, lock_text: str) -> dict[str, str]:
487
487
  return conda_expected_packages(dependency_file.content, lock_text or "")
488
488
  pinned = locked_versions(lock_text) if lock_text else {}
489
489
  names: list[str] = []
490
- for text in environment.spec.packages.python.dependencies:
490
+ for text in _declared_dependencies(environment):
491
491
  try:
492
492
  names.append(canonicalize_name(Requirement(text).name))
493
493
  except InvalidRequirement:
@@ -495,6 +495,42 @@ def expected_packages(environment: Any, lock_text: str) -> dict[str, str]:
495
495
  return {name: pinned[name] for name in names if name in pinned}
496
496
 
497
497
 
498
+ def _declared_dependencies(environment: Any) -> list[str]:
499
+ """The requirements a person declared, wherever the source keeps them.
500
+
501
+ A `dependencyFile` source names its packages in the file and leaves
502
+ `packages.python` empty, which is what the resolver reads too (E3-01).
503
+ Read from `packages.python` alone, check 5 was handed nothing for a
504
+ `requirements.txt` and passed for it, found live on 2026-09-18 (E3-08).
505
+ """
506
+ from .spec import parse_requirements_txt
507
+
508
+ build = environment.spec.build
509
+ dependency_file = build.dependency_file
510
+ if build.source == "dependencyFile" and dependency_file is not None:
511
+ if dependency_file.source_format == "requirements":
512
+ return parse_requirements_txt(dependency_file.content)
513
+ if dependency_file.source_format == "pyproject":
514
+ return _pyproject_dependencies(dependency_file.content)
515
+ return list(environment.spec.packages.python.dependencies)
516
+
517
+
518
+ def _pyproject_dependencies(text: str) -> list[str]:
519
+ """`[project].dependencies` of a `pyproject.toml`, or none when it cannot be read."""
520
+ try:
521
+ import tomllib as toml
522
+ except ImportError: # Python 3.10
523
+ try:
524
+ import tomli as toml # type: ignore[no-redef]
525
+ except ImportError:
526
+ return []
527
+ try:
528
+ project = toml.loads(text).get("project") or {}
529
+ except toml.TOMLDecodeError:
530
+ return []
531
+ return [str(item) for item in project.get("dependencies") or []]
532
+
533
+
498
534
  def run_core_tier(
499
535
  sandbox: Sandbox,
500
536
  *,
@@ -39,6 +39,7 @@ Where the solve runs is the :class:`ResolveRunner`'s business:
39
39
  from __future__ import annotations
40
40
 
41
41
  import hashlib
42
+ import os
42
43
  import re
43
44
  import shlex
44
45
  import shutil
@@ -860,9 +861,38 @@ def locked_versions(lock_text: str) -> dict[str, str]:
860
861
  ]
861
862
  if len(pinned) == 1:
862
863
  versions[canonicalize_name(requirement.name)] = pinned[0]
864
+ elif requirement.url:
865
+ wheel = _wheel_version(requirement.name, requirement.url)
866
+ if wheel:
867
+ versions[canonicalize_name(requirement.name)] = wheel
863
868
  return versions
864
869
 
865
870
 
871
+ def _wheel_version(name: str, url: str) -> str | None:
872
+ """The version of a direct reference to a wheel, read from its file name.
873
+
874
+ How a `pyproject` lock brings Datalayer's `jupyter-server` fork, which no
875
+ index serves (E3-08): `uv` records a hash only for a wheel it fetched by
876
+ URL, so the export pins it as `jupyter-server @ https://…whl`, with no
877
+ `==` for the version. The wheel's name carries one, and nothing else here
878
+ does. A reference that is not a wheel of the same distribution pins none.
879
+ """
880
+ from urllib.parse import unquote, urlsplit
881
+
882
+ from packaging.utils import InvalidWheelFilename, canonicalize_name, parse_wheel_filename
883
+
884
+ filename = unquote(urlsplit(url).path.rsplit("/", 1)[-1])
885
+ if not filename.endswith(".whl"):
886
+ return None
887
+ try:
888
+ wheel_name, version, _build, _tags = parse_wheel_filename(filename)
889
+ except InvalidWheelFilename:
890
+ return None
891
+ if wheel_name != canonicalize_name(name):
892
+ return None
893
+ return str(version)
894
+
895
+
866
896
  def apt_pins_in(lock_text: str) -> dict[str, str]:
867
897
  """The apt versions a lock records, by package.
868
898
 
@@ -1058,10 +1088,12 @@ def _verified_pyproject_lock(
1058
1088
  root = Path(directory)
1059
1089
  (root / "pyproject.toml").write_text(dependency_file.content, encoding="utf-8")
1060
1090
  (root / "uv.lock").write_text(dependency_file.lock_content, encoding="utf-8")
1091
+ environment = _uv_environment(root)
1061
1092
  try:
1062
1093
  checked = invoke(
1063
1094
  [resolved_uv, "lock", "--dry-run"],
1064
1095
  cwd=root,
1096
+ env=environment,
1065
1097
  capture_output=True,
1066
1098
  text=True,
1067
1099
  timeout=timeout,
@@ -1109,6 +1141,7 @@ def _verified_pyproject_lock(
1109
1141
  exported = invoke(
1110
1142
  [resolved_uv, "export", "--locked", "--format", "requirements.txt"],
1111
1143
  cwd=root,
1144
+ env=environment,
1112
1145
  capture_output=True,
1113
1146
  text=True,
1114
1147
  timeout=timeout,
@@ -1138,6 +1171,23 @@ def _verified_pyproject_lock(
1138
1171
  }
1139
1172
 
1140
1173
 
1174
+ def _uv_environment(root: Path) -> dict[str, str]:
1175
+ """What `uv` checks a lock with: its cache in the scratch directory, and no downloads.
1176
+
1177
+ The durable worker runs as a user with no home, so `uv`'s default cache
1178
+ under `~/.cache` could not be made, and neither could the interpreter it
1179
+ downloads when none satisfies `requires-python` (found on 2026-09-18, E3-08).
1180
+ The interpreter comes from the worker's image instead, where the base
1181
+ channel's own Python is installed: a check that fetched one per resolve would
1182
+ be a check that depends on the network in a way nothing records.
1183
+ """
1184
+ return {
1185
+ **os.environ,
1186
+ "UV_CACHE_DIR": str(root / ".uv-cache"),
1187
+ "UV_PYTHON_DOWNLOADS": "never",
1188
+ }
1189
+
1190
+
1141
1191
  def _refuse_unless_protected_pins_are_locked(packages: Mapping[str, str]) -> None:
1142
1192
  """A `pyproject` source is never merged with Datalayer's pins (E3-01):
1143
1193
  bringing your own lock is the point, so nothing is force-injected the
@@ -42,7 +42,7 @@ A document is refused with `DL_ENV_SPEC_INVALID` when a field is wrong, and with
42
42
  | `compatibility.variants.required` | `[datalayer]` | At least one of `datalayer`, `e2b`, `daytona`, `modal`. |
43
43
  | `compatibility.variants.optional` | none | The same variants, none of them also required. |
44
44
  | `compatibility.regions` | none | Region names. |
45
- | `build.source` | `packages` | `packages`, `dependencyFile` (a `requirements.txt`; a `pyproject.toml` whose own `uv.lock` is checked against Datalayer's protected pins and exported rather than re-resolved; or a conda `environment.yml`, resolved in its own `micromamba` solve into an explicit lock with the protected pins merged over its `pip:` layer) and `image` (an existing image from a bootstrap registry allowlist, replacing the approved base) all resolve on the Datalayer variant; `image` from a private registry, and `dockerfile`, are refused until they do. |
45
+ | `build.source` | `packages` | `packages`; `dependencyFile` (a `requirements.txt`; a `pyproject.toml` whose own `uv.lock` is checked against Datalayer's protected pins and exported rather than re-resolved; or a conda `environment.yml`, resolved in its own `micromamba` solve into an explicit lock with the protected pins merged over its `pip:` layer); `dockerfile` (`build.dockerfile.content`, inline: its `FROM` names an approved base with its channel as the tag, which is what resolves, and what its own instructions install is not locked, as the lock's header says); and `image` (an existing image from a bootstrap registry allowlist, replacing the approved base). `packages`, a `requirements.txt`, a conda `environment.yml` and `dockerfile` build on the Datalayer and Daytona variants. A `pyproject.toml` does not build yet: its `uv.lock` has to lock Datalayer's `jupyter-server` fork, which no package index serves. `image` resolves but does not yet start as a sandbox, because the imported image has no Datalayer runtime layer; `image` from a private registry, and a `COPY` from a Dockerfile's build context, are refused. |
46
46
 
47
47
  ## The spec digest
48
48
 
@@ -229,15 +229,33 @@ sandbox.files.read_bytes("/tmp/data.parquet")
229
229
 
230
230
  ## Environments
231
231
 
232
- An [Environment](/docs/environments) built for Daytona is a **snapshot** —
233
- built from the approved Datalayer base, `uv pip sync --require-hashes`
234
- against the resolved lock, your files and `postInstall` commands, and an
235
- explicit `tini -- sleep infinity` entrypoint (Daytona's own default,
236
- otherwise, is a bare `sleep infinity`, with no init to reap orphans). The
237
- base already carries `uv`, the doctor and the sandbox contract's user and
238
- locale, so none of that is reinstalled — only the lock is genuinely
239
- per-build. The snapshot's own `id` is the artifact; a build also names it,
240
- for operability, but a launch never uses the name.
232
+ An [Environment](/environments) built for Daytona is a **snapshot** —
233
+ built from the approved Datalayer base, the resolved lock, your files and
234
+ `postInstall` commands, and an explicit `tini -- sleep infinity` entrypoint
235
+ (Daytona's own default, otherwise, is a bare `sleep infinity`, with no init
236
+ to reap orphans). The base already carries `uv`, the doctor and the sandbox
237
+ contract's user and locale, so none of that is reinstalled — only the lock is
238
+ genuinely per-build. The snapshot's own `id` is the artifact; a build also
239
+ names it, for operability, but a launch never uses the name.
240
+
241
+ How the lock is installed depends on where the environment came from:
242
+
243
+ | Source | Installed with |
244
+ |---|---|
245
+ | A package list, a `requirements.txt`, a `pyproject.toml` and its `uv.lock` | `uv pip sync --require-hashes`: the snapshot holds exactly the lock |
246
+ | A conda `environment.yml` | `micromamba install` of the explicit lock into the base's own interpreter, then the pip layer the solve recorded |
247
+ | A Dockerfile | Daytona's `Image.from_dockerfile`, its approved `FROM` pinned to the resolved digest, then `uv pip install --require-hashes` of the lock — `install`, not `sync`, so what the Dockerfile installed stays |
248
+
249
+ **Deleted by id, and never stranded.** Retention deletes a snapshot nothing
250
+ keeps any more, by its `id` — a name is reused once its snapshot is gone —
251
+ and a snapshot already gone counts as deleted. A build that fails *after*
252
+ Daytona built its snapshot, at the core tier or at the record, deletes that
253
+ snapshot before it reports the failure, so nothing is left in your account
254
+ that no version names.
255
+
256
+ `make env-build-daytona`, in the `environments` folder of the
257
+ [examples repository](https://github.com/datalayer/examples), builds a
258
+ version for Daytona from the command line.
241
259
 
242
260
  **Bring your own account:** a build runs with the environment owner's own
243
261
  Daytona organization, not this package's ambient credentials — either
@@ -190,7 +190,7 @@ sandbox.files.read_bytes("/tmp/data.parquet")
190
190
 
191
191
  ## Environments
192
192
 
193
- An [Environment](/docs/environments) built for E2B is a **template**, not
193
+ An [Environment](/environments) built for E2B is a **template**, not
194
194
  built from the Datalayer base the other three variants share: E2B's own
195
195
  code-interpreter server (the FastAPI service `run_code` and contexts
196
196
  actually talk to) is proprietary and ships baked into E2B's own
@@ -74,7 +74,7 @@ with Sandbox.create(
74
74
 
75
75
  ## Environments
76
76
 
77
- An [Environment](/docs/environments) built for Modal is an **image id**,
77
+ An [Environment](/environments) built for Modal is an **image id**,
78
78
  `im-…` — pulled `from_aws_ecr` off the approved Datalayer base, with your
79
79
  lock installed by `uv pip sync --require-hashes` on top. A published name
80
80
  is worth having for operability, but a launch always pins the id: each
@@ -591,6 +591,19 @@ class TestARequirementsFile:
591
591
  assert from_packages["content"] == from_file["content"]
592
592
  assert from_packages["digest"] == from_file["digest"]
593
593
 
594
+ def test_check_five_is_handed_what_the_file_names(self) -> None:
595
+ """Read from `packages.python`, which a file source leaves empty, it
596
+ was handed nothing and check 5 passed for having nothing to check
597
+ (found live on 2026-09-18, E3-08)."""
598
+ from code_sandboxes.environments.conformance import expected_packages
599
+ from code_sandboxes.environments.spec import validate_environment
600
+
601
+ environment = validate_environment(
602
+ a_dependency_file_spec(content="geopandas==1.1.1 # inline\n-r other.txt\nsix>=1\n")
603
+ )
604
+ lock = "geopandas==1.1.1\nsix==1.16.0\nshapely==2.1.2\n"
605
+ assert expected_packages(environment, lock) == {"geopandas": "1.1.1", "six": "1.16.0"}
606
+
594
607
 
595
608
  # -- A pyproject.toml and its uv.lock (E3-01) ----------------------------------
596
609
 
@@ -606,9 +619,11 @@ class FakeUv:
606
619
  def __init__(self, *answers: tuple[int, str, str]) -> None:
607
620
  self.answers = list(answers)
608
621
  self.calls: list[list[str]] = []
622
+ self.environments: list[dict[str, str]] = []
609
623
 
610
- def __call__(self, argv, **_kwargs):
624
+ def __call__(self, argv, **kwargs):
611
625
  self.calls.append(list(argv))
626
+ self.environments.append(dict(kwargs.get("env") or {}))
612
627
  code, out, err = self.answers[min(len(self.calls), len(self.answers)) - 1]
613
628
  return subprocess.CompletedProcess(list(argv), code, out, err)
614
629
 
@@ -645,6 +660,13 @@ EXPORTED = (
645
660
  )
646
661
 
647
662
 
663
+ #: Where a `pyproject` author locks Datalayer's `jupyter-server` fork from.
664
+ FORK_WHEEL_URL = (
665
+ "https://github.com/datalayer-externals/jupyter-server/releases/download/"
666
+ "v2.21.0-datalayer.1/jupyter_server-2.21.0%2Bdatalayer.1-py3-none-any.whl"
667
+ )
668
+
669
+
648
670
  class TestAPyprojectFile:
649
671
  def test_a_current_lock_is_exported_rather_than_resolved(self) -> None:
650
672
  uv = FakeUv((0, "", "Resolved 2 packages in 1ms\n"), (0, EXPORTED, ""))
@@ -791,6 +813,70 @@ class TestAPyprojectFile:
791
813
  )
792
814
  assert raised.value.code.code == "DL_ENV_PROVIDER_ERROR"
793
815
 
816
+ def test_check_five_is_handed_the_projects_own_dependencies(self) -> None:
817
+ """`[project].dependencies`, not every package the export pins: most of
818
+ a lock is transitive, and check 5 imports what it is handed."""
819
+ from code_sandboxes.environments.conformance import expected_packages
820
+ from code_sandboxes.environments.spec import validate_environment
821
+
822
+ environment = validate_environment(a_pyproject_spec())
823
+ assert expected_packages(environment, EXPORTED) == {"six": "1.16.0"}
824
+
825
+ def test_the_fork_brought_by_url_is_a_locked_protected_pin(self) -> None:
826
+ """No index serves `2.21.0+datalayer.1`, and `uv` hashes a wheel only
827
+ when it fetched it by URL: so a real author's export names the fork
828
+ `jupyter-server @ https://…whl`, with no `==` (E3-08). Its version is
829
+ in the wheel's name, and that is what the pin is checked against."""
830
+ by_url = EXPORTED.replace(
831
+ "jupyter-server==2.21.0+datalayer.1 \\\n",
832
+ f"jupyter-server @ {FORK_WHEEL_URL} \\\n",
833
+ )
834
+ uv = FakeUv((0, "", "Resolved 7 packages in 1ms\n"), (0, by_url, ""))
835
+ answer = resolve_environment(
836
+ spec=a_pyproject_spec(),
837
+ variants=["datalayer"],
838
+ bases=BASES,
839
+ uv="/usr/bin/uv",
840
+ pyproject_run=uv,
841
+ )
842
+ assert answer["content"] == by_url
843
+ assert locked_versions(by_url)["jupyter-server"] == "2.21.0+datalayer.1"
844
+
845
+ def test_uv_keeps_its_cache_in_the_scratch_directory_and_downloads_no_python(self) -> None:
846
+ """The durable worker's user has no home: `uv`'s default cache could not
847
+ be made, nor the interpreter it would download (E3-08)."""
848
+ uv = FakeUv((0, "", "Resolved 7 packages in 1ms\n"), (0, EXPORTED, ""))
849
+ resolve_environment(
850
+ spec=a_pyproject_spec(),
851
+ variants=["datalayer"],
852
+ bases=BASES,
853
+ uv="/usr/bin/uv",
854
+ pyproject_run=uv,
855
+ )
856
+ assert len(uv.environments) == 2
857
+ for environment in uv.environments:
858
+ assert environment["UV_PYTHON_DOWNLOADS"] == "never"
859
+ assert environment["UV_CACHE_DIR"].endswith("/.uv-cache")
860
+ assert "/dl-pyproject-" in environment["UV_CACHE_DIR"]
861
+ assert environment["PATH"]
862
+
863
+ def test_a_url_that_is_not_that_distributions_wheel_pins_nothing(self) -> None:
864
+ assert (
865
+ locked_versions(
866
+ "jupyter-server @ https://example.org/jupyter_server-2.21.0.tar.gz\n"
867
+ f"ipykernel @ {FORK_WHEEL_URL}\n"
868
+ "six @ https://example.org/not-a-wheel-name.whl\n"
869
+ )
870
+ == {}
871
+ )
872
+
873
+ def test_a_pyproject_check_five_cannot_read_hands_it_nothing(self) -> None:
874
+ from code_sandboxes.environments.conformance import expected_packages
875
+ from code_sandboxes.environments.spec import validate_environment
876
+
877
+ environment = validate_environment(a_pyproject_spec(content="[project\n"))
878
+ assert expected_packages(environment, EXPORTED) == {}
879
+
794
880
 
795
881
  # -- An imported image (E3-04) -------------------------------------------------
796
882
 
File without changes