modAL 1.5.2__tar.gz → 1.5.2.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. {modal-1.5.2 → modal-1.5.2.dev1}/PKG-INFO +1 -1
  2. {modal-1.5.2 → modal-1.5.2.dev1}/modal/__init__.py +1 -2
  3. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_billing.py +53 -2
  4. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_environments.py +10 -7
  5. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_function_variants.py +0 -4
  6. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_functions.py +11 -3
  7. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_image.py +9 -38
  8. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/container_io_manager.py +1 -1
  9. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/blob_utils.py +0 -7
  10. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/grpc_utils.py +17 -80
  11. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/name_utils.py +2 -8
  12. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/task_command_router_client.py +8 -24
  13. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_workspace.py +35 -81
  14. {modal-1.5.2 → modal-1.5.2.dev1}/modal/app.py +3 -0
  15. {modal-1.5.2 → modal-1.5.2.dev1}/modal/app.pyi +2 -0
  16. {modal-1.5.2 → modal-1.5.2.dev1}/modal/billing.py +1 -2
  17. {modal-1.5.2 → modal-1.5.2.dev1}/modal/billing.pyi +28 -27
  18. {modal-1.5.2 → modal-1.5.2.dev1}/modal/call_graph.py +30 -1
  19. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/_download.py +1 -2
  20. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/_help.py +1 -7
  21. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/changelog.py +7 -7
  22. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/cluster.py +1 -1
  23. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/config.py +2 -2
  24. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/container.py +14 -33
  25. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/curl.py +5 -12
  26. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/dict.py +5 -5
  27. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/environment.py +14 -14
  28. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/image.py +0 -104
  29. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/network_file_system.py +6 -12
  30. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/profile.py +1 -1
  31. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/queues.py +5 -5
  32. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/secret.py +2 -2
  33. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/shell.py +2 -6
  34. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/volume.py +9 -17
  35. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/workspace.py +0 -46
  36. {modal-1.5.2 → modal-1.5.2.dev1}/modal/client.pyi +10 -2
  37. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cls.py +4 -5
  38. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cls.pyi +0 -4
  39. {modal-1.5.2 → modal-1.5.2.dev1}/modal/config.py +1 -1
  40. {modal-1.5.2 → modal-1.5.2.dev1}/modal/dict.py +13 -1
  41. {modal-1.5.2 → modal-1.5.2.dev1}/modal/dict.pyi +24 -4
  42. {modal-1.5.2 → modal-1.5.2.dev1}/modal/environments.pyi +17 -9
  43. {modal-1.5.2 → modal-1.5.2.dev1}/modal/file_io.py +1 -1
  44. {modal-1.5.2 → modal-1.5.2.dev1}/modal/file_io.pyi +9 -9
  45. {modal-1.5.2 → modal-1.5.2.dev1}/modal/functions.pyi +5 -6
  46. {modal-1.5.2 → modal-1.5.2.dev1}/modal/network_file_system.pyi +7 -7
  47. {modal-1.5.2 → modal-1.5.2.dev1}/modal/queue.py +13 -1
  48. {modal-1.5.2 → modal-1.5.2.dev1}/modal/queue.pyi +24 -4
  49. {modal-1.5.2 → modal-1.5.2.dev1}/modal/sandbox.py +50 -191
  50. {modal-1.5.2 → modal-1.5.2.dev1}/modal/sandbox.pyi +57 -277
  51. {modal-1.5.2 → modal-1.5.2.dev1}/modal/sandbox_fs.py +64 -1
  52. {modal-1.5.2 → modal-1.5.2.dev1}/modal/sandbox_fs.pyi +117 -14
  53. {modal-1.5.2 → modal-1.5.2.dev1}/modal/secret.py +13 -1
  54. {modal-1.5.2 → modal-1.5.2.dev1}/modal/secret.pyi +24 -4
  55. {modal-1.5.2 → modal-1.5.2.dev1}/modal/volume.py +39 -22
  56. {modal-1.5.2 → modal-1.5.2.dev1}/modal/volume.pyi +73 -34
  57. {modal-1.5.2 → modal-1.5.2.dev1}/modal/workspace.py +0 -5
  58. {modal-1.5.2 → modal-1.5.2.dev1}/modal/workspace.pyi +12 -140
  59. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/PKG-INFO +1 -1
  60. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/SOURCES.txt +0 -1
  61. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/gen_cli_docs.py +7 -22
  62. modal-1.5.2.dev1/modal_docs/gen_reference_docs.py +177 -0
  63. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/mdmd/mdmd.py +22 -129
  64. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/mdmd/signatures.py +0 -28
  65. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/api_grpc.py +0 -160
  66. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/api_pb2.py +970 -1133
  67. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/api_pb2.pyi +41 -386
  68. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/api_pb2_grpc.py +3 -333
  69. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/api_pb2_grpc.pyi +8 -108
  70. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/modal_api_grpc.py +0 -10
  71. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/task_command_router_grpc.py +0 -16
  72. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/task_command_router_pb2.py +41 -71
  73. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/task_command_router_pb2.pyi +3 -40
  74. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/task_command_router_pb2_grpc.py +0 -34
  75. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/task_command_router_pb2_grpc.pyi +0 -12
  76. {modal-1.5.2 → modal-1.5.2.dev1}/modal_version/__init__.py +1 -1
  77. modal-1.5.2/modal/types.py +0 -289
  78. modal-1.5.2/modal_docs/gen_reference_docs.py +0 -361
  79. {modal-1.5.2 → modal-1.5.2.dev1}/LICENSE +0 -0
  80. {modal-1.5.2 → modal-1.5.2.dev1}/README.md +0 -0
  81. {modal-1.5.2 → modal-1.5.2.dev1}/modal/__main__.py +0 -0
  82. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_clustered_functions.py +0 -0
  83. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_clustered_functions.pyi +0 -0
  84. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_container_entrypoint.py +0 -0
  85. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_grpc_client.py +0 -0
  86. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_ipython.py +0 -0
  87. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_load_context.py +0 -0
  88. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_location.py +0 -0
  89. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_logs.py +0 -0
  90. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_object.py +0 -0
  91. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_output/__init__.py +0 -0
  92. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_output/manager.py +0 -0
  93. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_output/pty.py +0 -0
  94. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_output/rich.py +0 -0
  95. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_output/status.py +0 -0
  96. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_partial_function.py +0 -0
  97. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_resolver.py +0 -0
  98. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_resources.py +0 -0
  99. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/__init__.py +0 -0
  100. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/asgi.py +0 -0
  101. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
  102. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/execution_context.py +0 -0
  103. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/execution_context.pyi +0 -0
  104. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  105. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/task_lifecycle_manager.py +0 -0
  106. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  107. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/telemetry.py +0 -0
  108. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/user_code_event_loop.py +0 -0
  109. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_runtime/user_code_imports.py +0 -0
  110. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_serialization.py +0 -0
  111. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_server.py +0 -0
  112. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_traceback.py +0 -0
  113. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_tunnel.py +0 -0
  114. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_tunnel.pyi +0 -0
  115. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_type_manager.py +0 -0
  116. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/__init__.py +0 -0
  117. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/app_utils.py +0 -0
  118. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/async_utils.py +0 -0
  119. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/auth_token_manager.py +0 -0
  120. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/browser_utils.py +0 -0
  121. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
  122. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/curl_utils.py +0 -0
  123. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/deprecation.py +0 -0
  124. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/docker_utils.py +0 -0
  125. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/function_utils.py +0 -0
  126. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/git_utils.py +0 -0
  127. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/grpc_testing.py +0 -0
  128. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/hash_utils.py +0 -0
  129. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/http_utils.py +0 -0
  130. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/jwt_utils.py +0 -0
  131. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/logger.py +0 -0
  132. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/mount_utils.py +0 -0
  133. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/package_utils.py +0 -0
  134. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/pattern_utils.py +0 -0
  135. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/proxy_support.py +0 -0
  136. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/rand_pb_testing.py +0 -0
  137. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/sandbox_fs_utils.py +0 -0
  138. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/shell_utils.py +0 -0
  139. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_utils/time_utils.py +0 -0
  140. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_vendor/__init__.py +0 -0
  141. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  142. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_vendor/cloudpickle.py +0 -0
  143. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_vendor/tblib.py +0 -0
  144. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_vendor/version.py +0 -0
  145. {modal-1.5.2 → modal-1.5.2.dev1}/modal/_watcher.py +0 -0
  146. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/2023.12.312.txt +0 -0
  147. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/2023.12.txt +0 -0
  148. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/2024.04.txt +0 -0
  149. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/2024.10.txt +0 -0
  150. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/2025.06.txt +0 -0
  151. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/PREVIEW.txt +0 -0
  152. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/README.md +0 -0
  153. {modal-1.5.2 → modal-1.5.2.dev1}/modal/builder/base-images.json +0 -0
  154. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/__init__.py +0 -0
  155. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/_traceback.py +0 -0
  156. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/app.py +0 -0
  157. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/billing.py +0 -0
  158. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/bootstrap.py +0 -0
  159. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/dashboard.py +0 -0
  160. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/endpoint.py +0 -0
  161. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/entry_point.py +0 -0
  162. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/import_refs.py +0 -0
  163. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/launch.py +0 -0
  164. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/logo.py +0 -0
  165. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/programs/__init__.py +0 -0
  166. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/programs/run_jupyter.py +0 -0
  167. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/programs/vscode.py +0 -0
  168. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/run.py +0 -0
  169. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/selector.py +0 -0
  170. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/skills.py +0 -0
  171. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/token.py +0 -0
  172. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cli/utils.py +0 -0
  173. {modal-1.5.2 → modal-1.5.2.dev1}/modal/client.py +0 -0
  174. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cloud_bucket_mount.py +0 -0
  175. {modal-1.5.2 → modal-1.5.2.dev1}/modal/cloud_bucket_mount.pyi +0 -0
  176. {modal-1.5.2 → modal-1.5.2.dev1}/modal/container_process.py +0 -0
  177. {modal-1.5.2 → modal-1.5.2.dev1}/modal/container_process.pyi +0 -0
  178. {modal-1.5.2 → modal-1.5.2.dev1}/modal/environments.py +0 -0
  179. {modal-1.5.2 → modal-1.5.2.dev1}/modal/exception.py +0 -0
  180. {modal-1.5.2 → modal-1.5.2.dev1}/modal/experimental/__init__.py +0 -0
  181. {modal-1.5.2 → modal-1.5.2.dev1}/modal/experimental/flash.py +0 -0
  182. {modal-1.5.2 → modal-1.5.2.dev1}/modal/experimental/flash.pyi +0 -0
  183. {modal-1.5.2 → modal-1.5.2.dev1}/modal/experimental/ipython.py +0 -0
  184. {modal-1.5.2 → modal-1.5.2.dev1}/modal/file_pattern_matcher.py +0 -0
  185. {modal-1.5.2 → modal-1.5.2.dev1}/modal/functions.py +0 -0
  186. {modal-1.5.2 → modal-1.5.2.dev1}/modal/image.py +0 -0
  187. {modal-1.5.2 → modal-1.5.2.dev1}/modal/image.pyi +0 -0
  188. {modal-1.5.2 → modal-1.5.2.dev1}/modal/io_streams.py +0 -0
  189. {modal-1.5.2 → modal-1.5.2.dev1}/modal/io_streams.pyi +0 -0
  190. {modal-1.5.2 → modal-1.5.2.dev1}/modal/mount.py +0 -0
  191. {modal-1.5.2 → modal-1.5.2.dev1}/modal/mount.pyi +0 -0
  192. {modal-1.5.2 → modal-1.5.2.dev1}/modal/network_file_system.py +0 -0
  193. {modal-1.5.2 → modal-1.5.2.dev1}/modal/object.py +0 -0
  194. {modal-1.5.2 → modal-1.5.2.dev1}/modal/object.pyi +0 -0
  195. {modal-1.5.2 → modal-1.5.2.dev1}/modal/output.py +0 -0
  196. {modal-1.5.2 → modal-1.5.2.dev1}/modal/parallel_map.py +0 -0
  197. {modal-1.5.2 → modal-1.5.2.dev1}/modal/parallel_map.pyi +0 -0
  198. {modal-1.5.2 → modal-1.5.2.dev1}/modal/partial_function.py +0 -0
  199. {modal-1.5.2 → modal-1.5.2.dev1}/modal/partial_function.pyi +0 -0
  200. {modal-1.5.2 → modal-1.5.2.dev1}/modal/proxy.py +0 -0
  201. {modal-1.5.2 → modal-1.5.2.dev1}/modal/proxy.pyi +0 -0
  202. {modal-1.5.2 → modal-1.5.2.dev1}/modal/py.typed +0 -0
  203. {modal-1.5.2 → modal-1.5.2.dev1}/modal/retries.py +0 -0
  204. {modal-1.5.2 → modal-1.5.2.dev1}/modal/runner.py +0 -0
  205. {modal-1.5.2 → modal-1.5.2.dev1}/modal/runner.pyi +0 -0
  206. {modal-1.5.2 → modal-1.5.2.dev1}/modal/running_app.py +0 -0
  207. {modal-1.5.2 → modal-1.5.2.dev1}/modal/schedule.py +0 -0
  208. {modal-1.5.2 → modal-1.5.2.dev1}/modal/scheduler_placement.py +0 -0
  209. {modal-1.5.2 → modal-1.5.2.dev1}/modal/server.py +0 -0
  210. {modal-1.5.2 → modal-1.5.2.dev1}/modal/server.pyi +0 -0
  211. {modal-1.5.2 → modal-1.5.2.dev1}/modal/serving.py +0 -0
  212. {modal-1.5.2 → modal-1.5.2.dev1}/modal/serving.pyi +0 -0
  213. {modal-1.5.2 → modal-1.5.2.dev1}/modal/skills/modal/SKILL.md +0 -0
  214. {modal-1.5.2 → modal-1.5.2.dev1}/modal/snapshot.py +0 -0
  215. {modal-1.5.2 → modal-1.5.2.dev1}/modal/snapshot.pyi +0 -0
  216. {modal-1.5.2 → modal-1.5.2.dev1}/modal/stream_type.py +0 -0
  217. {modal-1.5.2 → modal-1.5.2.dev1}/modal/token_flow.py +0 -0
  218. {modal-1.5.2 → modal-1.5.2.dev1}/modal/token_flow.pyi +0 -0
  219. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/dependency_links.txt +0 -0
  220. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/entry_points.txt +0 -0
  221. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/requires.txt +0 -0
  222. {modal-1.5.2 → modal-1.5.2.dev1}/modal.egg-info/top_level.txt +0 -0
  223. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/__init__.py +0 -0
  224. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/gen_cli_docs_main.py +0 -0
  225. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/gen_reference_docs_main.py +0 -0
  226. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/mdmd/__init__.py +0 -0
  227. {modal-1.5.2 → modal-1.5.2.dev1}/modal_docs/mdmd/types.py +0 -0
  228. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/__init__.py +0 -0
  229. {modal-1.5.2 → modal-1.5.2.dev1}/modal_proto/py.typed +0 -0
  230. {modal-1.5.2 → modal-1.5.2.dev1}/modal_version/__main__.py +0 -0
  231. {modal-1.5.2 → modal-1.5.2.dev1}/pyproject.toml +0 -0
  232. {modal-1.5.2 → modal-1.5.2.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2
3
+ Version: 1.5.2.dev1
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -9,7 +9,7 @@ if sys.version_info[:2] >= (3, 15):
9
9
  from modal_version import __version__
10
10
 
11
11
  try:
12
- from . import billing, types
12
+ from . import billing
13
13
  from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
14
14
  from ._tunnel import Tunnel, forward
15
15
  from .app import App
@@ -100,7 +100,6 @@ __all__ = [
100
100
  "interact",
101
101
  "method",
102
102
  "parameter",
103
- "types",
104
103
  "web_endpoint",
105
104
  "web_server",
106
105
  "wsgi_app",
@@ -1,7 +1,10 @@
1
1
  # Copyright Modal Labs 2025
2
- from datetime import datetime
2
+ from dataclasses import FrozenInstanceError, dataclass
3
+ from datetime import datetime, timezone
3
4
  from decimal import Decimal
4
- from typing import TypedDict
5
+ from typing import Any, Iterable, TypedDict
6
+
7
+ from modal_proto import api_pb2
5
8
 
6
9
  from ._utils.deprecation import deprecation_warning
7
10
  from .client import _Client
@@ -16,6 +19,54 @@ class WorkspaceBillingReportItem(TypedDict):
16
19
  tags: dict[str, str]
17
20
 
18
21
 
22
+ @dataclass(slots=True, frozen=True)
23
+ class BillingReportItem:
24
+ object_id: str
25
+ description: str
26
+ environment_name: str
27
+ interval_start: datetime
28
+ cost: Decimal
29
+ cost_by_resource: dict[str, Decimal]
30
+ tags: dict[str, str]
31
+
32
+ def __getitem__(self, key: str) -> Any:
33
+ """mdmd:ignore"""
34
+ if key not in self.__slots__:
35
+ raise KeyError(key)
36
+
37
+ return getattr(self, key)
38
+
39
+ def __setitem__(self, key: str, _: Any):
40
+ """mdmd:ignore"""
41
+ raise FrozenInstanceError(f"cannot assign to field {key!r}")
42
+
43
+ def keys(self) -> Iterable[str]:
44
+ """mdmd:ignore"""
45
+ yield from self.__slots__
46
+
47
+ def values(self) -> Iterable[Any]:
48
+ """mdmd:ignore"""
49
+ for k in self.__slots__:
50
+ yield getattr(self, k)
51
+
52
+ def items(self) -> Iterable[tuple[str, Any]]:
53
+ """mdmd:ignore"""
54
+ for k in self.__slots__:
55
+ yield k, getattr(self, k)
56
+
57
+ @classmethod
58
+ def _from_proto(cls, pb_item: api_pb2.WorkspaceBillingReportItem) -> "BillingReportItem":
59
+ return cls(
60
+ object_id=pb_item.object_id,
61
+ description=pb_item.description,
62
+ environment_name=pb_item.environment_name,
63
+ interval_start=pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
64
+ cost=Decimal(pb_item.cost),
65
+ cost_by_resource={k: Decimal(v) for k, v in pb_item.cost_by_resource.items()},
66
+ tags=dict(pb_item.tags),
67
+ )
68
+
69
+
19
70
  async def _workspace_billing_report(
20
71
  *,
21
72
  start: datetime, # Start of the report, inclusive
@@ -13,6 +13,7 @@ from synchronicity import classproperty
13
13
 
14
14
  from modal_proto import api_pb2
15
15
 
16
+ from ._billing import BillingReportItem
16
17
  from ._load_context import LoadContext
17
18
  from ._object import _Object
18
19
  from ._resolver import Resolver
@@ -20,7 +21,6 @@ from ._utils.name_utils import check_environment_name
20
21
  from .client import _Client
21
22
  from .config import config, logger
22
23
  from .exception import InvalidError, WorkspaceManagementError
23
- from .types import BillingReportItem
24
24
 
25
25
 
26
26
  @dataclass(frozen=True)
@@ -103,6 +103,9 @@ class _EnvironmentManager:
103
103
  await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
104
104
 
105
105
 
106
+ MemberRole = Literal["viewer", "contributor"]
107
+
108
+
106
109
  def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
107
110
  match role:
108
111
  case "viewer":
@@ -113,7 +116,7 @@ def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
113
116
  raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
114
117
 
115
118
 
116
- def _role_from_proto(proto_value: int) -> str:
119
+ def _role_from_proto(proto_value: int) -> MemberRole:
117
120
  match proto_value:
118
121
  case api_pb2.ENVIRONMENT_ROLE_VIEWER:
119
122
  return "viewer"
@@ -134,7 +137,7 @@ class _EnvironmentMembersManager:
134
137
  """mdmd:hidden"""
135
138
  self._environment = environment
136
139
 
137
- async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
140
+ async def list(self) -> dict[Literal["users", "service_users"], dict[str, MemberRole]]:
138
141
  """Return the members of a restricted Environment with their roles.
139
142
 
140
143
  **Examples:**
@@ -152,8 +155,8 @@ class _EnvironmentMembersManager:
152
155
  req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
153
156
  resp = await self._environment.client.stub.EnvironmentGetManaged(req)
154
157
 
155
- users: dict[str, str] = {}
156
- service_users: dict[str, str] = {}
158
+ users: dict[str, MemberRole] = {}
159
+ service_users: dict[str, MemberRole] = {}
157
160
  for principal in resp.principal_roles:
158
161
  role = _role_from_proto(principal.role)
159
162
  if principal.user_id:
@@ -166,8 +169,8 @@ class _EnvironmentMembersManager:
166
169
  async def update(
167
170
  self,
168
171
  *,
169
- users: Mapping[str, str] | None = None,
170
- service_users: Mapping[str, str] | None = None,
172
+ users: Mapping[str, MemberRole] | None = None,
173
+ service_users: Mapping[str, MemberRole] | None = None,
171
174
  ) -> None:
172
175
  """Add or modify roles for members of a restricted Environment.
173
176
 
@@ -51,7 +51,6 @@ class _FunctionOptions:
51
51
  target_concurrent_inputs: int | None = None
52
52
  batch_max_size: int | None = None
53
53
  batch_wait_ms: int | None = None
54
- routing_region: str | None = None
55
54
 
56
55
  @classmethod
57
56
  def new(
@@ -74,7 +73,6 @@ class _FunctionOptions:
74
73
  target_concurrent_inputs: int | None = None,
75
74
  batch_max_size: int | None = None,
76
75
  batch_wait_ms: int | None = None,
77
- routing_region: str | None = None,
78
76
  ) -> "_FunctionOptions":
79
77
  """Internal constructor that validates and normalizes public parameters."""
80
78
  retry_policy = _parse_retries(retries)
@@ -121,7 +119,6 @@ class _FunctionOptions:
121
119
  target_concurrent_inputs=target_concurrent_inputs,
122
120
  batch_max_size=batch_max_size,
123
121
  batch_wait_ms=batch_wait_ms,
124
- routing_region=routing_region,
125
122
  )
126
123
 
127
124
  def merge_options(self, new_options: "_FunctionOptions") -> "_FunctionOptions":
@@ -185,7 +182,6 @@ class _FunctionOptions:
185
182
  batch_linger_ms=self.batch_wait_ms,
186
183
  scheduler_placement=self.scheduler_placement,
187
184
  cloud_provider_str=self.cloud,
188
- routing_region=self.routing_region,
189
185
  )
190
186
 
191
187
 
@@ -90,7 +90,6 @@ from .proxy import _Proxy
90
90
  from .retries import Retries, RetryManager
91
91
  from .schedule import Schedule
92
92
  from .secret import _Secret
93
- from .types import FunctionStats
94
93
  from .volume import _Volume, _volume_to_mount_proto
95
94
 
96
95
  if TYPE_CHECKING:
@@ -542,6 +541,17 @@ class _InputPlaneInvocation:
542
541
  return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
543
542
 
544
543
 
544
+ # Wrapper type for api_pb2.FunctionStats
545
+ @dataclass(frozen=True)
546
+ class FunctionStats:
547
+ """Simple data structure storing stats for a running function."""
548
+
549
+ backlog: int
550
+ num_total_runners: int
551
+ num_running_inputs: int
552
+ input_headroom: int
553
+
554
+
545
555
  @dataclass
546
556
  class _FunctionSpec:
547
557
  """
@@ -1509,7 +1519,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1509
1519
  timeout: int | None = None,
1510
1520
  region: str | Sequence[str] | None = None,
1511
1521
  cloud: str | None = None,
1512
- routing_region: str | None = None,
1513
1522
  ) -> "_Function[P, ReturnType, OriginalReturnType]":
1514
1523
  """Dynamically override the static Function configuration with invocation-specific values.
1515
1524
 
@@ -1558,7 +1567,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1558
1567
  timeout=timeout,
1559
1568
  region=region,
1560
1569
  cloud=cloud,
1561
- routing_region=routing_region,
1562
1570
  )
1563
1571
 
1564
1572
  return self._apply_dynamic_config(options, "with_options")
@@ -117,34 +117,13 @@ def _validate_image_tag(tag: str) -> None:
117
117
  check_object_name(tag, "Image tag")
118
118
 
119
119
 
120
- def _parse_named_image_ref(name: str) -> tuple[str, str]:
121
- """Parse an image reference, returning (namespace_prefix, name_tag).
122
-
123
- If the name contains a '/', the part before the last '/' is extracted as
124
- a namespace prefix (intended for environment/name or workspace/env/name
125
- syntax). The actual image name (after the last '/') is validated as a
126
- standard object name.
127
-
128
- Returns a tuple of (prefix, "full_name:tag") where prefix is empty string
129
- if no '/' is present.
130
- """
120
+ def _parse_named_image_ref(name: str) -> str:
131
121
  image_name, sep, tag = name.partition(":")
132
122
  if not sep:
133
123
  tag = "latest"
134
-
135
- prefix = ""
136
- if "/" in image_name:
137
- prefix, image_name = image_name.rsplit("/", 1)
138
- if not prefix:
139
- raise InvalidError("Invalid Image name: '/' prefix must be non-empty.")
140
- if not image_name:
141
- raise InvalidError("Invalid Image name: name after '/' must be non-empty.")
142
-
143
124
  _validate_image_name(image_name)
