modal 1.2.5.dev4__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (204) hide show
  1. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/PKG-INFO +3 -3
  2. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/README.md +1 -1
  3. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/__init__.py +2 -2
  4. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/__main__.py +4 -29
  5. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_container_entrypoint.py +0 -1
  6. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_functions.py +11 -17
  7. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_grpc_client.py +48 -28
  8. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_output.py +10 -11
  9. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_partial_function.py +2 -0
  10. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.py +5 -6
  11. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/gpu_memory_snapshot.py +9 -7
  12. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/user_code_imports.py +105 -79
  13. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_traceback.py +1 -1
  14. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_tunnel.py +5 -9
  15. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/blob_utils.py +23 -9
  16. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/function_utils.py +17 -21
  17. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/grpc_utils.py +5 -8
  18. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/package_utils.py +0 -1
  19. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/task_command_router_client.py +130 -129
  20. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/app.py +62 -11
  21. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/app.pyi +8 -4
  22. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/entry_point.py +23 -0
  23. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/environment.py +2 -16
  24. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/launch.py +0 -74
  25. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/network_file_system.py +4 -16
  26. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/run.py +1 -1
  27. modal-1.2.7.dev11/modal/cli/shell.py +375 -0
  28. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/utils.py +1 -13
  29. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/volume.py +4 -16
  30. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/client.pyi +2 -2
  31. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cls.py +0 -6
  32. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/container_process.py +2 -2
  33. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/dict.py +44 -15
  34. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/dict.pyi +2 -0
  35. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/exception.py +146 -16
  36. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/experimental/__init__.py +8 -2
  37. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/experimental/flash.py +132 -16
  38. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/experimental/flash.pyi +60 -0
  39. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/file_io.py +39 -67
  40. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/file_io.pyi +12 -27
  41. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/functions.pyi +2 -1
  42. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/image.py +17 -14
  43. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/io_streams.py +100 -124
  44. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/io_streams.pyi +28 -26
  45. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/mount.py +23 -19
  46. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/mount.pyi +3 -3
  47. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/queue.py +12 -14
  48. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/runner.py +2 -9
  49. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/sandbox.py +50 -27
  50. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/sandbox.pyi +39 -0
  51. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/secret.py +4 -20
  52. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/volume.py +19 -18
  53. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/PKG-INFO +3 -3
  54. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/SOURCES.txt +0 -8
  55. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api.proto +24 -4
  56. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api_pb2.py +662 -656
  57. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api_pb2.pyi +37 -8
  58. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/task_command_router.proto +20 -0
  59. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/task_command_router_grpc.py +33 -0
  60. modal-1.2.7.dev11/modal_proto/task_command_router_pb2.py +180 -0
  61. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2.pyi +51 -0
  62. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.py +69 -0
  63. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +25 -0
  64. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_version/__init__.py +1 -1
  65. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_version/__main__.py +1 -1
  66. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/pyproject.toml +13 -6
  67. modal-1.2.5.dev4/modal/cli/programs/launch_instance_ssh.py +0 -94
  68. modal-1.2.5.dev4/modal/cli/programs/run_marimo.py +0 -95
  69. modal-1.2.5.dev4/modal/cli/shell.py +0 -237
  70. modal-1.2.5.dev4/modal_proto/sandbox_router.proto +0 -145
  71. modal-1.2.5.dev4/modal_proto/sandbox_router_grpc.py +0 -105
  72. modal-1.2.5.dev4/modal_proto/sandbox_router_pb2.py +0 -149
  73. modal-1.2.5.dev4/modal_proto/sandbox_router_pb2.pyi +0 -333
  74. modal-1.2.5.dev4/modal_proto/sandbox_router_pb2_grpc.py +0 -203
  75. modal-1.2.5.dev4/modal_proto/sandbox_router_pb2_grpc.pyi +0 -75
  76. modal-1.2.5.dev4/modal_proto/task_command_router_pb2.py +0 -149
  77. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/LICENSE +0 -0
  78. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_billing.py +0 -0
  79. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_clustered_functions.py +0 -0
  80. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_clustered_functions.pyi +0 -0
  81. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_ipython.py +0 -0
  82. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_load_context.py +0 -0
  83. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_location.py +0 -0
  84. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_object.py +0 -0
  85. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_pty.py +0 -0
  86. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_resolver.py +0 -0
  87. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_resources.py +0 -0
  88. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/__init__.py +0 -0
  89. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/asgi.py +0 -0
  90. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
  91. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/execution_context.py +0 -0
  92. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/execution_context.pyi +0 -0
  93. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/telemetry.py +0 -0
  94. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
  95. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_serialization.py +0 -0
  96. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_tunnel.pyi +0 -0
  97. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_type_manager.py +0 -0
  98. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/__init__.py +0 -0
  99. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/app_utils.py +0 -0
  100. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/async_utils.py +0 -0
  101. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/auth_token_manager.py +0 -0
  102. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
  103. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/deprecation.py +0 -0
  104. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/docker_utils.py +0 -0
  105. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/git_utils.py +0 -0
  106. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/grpc_testing.py +0 -0
  107. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/hash_utils.py +0 -0
  108. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/http_utils.py +0 -0
  109. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/jwt_utils.py +0 -0
  110. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/logger.py +0 -0
  111. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/mount_utils.py +0 -0
  112. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/name_utils.py +0 -0
  113. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/pattern_utils.py +0 -0
  114. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/rand_pb_testing.py +0 -0
  115. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/shell_utils.py +0 -0
  116. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_utils/time_utils.py +0 -0
  117. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_vendor/__init__.py +0 -0
  118. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  119. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_vendor/cloudpickle.py +0 -0
  120. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_vendor/tblib.py +0 -0
  121. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/_watcher.py +0 -0
  122. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/billing.py +0 -0
  123. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/2023.12.312.txt +0 -0
  124. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/2023.12.txt +0 -0
  125. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/2024.04.txt +0 -0
  126. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/2024.10.txt +0 -0
  127. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/2025.06.txt +0 -0
  128. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/PREVIEW.txt +0 -0
  129. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/README.md +0 -0
  130. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/builder/base-images.json +0 -0
  131. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/call_graph.py +0 -0
  132. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/__init__.py +0 -0
  133. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/_download.py +0 -0
  134. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/_traceback.py +0 -0
  135. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/app.py +0 -0
  136. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/cluster.py +0 -0
  137. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/config.py +0 -0
  138. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/container.py +0 -0
  139. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/dict.py +0 -0
  140. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/import_refs.py +0 -0
  141. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/profile.py +0 -0
  142. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/programs/__init__.py +0 -0
  143. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/programs/run_jupyter.py +0 -0
  144. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/programs/vscode.py +0 -0
  145. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/queues.py +0 -0
  146. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/secret.py +0 -0
  147. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cli/token.py +0 -0
  148. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/client.py +0 -0
  149. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.py +0 -0
  150. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.pyi +0 -0
  151. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/cls.pyi +0 -0
  152. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/config.py +0 -0
  153. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/container_process.pyi +0 -0
  154. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/environments.py +0 -0
  155. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/environments.pyi +0 -0
  156. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/experimental/ipython.py +0 -0
  157. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/file_pattern_matcher.py +0 -0
  158. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/functions.py +0 -0
  159. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/gpu.py +0 -0
  160. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/image.pyi +0 -0
  161. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/network_file_system.py +0 -0
  162. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/network_file_system.pyi +0 -0
  163. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/object.py +0 -0
  164. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/object.pyi +0 -0
  165. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/output.py +0 -0
  166. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/parallel_map.py +0 -0
  167. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/parallel_map.pyi +0 -0
  168. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/partial_function.py +0 -0
  169. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/partial_function.pyi +0 -0
  170. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/proxy.py +0 -0
  171. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/proxy.pyi +0 -0
  172. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/py.typed +0 -0
  173. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/queue.pyi +0 -0
  174. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/retries.py +0 -0
  175. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/runner.pyi +0 -0
  176. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/running_app.py +0 -0
  177. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/schedule.py +0 -0
  178. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/scheduler_placement.py +0 -0
  179. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/secret.pyi +0 -0
  180. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/serving.py +0 -0
  181. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/serving.pyi +0 -0
  182. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/snapshot.py +0 -0
  183. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/snapshot.pyi +0 -0
  184. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/stream_type.py +0 -0
  185. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/token_flow.py +0 -0
  186. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/token_flow.pyi +0 -0
  187. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal/volume.pyi +0 -0
  188. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/dependency_links.txt +0 -0
  189. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/entry_points.txt +0 -0
  190. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/requires.txt +0 -0
  191. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal.egg-info/top_level.txt +0 -0
  192. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/__init__.py +0 -0
  193. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/gen_cli_docs.py +0 -0
  194. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/gen_reference_docs.py +0 -0
  195. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/mdmd/__init__.py +0 -0
  196. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/mdmd/mdmd.py +0 -0
  197. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_docs/mdmd/signatures.py +0 -0
  198. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/__init__.py +0 -0
  199. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api_grpc.py +0 -0
  200. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.py +0 -0
  201. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.pyi +0 -0
  202. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/modal_api_grpc.py +0 -0
  203. {modal-1.2.5.dev4 → modal-1.2.7.dev11}/modal_proto/py.typed +0 -0
  204. {modal-1.2.5.dev4 → 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.5.dev4
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
@@ -13,7 +13,7 @@ Classifier: Topic :: System :: Distributed Computing
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: <3.14,>=3.9
16
+ Requires-Python: <3.14,>=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
@@ -51,7 +51,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
51
51
 
52
52
  ## Installation
53
53
 
54
- **This library requires Python 3.9 – 3.13.**
54
+ **This library requires Python 3.10 – 3.13.**
55
55
 
56
56
  Install the package with `uv` or `pip`:
57
57
 
@@ -17,7 +17,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
17
17
 
18
18
  ## Installation
19
19
 
20
- **This library requires Python 3.9 – 3.13.**
20
+ **This library requires Python 3.10 – 3.13.**
21
21
 
22
22
  Install the package with `uv` or `pip`:
23
23
 
@@ -1,8 +1,8 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import sys
3
3
 
4
- if sys.version_info[:2] < (3, 9):
5
- raise RuntimeError("This version of Modal requires at least Python 3.9")
4
+ if sys.version_info[:2] < (3, 10):
5
+ raise RuntimeError("This version of Modal requires at least Python 3.10")
6
6
  if sys.version_info[:2] >= (3, 14):
7
7
  raise RuntimeError("This version of Modal does not support Python 3.14+")
8
8
 
@@ -35,37 +35,12 @@ def main():
35
35
  ):
