modal 1.5.3.dev9__tar.gz → 1.5.3.dev10__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 (232) hide show
  1. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/PKG-INFO +1 -1
  2. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_function_variants.py +1 -1
  3. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_functions.py +1 -1
  4. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/task_command_router_client.py +5 -0
  5. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/client.pyi +2 -2
  6. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cloud_bucket_mount.py +15 -3
  7. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cloud_bucket_mount.pyi +2 -2
  8. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/functions.pyi +6 -6
  9. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox.py +89 -30
  10. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox.pyi +65 -9
  11. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/secret.py +9 -0
  12. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/secret.pyi +18 -0
  13. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/snapshot.py +17 -1
  14. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/snapshot.pyi +22 -0
  15. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/PKG-INFO +1 -1
  16. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2.py +396 -386
  17. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2.pyi +20 -1
  18. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_version/__init__.py +1 -1
  19. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/LICENSE +0 -0
  20. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/README.md +0 -0
  21. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/__init__.py +0 -0
  22. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/__main__.py +0 -0
  23. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_billing.py +0 -0
  24. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_clustered_functions.py +0 -0
  25. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_clustered_functions.pyi +0 -0
  26. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_container_entrypoint.py +0 -0
  27. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_environments.py +0 -0
  28. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_grpc_client.py +0 -0
  29. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_image.py +0 -0
  30. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_ipython.py +0 -0
  31. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_load_context.py +0 -0
  32. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_location.py +0 -0
  33. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_logs.py +0 -0
  34. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_object.py +0 -0
  35. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/__init__.py +0 -0
  36. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/manager.py +0 -0
  37. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/pty.py +0 -0
  38. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/rich.py +0 -0
  39. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/status.py +0 -0
  40. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_partial_function.py +0 -0
  41. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_resolver.py +0 -0
  42. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_resources.py +0 -0
  43. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/__init__.py +0 -0
  44. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/asgi.py +0 -0
  45. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/container_io_manager.py +0 -0
  46. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
  47. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/execution_context.py +0 -0
  48. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/execution_context.pyi +0 -0
  49. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  50. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/task_lifecycle_manager.py +0 -0
  51. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  52. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/telemetry.py +0 -0
  53. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
  54. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/user_code_imports.py +0 -0
  55. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_serialization.py +0 -0
  56. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_server.py +0 -0
  57. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_traceback.py +0 -0
  58. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_tunnel.py +0 -0
  59. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_tunnel.pyi +0 -0
  60. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_type_manager.py +0 -0
  61. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/__init__.py +0 -0
  62. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/app_utils.py +0 -0
  63. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/async_utils.py +0 -0
  64. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/auth_token_manager.py +0 -0
  65. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/blob_utils.py +0 -0
  66. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/browser_utils.py +0 -0
  67. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
  68. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/curl_utils.py +0 -0
  69. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/deprecation.py +0 -0
  70. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/docker_utils.py +0 -0
  71. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/function_utils.py +0 -0
  72. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/git_utils.py +0 -0
  73. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/grpc_testing.py +0 -0
  74. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/grpc_utils.py +0 -0
  75. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/hash_utils.py +0 -0
  76. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/http_utils.py +0 -0
  77. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/jwt_utils.py +0 -0
  78. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/logger.py +0 -0
  79. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/mount_utils.py +0 -0
  80. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/name_utils.py +0 -0
  81. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/package_utils.py +0 -0
  82. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/pattern_utils.py +0 -0
  83. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/proxy_support.py +0 -0
  84. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/rand_pb_testing.py +0 -0
  85. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
  86. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/shell_utils.py +0 -0
  87. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/time_utils.py +0 -0
  88. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/__init__.py +0 -0
  89. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  90. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/cloudpickle.py +0 -0
  91. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/tblib.py +0 -0
  92. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/version.py +0 -0
  93. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_watcher.py +0 -0
  94. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_workspace.py +0 -0
  95. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/app.py +0 -0
  96. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/app.pyi +0 -0
  97. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/billing.py +0 -0
  98. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/billing.pyi +0 -0
  99. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2023.12.312.txt +0 -0
  100. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2023.12.txt +0 -0
  101. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2024.04.txt +0 -0
  102. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2024.10.txt +0 -0
  103. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2025.06.txt +0 -0
  104. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/PREVIEW.txt +0 -0
  105. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/README.md +0 -0
  106. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/base-images.json +0 -0
  107. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/call_graph.py +0 -0
  108. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/__init__.py +0 -0
  109. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_download.py +0 -0
  110. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_help.py +0 -0
  111. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_traceback.py +0 -0
  112. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/app.py +0 -0
  113. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/billing.py +0 -0
  114. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/bootstrap.py +0 -0
  115. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/changelog.py +0 -0
  116. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/cluster.py +0 -0
  117. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/config.py +0 -0
  118. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/container.py +0 -0
  119. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/curl.py +0 -0
  120. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/dashboard.py +0 -0
  121. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/dict.py +0 -0
  122. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/endpoint.py +0 -0
  123. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/entry_point.py +0 -0
  124. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/environment.py +0 -0
  125. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/image.py +0 -0
  126. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/import_refs.py +0 -0
  127. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/launch.py +0 -0
  128. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/logo.py +0 -0
  129. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/network_file_system.py +0 -0
  130. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/profile.py +0 -0
  131. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/__init__.py +0 -0
  132. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/run_jupyter.py +0 -0
  133. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/vscode.py +0 -0
  134. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/queues.py +0 -0
  135. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/run.py +0 -0
  136. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/secret.py +0 -0
  137. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/selector.py +0 -0
  138. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/shell.py +0 -0
  139. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/skills.py +0 -0
  140. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/token.py +0 -0
  141. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/utils.py +0 -0
  142. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/volume.py +0 -0
  143. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/workspace.py +0 -0
  144. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/client.py +0 -0
  145. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cls.py +0 -0
  146. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cls.pyi +0 -0
  147. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/config.py +0 -0
  148. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/container_process.py +0 -0
  149. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/container_process.pyi +0 -0
  150. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/dict.py +0 -0
  151. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/dict.pyi +0 -0
  152. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/environments.py +0 -0
  153. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/environments.pyi +0 -0
  154. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/exception.py +0 -0
  155. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/__init__.py +0 -0
  156. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/flash.py +0 -0
  157. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/flash.pyi +0 -0
  158. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/ipython.py +0 -0
  159. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_io.py +0 -0
  160. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_io.pyi +0 -0
  161. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_pattern_matcher.py +0 -0
  162. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/functions.py +0 -0
  163. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/image.py +0 -0
  164. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/image.pyi +0 -0
  165. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/io_streams.py +0 -0
  166. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/io_streams.pyi +0 -0
  167. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/mount.py +0 -0
  168. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/mount.pyi +0 -0
  169. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/network_file_system.py +0 -0
  170. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/network_file_system.pyi +0 -0
  171. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/object.py +0 -0
  172. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/object.pyi +0 -0
  173. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/output.py +0 -0
  174. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/parallel_map.py +0 -0
  175. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/parallel_map.pyi +0 -0
  176. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/partial_function.py +0 -0
  177. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/partial_function.pyi +0 -0
  178. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/proxy.py +0 -0
  179. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/proxy.pyi +0 -0
  180. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/py.typed +0 -0
  181. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/queue.py +0 -0
  182. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/queue.pyi +0 -0
  183. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/retries.py +0 -0
  184. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/runner.py +0 -0
  185. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/runner.pyi +0 -0
  186. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/running_app.py +0 -0
  187. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox_fs.py +0 -0
  188. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox_fs.pyi +0 -0
  189. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/schedule.py +0 -0
  190. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/scheduler_placement.py +0 -0
  191. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/server.py +0 -0
  192. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/server.pyi +0 -0
  193. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/serving.py +0 -0
  194. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/serving.pyi +0 -0
  195. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/skills/modal/SKILL.md +0 -0
  196. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/stream_type.py +0 -0
  197. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/token_flow.py +0 -0
  198. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/token_flow.pyi +0 -0
  199. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/types.py +0 -0
  200. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/volume.py +0 -0
  201. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/volume.pyi +0 -0
  202. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/workspace.py +0 -0
  203. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/workspace.pyi +0 -0
  204. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/SOURCES.txt +0 -0
  205. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/dependency_links.txt +0 -0
  206. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/entry_points.txt +0 -0
  207. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/requires.txt +0 -0
  208. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/top_level.txt +0 -0
  209. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/__init__.py +0 -0
  210. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_cli_docs.py +0 -0
  211. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
  212. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_reference_docs.py +0 -0
  213. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
  214. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/__init__.py +0 -0
  215. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/mdmd.py +0 -0
  216. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/signatures.py +0 -0
  217. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/types.py +0 -0
  218. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/reference_intro.md +0 -0
  219. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/__init__.py +0 -0
  220. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_grpc.py +0 -0
  221. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2_grpc.py +0 -0
  222. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
  223. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/modal_api_grpc.py +0 -0
  224. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/py.typed +0 -0
  225. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_grpc.py +0 -0
  226. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2.py +0 -0
  227. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
  228. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  229. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  230. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_version/__main__.py +0 -0
  231. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/pyproject.toml +0 -0
  232. {modal-1.5.3.dev9 → modal-1.5.3.dev10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev9
3
+ Version: 1.5.3.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -171,7 +171,7 @@ class _FunctionOptions:
171
171
  replace_secret_ids=bool(self.secrets),
172
172
  replace_volume_mounts=len(volume_mounts) > 0,
173
173
  volume_mounts=volume_mounts,
174
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
174
+ cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts)[0],
175
175
  replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
