modal 1.4.3.dev27__tar.gz → 1.4.3.dev29__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 (214) hide show
  1. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/PKG-INFO +1 -1
  2. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_environments.py +2 -2
  3. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_functions.py +1 -1
  4. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_grpc_client.py +7 -0
  5. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_object.py +13 -13
  6. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_resolver.py +3 -5
  7. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/user_code_imports.py +2 -2
  8. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/task_command_router_client.py +54 -5
  9. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/queues.py +1 -1
  10. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/secret.py +1 -1
  11. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/client.pyi +2 -2
  12. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cls.py +2 -2
  13. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/dict.py +4 -4
  14. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/functions.pyi +6 -6
  15. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/mount.py +1 -1
  16. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/network_file_system.py +2 -2
  17. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/object.pyi +6 -6
  18. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/proxy.py +1 -1
  19. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/queue.py +4 -4
  20. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox.py +29 -4
  21. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox.pyi +19 -3
  22. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/secret.py +2 -2
  23. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/volume.py +4 -4
  24. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/PKG-INFO +1 -1
  25. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_grpc.py +32 -0
  26. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2.py +85 -35
  27. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2.pyi +95 -0
  28. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2_grpc.py +70 -0
  29. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2_grpc.pyi +32 -0
  30. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_version/__init__.py +1 -1
  31. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/LICENSE +0 -0
  32. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/README.md +0 -0
  33. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/__init__.py +0 -0
  34. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/__main__.py +0 -0
  35. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_billing.py +0 -0
  36. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_clustered_functions.py +0 -0
  37. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_clustered_functions.pyi +0 -0
  38. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_container_entrypoint.py +0 -0
  39. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_function_variants.py +0 -0
  40. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_ipython.py +0 -0
  41. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_load_context.py +0 -0
  42. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_location.py +0 -0
  43. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_logs.py +0 -0
  44. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/__init__.py +0 -0
  45. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/manager.py +0 -0
  46. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/pty.py +0 -0
  47. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/rich.py +0 -0
  48. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/status.py +0 -0
  49. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_partial_function.py +0 -0
  50. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_resources.py +0 -0
  51. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/__init__.py +0 -0
  52. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/asgi.py +0 -0
  53. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/container_io_manager.py +0 -0
  54. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/container_io_manager.pyi +0 -0
  55. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/execution_context.py +0 -0
  56. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/execution_context.pyi +0 -0
  57. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  58. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/telemetry.py +0 -0
  59. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/user_code_event_loop.py +0 -0
  60. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_serialization.py +0 -0
  61. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_server.py +0 -0
  62. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_traceback.py +0 -0
  63. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_tunnel.py +0 -0
  64. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_tunnel.pyi +0 -0
  65. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_type_manager.py +0 -0
  66. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/__init__.py +0 -0
  67. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/app_utils.py +0 -0
  68. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/async_utils.py +0 -0
  69. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/auth_token_manager.py +0 -0
  70. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/blob_utils.py +0 -0
  71. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/browser_utils.py +0 -0
  72. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/bytes_io_segment_payload.py +0 -0
  73. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/deprecation.py +0 -0
  74. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/docker_utils.py +0 -0
  75. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/function_utils.py +0 -0
  76. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/git_utils.py +0 -0
  77. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/grpc_testing.py +0 -0
  78. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/grpc_utils.py +0 -0
  79. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/hash_utils.py +0 -0
  80. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/http_utils.py +0 -0
  81. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/jwt_utils.py +0 -0
  82. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/logger.py +0 -0
  83. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/mount_utils.py +0 -0
  84. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/name_utils.py +0 -0
  85. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/package_utils.py +0 -0
  86. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/pattern_utils.py +0 -0
  87. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/rand_pb_testing.py +0 -0
  88. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/sandbox_fs_utils.py +0 -0
  89. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/shell_utils.py +0 -0
  90. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/time_utils.py +0 -0
  91. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/__init__.py +0 -0
  92. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  93. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/cloudpickle.py +0 -0
  94. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/tblib.py +0 -0
  95. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/version.py +0 -0
  96. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_watcher.py +0 -0
  97. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/app.py +0 -0
  98. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/app.pyi +0 -0
  99. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/billing.py +0 -0
  100. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2023.12.312.txt +0 -0
  101. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2023.12.txt +0 -0
  102. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2024.04.txt +0 -0
  103. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2024.10.txt +0 -0
  104. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2025.06.txt +0 -0
  105. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/PREVIEW.txt +0 -0
  106. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/README.md +0 -0
  107. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/base-images.json +0 -0
  108. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/call_graph.py +0 -0
  109. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/__init__.py +0 -0
  110. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_download.py +0 -0
  111. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_help.py +0 -0
  112. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_traceback.py +0 -0
  113. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/app.py +0 -0
  114. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/billing.py +0 -0
  115. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/changelog.py +0 -0
  117. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/cluster.py +0 -0
  118. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/config.py +0 -0
  119. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/container.py +0 -0
  120. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/dashboard.py +0 -0
  121. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/dict.py +0 -0
  122. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/entry_point.py +0 -0
  123. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/environment.py +0 -0
  124. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/import_refs.py +0 -0
  125. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/launch.py +0 -0
  126. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/logo.py +0 -0
  127. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/network_file_system.py +0 -0
  128. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/profile.py +0 -0
  129. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/__init__.py +0 -0
  130. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/run_jupyter.py +0 -0
  131. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/vscode.py +0 -0
  132. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/run.py +0 -0
  133. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/selector.py +0 -0
  134. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/shell.py +0 -0
  135. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/token.py +0 -0
  136. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/utils.py +0 -0
  137. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/volume.py +0 -0
  138. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/client.py +0 -0
  139. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cloud_bucket_mount.py +0 -0
  140. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cloud_bucket_mount.pyi +0 -0
  141. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cls.pyi +0 -0
  142. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/config.py +0 -0
  143. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/container_process.py +0 -0
  144. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/container_process.pyi +0 -0
  145. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/dict.pyi +0 -0
  146. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/environments.py +0 -0
  147. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/environments.pyi +0 -0
  148. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/exception.py +0 -0
  149. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/__init__.py +0 -0
  150. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/flash.py +0 -0
  151. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/flash.pyi +0 -0
  152. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/ipython.py +0 -0
  153. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_io.py +0 -0
  154. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_io.pyi +0 -0
  155. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_pattern_matcher.py +0 -0
  156. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/functions.py +0 -0
  157. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/image.py +0 -0
  158. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/image.pyi +0 -0
  159. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/io_streams.py +0 -0
  160. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/io_streams.pyi +0 -0
  161. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/mount.pyi +0 -0
  162. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/network_file_system.pyi +0 -0
  163. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/object.py +0 -0
  164. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/output.py +0 -0
  165. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/parallel_map.py +0 -0
  166. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/parallel_map.pyi +0 -0
  167. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/partial_function.py +0 -0
  168. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/partial_function.pyi +0 -0
  169. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/proxy.pyi +0 -0
  170. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/py.typed +0 -0
  171. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/queue.pyi +0 -0
  172. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/retries.py +0 -0
  173. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/runner.py +0 -0
  174. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/runner.pyi +0 -0
  175. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/running_app.py +0 -0
  176. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox_fs.py +0 -0
  177. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox_fs.pyi +0 -0
  178. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/schedule.py +0 -0
  179. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/scheduler_placement.py +0 -0
  180. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/secret.pyi +0 -0
  181. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/server.py +0 -0
  182. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/server.pyi +0 -0
  183. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/serving.py +0 -0
  184. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/serving.pyi +0 -0
  185. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/snapshot.py +0 -0
  186. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/snapshot.pyi +0 -0
  187. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/stream_type.py +0 -0
  188. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/token_flow.py +0 -0
  189. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/token_flow.pyi +0 -0
  190. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/volume.pyi +0 -0
  191. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/SOURCES.txt +0 -0
  192. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/dependency_links.txt +0 -0
  193. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/entry_points.txt +0 -0
  194. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/requires.txt +0 -0
  195. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/top_level.txt +0 -0
  196. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/__init__.py +0 -0
  197. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_cli_docs.py +0 -0
  198. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_cli_docs_main.py +0 -0
  199. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_reference_docs.py +0 -0
  200. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_reference_docs_main.py +0 -0
  201. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/__init__.py +0 -0
  202. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/mdmd.py +0 -0
  203. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/signatures.py +0 -0
  204. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/__init__.py +0 -0
  205. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_grpc.py +0 -0
  206. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2.py +0 -0
  207. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2.pyi +0 -0
  208. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2_grpc.py +0 -0
  209. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2_grpc.pyi +0 -0
  210. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/modal_api_grpc.py +0 -0
  211. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/py.typed +0 -0
  212. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_version/__main__.py +0 -0
  213. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/pyproject.toml +0 -0
  214. {modal-1.4.3.dev27 → modal-1.4.3.dev29}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.3.dev27