144
125
  _validate_image_tag(tag)
145
-
146
- full_name = f"{prefix}/{image_name}" if prefix else image_name
147
- return prefix, f"{full_name}:{tag}"
126
+ return f"{image_name}:{tag}"
148
127
 
149
128
 
150
129
  def _validate_python_version(
@@ -1043,7 +1022,7 @@ class _Image(_Object, type_prefix="im"):
1043
1022
  ```
1044
1023
  """
1045
1024
  if app.app_id is None:
1046
- raise InvalidError("App has not been initialized yet. Use the context manager `app.run()` or `App.lookup`")
1025
+ raise InvalidError("App has not been initialized yet. Use the content manager `app.run()` or `App.lookup`")
1047
1026
 
1048
1027
  resolver = Resolver()
1049
1028
  async with TaskContext() as tc:
@@ -2944,15 +2923,12 @@ class _Image(_Object, type_prefix="im"):
2944
2923
  ...
2945
2924
  ```
2946
2925
  """
2947
- namespace_prefix, tag = _parse_named_image_ref(name)
2948
-
2949
- if namespace_prefix and environment_name:
2950
- raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
2926
+ tag = _parse_named_image_ref(name)
2951
2927
 
2952
2928
  async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
2953
2929
  req = api_pb2.ImageGetByTagRequest(
2954
2930
  tag=tag,
2955
- environment_name="" if namespace_prefix else load_context.environment_name,
2931
+ environment_name=load_context.environment_name,
2956
2932
  )
2957
2933
  response = await load_context.client.stub.ImageGetByTag(req)
2958
2934
  self._hydrate(response.image_id, load_context.client, None)
@@ -2987,25 +2963,20 @@ class _Image(_Object, type_prefix="im"):
2987
2963
  image.publish("my-image-with-numpy:v1")
2988
2964
  ```
2989
2965
  """
2990
- namespace_prefix, tag = _parse_named_image_ref(name)
2991
-
2992
- if namespace_prefix:
2993
- if environment_name:
2994
- raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
2995
- resolved_env = ""
2996
- else:
2997
- resolved_env = environment_name or config.get("environment") or ""
2966
+ tag = _parse_named_image_ref(name)
2998
2967
 
2999
2968
  if self._object_id is None:
3000
2969
  raise InvalidError("Cannot publish an image that has not been created yet. Call `.build()` first.")
3001
2970
 
3002
2971
  _client = client or await _Client.from_env()
3003
2972
 
2973
+ resolved_env = environment_name or config.get("environment") or ""
2974
+
3004
2975
  await _client.stub.ImagePublish(
3005
2976
  api_pb2.ImagePublishRequest(
3006
2977
  image_id=self._object_id,
3007
2978
  environment_name=resolved_env,
3008
- allow_public=False,
2979
+ is_public=False,
3009
2980
  tag=tag,
3010
2981
  )
3011
2982
  )
@@ -264,7 +264,7 @@ class IOContext:
264
264
  if not inspect.isasyncgen(expected_async_gen):
265
265
  raise InvalidError(f"Async generator function returned value of type {type(expected_async_gen)}")
266
266
 
267
- async with aclosing(cast(AsyncGenerator[Any, None], expected_async_gen)) as gen:
267
+ async with aclosing(expected_async_gen) as gen:
268
268
  async for result in gen:
269
269
  yield result
270
270
  logger.debug(f"Finished generator input {self.input_ids}")
@@ -138,13 +138,6 @@ async def _upload_to_s3_url(
138
138
  text = await resp.text()
139
139
  except Exception:
140
140
  text = "<no body>"
141
-
142
- if "The Content-MD5 you specified did not match what we received." in text:
143
- raise ExecutionError(
144
- "Hash mismatch during upload. This may be caused by files changing while an upload is in "
145
- "progress. Please try again."
146
- )
147
-
148
141
  raise ExecutionError(f"Put to url {upload_url} failed with status {resp.status}: {text}")
149
142
 
150
143
  # client side ETag checksum verification
@@ -31,7 +31,7 @@ from modal_version import __version__
31
31
 
32
32
  from .._traceback import suppress_tb_frame
33
33
  from ..config import config
34
- from .async_utils import TaskContext, retry
34
+ from .async_utils import retry
35
35
  from .logger import logger
36
36
 
37
37
  RequestType = TypeVar("RequestType", bound=Message)
@@ -163,10 +163,8 @@ RETRYABLE_GRPC_STATUS_CODES = [
163
163
  Status.UNKNOWN,
164
164
  ]
165
165
  SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
166
+ # Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
166
167
  DEFAULT_MAX_RETRIES = 3
167
- GRPC_KEEPALIVE_TIME_SECS = 30.0
168
- GRPC_KEEPALIVE_TIMEOUT_SECS = 10.0
169
- GRPC_WINDOW_SIZE = 64 * 1024 * 1024 # 64 MiB
170
168
 
171
169
 
172
170
  @dataclass
@@ -192,7 +190,14 @@ class ConnectionManager:
192
190
 
193
191
  async def get_or_create_channel(self, server_url: str) -> grpclib.client.Channel:
194
192
  if server_url not in self._channels:
195
- self._channels[server_url] = await create_channel_with_fallbacks(server_url, self._metadata)
193
+ self._channels[server_url] = create_channel(
194
+ server_url,
195
+ self._metadata,
196
+ )
197
+ try:
198
+ await connect_channel(self._channels[server_url])
199
+ except (OSError, asyncio.TimeoutError) as exc:
200
+ raise ConnectionError("Could not connect to the Modal server.") from exc
196
201
  return self._channels[server_url]
197
202
 
198
203
  def close(self):
@@ -254,21 +259,6 @@ class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
254
259
  custom_detail_codec = CustomProtoStatusDetailsCodec()
255
260
 
256
261
 
257
- def create_channel_config() -> grpclib.config.Configuration:
258
- """Shared grpclib channel settings for Modal client connections.
259
-
260
- HTTP/2 keepalive probes keep idle transports warm through stateful
261
- middleboxes and surface dead connections before retries reuse them.
262
- """
263
- return grpclib.config.Configuration(
264
- _keepalive_time=GRPC_KEEPALIVE_TIME_SECS,
265
- _keepalive_timeout=GRPC_KEEPALIVE_TIMEOUT_SECS,
266
- _keepalive_permit_without_calls=True,
267
- http2_connection_window_size=GRPC_WINDOW_SIZE,
268
- http2_stream_window_size=GRPC_WINDOW_SIZE,
269
- )
270
-
271
-
272
262
  def create_channel(
273
263
  server_url: str,
274
264
  metadata: dict[str, str] = {},
@@ -280,7 +270,10 @@ def create_channel(
280
270
  o = urllib.parse.urlparse(server_url)
281
271
 
282
272
  channel: grpclib.client.Channel
283
- config = create_channel_config()
273
+ config = grpclib.config.Configuration(
274
+ http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
275
+ http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
276
+ )
284
277
 
285
278
  if o.scheme == "unix":
286
279
  channel = grpclib.client.Channel(path=o.path, config=config, status_details_codec=custom_detail_codec)
@@ -303,12 +296,6 @@ def create_channel(
303
296
  for k, v in metadata.items():
304
297
  event.metadata[k] = v
305
298
 
306
- if o.hostname is not None:
307
- # GRPC actually injects a :authority pseudo-header containing the hostname.
308
- # But this gets stripped out by the C/C++ extension server-side and is hard to fix.
309
- # So let's just add a separate header we can parse easily.
310
- event.metadata["x-modal-host"] = o.hostname
311
-
312
299
  idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
313
300
  if idempotency_key is None:
314
301
  logger.debug(f"Sending request to {event.method_name}")
@@ -322,59 +309,9 @@ def create_channel(
322
309
 
323
310
  # With 18 attempts, the max delays between calls is: 0.1 + 0.2 + 0.4 + 0.8 + 1.6 + 3.2 + 5*11 ~ 62
324
311
  @retry(n_attempts=18, base_delay=0.1, attempt_timeout=10.0, max_delay=5.0, total_timeout=63.0)
325
- async def create_channel_with_fallbacks(
326
- server_url: str,
327
- metadata: dict[str, str] = {},
328
- stagger_delay: float = 1.0,
329
- ) -> grpclib.client.Channel:
330
- """Create a connected channel, supporting comma-separated fallback URLs.
331
-
332
- `server_url` may be a single URL or a comma-separated list of URLs. The connection attempts
333
- are raced against each other and the first one to connect wins, which lets the client fail
334
- over to a secondary endpoint when the primary is unreachable (e.g. a DNS outage on the
335
- primary's domain).
336
-
337
- Attempts are staggered so earlier URLs (e.g. the primary) get a head start and are preferred
338
- when reachable: the candidate at index `i` waits up to `i * stagger_delay` seconds before
339
- starting. That wait is cut short as soon as the previous candidate *fails*, so a fast failure
340
- falls over to the next URL immediately instead of waiting out the full stagger. Losing attempts
341
- are cancelled and their channels closed.
342
- """
343
- urls = [url.strip() for url in server_url.split(",")]
344
-
345
- # Set when a candidate's connection attempt fails, letting the next candidate start immediately
346
- # instead of waiting out its stagger delay.
347
- failed = [asyncio.Event() for _ in urls]
348
-
349
- async def connect(index: int, url: str) -> grpclib.client.Channel:
350
- if index > 0:
351
- # Stagger the start so earlier URLs are preferred, but start early if the previous
352
- # candidate has already failed.
353
- try:
354
- await asyncio.wait_for(failed[index - 1].wait(), timeout=index * stagger_delay)
355
- except asyncio.TimeoutError:
356
- pass
357
- channel = create_channel(url, metadata)
358
- try:
359
- await channel.__connect__()
360
- except BaseException:
361
- # Close the channel of a losing/cancelled attempt so its socket doesn't leak.
362
- channel.close()
363
- failed[index].set()
364
- raise
365
- return channel
366
-
367
- first_exc: BaseException | None = None
368
- async with TaskContext() as tc:
369
- tasks = [tc.create_task(connect(i, url)) for i, url in enumerate(urls)]
370
- for attempt in asyncio.as_completed(tasks):
371
- try:
372
- return await attempt
373
- except Exception as exc:
374
- if first_exc is None:
375
- first_exc = exc
376
-
377
- raise ConnectionError("Could not connect to the Modal server.") from first_exc
312
+ async def connect_channel(channel: grpclib.client.Channel):
313
+ """Connect to socket and raise exceptions when there is a connection issue."""
314
+ await channel.__connect__()
378
315
 
379
316
 
380
317
  if typing.TYPE_CHECKING:
@@ -29,13 +29,7 @@ def is_valid_object_name(name: str) -> bool:
29
29
 
30
30
  def is_valid_environment_name(name: str) -> bool:
31
31
  # first char is alnum, the rest allows other chars
32
- # The "en-" prefix is reserved for environment IDs, so that references
33
- # to environments can be resolved by either name or ID unambiguously.
34
- return (
35
- len(name) <= 64
36
- and re.match(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]+$", name) is not None
37
- and not name.lower().startswith("en-")
38
- )
32
+ return len(name) <= 64 and re.match(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]+$", name) is not None
39
33
 
40
34
 
41
35
  def is_valid_tag(tag: str, max_length: int = 50) -> bool:
@@ -72,7 +66,7 @@ def check_environment_name(name: str) -> None:
72
66
  f"Invalid Environment name: '{name}'."
73
67
  "\n\nEnvironment names can only start with alphanumeric characters,"
74
68
  " may contain only alphanumeric characters, dashes, periods, and underscores,"
75
- " must be shorter than 64 characters, and cannot start with 'en-'."
69
+ " and must be shorter than 64 characters."
76
70
  )
77
71
  if not is_valid_environment_name(name):
78
72
  raise InvalidError(message)
@@ -13,6 +13,7 @@ from contextlib import suppress
13
13
  from typing import TypeVar
14
14
 
15
15
  import grpclib.client
16
+ import grpclib.config
16
17
  from grpclib import GRPCError, Status
17
18
  from grpclib.exceptions import StreamTerminatedError
18
19
 
@@ -22,7 +23,7 @@ from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
22
23
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
23
24
 
24
25
  from .._grpc_client import grpc_error_converter
25
- from .._utils.grpc_utils import ModalChannel, create_channel_config
26
+ from .._utils.grpc_utils import ModalChannel
26
27
  from .async_utils import aclosing, retry
27
28
  from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
28
29
 
@@ -31,8 +32,8 @@ from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
31
32
  async def _connect_channel(channel: grpclib.client.Channel):
32
33
  """Connect to the command router channel.
33
34
 
34
- Uses a longer retry budget than grpc_utils.create_channel_with_fallbacks. In rare cases the
35
- sandbox may take a long time to start on the worker after scheduling.
35
+ Uses a longer retry budget than grpc_utils.connect_channel. In rare cases the sandbox
36
+ may take a long time to start on the worker after scheduling.
36
37
 
37
38
  Retries with exponential backoff (1, 2, 4, 8, 10, 10, ...) capped at 10s per delay.
38
39
  Total sleep between attempts: 1 + 2 + 4 + 8 + 10*29 = 305s (~5 min).
@@ -221,7 +222,10 @@ class TaskCommandRouterClient:
221
222
  host,
222
223
  port,
223
224
  ssl=ssl_context,
224
- config=create_channel_config(),
225
+ config=grpclib.config.Configuration(
226
+ http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
227
+ http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
228
+ ),
225
229
  closed_error_message="Unable to perform operation on a detached sandbox",
226
230
  )
227
231
 
@@ -807,26 +811,6 @@ class TaskCommandRouterClient:
807
811
  lambda: self._call_with_auth_retry(self._stub.TaskSetNetworkAccess, request)
808
812
  )
809
813
 
810
- async def reload_volumes(self, task_id: str, timeout: float) -> sr_pb2.TaskReloadVolumesResponse:
811
- """Reload all Volumes mounted in the task to reflect their latest committed state.
812
-
813
- Args:
814
- task_id: The task whose mounted Volumes should be reloaded.
815
- timeout: Client-side deadline in seconds. If the reload does not complete within this
816
- window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
817
- """
818
- request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
819
- with grpc_error_converter():
820
- try:
821
- return await asyncio.wait_for(
822
- call_with_retries_on_transient_errors(
823
- lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
824
- ),
825
- timeout=timeout,
826
- )
827
- except asyncio.TimeoutError:
828
- raise ModalTimeoutError("Timeout expired")
829
-
830
814
  async def _snapshot_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
831
815
  # helper method for snapshot_directory and snapshot_filesystem to handle grpc
832
816
  # deadlines in a consistent way, converting any error to TimeoutError after passing