36
36
  raise
37
37
 
38
- from grpclib import GRPCError, Status
39
38
  from rich.panel import Panel
40
39
 
41
- if isinstance(exc, GRPCError):
42
- status_map = {
43
- Status.ABORTED: "Aborted",
44
- Status.ALREADY_EXISTS: "Already exists",
45
- Status.CANCELLED: "Cancelled",
46
- Status.DATA_LOSS: "Data loss",
47
- Status.DEADLINE_EXCEEDED: "Deadline exceeded",
48
- Status.FAILED_PRECONDITION: "Failed precondition",
49
- Status.INTERNAL: "Internal",
50
- Status.INVALID_ARGUMENT: "Invalid",
51
- Status.NOT_FOUND: "Not found",
52
- Status.OUT_OF_RANGE: "Out of range",
53
- Status.PERMISSION_DENIED: "Permission denied",
54
- Status.RESOURCE_EXHAUSTED: "Resource exhausted",
55
- Status.UNAUTHENTICATED: "Unauthenticaed",
56
- Status.UNAVAILABLE: "Unavailable",
57
- Status.UNIMPLEMENTED: "Unimplemented",
58
- Status.UNKNOWN: "Unknown",
59
- }
60
- title = f"Error: {status_map.get(exc.status, 'Unknown')}"
61
- content = str(exc.message)
62
- if exc.details:
63
- content += f"\n\nDetails: {exc.details}"
64
- else:
65
- title = "Error"
66
- content = str(exc)
67
- if notes := getattr(exc, "__notes__", []):
68
- content = f"{content}\n\nNote: {' '.join(notes)}"
40
+ title = "Error"
41
+ content = str(exc)
42
+ if notes := getattr(exc, "__notes__", []):
43
+ content = f"{content}\n\nNote: {' '.join(notes)}"
69
44
 