3
+ Version: 1.4.3.dev29
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -75,7 +75,7 @@ class _EnvironmentManager:
75
75
  item.environment_id,
76
76
  client,
77
77
  metadata,
78
- is_another_app=True,
78
+ skip_reload=True,
79
79
  rep=f"Environment.from_name({item.name!r})",
80
80
  )
81
81
  environments.append(env)
@@ -351,7 +351,7 @@ class _Environment(_Object, type_prefix="en"):
351
351
  return _Environment._from_loader(
352
352
  _load,
353
353
  repr,
354
- is_another_app=True,
354
+ skip_reload=True,
355
355
  hydrate_lazily=True,
356
356
  name=name,
357
357
  load_context_overrides=LoadContext(client=client),
@@ -1242,7 +1242,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1242
1242
  )
1243
1243
  rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
1244
1244
  return cls._from_loader(
1245
- _load_remote, rep, is_another_app=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
1245
+ _load_remote, rep, skip_reload=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
1246
1246
  )
1247
1247
 
1248
1248
  @classmethod
@@ -44,6 +44,10 @@ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
44
44
 
45
45
 
46
46
  class grpc_error_converter:
47
+ def __init__(self, *, expect_timeouts: bool = False):
48
+ """expect_timeouts: if this is set we convert cancelled/deadline_exceeded statuses to exception.TimeoutError"""
49
+ self._expect_timeouts = expect_timeouts
50
+
47
51
  def __enter__(self):