176
176
  resources=self.resources,
177
177
  retry_policy=self.retry_policy,
@@ -1000,7 +1000,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1000
1000
  untrusted=restrict_modal_access,
1001
1001
  single_use_containers=single_use_containers,
1002
1002
  max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
1003
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
1003
+ cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
1004
1004
  scheduler_placement=scheduler_placement,
1005
1005
  is_class=info.is_service_class(),
1006
1006
  class_parameter_info=info.class_parameter_info(),
@@ -974,3 +974,8 @@ class TaskCommandRouterClient:
974
974
  return await self._unary_call_with_deadline(
975
975
  self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
976
976
  )
977
+
978
+ async def snapshot_memory(
979
+ self, request: sr_pb2.TaskSnapshotMemoryRequest, *, timeout: float, **kwargs
980
+ ) -> sr_pb2.TaskSnapshotMemoryResponse:
981
+ return await self._unary_call_with_deadline(self._stub.TaskSnapshotMemory, request, timeout=timeout, **kwargs)
@@ -37,7 +37,7 @@ class _Client:
37
37
  server_url: str,
38
38
  client_type: int,
39
39
  credentials: typing.Optional[tuple[str, str]],
40
- version: str = "1.5.3.dev9",
40
+ version: str = "1.5.3.dev10",
41
41
  ):
42
42
  """mdmd:hidden
43
43
  The Modal client object is not intended to be instantiated directly by users.
@@ -207,7 +207,7 @@ class Client:
207
207
  server_url: str,
208
208
  client_type: int,
209
209
  credentials: typing.Optional[tuple[str, str]],
210
- version: str = "1.5.3.dev9",
210
+ version: str = "1.5.3.dev10",
211
211
  ):
212
212
  """mdmd:hidden
213
213
  The Modal client object is not intended to be instantiated directly by users.
@@ -121,11 +121,14 @@ class _CloudBucketMount:
121
121
  force_path_style: bool = False
122
122
 
123
123
 
124
- def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]) -> list[api_pb2.CloudBucketMount]:
124
+ def cloud_bucket_mounts_to_proto(
125
+ mounts: Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
126
+ ) -> tuple[list[api_pb2.CloudBucketMount], dict[str, api_pb2.StringMap]]:
125
127
  """mdmd:hidden
126
128
  Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
127
129
  """
128
130
  cloud_bucket_mounts: list[api_pb2.CloudBucketMount] = []
131
+ cloud_bucket_credentials: dict[str, api_pb2.StringMap] = {}
129
132
 
130
133
  for path, mount in mounts:
131
134
  # crude mapping from mount arguments to type.
@@ -153,11 +156,20 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
153
156
  else:
154
157
  key_prefix = mount.key_prefix
155
158
 
159
+ credentials_secret_id = ""
160
+ if secret := mount.secret:
161
+ if split_ephemeral_credentials and secret._load_env_dict:
162
+ env_dict = secret._load_env_dict()
163
+ credientials = api_pb2.StringMap(contents=env_dict)
164
+ cloud_bucket_credentials[path] = credientials
165
+ else:
166
+ credentials_secret_id = secret.object_id
167
+
156
168
  cloud_bucket_mount = api_pb2.CloudBucketMount(
157
169
  bucket_name=mount.bucket_name,
158
170
  bucket_endpoint_url=mount.bucket_endpoint_url,
159
171
  mount_path=path,
160
- credentials_secret_id=mount.secret.object_id if mount.secret else "",
172
+ credentials_secret_id=credentials_secret_id,
161
173
  read_only=mount.read_only,
162
174
  bucket_type=bucket_type,
163
175
  requester_pays=mount.requester_pays,
@@ -167,7 +179,7 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
167
179
  )
168
180
  cloud_bucket_mounts.append(cloud_bucket_mount)
169
181
 
170
- return cloud_bucket_mounts
182
+ return cloud_bucket_mounts, cloud_bucket_credentials
171
183
 
172
184
 
173
185
  CloudBucketMount = synchronize_api(_CloudBucketMount)
@@ -126,8 +126,8 @@ class _CloudBucketMount:
126
126
  ...
127
127
 
128
128
  def cloud_bucket_mounts_to_proto(
129
- mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]],
130
- ) -> list[modal_proto.api_pb2.CloudBucketMount]:
129
+ mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
130
+ ) -> tuple[list[modal_proto.api_pb2.CloudBucketMount], dict[str, modal_proto.api_pb2.StringMap]]:
131
131
  """mdmd:hidden
132
132
  Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
133
133
  """
