modal 1.5.6.dev25__tar.gz → 1.5.6.dev26__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 (239) hide show
  1. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/__init__.py +2 -2
  3. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_clustered_functions.py +1 -1
  4. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_environments.py +72 -15
  5. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_function_variants.py +4 -4
  6. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_functions.py +48 -25
  7. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_image.py +7 -7
  8. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_logs.py +5 -5
  9. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_logs_manager.py +2 -2
  10. modal-1.5.6.dev25/modal/outbound_policy.py → modal-1.5.6.dev26/modal/_outbound_policy.py +10 -4
  11. modal-1.5.6.dev25/modal/outbound_policy.pyi → modal-1.5.6.dev26/modal/_outbound_policy.pyi +10 -4
  12. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_output/pty.py +2 -2
  13. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_partial_function.py +45 -30
  14. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/container_io_manager.py +15 -15
  15. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/task_lifecycle_manager.py +4 -4
  16. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_tunnel.py +2 -2
  17. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/function_utils.py +2 -2
  18. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/grpc_utils.py +10 -6
  19. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/task_command_router_client.py +4 -3
  20. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_workspace.py +14 -14
  21. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/app.py +38 -12
  22. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/app.pyi +29 -0
  23. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/app.py +10 -10
  24. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/container.py +5 -5
  25. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/curl.py +1 -1
  26. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/dashboard.py +1 -1
  27. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/endpoint.py +5 -5
  28. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/entry_point.py +0 -3
  29. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/function.py +2 -2
  30. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/image.py +2 -2
  31. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/network_file_system.py +1 -1
  32. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/queues.py +1 -1
  33. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/secret.py +1 -1
  34. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/server.py +2 -2
  35. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/shell.py +27 -10
  36. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/token.py +1 -1
  37. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/utils.py +2 -2
  38. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/client.py +44 -20
  39. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/client.pyi +61 -29
  40. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cls.py +2 -2
  41. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/config.py +1 -1
  42. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/dict.py +17 -17
  43. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/environments.py +2 -0
  44. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/environments.pyi +53 -0
  45. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/exception.py +4 -0
  46. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/__init__.py +30 -8
  47. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/flash.py +5 -5
  48. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/flash_server.py +1 -1
  49. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/functions.pyi +6 -6
  50. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/io_streams.py +2 -2
  51. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/mount.py +5 -5
  52. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/network_file_system.py +11 -11
  53. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/parallel_map.py +17 -17
  54. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/partial_function.py +2 -0
  55. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/partial_function.pyi +31 -0
  56. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/proxy.py +1 -1
  57. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/queue.py +14 -14
  58. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/runner.py +12 -12
  59. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/sandbox.py +114 -94
  60. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/sandbox.pyi +69 -35
  61. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/secret.py +9 -9
  62. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/snapshot.py +1 -1
  63. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/token_flow.py +1 -1
  64. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/types.py +40 -6
  65. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/volume.py +24 -24
  66. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/PKG-INFO +1 -1
  67. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/SOURCES.txt +2 -3
  68. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/api_grpc.py +16 -0
  69. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/api_pb2.py +1423 -1413
  70. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/api_pb2.pyi +27 -2
  71. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/api_pb2_grpc.py +33 -0
  72. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/api_pb2_grpc.pyi +10 -0
  73. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/modal_api_grpc.py +1 -0
  74. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_version/__init__.py +1 -1
  75. modal-1.5.6.dev25/modal/cli/cluster.py +0 -117
  76. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/LICENSE +0 -0
  77. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/README.md +0 -0
  78. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/__main__.py +0 -0
  79. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_billing.py +0 -0
  80. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_clustered_functions.pyi +0 -0
  81. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_container_entrypoint.py +0 -0
  82. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_grpc_client.py +0 -0
  83. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_ipython.py +0 -0
  84. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_load_context.py +0 -0
  85. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_location.py +0 -0
  86. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_logs_manager.pyi +0 -0
  87. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_object.py +0 -0
  88. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_output/__init__.py +0 -0
  89. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_output/manager.py +0 -0
  90. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_output/rich.py +0 -0
  91. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_output/status.py +0 -0
  92. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_resolver.py +0 -0
  93. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_resources.py +0 -0
  94. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/__init__.py +0 -0
  95. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/asgi.py +0 -0
  96. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/container_io_manager.pyi +0 -0
  97. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/execution_context.py +0 -0
  98. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/execution_context.pyi +0 -0
  99. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  100. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  101. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/telemetry.py +0 -0
  102. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/user_code_event_loop.py +0 -0
  103. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_runtime/user_code_imports.py +0 -0
  104. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_serialization.py +0 -0
  105. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_server.py +0 -0
  106. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_supports_logs.py +0 -0
  107. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_traceback.py +0 -0
  108. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_tunnel.pyi +0 -0
  109. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_type_manager.py +0 -0
  110. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/__init__.py +0 -0
  111. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/app_utils.py +0 -0
  112. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/async_utils.py +0 -0
  113. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/auth_token_manager.py +0 -0
  114. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/blob_utils.py +0 -0
  115. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/browser_utils.py +0 -0
  116. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/bytes_io_segment_payload.py +0 -0
  117. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/curl_utils.py +0 -0
  118. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/deprecation.py +0 -0
  119. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/display_utils.py +0 -0
  120. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/docker_utils.py +0 -0
  121. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/git_utils.py +0 -0
  122. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/grpc_testing.py +0 -0
  123. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/hash_utils.py +0 -0
  124. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/http_utils.py +0 -0
  125. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/idle_countdown.py +0 -0
  126. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/jwt_utils.py +0 -0
  127. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/logger.py +0 -0
  128. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/mount_utils.py +0 -0
  129. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/name_utils.py +0 -0
  130. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/package_utils.py +0 -0
  131. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/pattern_utils.py +0 -0
  132. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/proxy_support.py +0 -0
  133. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/rand_pb_testing.py +0 -0
  134. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/sandbox_fs_utils.py +0 -0
  135. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/shell_utils.py +0 -0
  136. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_utils/time_utils.py +0 -0
  137. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_vendor/__init__.py +0 -0
  138. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  139. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_vendor/cloudpickle.py +0 -0
  140. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_vendor/tblib.py +0 -0
  141. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_vendor/version.py +0 -0
  142. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/_watcher.py +0 -0
  143. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/billing.py +0 -0
  144. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/billing.pyi +0 -0
  145. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/2023.12.312.txt +0 -0
  146. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/2023.12.txt +0 -0
  147. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/2024.04.txt +0 -0
  148. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/2024.10.txt +0 -0
  149. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/2025.06.txt +0 -0
  150. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/PREVIEW.txt +0 -0
  151. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/README.md +0 -0
  152. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/builder/base-images.json +0 -0
  153. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/call_graph.py +0 -0
  154. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/__init__.py +0 -0
  155. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/_download.py +0 -0
  156. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/_help.py +0 -0
  157. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/_logs.py +0 -0
  158. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/_stats.py +0 -0
  159. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/_traceback.py +0 -0
  160. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/billing.py +0 -0
  161. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/changelog.py +0 -0
  162. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/config.py +0 -0
  163. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/dict.py +0 -0
  164. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/environment.py +0 -0
  165. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/import_refs.py +0 -0
  166. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/logo.py +0 -0
  167. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/profile.py +0 -0
  168. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/run.py +0 -0
  169. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/selector.py +0 -0
  170. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/skills.py +0 -0
  171. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/volume.py +0 -0
  172. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cli/workspace.py +0 -0
  173. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cloud_bucket_mount.py +0 -0
  174. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cloud_bucket_mount.pyi +0 -0
  175. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/cls.pyi +0 -0
  176. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/container_process.py +0 -0
  177. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/container_process.pyi +0 -0
  178. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/dict.pyi +0 -0
  179. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/_flash_container_entry.py +0 -0
  180. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/flash.pyi +0 -0
  181. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/flash_server.pyi +0 -0
  182. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/experimental/ipython.py +0 -0
  183. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/file_pattern_matcher.py +0 -0
  184. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/functions.py +0 -0
  185. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/image.py +0 -0
  186. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/image.pyi +0 -0
  187. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/io_streams.pyi +0 -0
  188. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/mount.pyi +0 -0
  189. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/network_file_system.pyi +0 -0
  190. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/object.py +0 -0
  191. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/object.pyi +0 -0
  192. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/output.py +0 -0
  193. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/parallel_map.pyi +0 -0
  194. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/proxy.pyi +0 -0
  195. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/py.typed +0 -0
  196. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/queue.pyi +0 -0
  197. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/retries.py +0 -0
  198. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/runner.pyi +0 -0
  199. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/running_app.py +0 -0
  200. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/sandbox_fs.py +0 -0
  201. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/sandbox_fs.pyi +0 -0
  202. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/schedule.py +0 -0
  203. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/scheduler_placement.py +0 -0
  204. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/secret.pyi +0 -0
  205. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/server.py +0 -0
  206. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/server.pyi +0 -0
  207. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/serving.py +0 -0
  208. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/serving.pyi +0 -0
  209. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/skills/modal/SKILL.md +0 -0
  210. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/snapshot.pyi +0 -0
  211. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/stream_type.py +0 -0
  212. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/token_flow.pyi +0 -0
  213. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/volume.pyi +0 -0
  214. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/workspace.py +0 -0
  215. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal/workspace.pyi +0 -0
  216. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/dependency_links.txt +0 -0
  217. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/entry_points.txt +0 -0
  218. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/requires.txt +0 -0
  219. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal.egg-info/top_level.txt +0 -0
  220. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/__init__.py +0 -0
  221. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/gen_cli_docs.py +0 -0
  222. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/gen_cli_docs_main.py +0 -0
  223. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/gen_reference_docs.py +0 -0
  224. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/gen_reference_docs_main.py +0 -0
  225. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/mdmd/__init__.py +0 -0
  226. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/mdmd/mdmd.py +0 -0
  227. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/mdmd/signatures.py +0 -0
  228. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/mdmd/types.py +0 -0
  229. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_docs/reference_intro.md +0 -0
  230. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/__init__.py +0 -0
  231. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/py.typed +0 -0
  232. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/task_command_router_grpc.py +0 -0
  233. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/task_command_router_pb2.py +0 -0
  234. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/task_command_router_pb2.pyi +0 -0
  235. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  236. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  237. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/modal_version/__main__.py +0 -0
  238. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/pyproject.toml +0 -0
  239. {modal-1.5.6.dev25 → modal-1.5.6.dev26}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev25