48
52
  pass
49
53
 
@@ -52,6 +56,9 @@ class grpc_error_converter:
52
56
  use_full_traceback = config.get("traceback")
53
57
  with suppress_tb_frame():
54
58
  if isinstance(exc, GRPCError):
59
+ if self._expect_timeouts and exc.status in (Status.CANCELLED, Status.DEADLINE_EXCEEDED):
60
+ raise exception.TimeoutError("Timeout expired")
61
+
55
62
  modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
56
63
  modal_exc._grpc_message = exc.message
57
64
  modal_exc._grpc_status = exc.status
@@ -82,7 +82,7 @@ class _Object:
82
82
  _load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None
83
83
  _preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
84
84
  _rep: str
85
- _is_another_app: bool
85
+ _skip_reload: bool
86
86
  _hydrate_lazily: bool
87
87
  _deps: Optional[Callable[..., Sequence["_Object"]]]
88
88
  _deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
@@ -113,9 +113,9 @@ class _Object:
113
113
  self,
114
114
  rep: str,
115
115
  load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
116
- is_another_app: bool = False,
117
116
  preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
118
117
  hydrate_lazily: bool = False,
118
+ skip_reload: bool = False,
119
119
  deps: Optional[Callable[..., Sequence["_Object"]]] = None,
120
120
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
121
121
  name: Optional[str] = None,
@@ -123,10 +123,10 @@ class _Object:
123
123
  load_context_overrides: Optional[LoadContext] = None,
124
124
  ):
125
125
  self._local_uuid = str(uuid.uuid4())
126
+ self._rep = rep
126
127
  self._load = load
127
128
  self._preload = preload
128
- self._rep = rep
129
- self._is_another_app = is_another_app
129
+ self._skip_reload = skip_reload
130
130
  self._hydrate_lazily = hydrate_lazily
131
131
  self._deps = deps
132
132
  self._deduplication_key = deduplication_key
@@ -218,7 +218,7 @@ class _Object:
218
218
  cls,
219
219
  load: Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]],
220
220
  rep: str,
221
- is_another_app: bool = False,
221
+ skip_reload: bool = False,
222
222
  preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
223
223
  hydrate_lazily: bool = False,
224
224
  deps: Optional[Callable[..., Sequence["_Object"]]] = None,
@@ -232,12 +232,12 @@ class _Object:
232
232
  obj._init(
233
233
  rep,
234
234
  load,
235
- is_another_app,
236
- preload,
237
- hydrate_lazily,
238
- deps,
239
- deduplication_key,
240
- name,
235
+ skip_reload=skip_reload,
236
+ preload=preload,
237
+ hydrate_lazily=hydrate_lazily,
238
+ deps=deps,
239
+ deduplication_key=deduplication_key,
240
+ name=name,
241
241
  load_context_overrides=load_context_overrides,
242
242
  )
243
243
  return obj
@@ -268,7 +268,7 @@ class _Object:
268
268
  object_id: str,
269
269
  client: _Client,
270
270
  handle_metadata: Optional[Message],
271
- is_another_app: bool = False,
271
+ skip_reload: bool = False,
272
272
  rep: Optional[str] = None,
273
273
  ) -> Self:
274
274
  obj_cls: type[Self]