@@ -493,7 +493,7 @@ class Function(
493
493
 
494
494
  _call_generator: ___call_generator_spec
495
495
 
496
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
496
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
497
497
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
498
498
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
499
499
 
@@ -518,7 +518,7 @@ class Function(
518
518
  """
519
519
  ...
520
520
 
521
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
521
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
522
522
 
523
523
  class __remote_gen_spec(typing_extensions.Protocol):
524
524
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -568,7 +568,7 @@ class Function(
568
568
  """
569
569
  ...
570
570
 
571
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
571
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
572
572
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
573
573
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
574
574
 
@@ -601,7 +601,7 @@ class Function(
601
601
  """
602
602
  ...
603
603
 
604
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
604
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
605
605
 
606
606
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
607
607
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -609,7 +609,7 @@ class Function(
609
609
 
610
610
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
611
611
 
612
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
612
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
613
613
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
614
614
  """Calls the function with the given arguments, without waiting for the results.
615
615
 
@@ -642,7 +642,7 @@ class Function(
642
642
  """
643
643
  ...
644
644
 
645
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
645
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
646
646
 
647
647
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
648
648
  """Return the inner Python object wrapped by this Modal Function.
@@ -472,7 +472,7 @@ class _Sandbox(_Object, type_prefix="sb"):
472
472
  nfs_mounts=network_file_system_mount_protos(validated_network_file_systems),
473
473
  runtime=config.get("function_runtime"),
474
474
  runtime_debug=config.get("function_runtime_debug"),
475
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
475
+ cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
476
476
  volume_mounts=volume_mounts,
477
477
  pty_info=pty_info,
478
478
  scheduler_placement=scheduler_placement,
@@ -845,6 +845,7 @@ class _Sandbox(_Object, type_prefix="sb"):
845
845
  verbose: bool = False,
846
846
  custom_domain: str | None = None,
847
847
  client: _Client | None = None,
848
+ _experimental_enable_snapshot: bool = False,
848
849
  ) -> "_Sandbox":
849
850
  """Create a sandbox using the V2 backend.
850
851
 
@@ -860,8 +861,9 @@ class _Sandbox(_Object, type_prefix="sb"):
860
861
  tuple that additionally sets a hard limit (fractional CPU cores; memory
861
862
  in MiB), matching `Sandbox.create()`.
862
863
 
863
- Features like memory snapshots, network file systems, and GPUs are not
864
- supported.
864
+ Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
865
+ snapshotted with `._experimental_snapshot()`. Features like network
866
+ file systems and GPUs are not supported.
865
867
 
866
868
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
867
869
  workspace can address each other directly at their `i6pn.modal.local`
@@ -961,8 +963,9 @@ class _Sandbox(_Object, type_prefix="sb"):
961
963
  for _, vol in validated_volumes:
962
964
  dep_tasks.append(resolver.load(vol, load_context))
963
965
  for _, cloud_bucket_mount in cloud_bucket_mounts:
964
- if cloud_bucket_mount.secret:
965
- dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
966
+ if secret := cloud_bucket_mount.secret:
967
+ if not secret._is_ephemeral:
968
+ dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
966
969
  if proxy:
967
970
  dep_tasks.append(resolver.load(proxy, load_context))
968
971
  dep_timings = await _gather_load_with_timings(dep_tasks) if dep_tasks else []
@@ -971,6 +974,10 @@ class _Sandbox(_Object, type_prefix="sb"):
971
974
 
972
975
  volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in validated_volumes]
973
976
 
977
+ cloud_bucket_mount_protos, cloud_bucket_credientials = cloud_bucket_mounts_to_proto(
978
+ cloud_bucket_mounts, split_ephemeral_credentials=True
979
+ )
980
+
974
981
  definition = api_pb2.Sandbox(
975
982
  entrypoint_args=args,
976
983
  image_id=image.object_id,
@@ -995,12 +1002,13 @@ class _Sandbox(_Object, type_prefix="sb"):
995
1002
  inbound_cidr_allowlist=list(inbound_cidr_allowlist) if inbound_cidr_allowlist is not None else [],
996
1003
  i6pn_enabled=i6pn,
997
1004
  volume_mounts=volume_mounts,
998
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
1005
+ cloud_bucket_mounts=cloud_bucket_mount_protos,
999
1006
  readiness_probe=(readiness_probe._to_proto() if readiness_probe else None),
1000
1007
  experimental_options_v2=(
1001
1008
  {k: str(v) for k, v in experimental_options.items()} if experimental_options else None
1002
1009
  ),
1003
1010
  custom_domain=custom_domain,
1011
+ enable_snapshot=_experimental_enable_snapshot,
1004
1012
  )
1005
1013
 
1006
1014
  tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
@@ -1009,6 +1017,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1009
1017
  definition=definition,
1010
1018
  ephemeral_secrets=api_pb2.StringMap(contents=env_dict) if env_dict else None,
1011
1019
  tags=tag_protos,
1020
+ cloud_bucket_mount_credentials=cloud_bucket_credientials,
1012
1021
  )
1013
1022
  assert load_context.client._auth_token_manager
1014
1023
  auth_token = await load_context.client._auth_token_manager.get_token()
@@ -2079,19 +2088,27 @@ class _Sandbox(_Object, type_prefix="sb"):
2079
2088
  )
2080
2089
 
2081
2090
  async def _experimental_snapshot(self) -> _SandboxSnapshot:
2082
- self._ensure_v1("_experimental_snapshot")
2083
- await self._get_task_id()
2084
- snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
2085
- snap_resp = await self._client.stub.SandboxSnapshot(snap_req)
2091
+ if self._is_v2:
2092
+ task_id = await self._get_task_id()
2093
+ command_router_client = await self._get_command_router_client(task_id)
2094
+ snap_v2_resp = await command_router_client.snapshot_memory(
2095
+ sr_pb2.TaskSnapshotMemoryRequest(task_id=task_id, idempotency_key=str(uuid.uuid4())),
2096
+ timeout=55.0,
2097
+ )
2098
+ snapshot_id = snap_v2_resp.snapshot_id
2099
+ else:
2100
+ await self._get_task_id()
2101
+ snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
2102
+ snap_resp = await self._client.stub.SandboxSnapshot(snap_req)
2086
2103
 
2087
- snapshot_id = snap_resp.snapshot_id
2104
+ snapshot_id = snap_resp.snapshot_id
2088
2105
 
2089
- # wait for the snapshot to succeed. this is implemented as a second idempotent rpc
2090
- # because the snapshot itself may take a while to complete.
2091
- wait_req = api_pb2.SandboxSnapshotWaitRequest(snapshot_id=snapshot_id, timeout=55.0)
2092
- wait_resp = await self._client.stub.SandboxSnapshotWait(wait_req)
2093
- if wait_resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
2094
- raise ExecutionError(wait_resp.result.exception)
2106
+ # wait for the snapshot to succeed. this is implemented as a second idempotent rpc
2107
+ # because the snapshot itself may take a while to complete.
2108
+ wait_req = api_pb2.SandboxSnapshotWaitRequest(snapshot_id=snapshot_id, timeout=55.0)
2109
+ wait_resp = await self._client.stub.SandboxSnapshotWait(wait_req)
2110
+ if wait_resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
2111
+ raise ExecutionError(wait_resp.result.exception)
2095
2112
 
2096
2113
  async def _load(
2097
2114
  self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None
@@ -2102,7 +2119,8 @@ class _Sandbox(_Object, type_prefix="sb"):
2102
2119
  rep = "SandboxSnapshot()"
2103
2120
  # TODO: use ._new_hydrated instead
2104
2121
  obj = _SandboxSnapshot._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
2105
- obj._hydrate(snapshot_id, self._client, None)
2122
+ metadata = api_pb2.SandboxSnapshotHandleMetadata(is_v2=self._is_v2)
2123
+ obj._hydrate(snapshot_id, self._client, metadata)
2106
2124
 
2107
2125
  return obj
2108
2126
 
@@ -2113,27 +2131,41 @@ class _Sandbox(_Object, type_prefix="sb"):
2113
2131
  *,
2114
2132
  name: str | None = _DEFAULT_SANDBOX_NAME_OVERRIDE,
2115
2133
  ):
2134
+ """Restore a Sandbox from a memory snapshot.
2135
+
2136
+ The restore targets the same backend the snapshot was taken from. A V1
2137
+ snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
2138
+ """
2116
2139
  client = client or await _Client.from_env()
2117
2140
 
2141
+ if snapshot._is_v2 is None:
2142
+ await snapshot.hydrate(client=client)
2143
+ use_v2 = bool(snapshot._is_v2)
2144
+
2118
2145
  if name is not None and name != _DEFAULT_SANDBOX_NAME_OVERRIDE:
2119
2146
  check_object_name(name, "Sandbox")
2120
2147
 
2121
2148
  if name is _DEFAULT_SANDBOX_NAME_OVERRIDE:
2122
- restore_req = api_pb2.SandboxRestoreRequest(
2123
- snapshot_id=snapshot.object_id,
2124
- sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED,
2125
- )
2149
+ override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED
2150
+ name_override = None
2126
2151
  elif name is None:
2127
- restore_req = api_pb2.SandboxRestoreRequest(
2128
- snapshot_id=snapshot.object_id,
2129
- sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE,
2130
- )
2152
+ override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE
2153
+ name_override = None
2131
2154
  else:
2132
- restore_req = api_pb2.SandboxRestoreRequest(
2133
- snapshot_id=snapshot.object_id,
2134
- sandbox_name_override=name,
2135
- sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_STRING,
2155
+ override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_STRING
2156
+ name_override = name
2157
+
2158
+ if use_v2:
2159
+ return await _Sandbox._experimental_from_snapshot_v2(
2160
+ snapshot, client, override_type=override_type, name_override=name_override
2136
2161
  )
2162
+
2163
+ restore_req = api_pb2.SandboxRestoreRequest(
2164
+ snapshot_id=snapshot.object_id,
2165
+ sandbox_name_override_type=override_type,
2166
+ )
2167
+ if name_override is not None:
2168
+ restore_req.sandbox_name_override = name_override
2137
2169
  # Pin the restored Sandbox to a specific worker when MODAL_WORKER_ID is
2138
2170
  # set.
2139
2171
  if worker_id := config.get("worker_id"):
@@ -2153,6 +2185,33 @@ class _Sandbox(_Object, type_prefix="sb"):
2153
2185
  raise ExecutionError(resp.task_result.exception)
2154
2186
  return sandbox
2155
2187
 
2188
+ @staticmethod
2189
+ async def _experimental_from_snapshot_v2(
2190
+ snapshot: _SandboxSnapshot,
2191
+ client: _Client,
2192
+ *,
2193
+ override_type: "api_pb2.SandboxRestoreRequest.SandboxNameOverrideType.ValueType",
2194
+ name_override: str | None,
2195
+ ) -> "_Sandbox":
2196
+ restore_req = api_pb2.SandboxRestoreV2Request(
2197
+ snapshot_id=snapshot.object_id,
2198
+ sandbox_name_override_type=override_type,
2199
+ )
2200
+ if name_override is not None:
2201
+ restore_req.sandbox_name_override = name_override
2202
+ if worker_id := config.get("worker_id"):
2203
+ restore_req.worker_id = worker_id
2204
+
2205
+ assert client._auth_token_manager
2206
+ auth_token = await client._auth_token_manager.get_token()
2207
+ restore_resp: api_pb2.SandboxRestoreV2Response = await client.stub.SandboxRestoreV2(
2208
+ restore_req, metadata=[("x-modal-auth-token", auth_token)]
2209
+ )
2210
+
2211
+ sandbox = await _Sandbox.from_id(restore_resp.sandbox_id, client)
2212
+ sandbox._task_id = restore_resp.task_id
2213
+ return sandbox
2214
+
2156
2215
  @property
2157
2216
  def filesystem(self) -> _SandboxFilesystem:
2158
2217
  """Namespace for Sandbox filesystem APIs."""
@@ -393,6 +393,7 @@ class _Sandbox(modal._object._Object):
393
393
  verbose: bool = False,
394
394
  custom_domain: typing.Optional[str] = None,
395
395
  client: typing.Optional[modal.client._Client] = None,
396
+ _experimental_enable_snapshot: bool = False,
396
397
  ) -> _Sandbox:
397
398
  """Create a sandbox using the V2 backend.
398
399
 
@@ -408,8 +409,9 @@ class _Sandbox(modal._object._Object):
408
409
  tuple that additionally sets a hard limit (fractional CPU cores; memory
409
410
  in MiB), matching `Sandbox.create()`.
410
411
 
411
- Features like memory snapshots, network file systems, and GPUs are not
412
- supported.
412
+ Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
413
+ snapshotted with `._experimental_snapshot()`. Features like network
414
+ file systems and GPUs are not supported.
413
415
 
414
416
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
415
417
  workspace can address each other directly at their `i6pn.modal.local`
@@ -852,7 +854,22 @@ class _Sandbox(modal._object._Object):
852
854
  client: typing.Optional[modal.client._Client] = None,
853
855
  *,
854
856
  name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
855
- ): ...
857
+ ):
858
+ """Restore a Sandbox from a memory snapshot.
859
+
860
+ The restore targets the same backend the snapshot was taken from. A V1
861
+ snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
862
+ """
863
+ ...
864
+
865
+ @staticmethod
866
+ async def _experimental_from_snapshot_v2(
867
+ snapshot: modal.snapshot._SandboxSnapshot,
868
+ client: modal.client._Client,
869
+ *,
870
+ override_type: int,
871
+ name_override: typing.Optional[str],
872
+ ) -> _Sandbox: ...
856
873
  @property
857
874
  def filesystem(self) -> modal.sandbox_fs._SandboxFilesystem:
858
875
  """Namespace for Sandbox filesystem APIs."""
@@ -1768,6 +1785,7 @@ class Sandbox(modal.object.Object):
1768
1785
  verbose: bool = False,
1769
1786
  custom_domain: typing.Optional[str] = None,
1770
1787
  client: typing.Optional[modal.client.Client] = None,
1788
+ _experimental_enable_snapshot: bool = False,
1771
1789
  ) -> Sandbox:
1772
1790
  """Create a sandbox using the V2 backend.
1773
1791
 
@@ -1783,8 +1801,9 @@ class Sandbox(modal.object.Object):
1783
1801
  tuple that additionally sets a hard limit (fractional CPU cores; memory
1784
1802
  in MiB), matching `Sandbox.create()`.
1785
1803
 
1786
- Features like memory snapshots, network file systems, and GPUs are not
1787
- supported.
1804
+ Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
1805
+ snapshotted with `._experimental_snapshot()`. Features like network
1806
+ file systems and GPUs are not supported.
1788
1807
 
1789
1808
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
1790
1809
  workspace can address each other directly at their `i6pn.modal.local`
@@ -1837,6 +1856,7 @@ class Sandbox(modal.object.Object):
1837
1856
  verbose: bool = False,
1838
1857
  custom_domain: typing.Optional[str] = None,
1839
1858
  client: typing.Optional[modal.client.Client] = None,
1859
+ _experimental_enable_snapshot: bool = False,
1840
1860
  ) -> Sandbox:
1841
1861
  """Create a sandbox using the V2 backend.
1842
1862
 
@@ -1852,8 +1872,9 @@ class Sandbox(modal.object.Object):
1852
1872
  tuple that additionally sets a hard limit (fractional CPU cores; memory
1853
1873
  in MiB), matching `Sandbox.create()`.
