modAL 1.2.7.dev9__tar.gz → 1.2.7.dev11__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 (194) hide show
  1. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/PKG-INFO +1 -1
  2. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_container_entrypoint.py +0 -1
  3. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_grpc_client.py +1 -1
  4. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_output.py +8 -9
  5. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/function_utils.py +8 -9
  6. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/grpc_utils.py +0 -2
  7. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/package_utils.py +0 -1
  8. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/task_command_router_client.py +14 -0
  9. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/client.pyi +2 -2
  10. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/dict.py +12 -6
  11. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/exception.py +5 -1
  12. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_io.py +10 -13
  13. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/image.py +12 -6
  14. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/io_streams.py +10 -13
  15. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/mount.py +2 -3
  16. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/queue.py +11 -5
  17. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/sandbox.py +44 -0
  18. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/sandbox.pyi +39 -0
  19. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_version/__init__.py +1 -1
  21. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/LICENSE +0 -0
  22. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/README.md +0 -0
  23. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/__init__.py +0 -0
  24. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/__main__.py +0 -0
  25. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_billing.py +0 -0
  26. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_clustered_functions.py +0 -0
  27. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_clustered_functions.pyi +0 -0
  28. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_functions.py +0 -0
  29. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_ipython.py +0 -0
  30. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_load_context.py +0 -0
  31. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_location.py +0 -0
  32. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_object.py +0 -0
  33. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_partial_function.py +0 -0
  34. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_pty.py +0 -0
  35. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_resolver.py +0 -0
  36. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_resources.py +0 -0
  37. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/__init__.py +0 -0
  38. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/asgi.py +0 -0
  39. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.py +0 -0
  40. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
  41. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/execution_context.py +0 -0
  42. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/execution_context.pyi +0 -0
  43. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  44. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/telemetry.py +0 -0
  45. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
  46. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/user_code_imports.py +0 -0
  47. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_serialization.py +0 -0
  48. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_traceback.py +0 -0
  49. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_tunnel.py +0 -0
  50. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_tunnel.pyi +0 -0
  51. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_type_manager.py +0 -0
  52. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/__init__.py +0 -0
  53. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/app_utils.py +0 -0
  54. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/async_utils.py +0 -0
  55. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/auth_token_manager.py +0 -0
  56. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/blob_utils.py +0 -0
  57. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
  58. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/deprecation.py +0 -0
  59. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/docker_utils.py +0 -0
  60. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/git_utils.py +0 -0
  61. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/grpc_testing.py +0 -0
  62. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/hash_utils.py +0 -0
  63. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/http_utils.py +0 -0
  64. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/jwt_utils.py +0 -0
  65. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/logger.py +0 -0
  66. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/mount_utils.py +0 -0
  67. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/name_utils.py +0 -0
  68. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/pattern_utils.py +0 -0
  69. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/rand_pb_testing.py +0 -0
  70. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/shell_utils.py +0 -0
  71. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/time_utils.py +0 -0
  72. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/__init__.py +0 -0
  73. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  74. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/cloudpickle.py +0 -0
  75. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/tblib.py +0 -0
  76. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_watcher.py +0 -0
  77. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/app.py +0 -0
  78. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/app.pyi +0 -0
  79. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/billing.py +0 -0
  80. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2023.12.312.txt +0 -0
  81. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2023.12.txt +0 -0
  82. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2024.04.txt +0 -0
  83. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2024.10.txt +0 -0
  84. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2025.06.txt +0 -0
  85. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/PREVIEW.txt +0 -0
  86. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/README.md +0 -0
  87. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/base-images.json +0 -0
  88. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/call_graph.py +0 -0
  89. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/__init__.py +0 -0
  90. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/_download.py +0 -0
  91. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/_traceback.py +0 -0
  92. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/app.py +0 -0
  93. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/cluster.py +0 -0
  94. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/config.py +0 -0
  95. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/container.py +0 -0
  96. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/dict.py +0 -0
  97. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/entry_point.py +0 -0
  98. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/environment.py +0 -0
  99. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/import_refs.py +0 -0
  100. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/launch.py +0 -0
  101. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/network_file_system.py +0 -0
  102. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/profile.py +0 -0
  103. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/__init__.py +0 -0
  104. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/run_jupyter.py +0 -0
  105. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/vscode.py +0 -0
  106. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/queues.py +0 -0
  107. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/run.py +0 -0
  108. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/secret.py +0 -0
  109. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/shell.py +0 -0
  110. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/token.py +0 -0
  111. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/utils.py +0 -0
  112. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/volume.py +0 -0
  113. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/client.py +0 -0
  114. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.py +0 -0
  115. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.pyi +0 -0
  116. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cls.py +0 -0
  117. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cls.pyi +0 -0
  118. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/config.py +0 -0
  119. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/container_process.py +0 -0
  120. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/container_process.pyi +0 -0
  121. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/dict.pyi +0 -0
  122. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/environments.py +0 -0
  123. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/environments.pyi +0 -0
  124. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/__init__.py +0 -0
  125. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/flash.py +0 -0
  126. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/flash.pyi +0 -0
  127. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/ipython.py +0 -0
  128. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_io.pyi +0 -0
  129. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_pattern_matcher.py +0 -0
  130. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/functions.py +0 -0
  131. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/functions.pyi +0 -0
  132. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/gpu.py +0 -0
  133. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/image.pyi +0 -0
  134. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/io_streams.pyi +0 -0
  135. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/mount.pyi +0 -0
  136. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/network_file_system.py +0 -0
  137. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/object.py +0 -0
  139. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/object.pyi +0 -0
  140. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/output.py +0 -0
  141. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/parallel_map.py +0 -0
  142. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/partial_function.py +0 -0
  144. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/proxy.py +0 -0
  146. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/proxy.pyi +0 -0
  147. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/py.typed +0 -0
  148. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/queue.pyi +0 -0
  149. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/retries.py +0 -0
  150. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/runner.py +0 -0
  151. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/runner.pyi +0 -0
  152. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/running_app.py +0 -0
  153. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/schedule.py +0 -0
  154. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/scheduler_placement.py +0 -0
  155. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/secret.py +0 -0
  156. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/secret.pyi +0 -0
  157. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/serving.py +0 -0
  158. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/serving.pyi +0 -0
  159. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/snapshot.py +0 -0
  160. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/snapshot.pyi +0 -0
  161. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/stream_type.py +0 -0
  162. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/token_flow.py +0 -0
  163. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/token_flow.pyi +0 -0
  164. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/volume.py +0 -0
  165. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/volume.pyi +0 -0
  166. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/SOURCES.txt +0 -0
  167. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/dependency_links.txt +0 -0
  168. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/entry_points.txt +0 -0
  169. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/requires.txt +0 -0
  170. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/top_level.txt +0 -0
  171. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/__init__.py +0 -0
  172. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/gen_cli_docs.py +0 -0
  173. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/gen_reference_docs.py +0 -0
  174. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/__init__.py +0 -0
  175. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/mdmd.py +0 -0
  176. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/signatures.py +0 -0
  177. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/__init__.py +0 -0
  178. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api.proto +0 -0
  179. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_grpc.py +0 -0
  180. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2.py +0 -0
  181. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2.pyi +0 -0
  182. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.py +0 -0
  183. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.pyi +0 -0
  184. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/modal_api_grpc.py +0 -0
  185. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/py.typed +0 -0
  186. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router.proto +0 -0
  187. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_grpc.py +0 -0
  188. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2.py +0 -0
  189. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
  190. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  191. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_version/__main__.py +0 -0
  193. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/pyproject.toml +0 -0
  194. {modal-1.2.7.dev9 → modal-1.2.7.dev11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev9
3
+ Version: 1.2.7.dev11
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -43,7 +43,6 @@ from ._runtime.container_io_manager import (
43
43
  )
44
44
 
45
45
  if TYPE_CHECKING:
46
- import modal._object
47
46
  import modal._runtime.container_io_manager
48
47
  import modal._runtime.user_code_imports
49
48
 
@@ -38,7 +38,7 @@ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
38
38
  Status.UNIMPLEMENTED: exception.UnimplementedError,
39
39
  Status.INTERNAL: exception.InternalError,
40
40
  Status.UNAVAILABLE: exception.ServiceError,
41
- Status.DATA_LOSS: exception.ServiceError,
41
+ Status.DATA_LOSS: exception.DataLossError,
42
42
  Status.UNAUTHENTICATED: exception.AuthError,
43
43
  }