@@ -287,7 +287,7 @@ class _Object:
287
287
  # Instantiate provider
288
288
  obj = _Object.__new__(obj_cls)
289
289
  rep = rep or f"modal.{obj_cls.__name__.strip('_')}.from_id({object_id!r})"
290
- obj._init(rep, is_another_app=is_another_app)
290
+ obj._init(rep, skip_reload=skip_reload)
291
291
  obj._hydrate(object_id, client, handle_metadata)
292
292
 
293
293
  return obj
@@ -44,8 +44,7 @@ class Resolver:
44
44
  *,
45
45
  existing_object_id: Optional[str] = None,
46
46
  ):
47
- if obj._is_hydrated and obj._is_another_app:
48
- # No need to reload this, it won't typically change
47
+ if obj._is_hydrated and obj._skip_reload:
49
48
  if obj.local_uuid not in self._local_uuid_to_future:
50
49
  # a bit dumb - but we still need to store a reference to the object here
51
50
  # to be able to include all referenced objects when setting up the app
@@ -89,12 +88,11 @@ class Resolver:
89
88
  await obj._load(obj, self, load_context, existing_object_id)
90
89
 
91
90
  # Check that the id of functions didn't change
92
- # Persisted refs are ignored because their life cycle is managed independently.
93
91
  if (
94
- not obj._is_another_app
95
- and existing_object_id is not None
92
+ existing_object_id is not None
96
93
  and existing_object_id.startswith("fu-")
97
94
  and obj.object_id != existing_object_id
95
+ and not obj._skip_reload
98
96
  ):
99
97
  raise Exception(
100
98
  f"Tried creating an object using existing id {existing_object_id} "
@@ -570,7 +570,7 @@ def import_class_service(
570
570
  service_function_hydration_data.object_id,
571
571
  _client,
572
572
  service_function_hydration_data.function_handle_metadata,
573
- is_another_app=True, # this skips re-loading the function, which is required since it doesn't have a loader
573
+ skip_reload=True, # this skips re-loading the function, which is required since it doesn't have a loader
574
574
  )
575
575
  _cls = modal.cls._Cls.from_local(cls_or_user_cls, active_app, _service_function)
576
576
  # hydration of the class itself - just sets the id and triggers some side effects
@@ -625,7 +625,7 @@ def import_server_service(
625
625
  service_function_hydration_data.object_id,
626
626
  _client,
627
627
  service_function_hydration_data.function_handle_metadata,
628
- is_another_app=True, # this skips re-loading the function, which is required since it doesn't have a loader
628
+ skip_reload=True, # this skips re-loading the function, which is required since it doesn't have a loader
629
629
  )
630
630
 
631
631
  _server = modal._server._Server._from_local(cls_or_user_cls, active_app, _service_function)
@@ -5,6 +5,7 @@ import json
5
5
  import socket
6
6
  import ssl
7
7
  import time
8
+ import typing
8
9
  import urllib.parse
9
10
  import weakref
10
11
  from contextlib import suppress
@@ -16,7 +17,7 @@ from grpclib import GRPCError, Status
16
17
  from grpclib.exceptions import StreamTerminatedError
17
18
 
18
19
  from modal.config import logger
19
- from modal.exception import ExecTimeoutError
20
+ from modal.exception import ExecTimeoutError, TimeoutError as ModalTimeoutError
20
21
  from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
21
22
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
22
23
 
@@ -72,13 +73,24 @@ async def call_with_retries_on_transient_errors(
72
73
  base_delay_secs: float = 0.01,
73
74
  delay_factor: float = 2,
74
75
  max_retries: Optional[int] = 10,
76
+ exclude_status_codes: Optional[list[Status]] = None,
77
+ expect_timeouts: bool = False, # when True we convert TimeoutError to exception.TimeoutError and don't retry"""
75
78
  ):
76
79
  """Call func() with transient error retries and exponential backoff.
77
80
 
78
81
  Authentication retries are expected to be handled by the caller.
82
+
83
+ Args:
84
+ exclude_status_codes: gRPC status codes to exclude from retry logic even if
85
+ they are in RETRYABLE_GRPC_STATUS_CODES. Use this to let certain errors
86
+ (e.g. DEADLINE_EXCEEDED) propagate immediately rather than being retried.
79
87
  """
80
88
  delay_secs = base_delay_secs
81
89
  num_retries = 0
90
+ exclude_status_codes = exclude_status_codes or []
91
+
92
+ def is_retryable_status(status: Status) -> bool:
93
+ return status in RETRYABLE_GRPC_STATUS_CODES and status not in exclude_status_codes
82
94
 
83
95
  async def sleep_and_update_delay_and_num_retries_remaining(e: Exception):
84
96
  nonlocal delay_secs, num_retries
@@ -91,7 +103,7 @@ async def call_with_retries_on_transient_errors(
91
103
  try:
92
104
  return await func()
93
105
  except GRPCError as e:
94
- if (max_retries is None or num_retries < max_retries) and e.status in RETRYABLE_GRPC_STATUS_CODES:
106
+ if (max_retries is None or num_retries < max_retries) and is_retryable_status(e.status):
95
107
  await sleep_and_update_delay_and_num_retries_remaining(e)
96
108
  else:
97
109
  raise e
@@ -108,7 +120,16 @@ async def call_with_retries_on_transient_errors(
108
120
  await sleep_and_update_delay_and_num_retries_remaining(e)
109
121
  else:
110
122
  raise e
111
- except (OSError, asyncio.TimeoutError) as e:
123
+ except asyncio.TimeoutError as e:
124
+ if expect_timeouts:
125
+ # grpclib raises TimeoutError for the client-side timeout/deadlines
126
+ raise ModalTimeoutError("Timeout expired")
127
+
128
+ if max_retries is None or num_retries < max_retries:
129
+ await sleep_and_update_delay_and_num_retries_remaining(e)
130
+ else:
131
+ raise ConnectionError(str(e))
132
+ except OSError as e:
112
133
  if max_retries is None or num_retries < max_retries:
113
134
  await sleep_and_update_delay_and_num_retries_remaining(e)
114
135
  else:
@@ -186,6 +207,15 @@ class TaskCommandRouterClient:
186
207
  closed_error_message="Unable to perform operation on a detached sandbox",
187
208
  )
188
209
 
210
+ async def send_request(event: grpclib.events.SendRequest) -> None:
211
+ idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
212
+ if idempotency_key is None:
213
+ logger.debug(f"Sending request to {event.method_name}")
214
+ else:
215
+ logger.debug(f"Sending request to {event.method_name} ({idempotency_key[:8]})")
216
+
217
+ grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
218
+
189
219
  try:
190
220
  await _connect_channel(channel)
191
221
  except socket.gaierror as exc:
@@ -618,9 +648,28 @@ class TaskCommandRouterClient:
618
648
  )
619
649
 
620
650
  async def snapshot_directory(
621
- self, request: sr_pb2.TaskSnapshotDirectoryRequest
651
+ self, request: sr_pb2.TaskSnapshotDirectoryRequest, **kwargs
622
652
  ) -> sr_pb2.TaskSnapshotDirectoryResponse:
623
653
  with grpc_error_converter():
624
654
  return await call_with_retries_on_transient_errors(
625
- lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request)
655
+ lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request, **kwargs)
656
+ )
657
+
658
+ async def snapshot_filesystem(
659
+ self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout: Optional[int] = None, **kwargs
660
+ ) -> sr_pb2.TaskSnapshotFilesystemResponse:
661
+ expect_timeouts = timeout is not None
662
+ with grpc_error_converter(expect_timeouts=expect_timeouts):
663
+ # note: TaskSnapshotFilesystem has a timeout concept with multiple variants:
664
+ # * Normally it would time out on the client side inside of grpclib - this causes an asyncio.TimeoutError
665
+ # * It also propagates the timeout to the server however, which could potentially trigger, in particular
666
+ # if an in-progress requests is idempotently rejoined as a retry. These errors appear to be propagated
667
+ # as Status.CANCELLED at the time of writing this, but we may change the backend to return
668
+ # DEADLINE_EXCEEDED in the future, so we want to make the code compatible with both here:
669
+ return await call_with_retries_on_transient_errors(
670
+ lambda: self._call_with_auth_retry(
671
+ self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
672
+ ),
673
+ exclude_status_codes=[Status.DEADLINE_EXCEEDED, Status.CANCELLED],
674
+ expect_timeouts=expect_timeouts, # client wanted a timeout - raise as such immediately
626
675
  )
@@ -106,7 +106,7 @@ async def list_(*, json: bool = False, env: Optional[str] = None):
106
106
  break
107
107
  finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
108
108
 
109
- queues = [_Queue._new_hydrated(item.queue_id, client, item.metadata, is_another_app=True) for item in items]
109
+ queues = [_Queue._new_hydrated(item.queue_id, client, item.metadata, skip_reload=True) for item in items]
110
110
 
111
111
  rows = []
112
112
  for obj, resp_data in zip(queues, items):
@@ -55,7 +55,7 @@ async def list_(env: Optional[str] = None, json: bool = False):
55
55
  break
56
56
  finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
57
57
 
58
- secrets = [_Secret._new_hydrated(item.secret_id, client, item.metadata, is_another_app=True) for item in items]
58
+ secrets = [_Secret._new_hydrated(item.secret_id, client, item.metadata, skip_reload=True) for item in items]
59
59
 
60
60
  rows = []
61
61
  for obj, resp_data in zip(secrets, items):
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.4.3.dev27",
38
+ version: str = "1.4.3.dev29",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -175,7 +175,7 @@ class Client:
175
175
  server_url: str,
176
176
  client_type: int,
177
177
  credentials: typing.Optional[tuple[str, str]],
178
- version: str = "1.4.3.dev27",
178
+ version: str = "1.4.3.dev29",
179
179
  ):
180
180
  """mdmd:hidden
181
181
  The Modal client object is not intended to be instantiated directly by users.
@@ -519,7 +519,7 @@ class _Cls(_Object, type_prefix="cs"):
519
519
  new_cls = _Cls._from_loader(
520
520
  _load_from_base,
521
521
  rep=f"{self._name}.{method_name}(...)",
522
- is_another_app=True,
522
+ skip_reload=True,
523
523
  deps=lambda: [],
524
524
  load_context_overrides=self._load_context_overrides,
525
525
  hydrate_lazily=True,
@@ -680,7 +680,7 @@ More information on class parameterization can be found here: https://modal.com/
680
680
  cls = cls._from_loader(
681
681
  _load_remote,
682
682
  rep,
683
- is_another_app=True,
683
+ skip_reload=True,
684
684
  hydrate_lazily=True,
685
685
  load_context_overrides=load_context_overrides,
686
686
  )
@@ -198,7 +198,7 @@ class _DictManager:
198
198
  item.dict_id,
199
199
  client,
200
200
  item.metadata,
201
- is_another_app=True,
201
+ skip_reload=True,
202
202
  rep=_Dict._repr(item.name, environment_name),
203
203
  )
204
204
  for item in items
@@ -354,7 +354,7 @@ class _Dict(_Object, type_prefix="di"):
354
354
  response.dict_id,
355
355
  client,
356
356
  response.metadata,
357
- is_another_app=True,
357
+ skip_reload=True,
358
358
  rep="modal.Dict.ephemeral()",
359
359
  )
360
360
 
@@ -393,7 +393,7 @@ class _Dict(_Object, type_prefix="di"):
393
393
  return _Dict._from_loader(
394
394
  _load,
395
395
  rep,
396
- is_another_app=True,
396
+ skip_reload=True,
397
397
  hydrate_lazily=True,
398
398
  name=name,
399
399
  load_context_overrides=LoadContext(environment_name=environment_name, client=client),
@@ -436,7 +436,7 @@ class _Dict(_Object, type_prefix="di"):
436
436
  return _Dict._from_loader(
437
437
  _load,
438
438
  rep,
439
- is_another_app=True,
439
+ skip_reload=True,
440
440
  hydrate_lazily=True,
441
441
  load_context_overrides=LoadContext(client=client),
442
442
  )
@@ -420,7 +420,7 @@ class Function(
420
420
 
421
421
  _call_generator: ___call_generator_spec
422
422
 
423
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
423
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
424
424
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
425
425
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
426
426
  ...
@@ -429,7 +429,7 @@ class Function(
429
429
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
430
430
  ...
431
431
 
432
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
432
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
433
433
 
434
434
  class __remote_gen_spec(typing_extensions.Protocol):
435
435
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -456,7 +456,7 @@ class Function(
456
456
  """
457
457
  ...
458
458
 
459
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
459
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
460
460
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
461
461
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
462
462
 