1854
1874
 
1855
- Features like memory snapshots, network file systems, and GPUs are not
1856
- supported.
1875
+ Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
1876
+ snapshotted with `._experimental_snapshot()`. Features like network
1877
+ file systems and GPUs are not supported.
1857
1878
 
1858
1879
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
1859
1880
  workspace can address each other directly at their `i6pn.modal.local`
@@ -2802,7 +2823,14 @@ class Sandbox(modal.object.Object):
2802
2823
  client: typing.Optional[modal.client.Client] = None,
2803
2824
  *,
2804
2825
  name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
2805
- ): ...
2826
+ ):
2827
+ """Restore a Sandbox from a memory snapshot.
2828
+
2829
+ The restore targets the same backend the snapshot was taken from. A V1
2830
+ snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
2831
+ """
2832
+ ...
2833
+
2806
2834
  async def aio(
2807
2835
  self,
2808
2836
  /,
@@ -2810,10 +2838,38 @@ class Sandbox(modal.object.Object):
2810
2838
  client: typing.Optional[modal.client.Client] = None,
2811
2839
  *,
2812
2840
  name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
2813
- ): ...
2841
+ ):
2842
+ """Restore a Sandbox from a memory snapshot.
2843
+
2844
+ The restore targets the same backend the snapshot was taken from. A V1
2845
+ snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
2846
+ """
2847
+ ...
2814
2848
 
2815
2849
  _experimental_from_snapshot: typing.ClassVar[___experimental_from_snapshot_spec]
2816
2850
 
2851
+ class ___experimental_from_snapshot_v2_spec(typing_extensions.Protocol):
2852
+ def __call__(
2853
+ self,
2854
+ /,
2855
+ snapshot: modal.snapshot.SandboxSnapshot,
2856
+ client: modal.client.Client,
2857
+ *,
2858
+ override_type: int,
2859
+ name_override: typing.Optional[str],
2860
+ ) -> Sandbox: ...
2861
+ async def aio(
2862
+ self,
2863
+ /,
2864
+ snapshot: modal.snapshot.SandboxSnapshot,
2865
+ client: modal.client.Client,
2866
+ *,
2867
+ override_type: int,
2868
+ name_override: typing.Optional[str],
2869
+ ) -> Sandbox: ...
2870
+
2871
+ _experimental_from_snapshot_v2: typing.ClassVar[___experimental_from_snapshot_v2_spec]
2872
+
2817
2873
  @property
2818
2874
  def filesystem(self) -> modal.sandbox_fs.SandboxFilesystem:
2819
2875
  """Namespace for Sandbox filesystem APIs."""
@@ -263,6 +263,15 @@ class _Secret(_Object, type_prefix="st"):
263
263
  assert self._metadata
264
264
  return self._metadata
265
265
 