44
44
 
@@ -12,7 +12,7 @@ from collections.abc import Generator
12
12
  from datetime import timedelta
13
13
  from typing import Callable, ClassVar
14
14
 
15
- from grpclib.exceptions import GRPCError, StreamTerminatedError
15
+ from grpclib.exceptions import StreamTerminatedError
16
16
  from rich.console import Console, Group, RenderableType
17
17
  from rich.live import Live
18
18
  from rich.panel import Panel
@@ -34,10 +34,11 @@ from rich.text import Text
34
34
  from modal._utils.time_utils import timestamp_to_localized_str
35
35
  from modal_proto import api_pb2
36
36
 
37
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, Retry
37
+ from ._utils.grpc_utils import Retry
38
38
  from ._utils.shell_utils import stream_from_stdin, write_to_fd
39
39
  from .client import _Client
40
40
  from .config import logger
41
+ from .exception import InternalError, ServiceError
41
42
 
42
43
  if platform.system() == "Windows":
43
44
  default_spinner = "line"
@@ -644,13 +645,11 @@ async def get_app_logs_loop(
644
645
  while True:
645
646
  try:
646
647
  await _get_logs()
647
- except (GRPCError, StreamTerminatedError, socket.gaierror, AttributeError) as exc:
648
- if isinstance(exc, GRPCError):
649
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
650
- # Try again if we had a temporary connection drop,
651
- # for example if computer went to sleep.
652
- logger.debug("Log fetching timed out. Retrying ...")
653
- continue
648
+ except (ServiceError, InternalError, StreamTerminatedError, socket.gaierror, AttributeError) as exc:
649
+ if isinstance(exc, (ServiceError, InternalError)):
650
+ # Try again if we had a temporary connection drop, for example if computer went to sleep.
651
+ logger.debug("Log fetching timed out. Retrying ...")
652
+ continue
654
653
  elif isinstance(exc, StreamTerminatedError):
655
654
  logger.debug("Stream closed. Retrying ...")
656
655
  continue
@@ -8,7 +8,6 @@ from enum import Enum
8
8
  from pathlib import Path, PurePosixPath
9
9
  from typing import Any, Callable, Literal, Optional
10
10
 
11
- from grpclib import GRPCError
12
11
  from grpclib.exceptions import StreamTerminatedError
13
12
 
14
13
  import modal_proto
@@ -29,9 +28,11 @@ from ..exception import (
29
28
  DeserializationError,
30
29
  ExecutionError,
31
30
  FunctionTimeoutError,
31
+ InternalError,
32
32
  InternalFailure,
33
33
  InvalidError,
34
34
  RemoteError,
35
+ ServiceError,
35
36
  )
36
37
  from ..mount import ROOT_DIR, _is_modal_path, _Mount
37
38
  from .blob_utils import (
@@ -39,7 +40,6 @@ from .blob_utils import (
39
40
  blob_download,
40
41
  blob_upload_with_r2_failure_info,
41
42
  )
42
- from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
43
43
 
44
44
  if typing.TYPE_CHECKING:
45
45
  import modal._functions
@@ -437,15 +437,14 @@ async def _stream_function_call_data(
437
437
 
438
438
  last_index = chunk.index
439
439
  yield message
440
- except (GRPCError, StreamTerminatedError) as exc:
440
+ except (ServiceError, InternalError, StreamTerminatedError) as exc:
441
441
  if retries_remaining > 0:
442
442
  retries_remaining -= 1
443
- if isinstance(exc, GRPCError):
444
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
445
- logger.debug(f"{variant} stream retrying with delay {delay_ms}ms due to {exc}")
446
- await asyncio.sleep(delay_ms / 1000)
447
- delay_ms = min(1000, delay_ms * 10)
448
- continue
443
+ if isinstance(exc, (ServiceError, InternalError)):
444
+ logger.debug(f"{variant} stream retrying with delay {delay_ms}ms due to {exc}")
445
+ await asyncio.sleep(delay_ms / 1000)
446
+ delay_ms = min(1000, delay_ms * 10)
447
+ continue
449
448
  elif isinstance(exc, StreamTerminatedError):
450
449
  continue
451
450
  raise
@@ -16,8 +16,6 @@ from typing import Any, Optional, Sequence, TypeVar
16
16
  import grpclib.client
17
17
  import grpclib.config
18
18
  import grpclib.events
19
- import grpclib.protocol
20
- import grpclib.stream
21
19
  from google.protobuf.message import Message
22
20
  from google.protobuf.symbol_database import SymbolDatabase
23
21
  from grpclib import GRPCError, Status
@@ -1,5 +1,4 @@
1
1
  # Copyright Modal Labs 2022
2
- import importlib
3
2
  import importlib.util
4
3
  import typing
5
4
  from importlib.metadata import PackageNotFoundError, files
@@ -521,3 +521,17 @@ class TaskCommandRouterClient:
521
521
  await sleep_and_update_delay_and_num_retries_remaining(e)
522
522
  else:
523
523
  raise ConnectionError(str(e))
524
+
525
+ async def mount_image(self, request: sr_pb2.TaskMountDirectoryRequest):
526
+ with grpc_error_converter():
527
+ return await call_with_retries_on_transient_errors(
528
+ lambda: self._call_with_auth_retry(self._stub.TaskMountDirectory, request)
529
+ )
530
+
531
+ async def snapshot_directory(
532
+ self, request: sr_pb2.TaskSnapshotDirectoryRequest
533
+ ) -> sr_pb2.TaskSnapshotDirectoryResponse:
534
+ with grpc_error_converter():
535
+ return await call_with_retries_on_transient_errors(
536
+ lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request)
537
+ )
@@ -32,7 +32,7 @@ class _Client:
32
32
  server_url: str,
33
33
  client_type: int,
34
34
  credentials: typing.Optional[tuple[str, str]],
35
- version: str = "1.2.7.dev9",
35
+ version: str = "1.2.7.dev11",
36
36
  ):
37
37
  """mdmd:hidden
38
38
  The Modal client object is not intended to be instantiated directly by users.
@@ -163,7 +163,7 @@ class Client:
163
163
  server_url: str,
164
164
  client_type: int,
165
165
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.2.7.dev9",
166
+ version: str = "1.2.7.dev11",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -5,7 +5,6 @@ from datetime import datetime
5
5
  from typing import Any, Optional, Union
6
6
 
7
7
  from google.protobuf.message import Message
8
- from grpclib import GRPCError
9
8
  from synchronicity import classproperty
10
9
  from synchronicity.async_wrap import asynccontextmanager
11
10
 
@@ -28,7 +27,14 @@ from ._utils.name_utils import check_object_name
28
27
  from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
29
28
  from .client import _Client
30
29
  from .config import logger
31
- from .exception import AlreadyExistsError, DeserializationError, InvalidError, NotFoundError, RequestSizeError
30
+ from .exception import (
31
+ AlreadyExistsError,
32
+ DeserializationError,
33
+ Error,
34
+ InvalidError,
35
+ NotFoundError,
36
+ RequestSizeError,
37
+ )
32
38
 
33
39
 
34
40
  class _NoDefaultSentinel:
@@ -502,8 +508,8 @@ class _Dict(_Object, type_prefix="di"):
502
508
  req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized)
503
509
  try:
504
510
  await self._client.stub.DictUpdate(req)
505
- except GRPCError as exc:
506
- if "status = '413'" in exc.message:
511
+ except Error as exc:
512
+ if "status = '413'" in str(exc):
507
513
  raise RequestSizeError("Dict.update request is too large") from exc
508
514
  else:
509
515
  raise exc
@@ -521,8 +527,8 @@ class _Dict(_Object, type_prefix="di"):
521
527
  try:
522
528
  resp = await self._client.stub.DictUpdate(req)
523
529
  return resp.created
524
- except GRPCError as exc:
525
- if "status = '413'" in exc.message:
530
+ except Error as exc:
531
+ if "status = '413'" in str(exc):
526
532
  raise RequestSizeError("Dict.put request is too large") from exc
527
533
  else:
528
534
  raise exc
@@ -29,7 +29,7 @@ OUT_OF_RANGE -> InvalidError
29
29
  UNIMPLEMENTED -> UnimplementedError
30
30
  INTERNAL -> InternalError
31
31
  UNAVAILABLE -> ServiceError
32
- DATA_LOSS -> ServiceError
32
+ DATA_LOSS -> DataLossError
33
33
  UNAUTHENTICATED -> AuthError
34
34
  ```
35
35
 
@@ -150,6 +150,10 @@ class ConflictError(InvalidError, _GRPCErrorWrapper):
150
150
  """Raised when a resource conflict occurs between the request and current system state."""
151
151
 
152
152
 
153
+ class DataLossError(Error, _GRPCErrorWrapper):
154
+ """Raised when data is lost or corrupted."""
155
+
156
+
153
157
  class NotFoundError(Error, _GRPCErrorWrapper):
154
158
  """Raised when a requested resource was not found."""
155
159
 
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
 
11
11
  import json
12
12
 
13
- from grpclib.exceptions import GRPCError, StreamTerminatedError
13
+ from grpclib.exceptions import StreamTerminatedError
14
14
 
15
15
  from modal._utils.async_utils import TaskContext
16
16
  from modal.exception import ClientClosed
@@ -18,9 +18,8 @@ from modal_proto import api_pb2
18
18
 
19
19
  from ._utils.async_utils import synchronize_api
20
20
  from ._utils.deprecation import deprecation_error
21
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
22
21
  from .client import _Client
23
- from .exception import FilesystemExecutionError
22
+ from .exception import FilesystemExecutionError, InternalError, ServiceError
24
23
 
25
24
  WRITE_CHUNK_SIZE = 16 * 1024 * 1024 # 16 MiB
26
25
  WRITE_FILE_SIZE_LIMIT = 1024 * 1024 * 1024 # 1 GiB
@@ -166,13 +165,12 @@ class _FileIO(Generic[T]):
166
165
  completed = True
167
166
  break
168
167
 
169
- except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
168
+ except (ServiceError, InternalError, StreamTerminatedError, ClientClosed) as exc:
170
169
  if retries_remaining > 0:
171
170
  retries_remaining -= 1
172
- if isinstance(exc, GRPCError):
173
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
174
- await asyncio.sleep(1.0)
175
- continue
171
+ if isinstance(exc, (ServiceError, InternalError)):
172
+ await asyncio.sleep(1.0)
173
+ continue
176
174
  elif isinstance(exc, StreamTerminatedError):
177
175
  continue
178
176
  elif isinstance(exc, ClientClosed):
@@ -202,13 +200,12 @@ class _FileIO(Generic[T]):
202
200
  if isinstance(data, Exception):
203
201
  raise data
204
202
  output_buffer.write(data)
205
- except (GRPCError, StreamTerminatedError) as exc:
203
+ except (ServiceError, InternalError, StreamTerminatedError) as exc:
206
204
  if retries_remaining > 0:
207
205
  retries_remaining -= 1
208
- if isinstance(exc, GRPCError):
209
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
210
- await asyncio.sleep(1.0)
211
- continue
206
+ if isinstance(exc, (ServiceError, InternalError)):
207
+ await asyncio.sleep(1.0)
208
+ continue
212
209
  elif isinstance(exc, StreamTerminatedError):
213
210
  continue
214
211
  raise
@@ -22,7 +22,7 @@ from typing import (
22
22
  )
23
23
 
24
24
  from google.protobuf.message import Message
25
- from grpclib.exceptions import GRPCError, StreamTerminatedError
25
+ from grpclib.exceptions import StreamTerminatedError
26
26
  from typing_extensions import Self
27
27
 
28
28
  from modal._serialization import serialize_data_format
@@ -39,13 +39,20 @@ from ._utils.docker_utils import (
39
39
  find_dockerignore_file,
40
40
  )
41
41
  from ._utils.function_utils import FunctionInfo
42
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
43
42
  from ._utils.mount_utils import validate_only_modal_volumes
44
43
  from .client import _Client
45
44
  from .cloud_bucket_mount import _CloudBucketMount
46
45
  from .config import config, logger, user_config_path
47
46
  from .environments import _get_environment_cached
48
- from .exception import ExecutionError, InvalidError, NotFoundError, RemoteError, VersionError
47
+ from .exception import (
48
+ ExecutionError,
49
+ InternalError,
50
+ InvalidError,
51
+ NotFoundError,
52
+ RemoteError,
53
+ ServiceError,
54
+ VersionError,
55
+ )
49
56
  from .file_pattern_matcher import NON_PYTHON_FILES, FilePatternMatcher, _ignore_fn
50
57
  from .gpu import GPU_T, parse_gpu_config
51
58
  from .mount import _Mount, python_standalone_mount_name
@@ -374,9 +381,7 @@ async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.I
374
381
  while result_response is None:
375
382
  try:
376
383
  await join()
377
- except (StreamTerminatedError, GRPCError) as exc:
378
- if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
379
- raise exc
384
+ except (ServiceError, InternalError, StreamTerminatedError) as exc:
380
385
  retry_count += 1
381
386
  if retry_count >= 3:
382
387
  raise exc
@@ -874,6 +879,7 @@ class _Image(_Object, type_prefix="im"):
874
879
 
875
880
  rep = f"Image.from_id({image_id!r})"
876
881
  obj = _Image._from_loader(_load, rep, load_context_overrides=LoadContext(client=client))
882
+ obj._object_id = image_id
877
883
 
878
884
  return obj
879
885
 
@@ -18,17 +18,16 @@ from typing import (
18
18
  cast,
19
19
  )
20
20
 
21
- from grpclib.exceptions import GRPCError, StreamTerminatedError
21
+ from grpclib.exceptions import StreamTerminatedError
22
22
 
23
23
  from modal.exception import ClientClosed, ExecTimeoutError, InvalidError
24
24
  from modal_proto import api_pb2
25
25
 
26
26
  from ._utils.async_utils import aclosing, synchronize_api, synchronizer
27
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
28
27
  from ._utils.task_command_router_client import TaskCommandRouterClient
29
28
  from .client import _Client
30
29
  from .config import logger
31
- from .exception import ConflictError
30
+ from .exception import ConflictError, InternalError, ServiceError
32
31
  from .stream_type import StreamType
33
32
 
34
33
  if TYPE_CHECKING:
@@ -193,13 +192,12 @@ class _StreamReaderThroughServer(Generic[T]):
193
192
  else:
194
193
  last_index = batch_index
195
194
 
196
- except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
195
+ except (ServiceError, InternalError, StreamTerminatedError, ClientClosed) as exc:
197
196
  if retries_remaining > 0:
198
197
  retries_remaining -= 1
199
- if isinstance(exc, GRPCError):
200
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
201
- await asyncio.sleep(1.0)
202
- continue
198
+ if isinstance(exc, (ServiceError, InternalError)):
199
+ await asyncio.sleep(1.0)
200
+ continue
203
201
  elif isinstance(exc, StreamTerminatedError):
204
202
  continue
205
203
  elif isinstance(exc, ClientClosed):
@@ -270,13 +268,12 @@ class _StreamReaderThroughServer(Generic[T]):
270
268
 
271
269
  yield message
272
270
 
273
- except (GRPCError, StreamTerminatedError) as exc:
271
+ except (ServiceError, InternalError, StreamTerminatedError) as exc:
274
272
  if retries_remaining > 0:
275
273
  retries_remaining -= 1
276
- if isinstance(exc, GRPCError):
277
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
278
- await asyncio.sleep(1.0)
279
- continue
274
+ if isinstance(exc, (ServiceError, InternalError)):
275
+ await asyncio.sleep(1.0)
276
+ continue
280
277
  elif isinstance(exc, StreamTerminatedError):
281
278
  continue
282
279
  raise
@@ -13,7 +13,6 @@ from pathlib import Path, PurePosixPath
13
13
  from typing import Callable, Optional, Sequence, Union
14
14
 
15
15
  from google.protobuf.message import Message
16
- from grpclib import GRPCError
17
16
 
18
17
  import modal.exception
19
18
  import modal.file_pattern_matcher
@@ -838,8 +837,8 @@ async def _create_single_client_dependency_mount(
838
837
  client=client,
839
838
  )
840
839
  print(f"✅ Deployed mount {mount_name} to global namespace.") # noqa: T201
841
- except GRPCError as e:
842
- print(f"⚠️ Mount creation failed with {e.status}: {e.message}") # noqa: T201
840
+ except modal.exception.Error as e:
841
+ print(f"⚠️ Mount creation failed with {type(e).__name__}: {e}") # noqa: T201
843
842
  else:
844
843
  print(f"Dry run - skipping deployment of mount {mount_name}") # noqa: T201
845
844
 
@@ -30,7 +30,7 @@ from ._utils.grpc_utils import Retry
30
30
  from ._utils.name_utils import check_object_name
31
31
  from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
32
32
  from .client import _Client
33
- from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError, ResourceExhaustedError
33
+ from .exception import AlreadyExistsError, Error, InvalidError, NotFoundError, RequestSizeError, ResourceExhaustedError
34
34
 
35
35
 
36
36
  @dataclass
@@ -595,11 +595,14 @@ class _Queue(_Object, type_prefix="qu"):
595
595
  total_timeout=timeout,
596
596
  ),
597
597
  )
598
- except ResourceExhaustedError as exc:
598
+ except Error as exc:
599
599
  if "status = '413'" in str(exc):
600
600
  method = "put_many" if len(vs) > 1 else "put"
601
601
  raise RequestSizeError(f"Queue.{method} request is too large") from exc
602
- raise queue.Full(str(exc))
602
+ elif isinstance(exc, ResourceExhaustedError):
603
+ raise queue.Full(str(exc))
604
+ else:
605
+ raise exc
603
606
 
604
607
  async def _put_many_nonblocking(self, partition: Optional[str], partition_ttl: int, vs: list[Any]):
605
608
  vs_encoded = [serialize(v) for v in vs]
@@ -611,11 +614,14 @@ class _Queue(_Object, type_prefix="qu"):
611
614
  )
612
615
  try:
613
616
  await self._client.stub.QueuePut(request)
614
- except ResourceExhaustedError as exc:
617
+ except Error as exc:
615
618
  if "status = '413'" in str(exc):
616
619
  method = "put_many" if len(vs) > 1 else "put"
617
620
  raise RequestSizeError(f"Queue.{method} request is too large") from exc
618
- raise queue.Full(str(exc)) from exc
621
+ elif isinstance(exc, ResourceExhaustedError):
622
+ raise queue.Full(str(exc))
623
+ else:
624
+ raise exc
619
625
 
620
626
  @live_method
621
627
  async def len(self, *, partition: Optional[str] = None, total: bool = False) -> int:
@@ -6,6 +6,7 @@ import time
6
6
  import uuid
7
7
  from collections.abc import AsyncGenerator, Collection, Sequence
8
8
  from dataclasses import dataclass
9
+ from pathlib import PurePosixPath
9
10
  from typing import TYPE_CHECKING, Any, AsyncIterator, Literal, Optional, Union, overload
10
11
 
11
12
  from ._pty import get_pty_info
@@ -600,6 +601,49 @@ class _Sandbox(_Object, type_prefix="sb"):
600
601
 
601
602
  return image
602
603
 
604
+ async def _experimental_mount_image(self, path: Union[PurePosixPath, str], image: Optional[_Image]):
605
+ """Mount an Image at a path in the Sandbox filesystem."""
606
+
607
+ image_id = None
608
+
609
+ if image:
610
+ if not image._object_id:
611
+ # FIXME
612
+ raise InvalidError("Image has not been built.")
613
+ image_id = image._object_id
614
+ else:
615
+ image_id = "" # empty string indicates mount an empty dir
616
+
617
+ task_id = await self._get_task_id()
618
+ if (command_router_client := await self._get_command_router_client(task_id)) is None:
619
+ raise InvalidError("Mounting directories requires direct Sandbox control - please contact Modal support.")
620
+
621
+ posix_path = PurePosixPath(path)
622
+ if not posix_path.is_absolute():
623
+ raise InvalidError(f"Mount path must be absolute; got: {posix_path}")
624
+ path_bytes = posix_path.as_posix().encode("utf8")
625
+
626
+ req = sr_pb2.TaskMountDirectoryRequest(task_id=task_id, path=path_bytes, image_id=image_id)
627
+ await command_router_client.mount_image(req)
628
+
629
+ async def _experimental_snapshot_directory(self, path: Union[PurePosixPath, str]) -> _Image:
630
+ """Snapshot local changes to a previously mounted Image, creating a new Image."""
631
+
632
+ task_id = await self._get_task_id()
633
+ if (command_router_client := await self._get_command_router_client(task_id)) is None:
634
+ raise InvalidError(
635
+ "Snapshotting directories requires direct Sandbox control - please contact Modal support."
636
+ )
637
+
638
+ posix_path = PurePosixPath(path)
639
+ if not posix_path.is_absolute():
640
+ raise InvalidError(f"Snapshot path must be absolute; got: {posix_path}")
641
+ path_bytes = posix_path.as_posix().encode("utf8")
642
+
643
+ req = sr_pb2.TaskSnapshotDirectoryRequest(task_id=task_id, path=path_bytes)
644
+ res = await command_router_client.snapshot_directory(req)
645
+ return _Image._new_hydrated(res.image_id, self._client, None)
646
+
603
647
  # Live handle methods
604
648
 
605
649
  async def wait(self, raise_on_termination: bool = True):
@@ -22,6 +22,7 @@ import modal.stream_type
22
22
  import modal.volume
23
23
  import modal_proto.api_pb2
24
24
  import os
25
+ import pathlib
25
26
  import typing
26
27
  import typing_extensions
27
28
 
@@ -254,6 +255,18 @@ class _Sandbox(modal._object._Object):
254
255
  """
255
256
  ...
256
257
 
258
+ async def _experimental_mount_image(
259
+ self, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image._Image]
260
+ ):
261
+ """Mount an Image at a path in the Sandbox filesystem."""
262
+ ...
263
+
264
+ async def _experimental_snapshot_directory(
265
+ self, path: typing.Union[pathlib.PurePosixPath, str]
266
+ ) -> modal.image._Image:
267
+ """Snapshot local changes to a previously mounted Image, creating a new Image."""
268
+ ...
269
+
257
270
  async def wait(self, raise_on_termination: bool = True):