@@ -479,7 +479,7 @@ class Function(
479
479
  """
480
480
  ...
481
481
 
482
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
482
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
483
483
 
484
484
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
485
485
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -487,7 +487,7 @@ class Function(
487
487
 
488
488
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
489
489
 
490
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
490
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
491
491
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
492
492
  """Calls the function with the given arguments, without waiting for the results.
493
493
 
@@ -508,7 +508,7 @@ class Function(
508
508
  """
509
509
  ...
510
510
 
511
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
511
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
512
512
 
513
513
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
514
514
  """Return the inner Python object wrapped by this Modal Function."""
@@ -664,7 +664,7 @@ class _Mount(_Object, type_prefix="mo"):
664
664
  return _Mount._from_loader(
665
665
  _load,
666
666
  "Mount()",
667
- is_another_app=True,
667
+ skip_reload=True,
668
668
  hydrate_lazily=True,
669
669
  load_context_overrides=LoadContext(environment_name=environment_name, client=client),
670
670
  )
@@ -134,7 +134,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
134
134
  return _NetworkFileSystem._from_loader(
135
135
  _load,
136
136
  "NetworkFileSystem()",
137
- is_another_app=True,
137
+ skip_reload=True,
138
138
  hydrate_lazily=True,
139
139
  load_context_overrides=LoadContext(environment_name=environment_name, client=client),
140
140
  )
@@ -174,7 +174,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
174
174
  response.shared_volume_id,
175
175
  client,
176
176
  None,
177
- is_another_app=True,
177
+ skip_reload=True,
178
178
  rep="modal.NetworkFileSystem.ephemeral()",
179
179
  )
180
180
 
@@ -24,7 +24,7 @@ class Object:
24
24
  ]
25
25
  ]
26
26
  _rep: str
27
- _is_another_app: bool
27
+ _skip_reload: bool
28
28
  _hydrate_lazily: bool
29
29
  _deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]]
30
30
  _deduplication_key: typing.Optional[
@@ -60,7 +60,6 @@ class Object:
60
60
  None,
61
61
  ]
62
62
  ] = None,
63
- is_another_app: bool = False,
64
63
  preload: typing.Optional[
65
64
  collections.abc.Callable[
66
65
  [
@@ -73,6 +72,7 @@ class Object:
73
72
  ]
74
73
  ] = None,
75
74
  hydrate_lazily: bool = False,
75
+ skip_reload: bool = False,
76
76
  deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
77
77
  deduplication_key: typing.Optional[collections.abc.Callable[[], collections.abc.Hashable]] = None,
78
78
  name: typing.Optional[str] = None,
@@ -94,7 +94,6 @@ class Object:
94
94
  collections.abc.Awaitable[None],
95
95
  ]
96
96
  ] = None,
97
- is_another_app: bool = False,
98
97
  preload: typing.Optional[
99
98
  collections.abc.Callable[
100
99
  [
@@ -107,6 +106,7 @@ class Object:
107
106
  ]
108
107
  ] = None,
109
108
  hydrate_lazily: bool = False,
109
+ skip_reload: bool = False,
110
110
  deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
111
111
  deduplication_key: typing.Optional[
112
112
  collections.abc.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
@@ -148,7 +148,7 @@ class Object:
148
148
  None,
149
149
  ],
150
150
  rep: str,
151
- is_another_app: bool = False,
151
+ skip_reload: bool = False,
152
152
  preload: typing.Optional[
153
153
  collections.abc.Callable[
154
154
  [
@@ -180,7 +180,7 @@ class Object:
180
180
  collections.abc.Awaitable[None],
181
181
  ],
182
182
  rep: str,
183
- is_another_app: bool = False,
183
+ skip_reload: bool = False,
184
184
  preload: typing.Optional[
185
185
  collections.abc.Callable[
186
186
  [
@@ -216,7 +216,7 @@ class Object:
216
216
  object_id: str,
217
217
  client: modal.client.Client,
218
218
  handle_metadata: typing.Optional[google.protobuf.message.Message],
219
- is_another_app: bool = False,
219
+ skip_reload: bool = False,
220
220
  rep: typing.Optional[str] = None,
221
221
  ) -> typing_extensions.Self: ...
222
222
  def _hydrate_from_other(self, other: typing_extensions.Self): ...
@@ -43,7 +43,7 @@ class _Proxy(_Object, type_prefix="pr"):
43
43
  return _Proxy._from_loader(
44
44
  _load,
45
45
  rep,
46
- is_another_app=True,
46
+ skip_reload=True,
47
47
  load_context_overrides=LoadContext(client=client, environment_name=environment_name),
48
48
  )
49
49
 
@@ -163,7 +163,7 @@ class _QueueManager:
163
163
  item.queue_id,
164
164
  client,
165
165
  item.metadata,
166
- is_another_app=True,
166
+ skip_reload=True,
167
167
  rep=_Queue._repr(item.name, environment_name),
168
168
  )
169
169
  for item in items
@@ -353,7 +353,7 @@ class _Queue(_Object, type_prefix="qu"):
353
353
  async with TaskContext() as tc:
354
354
  request = api_pb2.QueueHeartbeatRequest(queue_id=response.queue_id)
355
355
  tc.infinite_loop(lambda: client.stub.QueueHeartbeat(request), sleep=_heartbeat_sleep)
356
- yield cls._new_hydrated(response.queue_id, client, response.metadata, is_another_app=True)
356
+ yield cls._new_hydrated(response.queue_id, client, response.metadata, skip_reload=True)
357
357
 
358
358
  @staticmethod
359
359
  def from_name(
@@ -389,7 +389,7 @@ class _Queue(_Object, type_prefix="qu"):
389
389
  return _Queue._from_loader(
390
390
  _load,
391
391
  rep,
392
- is_another_app=True,
392
+ skip_reload=True,
393
393
  hydrate_lazily=True,
394
394
  name=name,
395
395
  load_context_overrides=LoadContext(environment_name=environment_name, client=client),
@@ -432,7 +432,7 @@ class _Queue(_Object, type_prefix="qu"):
432
432
  return _Queue._from_loader(
433
433
  _load,
434
434
  rep,
435
- is_another_app=True,
435
+ skip_reload=True,
436
436
  hydrate_lazily=True,
437
437
  load_context_overrides=LoadContext(client=client),
438
438
  )
@@ -74,7 +74,7 @@ _default_image: _Image = _Image.debian_slim()
74
74
  # We need some bytes of overhead for the rest of the command line besides the args,
75
75
  # e.g. 'runsc exec ...'. So we use 2**16 as the limit.
76
76
  ARG_MAX_BYTES = 2**16
77
-
77
+ TTL_NO_EXPIRY_SENTINEL = -1
78
78
 
79
79
  if TYPE_CHECKING:
80
80
  import modal.app
@@ -955,9 +955,29 @@ class _Sandbox(_Object, type_prefix="sb"):
955
955
 
956
956
  Returns an [`Image`](https://modal.com/docs/reference/modal.Image) object which
957
957
  can be used to spawn a new Sandbox with the same filesystem.
958
+
959
+ `timeout` If the snapshot does not return within that window, the call is cancelled
960
+ and `modal.exception.TimeoutError` is raised.
958
961
  """
962
+ if os.environ.get("MODAL_USE_LEGACY_FILESYSTEM_SNAPSHOT") == "1":
963
+ return await self._legacy_snapshot_filesystem(timeout)
964
+
959
965
  self._ensure_v1("snapshot_filesystem")
960
- await self._get_task_id() # Ensure the sandbox has started
966
+
967
+ task_id = await self._get_task_id()
968
+ command_router_client = await self._get_command_router_client(task_id)
969
+
970
+ req = sr_pb2.TaskSnapshotFilesystemRequest(
971
+ task_id=task_id,
972
+ snapshot_id=str(uuid.uuid4()),
973
+ ttl_seconds=TTL_NO_EXPIRY_SENTINEL,
974
+ )
975
+ res = await command_router_client.snapshot_filesystem(req, timeout=float(timeout))
976
+ return _Image._new_hydrated(res.image_id, self._client, None)
977
+
978
+ async def _legacy_snapshot_filesystem(self, timeout: int = 55) -> _Image:
979
+ self._ensure_v1("snapshot_filesystem")
980
+ await self._get_task_id()
961
981
  req = api_pb2.SandboxSnapshotFsRequest(sandbox_id=self.object_id, timeout=timeout)
962
982
  resp = await self._client.stub.SandboxSnapshotFs(req)
963
983
 
@@ -1048,7 +1068,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1048
1068
  async def snapshot_directory(self, path: Union[PurePosixPath, str]) -> _Image:
1049
1069
  """Snapshot a directory in a running Sandbox, creating a new Image with its content.
1050
1070
 
1051
- Directory snapshots are currently persisted for 30 days after they were last created or used.
1071
+ Directory snapshots are currently persisted for 30 days after they were created.
1052
1072
 
1053
1073
  Usage:
1054
1074
  ```py notest
@@ -1071,7 +1091,12 @@ class _Sandbox(_Object, type_prefix="sb"):
1071
1091
  path_bytes = posix_path.as_posix().encode("utf8")
1072
1092
 
1073
1093
  snapshot_id = str(uuid.uuid4())
1074
- req = sr_pb2.TaskSnapshotDirectoryRequest(task_id=task_id, path=path_bytes, snapshot_id=snapshot_id)
1094
+ req = sr_pb2.TaskSnapshotDirectoryRequest(
1095
+ task_id=task_id,
1096
+ path=path_bytes,
1097
+ snapshot_id=snapshot_id,
1098
+ ttl_seconds=None, # pretending to be old client for now - set server decide on retention
1099
+ )
1075
1100
  res = await command_router_client.snapshot_directory(req)
1076
1101
  return _Image._new_hydrated(res.image_id, self._client, None)
1077
1102