266
+ @property
267
+ def _is_ephemeral(self) -> bool:
268
+ """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
269
+
270
+ True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
271
+ can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
272
+ """
273
+ return self._load_env_dict is not None
274
+
266
275
  @staticmethod
267
276
  def from_dict(
268
277
  env_dict: dict[str, str | None] = {},
@@ -432,6 +432,15 @@ class _Secret(modal._object._Object):
432
432
  def name(self) -> typing.Optional[str]: ...
433
433
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
434
434
  def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
435
+ @property
436
+ def _is_ephemeral(self) -> bool:
437
+ """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
438
+
439
+ True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
440
+ can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
441
+ """
442
+ ...
443
+
435
444
  @staticmethod
436
445
  def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret:
437
446
  """Create a Secret from a dictionary of environment variable names to string values.
@@ -588,6 +597,15 @@ class Secret(modal.object.Object):
588
597
  def name(self) -> typing.Optional[str]: ...
589
598
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
590
599
  def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
600
+ @property
601
+ def _is_ephemeral(self) -> bool:
602
+ """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
603
+
604
+ True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
605
+ can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
606
+ """
607
+ ...
608
+
591
609
  @staticmethod
592
610
  def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret:
593
611
  """Create a Secret from a dictionary of environment variable names to string values.
@@ -2,6 +2,7 @@
2
2
  from typing import cast
3
3
 
4
4
  import typing_extensions
5
+ from google.protobuf.message import Message
5
6
 
6
7
  import modal.client
7
8
  from modal_proto import api_pb2
@@ -22,6 +23,21 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
22
23
  the original Sandbox at the time the snapshot was taken.
23
24
  """
24
25
 
26
+ _metadata: "api_pb2.SandboxSnapshotHandleMetadata | None" = None
27
+
28
+ def _hydrate_metadata(self, metadata: Message | None):
29
+ if metadata:
30
+ assert isinstance(metadata, api_pb2.SandboxSnapshotHandleMetadata)
31
+ self._metadata = metadata
32
+
33
+ def _get_metadata(self) -> "api_pb2.SandboxSnapshotHandleMetadata | None":
34
+ return self._metadata
35
+
36
+ @property
37
+ def _is_v2(self) -> bool | None:
38
+ """Whether the snapshot came from a V2 sandbox."""
39
+ return self._metadata.is_v2 if self._metadata is not None else None
40
+
25
41
  @deprecate_aio_usage((2025, 12, 5), "SandboxSnapshot.from_id")
26
42
  @classmethod
27
43
  def from_id(cls, sandbox_snapshot_id: str, client: "modal.client.Client | None" = None) -> typing_extensions.Self:
@@ -45,7 +61,7 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
45
61
  resp: api_pb2.SandboxSnapshotGetResponse = await load_context.client.stub.SandboxSnapshotGet(
46
62
  api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
47
63
  )
48
- self._hydrate(resp.snapshot_id, load_context.client, None)
64
+ self._hydrate(resp.snapshot_id, load_context.client, resp.handle_metadata)
49
65
 
50
66
  rep = "SandboxSnapshot()"
51
67
  obj = _SandboxSnapshot._from_loader(
@@ -1,6 +1,8 @@
1
+ import google.protobuf.message
1
2
  import modal._object
2
3
  import modal.client
3
4
  import modal.object
5
+ import modal_proto.api_pb2
4
6
  import typing
5
7
  import typing_extensions
6
8
 
@@ -13,6 +15,16 @@ class _SandboxSnapshot(modal._object._Object):
13
15
  `._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
14
16
  the original Sandbox at the time the snapshot was taken.
15
17
  """
18
+
19
+ _metadata: typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]
20
+
21
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
22
+ def _get_metadata(self) -> typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]: ...
23
+ @property
24
+ def _is_v2(self) -> typing.Optional[bool]:
25
+ """Whether the snapshot came from a V2 sandbox."""
26
+ ...
27
+
16
28
  class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
17
29
  def __call__(
18
30
  self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
@@ -39,10 +51,20 @@ class SandboxSnapshot(modal.object.Object):
39
51
  `._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
40
52
  the original Sandbox at the time the snapshot was taken.
41
53
  """
54
+
55
+ _metadata: typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]
56
+
42
57
  def __init__(self, *args, **kwargs):
43
58
  """mdmd:hidden"""
44
59
  ...
45
60
 
61
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
62
+ def _get_metadata(self) -> typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]: ...
63
+ @property
64
+ def _is_v2(self) -> typing.Optional[bool]:
65
+ """Whether the snapshot came from a V2 sandbox."""
66
+ ...
67
+
46
68
  class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
47
69
  def __call__(
48
70
  self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev9
3
+ Version: 1.5.3.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0