70
45
  console = make_console(stderr=True)
71
46
  panel = Panel(content, title=title, title_align="left", border_style="red")
@@ -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
 
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
13
13
 
14
14
  import typing_extensions
15
15
  from google.protobuf.message import Message
16
- from grpclib import GRPCError, Status
16
+ from grpclib import Status
17
17
  from synchronicity.combined_types import MethodWithAio
18
18
 
19
19
  from modal_proto import api_pb2
@@ -694,13 +694,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
694
694
  # Experimental: Clustered functions
695
695
  cluster_size: Optional[int] = None,
696
696
  rdma: Optional[bool] = None,
697
- max_inputs: Optional[int] = None,
697
+ single_use_containers: bool = False,
698
698
  ephemeral_disk: Optional[int] = None,
699
699
  include_source: bool = True,
700
700
  experimental_options: Optional[dict[str, str]] = None,
701
701
  _experimental_proxy_ip: Optional[str] = None,
702
702
  _experimental_custom_scaling_factor: Optional[float] = None,
703
703
  restrict_output: bool = False,
704
+ http_config: Optional[api_pb2.HTTPConfig] = None,
704
705
  ) -> "_Function":
705
706
  """mdmd:hidden
706
707
 
@@ -809,14 +810,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
809
810
  if arg.default is not inspect.Parameter.empty:
810
811
  raise InvalidError(f"Modal batched function {func_name} does not accept default arguments.")
811
812
 
812
- if max_inputs is not None:
813
- if not isinstance(max_inputs, int):
814
- raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
815
- if max_inputs <= 0:
816
- raise InvalidError("`max_inputs` must be positive")
817
- if max_inputs > 1:
818
- raise InvalidError("Only `max_inputs=1` is currently supported")
819
-
820
813
  # Validate volumes
821
814
  validated_volumes = validate_volumes(volumes)
822
815
  cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
@@ -987,6 +980,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
987
980
  function_definition = api_pb2.Function(
988
981
  module_name=info.module_name or "",
989
982
  function_name=info.function_name,
983
+ implementation_name=info.implementation_name,
990
984
  mount_ids=loaded_mount_ids,
991
985
  secret_ids=[secret.object_id for secret in secrets],
992
986
  image_id=(image.object_id if image else ""),
@@ -1022,7 +1016,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1022
1016
  object_dependencies=object_dependencies,
1023
1017
  block_network=block_network,
1024
1018
  untrusted=restrict_modal_access,
1025
- max_inputs=max_inputs or 0,
1019
+ single_use_containers=single_use_containers,
1020
+ max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
1026
1021
  cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
1027
1022
  scheduler_placement=scheduler_placement,
1028
1023
  is_class=info.is_service_class(),
@@ -1046,12 +1041,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1046
1041
  function_schema=function_schema,
1047
1042
  supported_input_formats=supported_input_formats,
1048
1043
  supported_output_formats=supported_output_formats,
1044
+ http_config=http_config,
1049
1045
  )
1050
1046
 
1051
1047
  if isinstance(gpu, list):
1052
1048
  function_data = api_pb2.FunctionData(
1053
1049
  module_name=function_definition.module_name,
1054
1050
  function_name=function_definition.function_name,
1051
+ implementation_name=function_definition.implementation_name,
1055
1052
  function_type=function_definition.function_type,
1056
1053
  warm_pool_size=function_definition.warm_pool_size,
1057
1054
  concurrency_limit=function_definition.concurrency_limit,
@@ -1083,6 +1080,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1083
1080
  untrusted=function_definition.untrusted,
1084
1081
  supported_input_formats=supported_input_formats,
1085
1082
  supported_output_formats=supported_output_formats,
1083
+ http_config=http_config,
1086
1084
  )
1087
1085
 
1088
1086
  ranked_functions = []
@@ -1121,12 +1119,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1121
1119
  )
1122
1120
  try:
1123
1121
  response: api_pb2.FunctionCreateResponse = await load_context.client.stub.FunctionCreate(request)
1124
- except GRPCError as exc:
1125
- if exc.status == Status.INVALID_ARGUMENT:
1126
- raise InvalidError(exc.message)
1127
- if exc.status == Status.FAILED_PRECONDITION:
1128
- raise InvalidError(exc.message)
1129
- if exc.message and "Received :status = '413'" in exc.message:
1122
+ except Exception as exc:
1123
+ if "Received :status = '413'" in str(exc):
1130
1124
  raise InvalidError(f"Function {info.function_name} is too large to deploy.")
1131
1125
  raise
1132
1126
  function_creation_status.set_response(response)
@@ -5,10 +5,10 @@ import grpclib.client
5
5
  from google.protobuf.message import Message
6
6
  from grpclib import GRPCError, Status
7
7
 
8
+ from . import exception
8
9
  from ._traceback import suppress_tb_frames
9
10
  from ._utils.grpc_utils import Retry, _retry_transient_errors
10
11
  from .config import config, logger
11
- from .exception import InvalidError, NotFoundError
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from .client import _Client
@@ -20,6 +20,29 @@ RequestType = TypeVar("RequestType", bound=Message)
20
20
  ResponseType = TypeVar("ResponseType", bound=Message)
21
21
 
22
22
 
23
+ class WrappedGRPCError(exception.Error, exception._GRPCErrorWrapper): ...
24
+
25
+
26
+ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
27
+ Status.CANCELLED: exception.ServiceError,
28
+ Status.UNKNOWN: exception.ServiceError,
29
+ Status.INVALID_ARGUMENT: exception.InvalidError,
30
+ Status.DEADLINE_EXCEEDED: exception.ServiceError,
31
+ Status.NOT_FOUND: exception.NotFoundError,
32
+ Status.ALREADY_EXISTS: exception.AlreadyExistsError,
33
+ Status.PERMISSION_DENIED: exception.PermissionDeniedError,
34
+ Status.RESOURCE_EXHAUSTED: exception.ResourceExhaustedError,
35
+ Status.FAILED_PRECONDITION: exception.ConflictError,
36
+ Status.ABORTED: exception.ConflictError,
37
+ Status.OUT_OF_RANGE: exception.InvalidError,
38
+ Status.UNIMPLEMENTED: exception.UnimplementedError,
39
+ Status.INTERNAL: exception.InternalError,
40
+ Status.UNAVAILABLE: exception.ServiceError,
41
+ Status.DATA_LOSS: exception.DataLossError,
42
+ Status.UNAUTHENTICATED: exception.AuthError,
43
+ }
44
+
45
+
23
46
  class grpc_error_converter:
24
47
  def __enter__(self):
25
48
  pass
@@ -29,20 +52,14 @@ class grpc_error_converter:
29
52
  use_full_traceback = config.get("traceback")
30
53
  with suppress_tb_frames(1):
31
54
  if isinstance(exc, GRPCError):
32
- if exc.status == Status.NOT_FOUND:
33
- if use_full_traceback:
34
- raise NotFoundError(exc.message)
35
- else:
36
- raise NotFoundError(exc.message) from None # from None to skip the grpc-internal cause
37
-
38
- if not use_full_traceback:
39
- # just include the frame in grpclib that actually raises the GRPCError
40
- tb = exc.__traceback__
41
- while tb.tb_next:
42
- tb = tb.tb_next
43
- exc.with_traceback(tb)
44
- raise exc from None # from None to skip the grpc-internal cause
45
- raise exc
55
+ modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
56
+ modal_exc._grpc_message = exc.message
57
+ modal_exc._grpc_status = exc.status
58
+ modal_exc._grpc_details = exc.details
59
+ if use_full_traceback:
60
+ raise modal_exc
61
+ else:
62
+ raise modal_exc from None # from None to skip the grpc-internal cause
46
63
 
47
64
  return False
48
65
 
@@ -100,17 +117,20 @@ class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
100
117
  ) -> ResponseType:
101
118
  with suppress_tb_frames(1):
102
119
  if timeout is not None and retry is not None:
103
- raise InvalidError("Retry must be None when timeout is set")
120
+ raise exception.InvalidError("Retry must be None when timeout is set")
104
121
 
105
122
  if retry is None:
106
- return await self.direct(req, timeout=timeout, metadata=metadata)
107
-
108
- return await _retry_transient_errors(
109
- self, # type: ignore
110
- req,
111
- retry=retry,
112
- metadata=metadata,
113
- )
123
+ with grpc_error_converter():
124
+ return await self.direct(req, timeout=timeout, metadata=metadata)
125
+
126
+ # TODO do we need suppress_error_frames(1) here too?
127
+ with grpc_error_converter():
128
+ return await _retry_transient_errors(
129
+ self, # type: ignore
130
+ req,
131
+ retry=retry,
132
+ metadata=metadata,
133
+ )
114
134
 
115
135
  async def direct(
116
136
  self,
@@ -135,8 +155,7 @@ class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
135
155
  #
136
156
  # [1]: https://github.com/vmagamedov/grpclib/blob/62f968a4c84e3f64e6966097574ff0a59969ea9b/grpclib/client.py#L844
137
157
  self.wrapped_method.channel = await self.client._get_channel(self.server_url)
138
- with suppress_tb_frames(1), grpc_error_converter():
139
- return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
158
+ return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
140
159
 
141
160
 
142
161
  class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
@@ -167,5 +186,6 @@ class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
167
186
  logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
168
187
  self.client = await _Client.from_env()
169
188
  self.wrapped_method.channel = await self.client._get_channel(self.server_url)
170
- async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
171
- yield response
189
+ with grpc_error_converter():
190
+ async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
191
+ yield response
@@ -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"
@@ -556,7 +557,7 @@ async def get_app_logs_loop(
556
557
  async def stop_pty_shell():
557
558
  nonlocal pty_shell_finish_event, pty_shell_input_task
558
559
  if pty_shell_finish_event:
559
- print("\r", end="") # move cursor to beginning of line
560
+ print("\r", end="") # move cursor to beginning of line # noqa: T201
560
561
  pty_shell_finish_event.set()
561
562
  pty_shell_finish_event = None
562
563
 
@@ -623,7 +624,7 @@ async def get_app_logs_loop(
623
624
  # This corresponds to the `modal run -i` use case where a breakpoint
624
625
  # triggers and the task drops into an interactive PTY mode
625
626
  if pty_shell_finish_event:
626
- print("ERROR: concurrent PTY shells are not supported.")
627
+ print("ERROR: concurrent PTY shells are not supported.") # noqa: T201
627
628
  else:
628
629
  pty_shell_stdout = output_mgr._stdout
629
630
  pty_shell_finish_event = asyncio.Event()
@@ -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
@@ -46,6 +46,7 @@ class _PartialFunctionFlags(enum.IntFlag):
46
46
  BATCHED = 64
47
47
  CONCURRENT = 128
48
48
  CLUSTERED = 256 # Experimental: Clustered functions
49
+ HTTP_WEB_INTERFACE = 512 # Experimental: HTTP server
49
50
 
50
51
  @staticmethod
51
52
  def all() -> int:
@@ -76,6 +77,7 @@ class _PartialFunctionParams:
76
77
  target_concurrent_inputs: Optional[int] = None
77
78
  build_timeout: Optional[int] = None
78
79
  rdma: Optional[bool] = None
80
+ http_config: Optional[api_pb2.HTTPConfig] = None
79
81
 
80
82
  def update(self, other: "_PartialFunctionParams") -> None:
81
83
  """Update self with params set in other."""
@@ -845,8 +845,9 @@ class _ContainerIOManager:
845
845
  yield inputs
846
846
  yielded = True
847
847
 
848
- # We only support max_inputs = 1 at the moment
849
- if final_input_received or self.function_def.max_inputs == 1:
848
+ # TODO(michael): Remove use of max_inputs after worker rollover
849
+ single_use_container = self.function_def.single_use_containers or self.function_def.max_inputs == 1
850
+ if final_input_received or single_use_container:
850
851
  return
851
852
  finally:
852
853
  if not yielded:
@@ -991,12 +992,10 @@ class _ContainerIOManager:
991
992
  # Busy-wait for restore. `/__modal/restore-state.json` is created
992
993
  # by the worker process with updates to the container config.
993
994
  restored_path = Path(config.get("restore_state_path"))
994
- start = time.perf_counter()
995
+ logger.debug("Waiting for restore")
995
996
  while not restored_path.exists():
996
- logger.debug(f"Waiting for restore (elapsed={time.perf_counter() - start:.3f}s)")
997
997
  await asyncio.sleep(0.01)
998
998
  continue
999
-
1000
999
  logger.debug("Container: restored")
1001
1000
 
1002
1001
  # Look for state file and create new client with updated credentials.
@@ -1007,7 +1006,7 @@ class _ContainerIOManager:
1007
1006
  # Start a debugger if the worker tells us to
1008
1007
  if int(restored_state.get("snapshot_debug", 0)):
1009
1008
  logger.debug("Entering snapshot debugger")
1010
- breakpoint()
1009
+ breakpoint() # noqa: T100
1011
1010
 
1012
1011
  # Local ContainerIOManager state.
1013
1012
  for key in ["task_id", "function_id"]:
@@ -18,11 +18,14 @@ from modal.config import config, logger
18
18
 
19
19
  CUDA_CHECKPOINT_PATH: str = config.get("cuda_checkpoint_path")
20
20
 
21
- # Maximum total duration for an entire toggle operation.
22
- CUDA_CHECKPOINT_TOGGLE_TIMEOUT: float = 5 * 60.0
23
-
24
21
  # Maximum total duration for each individual `cuda-checkpoint` invocation.
25
- CUDA_CHECKPOINT_TIMEOUT: float = 90
22
+ CUDA_CHECKPOINT_TIMEOUT: float = 3 * 60.0
23
+
24
+ # Number of retries for each individual `cuda-checkpoint --toggle` invocation.
25
+ CUDA_CHECKPOINT_TOGGLE_NUM_RETRIES: int = 3
26
+
27
+ # Maximum total duration for an entire toggle operation.
28
+ CUDA_CHECKPOINT_TOGGLE_TIMEOUT: float = CUDA_CHECKPOINT_TOGGLE_NUM_RETRIES * CUDA_CHECKPOINT_TIMEOUT
26
29
 
27
30
 
28
31
  class CudaCheckpointState(Enum):
@@ -58,7 +61,7 @@ class CudaCheckpointProcess:
58
61
 
59
62
  start_time = time.monotonic()
60
63
  retry_count = 0
61
- max_retries = 3
64
+ max_retries = CUDA_CHECKPOINT_TOGGLE_NUM_RETRIES
62
65
 
63
66
  attempts = 0
64
67
  while self._should_continue_toggle(
@@ -201,8 +204,7 @@ class CudaCheckpointSession:
201
204
  [CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)],
202
205
  capture_output=True,
203
206
  text=True,
204
- # This should be quick since no checkpoint has taken place yet
205
- timeout=5,
207
+ timeout=CUDA_CHECKPOINT_TIMEOUT,
206
208
  )
207
209
 
208
210
  # If the command succeeds (return code 0), this PID has a CUDA session