258
271
  """Wait for the Sandbox to finish running."""
259
272
  ...
@@ -820,6 +833,32 @@ class Sandbox(modal.object.Object):
820
833
 
821
834
  snapshot_filesystem: __snapshot_filesystem_spec[typing_extensions.Self]
822
835
 
836
+ class ___experimental_mount_image_spec(typing_extensions.Protocol[SUPERSELF]):
837
+ def __call__(
838
+ self, /, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image.Image]
839
+ ):
840
+ """Mount an Image at a path in the Sandbox filesystem."""
841
+ ...
842
+
843
+ async def aio(
844
+ self, /, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image.Image]
845
+ ):
846
+ """Mount an Image at a path in the Sandbox filesystem."""
847
+ ...
848
+
849
+ _experimental_mount_image: ___experimental_mount_image_spec[typing_extensions.Self]
850
+
851
+ class ___experimental_snapshot_directory_spec(typing_extensions.Protocol[SUPERSELF]):
852
+ def __call__(self, /, path: typing.Union[pathlib.PurePosixPath, str]) -> modal.image.Image:
853
+ """Snapshot local changes to a previously mounted Image, creating a new Image."""
854
+ ...
855
+
856
+ async def aio(self, /, path: typing.Union[pathlib.PurePosixPath, str]) -> modal.image.Image:
857
+ """Snapshot local changes to a previously mounted Image, creating a new Image."""
858
+ ...
859
+
860
+ _experimental_snapshot_directory: ___experimental_snapshot_directory_spec[typing_extensions.Self]
861
+
823
862
  class __wait_spec(typing_extensions.Protocol[SUPERSELF]):
824
863
  def __call__(self, /, raise_on_termination: bool = True):
825
864
  """Wait for the Sandbox to finish running."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev9
3
+ Version: 1.2.7.dev11
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.2.7.dev9"
4
+ __version__ = "1.2.7.dev11"
File without changes
File without changes
File without changes
File without changes