3
+ Version: 1.5.6.dev26
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -23,11 +23,11 @@ try:
23
23
  from .functions import Function, FunctionCall
24
24
  from .image import Image
25
25
  from .network_file_system import NetworkFileSystem
26
- from .outbound_policy import OutboundPolicy
27
26
  from .output import enable_output
28
27
  from .partial_function import (
29
28
  asgi_app,
30
29
  batched,
30
+ clustered,
31
31
  concurrent,
32
32
  enter,
33
33
  exit,
@@ -72,7 +72,6 @@ __all__ = [
72
72
  "FunctionCall",
73
73
  "Image",
74
74
  "NetworkFileSystem",
75
- "OutboundPolicy",
76
75
  "Period",
77
76
  "Probe",
78
77
  "Proxy",
@@ -90,6 +89,7 @@ __all__ = [
90
89
  "asgi_app",
91
90
  "batched",
92
91
  "billing",
92
+ "clustered",
93
93
  "concurrent",
94
94
  "current_function_call_id",
95
95
  "current_input_id",
@@ -71,7 +71,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
71
71
  os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
72
72
 
73
73
  if world_size > 1:
74
- resp = await client.stub.TaskClusterHello(
74
+ resp = await client._stub.TaskClusterHello(
75
75
  api_pb2.TaskClusterHelloRequest(
76
76
  task_id=task_id,
77
77
  container_ip=container_ip,
@@ -4,7 +4,7 @@ import builtins
4
4
  from collections.abc import Iterable, Mapping
5
5
  from dataclasses import dataclass
6
6
  from datetime import datetime, timezone
7
- from typing import Any, Literal
7
+ from typing import TYPE_CHECKING, Any, Literal
8
8
 
9
9
  from google.protobuf.empty_pb2 import Empty
10
10
  from google.protobuf.message import Message
@@ -24,6 +24,9 @@ from .config import config, logger
24
24
  from .exception import InvalidError, WorkspaceManagementError
25
25
  from .types import BillingReportItem, EnvironmentBillingSummary
26
26
 
27
+ if TYPE_CHECKING:
28
+ import modal
29
+
27
30
 
28
31
  @dataclass(frozen=True)
29
32
  class EnvironmentSettings:
@@ -59,7 +62,7 @@ class _EnvironmentManager:
59
62
  request.default_member_role_str = default_role
60
63
  if (experimental_options or {}).get("is_public", False):
61
64
  request.environment_type = api_pb2.ENVIRONMENT_TYPE_PUBLIC
62
- await client.stub.EnvironmentCreate(request)
65
+ await client._stub.EnvironmentCreate(request)
63
66
 
64
67
  async def list(
65
68
  self,
@@ -76,7 +79,7 @@ class _EnvironmentManager:
76
79
  ```
77
80
  """
78
81
  client = await _Client.from_env() if client is None else client
79
- resp = await client.stub.EnvironmentList(Empty())
82
+ resp = await client._stub.EnvironmentList(Empty())
80
83
  environments = []
81
84
  for item in resp.items:
82
85
  metadata = api_pb2.EnvironmentMetadata(
@@ -110,7 +113,7 @@ class _EnvironmentManager:
110
113
  ```
111
114
  """
112
115
  client = await _Client.from_env() if client is None else client
113
- await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
116
+ await client._stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
114
117
 
115
118
 
116
119
  class _EnvironmentRolesManager:
@@ -139,7 +142,7 @@ class _EnvironmentRolesManager:
139
142
  """
140
143
  await self._environment.hydrate()
141
144
  req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
142
- resp = await self._environment.client.stub.EnvironmentGetRoles(req)
145
+ resp = await self._environment.client._stub.EnvironmentGetRoles(req)
143
146
 
144
147
  users: dict[str, str] = {}
145
148
  service_users: dict[str, str] = {}
@@ -180,7 +183,7 @@ class _EnvironmentRolesManager:
180
183
  service_users = service_users or {}
181
184
 
182
185
  req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
183
- resp = await self._environment.client.stub.EnvironmentGetRoles(req)
186
+ resp = await self._environment.client._stub.EnvironmentGetRoles(req)
184
187
 
185
188
  # EnvironmentGetRoles returns every workspace principal
186
189
  user_name_to_id: dict[str, str] = {}
@@ -214,7 +217,7 @@ class _EnvironmentRolesManager:
214
217
  async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
215
218
  """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
216
219
  results = await asyncio.gather(
217
- *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
220
+ *(self._environment.client._stub.EnvironmentRoleSet(req) for req in requests.values()),
218
221
  return_exceptions=True,
219
222
  )
220
223
  errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
@@ -269,7 +272,7 @@ class _EnvironmentMembersManager:
269
272
  service_users = service_users or []
270
273
 
271
274
  req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
272
- resp = await self._environment.client.stub.EnvironmentGetRoles(req)
275
+ resp = await self._environment.client._stub.EnvironmentGetRoles(req)
273
276
 
274
277
  user_name_to_id: dict[str, str] = {}
275
278
  service_user_name_to_id: dict[str, str] = {}
@@ -300,6 +303,55 @@ class _EnvironmentMembersManager:
300
303
  await _EnvironmentRolesManager(self._environment)._dispatch_role_updates(requests)
301
304
 
302
305
 
306
+ class _EnvironmentAppsManager:
307
+ """mdmd:namespace"""
308
+
309
+ def __init__(self, environment: "_Environment"):
310
+ """mdmd:hidden"""
311
+ self._environment = environment
312
+
313
+ async def list(self) -> builtins.list["modal.app._App"]:
314
+ """Return handles for live Apps in this Environment.
315
+
316
+ The returned handles reference existing remote Apps and can be used with APIs such as
317
+ [`App.info()`](https://modal.com/docs/sdk/py/latest/App#info),
318
+ [`App.logs`](https://modal.com/docs/sdk/py/latest/App#logs), and
319
+ [`Sandbox.create()`](https://modal.com/docs/sdk/py/latest/Sandbox#create). Listing Apps does not create
320
+ Apps.
321
+
322
+ Examples:
323
+
324
+ ```python notest
325
+ environment = modal.Environment.from_name("prod")
326
+ apps = environment.apps.list()
327
+ print([app.name for app in apps])
328
+ ```
329
+ """
330
+ from .app import _App
331
+
332
+ await self._environment.hydrate()
333
+ assert self._environment.name is not None
334
+
335
+ response = await self._environment.client._stub.AppList(
336
+ api_pb2.AppListRequest(environment_name=self._environment.name)
337
+ )
338
+ stopped_app_states = (
339
+ api_pb2.APP_STATE_STOPPED,
340
+ api_pb2.APP_STATE_DISABLED,
341
+ )
342
+ return [
343
+ _App._new_remote(
344
+ item.name or None,
345
+ item.app_id,
346
+ self._environment.name,
347
+ self._environment.client,
348
+ description=item.description or None,
349
+ )
350
+ for item in response.apps
351
+ if item.state not in stopped_app_states
352
+ ]
353
+
354
+
303
355
  class _Environment(_Object, type_prefix="en"):
304
356
  _name: str | None = None
305
357
  _settings: EnvironmentSettings
@@ -334,6 +386,11 @@ class _Environment(_Object, type_prefix="en"):
334
386
  # Deprecated alias for `Environment.roles`.
335
387
  return _EnvironmentMembersManager(self)
336
388
 
389
+ @property
390
+ def apps(self) -> "_EnvironmentAppsManager":
391
+ """Namespace for accessing Apps deployed in this Environment."""
392
+ return _EnvironmentAppsManager(self)
393
+
337
394
  # TODO(michael) Keeping this private for now until we decide what else should be in it
338
395
  # And what the rules should be about updates / mutability
339
396
  # @property
@@ -366,7 +423,7 @@ class _Environment(_Object, type_prefix="en"):
366
423
  else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
367
424
  ),
368
425
  )
369
- response = await load_context.client.stub.EnvironmentGetOrCreate(request)
426
+ response = await load_context.client._stub.EnvironmentGetOrCreate(request)
370
427
  logger.debug(f"Created environment with id {response.environment_id}")
371
428
  self._hydrate(response.environment_id, load_context.client, response.metadata)
372
429
 
@@ -491,7 +548,7 @@ class _EnvironmentBillingManager:
491
548
 
492
549
  return [
493
550
  BillingReportItem._from_proto(pb_item)
494
- async for pb_item in self._environment.client.stub.WorkspaceBillingReport.unary_stream(request)
551
+ async for pb_item in self._environment.client._stub.WorkspaceBillingReport.unary_stream(request)
495
552
  ]
496
553
 
497
554
  async def summary(
@@ -556,7 +613,7 @@ class _EnvironmentBillingManager:
556
613
  request.start_timestamp.FromDatetime(cycle)
557
614
 
558
615
  return EnvironmentBillingSummary._from_proto(
559
- await self._environment.client.stub.EnvironmentBillingSummary(request)
616
+ await self._environment.client._stub.EnvironmentBillingSummary(request)
560
617
  )
561
618
 
562
619
 
@@ -582,7 +639,7 @@ async def _get_environment_cached(name: str, client: _Client) -> _Environment:
582
639
  async def _delete_environment(name: str, client: _Client | None = None):
583
640
  if client is None:
584
641
  client = await _Client.from_env()
585
- await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
642
+ await client._stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
586
643
 
587
644
 
588
645
  async def _update_environment(
@@ -608,19 +665,19 @@ async def _update_environment(
608
665
  )
609
666
  if client is None:
610
667
  client = await _Client.from_env()
611
- await client.stub.EnvironmentUpdate(update_payload)
668
+ await client._stub.EnvironmentUpdate(update_payload)
612
669
 
613
670
 
614
671
  async def _create_environment(name: str, client: _Client | None = None):
615
672
  if client is None:
616
673
  client = await _Client.from_env()
617
- await client.stub.EnvironmentCreate(api_pb2.EnvironmentCreateRequest(name=name))
674
+ await client._stub.EnvironmentCreate(api_pb2.EnvironmentCreateRequest(name=name))
618
675
 
619
676
 
620
677
  async def _list_environments(client: _Client | None = None) -> list[api_pb2.EnvironmentListItem]:
621
678
  if client is None:
622
679
  client = await _Client.from_env()
623
- resp = await client.stub.EnvironmentList(Empty())
680
+ resp = await client._stub.EnvironmentList(Empty())
624
681
  return list(resp.items)
625
682
 
626
683
 
@@ -335,7 +335,7 @@ async def _list_function_variants(
335
335
  cursor = None
336
336
  while True:
337
337
  request = api_pb2.FunctionListVariantsRequest(function_id=function_id, cursor=cursor, limit=limit or 0)
338
- response = await client.stub.FunctionListVariants(request)
338
+ response = await client._stub.FunctionListVariants(request)
339
339
  # An ordered listing arrives as a single response, so there is nothing to reconcile across
340
340
  # pages: every response of a multi-page listing reports itself unordered.
341
341
  ordered_by_task_count = response.ordered_by_task_count
@@ -376,8 +376,8 @@ async def _function_bind_params_cached(
376
376
  response.ParseFromString(cached_response)
377
377
  return response
378
378
 
379
- assert base_function._client and base_function._client.stub
380
- response = await base_function._client.stub.FunctionBindParams(req)
379
+ assert base_function._client and base_function._client._stub
380
+ response = await base_function._client._stub.FunctionBindParams(req)
381
381
 
382
382
  cache[cache_key] = response.SerializeToString(deterministic=True)
383
383
  cache.move_to_end(cache_key)
@@ -407,7 +407,7 @@ def _make_function_variant(
407
407
  if not base_function._is_hydrated:
408
408
  await base_function.hydrate(load_context.client)
409
409
 
410
- assert base_function._client and base_function._client.stub
410
+ assert base_function._client and base_function._client._stub
411
411
 
412
412
  if parameter_schema is None:
413
413
  # This branch is about backwards compatibility.
@@ -158,8 +158,8 @@ class _Invocation:
158
158
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
159
159
  from_spawn_map: bool = False,
160
160
  ) -> "_Invocation":
161
- assert client.stub
162
- stub = client.stub
161
+ assert client._stub
162
+ stub = client._stub
163
163
 
164
164
  function_id = function.object_id
165
165
  item = await _create_input(
@@ -180,7 +180,7 @@ class _Invocation:
180
180
 
181
181
  if from_spawn_map:
182
182
  request.from_spawn_map = True
183
- response = await client.stub.FunctionMap(
183
+ response = await client._stub.FunctionMap(
184
184
  request,
185
185
  retry=Retry(
186
186
  max_retries=None,
@@ -195,7 +195,7 @@ class _Invocation:
195
195
  ),
196
196
  )
197
197
  else:
198
- response = await client.stub.FunctionMap(request)
198
+ response = await client._stub.FunctionMap(request)
199
199
 
200
200
  function_call_id = response.function_call_id
201
201
  if response.pipelined_inputs:
@@ -215,7 +215,7 @@ class _Invocation:
215
215
  request_put = api_pb2.FunctionPutInputsRequest(
216
216
  function_id=function_id, inputs=[item], function_call_id=function_call_id
217
217
  )
218
- inputs_response: api_pb2.FunctionPutInputsResponse = await client.stub.FunctionPutInputs(request_put)
218
+ inputs_response: api_pb2.FunctionPutInputsResponse = await client._stub.FunctionPutInputs(request_put)
219
219
  processed_inputs = inputs_response.inputs
220
220
  if not processed_inputs:
221
221
  raise Exception("Could not create function call - the input queue seems to be full")
@@ -438,10 +438,10 @@ class _InputPlaneInvocation:
438
438
  input_plane_region: str,
439
439
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
440
440
  ) -> "_InputPlaneInvocation":
441
- stub = await client.get_stub(input_plane_url)
441
+ stub = await client._get_stub(input_plane_url)
442
442
 
443
443
  function_id = function.object_id
444
- control_plane_stub = client.stub
444
+ control_plane_stub = client._stub
445
445
  # Note: Blob upload is done on the control plane stub, not the input plane stub!
446
446
  input_item = await _create_input(
447
447
  args,
@@ -491,7 +491,7 @@ class _InputPlaneInvocation:
491
491
  # If we have a final output, return.
492
492
  if await_response.output.result.status in TERMINAL_STATUSES:
493
493
  return await _process_result(
494
- await_response.output.result, await_response.output.data_format, self.client.stub, self.client
494
+ await_response.output.result, await_response.output.data_format, self.client._stub, self.client
495
495
  )
496
496
 
497
497
  # We have a failure (internal or application), so see if there are any retries left, and if so, retry.
@@ -510,7 +510,7 @@ class _InputPlaneInvocation:
510
510
 
511
511
  # No more retries left.
512
512
  return await _process_result(
513
- await_response.output.result, await_response.output.data_format, self.client.stub, self.client
513
+ await_response.output.result, await_response.output.data_format, self.client._stub, self.client
514
514
  )
515
515
 
516
516
  async def _retry_input(self, metadata: list[tuple[str, str]]) -> str:
@@ -731,6 +731,25 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
731
731
  # Needed to avoid circular imports
732
732
  from ._partial_function import _find_partial_methods_for_user_cls, _PartialFunctionFlags
733
733
 
734
+ experimental_options = dict(experimental_options or {})
735
+ if "fabric_size" in experimental_options:
736
+ if fabric_size is not None:
737
+ raise InvalidError(
738
+ "Specify fabric_size only once, in experimental_options or the experimental decorator"
739
+ )
740
+ try:
741
+ fabric_size = int(experimental_options.pop("fabric_size"))
742
+ except ValueError:
743
+ raise InvalidError("fabric_size must be a positive integer") from None
744
+ if fabric_size <= 0:
745
+ raise InvalidError("fabric_size must be a positive integer")
746
+ if not cluster_size:
747
+ raise InvalidError("fabric_size requires @modal.clustered")
748
+ if cluster_size % fabric_size != 0:
749
+ raise InvalidError(
750
+ f"fabric_size must evenly divide the cluster size ({cluster_size} % {fabric_size} != 0)"
751
+ )
752
+
734
753
  tag = info.get_tag()
735
754
 
736
755
  if info.raw_f:
@@ -818,6 +837,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
818
837
  validate_target_concurrency(target_concurrent_inputs, "target_inputs", allow_fractional=False)
819
838
  target_concurrent_inputs_int = int(target_concurrent_inputs)
820
839
 
840
+ if cluster_size and batch_max_size:
841
+ raise InvalidError("Clustered Functions do not support dynamic batching.")
842
+
821
843
  # For clustered functions, container settings must be multiples of cluster_size
822
844
  if cluster_size is not None and cluster_size > 1:
823
845
  for field in ["min_containers", "max_containers", "buffer_containers"]:
@@ -941,7 +963,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
941
963
  elif webhook_config:
942
964
  req.webhook_config.CopyFrom(webhook_config)
943
965
 
944
- response = await load_context.client.stub.FunctionPrecreate(req)
966
+ response = await load_context.client._stub.FunctionPrecreate(req)
945
967
  self._hydrate(response.function_id, load_context.client, response.handle_metadata)
946
968
 
947
969
  async def _load(self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
@@ -1155,7 +1177,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1155
1177
  existing_function_id=existing_object_id or "",
1156
1178
  )
1157
1179
  try:
1158
- response: api_pb2.FunctionCreateResponse = await load_context.client.stub.FunctionCreate(request)
1180
+ response: api_pb2.FunctionCreateResponse = await load_context.client._stub.FunctionCreate(request)
1159
1181
  except Exception as exc:
1160
1182
  if "Received :status = '413'" in str(exc):
1161
1183
  raise InvalidError(f"Function {info.function_name} is too large to deploy.")
@@ -1230,7 +1252,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1230
1252
  },
1231
1253
  # We don't have any secret IDs yet as we are dehydrated, so we return the reprs instead
1232
1254
  secrets=[repr(s) for s in secrets],
1233
- http_info=HttpInfo._from_proto(http_config) if http_config else None,
1255
+ _http_info=HttpInfo._from_proto(http_config) if http_config else None,
1256
+ web_info=FunctionInfo.WebInfo._from_proto(webhook_config) if webhook_config else None,
1234
1257
  )
1235
1258
 
1236
1259
  return obj
@@ -1256,7 +1279,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1256
1279
  scaledown_window=scaledown_window,
1257
1280
  )
1258
1281
  request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
1259
- response = await self.client.stub.FunctionUpdateSchedulingParams(request)
1282
+ response = await self.client._stub.FunctionUpdateSchedulingParams(request)
1260
1283
 
1261
1284
  return FunctionAutoscalerSettings._from_proto(response.current_settings)
1262
1285
 
@@ -1281,7 +1304,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1281
1304
  validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
1282
1305
  settings.target_concurrency_float = normalize_fractional_target_concurrency(target_concurrency)
1283
1306
  request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
1284
- response = await self.client.stub.FunctionUpdateSchedulingParams(request)
1307
+ response = await self.client._stub.FunctionUpdateSchedulingParams(request)
1285
1308
 
1286
1309
  return ServerAutoscalerSettings._from_proto(response.current_settings)
1287
1310
 
@@ -1359,7 +1382,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1359
1382
  app_version=version or 0,
1360
1383
  )
1361
1384
  try:
1362
- response = await load_context.client.stub.FunctionGet(request)
1385
+ response = await load_context.client._stub.FunctionGet(request)
1363
1386
  except NotFoundError as exc:
1364
1387
  # refine the error message
1365
1388
  env_context = (
@@ -1403,7 +1426,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1403
1426
  ):
1404
1427
  request = api_pb2.FunctionGetByIdRequest(function_id=function_id)
1405
1428
  try:
1406
- response = await load_context.client.stub.FunctionGetById(request)
1429
+ response = await load_context.client._stub.FunctionGetById(request)
1407
1430
  except NotFoundError as exc:
1408
1431
  # refine the error message
1409
1432
  msg = f"Lookup failed for {called_from} '{function_id}': {exc}."
@@ -1563,7 +1586,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1563
1586
  if refresh or self._function_info is None:
1564
1587
  # Accessing `.client` or `.object_id` here can't throw since we only get to this branch on a
1565
1588
  # hydrated handle
1566
- response = await self.client.stub.FunctionGetById(
1589
+ response = await self.client._stub.FunctionGetById(
1567
1590
  api_pb2.FunctionGetByIdRequest(
1568
1591
  function_id=self.object_id,
1569
1592
  )
@@ -2224,7 +2247,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2224
2247
  Returns:
2225
2248
  A `FunctionCurrentStats` object containing live input and container counts.
2226
2249
  """
2227
- resp = await self.client.stub.FunctionGetCurrentStats(
2250
+ resp = await self.client._stub.FunctionGetCurrentStats(
2228
2251
  api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
2229
2252
  retry=Retry(total_timeout=10.0),
2230
2253
  )
@@ -2270,7 +2293,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2270
2293
  historical_request.until.FromDatetime(until)
2271
2294
  if container:
2272
2295
  historical_request.container_id = container
2273
- stats = await self.client.stub.FunctionGetTimeRangeStats(historical_request)
2296
+ stats = await self.client._stub.FunctionGetTimeRangeStats(historical_request)
2274
2297
  return FunctionStats._from_proto(stats)
2275
2298
 
2276
2299
  @live_method
@@ -2307,12 +2330,12 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2307
2330
  _function_id: str | None = None
2308
2331
 
2309
2332
  def _invocation(self):
2310
- return _Invocation(self.client.stub, self.object_id, self.client)
2333
+ return _Invocation(self.client._stub, self.object_id, self.client)
2311
2334
 
2312
2335
  async def _hydrate_from_id_metadata(self) -> None:
2313
2336
  """Hydrate metadata only when needed for FunctionCall fields."""
2314
2337
  request = api_pb2.FunctionCallFromIdRequest(function_call_id=self.object_id)
2315
- resp = await self.client.stub.FunctionCallFromId(request)
2338
+ resp = await self.client._stub.FunctionCallFromId(request)
2316
2339
  self._hydrate_metadata(resp)
2317
2340
 
2318
2341
  async def _get_log_query_data(self) -> _LogQueryData:
@@ -2428,9 +2451,9 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2428
2451
  Returns:
2429
2452
  A list of `InputInfo` nodes describing the call graph.
2430
2453
  """
2431
- assert self._client and self._client.stub
2454
+ assert self._client and self._client._stub
2432
2455
  request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
2433
- response = await self._client.stub.FunctionGetCallGraph(request)
2456
+ response = await self._client._stub.FunctionGetCallGraph(request)
2434
2457
  if response.truncated:
2435
2458
  logger.warning(
2436
2459
  f"Call graph for {self.object_id} was truncated; "
@@ -2450,8 +2473,8 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2450
2473
  request = api_pb2.FunctionCallCancelRequest(
2451
2474
  function_call_id=self.object_id, terminate_containers=terminate_containers
2452
2475
  )
2453
- assert self._client and self._client.stub
2454
- await self._client.stub.FunctionCallCancel(request)
2476
+ assert self._client and self._client._stub
2477
+ await self._client._stub.FunctionCallCancel(request)
2455
2478
 
2456
2479
  @deprecate_aio_usage((2025, 11, 14), "FunctionCall.from_id")
2457
2480
  @classmethod
@@ -438,7 +438,7 @@ async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.I
438
438
  nonlocal last_entry_id, result_response
439
439
 
440
440
  request = api_pb2.ImageJoinStreamingRequest(image_id=image_id, timeout=55, last_entry_id=last_entry_id)
441
- async for response in client.stub.ImageJoinStreaming.unary_stream(request):
441
+ async for response in client._stub.ImageJoinStreaming.unary_stream(request):
442
442
  if response.entry_id:
443
443
  last_entry_id = response.entry_id
444
444
  if response.result.status:
@@ -746,7 +746,7 @@ class _Image(_Object, type_prefix="im"):
746
746
  allow_global_deployment=os.environ.get("MODAL_IMAGE_ALLOW_GLOBAL_DEPLOYMENT") == "1",
747
747
  ignore_cache=config.get("ignore_cache"),
748
748
  )
749
- resp = await load_context.client.stub.ImageGetOrCreate(req)
749
+ resp = await load_context.client._stub.ImageGetOrCreate(req)
750
750
  image_id = resp.image_id
751
751
  result: api_pb2.GenericResult
752
752
  metadata: api_pb2.ImageMetadata | None = None
@@ -1013,7 +1013,7 @@ class _Image(_Object, type_prefix="im"):
1013
1013
  _client = typing.cast(_Client, synchronizer._translate_in(client))
1014
1014
 
1015
1015
  async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
1016
- resp = await load_context.client.stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
1016
+ resp = await load_context.client._stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
1017
1017
  self._hydrate(resp.image_id, load_context.client, resp.metadata)
1018
1018
 
1019
1019
  rep = f"Image.from_id({image_id!r})"
@@ -2959,7 +2959,7 @@ class _Image(_Object, type_prefix="im"):
2959
2959
  request = api_pb2.ImageJoinStreamingRequest(
2960
2960
  image_id=self.object_id, timeout=55, last_entry_id=last_entry_id, include_logs_for_finished=True
2961
2961
  )
2962
- async for response in self.client.stub.ImageJoinStreaming.unary_stream(request):
2962
+ async for response in self.client._stub.ImageJoinStreaming.unary_stream(request):
2963
2963
  if response.result.status:
2964
2964
  return
2965
2965
  if response.entry_id:
@@ -2995,7 +2995,7 @@ class _Image(_Object, type_prefix="im"):
2995
2995
  req = api_pb2.ImageGetByTagRequest(
2996
2996
  tag=_upgrade_image_name(name, fallback_environment_name=load_context.environment_name),
2997
2997
  )
2998
- response = await load_context.client.stub.ImageGetByTag(req)
2998
+ response = await load_context.client._stub.ImageGetByTag(req)
2999
2999
  self._hydrate(response.image_id, load_context.client, None)
3000
3000
 
3001
3001
  rep = _Image._repr(name, environment_name)
@@ -3039,7 +3039,7 @@ class _Image(_Object, type_prefix="im"):
3039
3039
  fallback_environment_name=config.get("environment"),
3040
3040
  )
3041
3041
 
3042
- await _client.stub.ImagePublish(
3042
+ await _client._stub.ImagePublish(
3043
3043
  api_pb2.ImagePublishRequest(
3044
3044
  image_id=self._object_id,
3045
3045
  allow_public=bool((experimental_options or {}).get("is_public", False)),
@@ -3074,7 +3074,7 @@ class _Image(_Object, type_prefix="im"):
3074
3074
 
3075
3075
  if self._build_steps is None:
3076
3076
  request = api_pb2.ImageBuildChainGetRequest(image_id=image_id)
3077
- response = await client.stub.ImageBuildChainGet(request)
3077
+ response = await client._stub.ImageBuildChainGet(request)
3078
3078
  self._build_steps = list(response.build_steps)
3079
3079
  return _ImageLogQueryData(client, self._build_steps or [], image_id)
3080
3080
 
@@ -256,7 +256,7 @@ async def _refine_dense_ranges(
256
256
  sandbox_id=filters.sandbox_id,
257
257
  search_text=filters.search_text,
258
258
  )
259
- sub_resp = await client.stub.AppCountLogs(sub_req)
259
+ sub_resp = await client._stub.AppCountLogs(sub_req)
260
260
  sub_ranges = _buckets_to_ranges(list(sub_resp.buckets), smaller_secs)
261
261
  # Clamp the edge sub-ranges to the parent boundaries. Bucket
262
262
  # alignment can make the first bucket begin before the parent
@@ -311,7 +311,7 @@ async def _fetch_interval(
311
311
  sandbox_id=filters.sandbox_id,
312
312
  search_text=filters.search_text,
313
313
  )
314
- resp: api_pb2.AppFetchLogsResponse = await client.stub.AppFetchLogs(req)
314
+ resp: api_pb2.AppFetchLogsResponse = await client._stub.AppFetchLogs(req)
315
315
  return list(resp.batches)
316
316
 
317
317
 
@@ -365,7 +365,7 @@ async def tail_logs(
365
365
  sandbox_id=filters.sandbox_id,
366
366
  search_text=filters.search_text,
367
367
  )
368
- resp = await client.stub.AppFetchLogs(req)
368
+ resp = await client._stub.AppFetchLogs(req)
369
369
  for batch in resp.batches:
370
370
  yield batch
371
371
  return
@@ -387,7 +387,7 @@ async def tail_logs(
387
387
  sandbox_id=filters.sandbox_id,
388
388
  search_text=filters.search_text,
389
389
  )
390
- resp = await client.stub.AppFetchLogs(req)
390
+ resp = await client._stub.AppFetchLogs(req)
391
391
 
392
392
  total_items = sum(len(b.items) for b in resp.batches)
393
393
  if total_items >= n or lookback == _TAIL_LOOKBACKS[-1]:
@@ -433,7 +433,7 @@ async def fetch_logs(
433
433
  sandbox_id=filters.sandbox_id,
434
434
  search_text=filters.search_text,
435
435
  )
436
- count_resp: api_pb2.AppCountLogsResponse = await client.stub.AppCountLogs(count_req)
436
+ count_resp: api_pb2.AppCountLogsResponse = await client._stub.AppCountLogs(count_req)
437
437
 
438
438
  ranges = _buckets_to_ranges(list(count_resp.buckets), bucket_secs)
439
439
  total_logs = sum(count for _, _, count in ranges)
@@ -190,7 +190,7 @@ class _LogsManager:
190
190
  task_id=params.filters.task_id,
191
191
  sandbox_id=params.filters.sandbox_id,
192
192
  )
193
- return params.client.stub.AppGetLogs.unary_stream(request)
193
+ return params.client._stub.AppGetLogs.unary_stream(request)
194
194
 
195
195
  def _stream_entries(self, batch: api_pb2.TaskLogsBatch, source_object_id: str):
196
196
  for item in batch.items:
@@ -544,7 +544,7 @@ class _FunctionCallLogsManager:
544
544
  function_id=self._function_id,
545
545
  function_call_id=params.filters.function_call_id,
546
546
  )
547
- response = await params.client.stub.FunctionCallGetInfo(request)
547
+ response = await params.client._stub.FunctionCallGetInfo(request)
548
548
  return response.info
549
549
  except NotFoundError:
550
550
  if i < 4: