modAL 1.4.2__tar.gz → 1.4.2.dev0__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 (212) hide show
  1. {modal-1.4.2 → modal-1.4.2.dev0}/PKG-INFO +1 -1
  2. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_functions.py +3 -0
  3. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/asgi.py +2 -5
  4. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/grpc_utils.py +5 -29
  5. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/rand_pb_testing.py +1 -3
  6. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/sandbox_fs_utils.py +0 -37
  7. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/task_command_router_client.py +24 -79
  8. {modal-1.4.2 → modal-1.4.2.dev0}/modal/app.py +6 -2
  9. {modal-1.4.2 → modal-1.4.2.dev0}/modal/app.pyi +8 -4
  10. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/app.py +21 -171
  11. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/container.py +2 -20
  12. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/entry_point.py +24 -8
  13. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/import_refs.py +2 -21
  14. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/run.py +3 -3
  15. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/selector.py +4 -12
  16. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/shell.py +1 -1
  17. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/utils.py +17 -10
  18. {modal-1.4.2 → modal-1.4.2.dev0}/modal/client.py +0 -7
  19. {modal-1.4.2 → modal-1.4.2.dev0}/modal/client.pyi +10 -14
  20. {modal-1.4.2 → modal-1.4.2.dev0}/modal/config.py +1 -2
  21. {modal-1.4.2 → modal-1.4.2.dev0}/modal/dict.py +5 -5
  22. {modal-1.4.2 → modal-1.4.2.dev0}/modal/dict.pyi +8 -8
  23. {modal-1.4.2 → modal-1.4.2.dev0}/modal/exception.py +0 -6
  24. {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_io.py +2 -2
  25. {modal-1.4.2 → modal-1.4.2.dev0}/modal/functions.pyi +1 -0
  26. {modal-1.4.2 → modal-1.4.2.dev0}/modal/image.py +0 -2
  27. {modal-1.4.2 → modal-1.4.2.dev0}/modal/image.pyi +0 -2
  28. {modal-1.4.2 → modal-1.4.2.dev0}/modal/queue.py +5 -5
  29. {modal-1.4.2 → modal-1.4.2.dev0}/modal/queue.pyi +8 -8
  30. {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox.py +22 -97
  31. {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox.pyi +27 -87
  32. {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox_fs.py +7 -44
  33. modal-1.4.2.dev0/modal/sandbox_fs.pyi +627 -0
  34. {modal-1.4.2 → modal-1.4.2.dev0}/modal/secret.py +5 -5
  35. {modal-1.4.2 → modal-1.4.2.dev0}/modal/secret.pyi +8 -8
  36. {modal-1.4.2 → modal-1.4.2.dev0}/modal/volume.py +5 -5
  37. {modal-1.4.2 → modal-1.4.2.dev0}/modal/volume.pyi +8 -8
  38. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/PKG-INFO +1 -1
  39. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/SOURCES.txt +0 -2
  40. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/mdmd.py +13 -30
  41. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_grpc.py +0 -128
  42. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2.py +1051 -1250
  43. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2.pyi +5 -453
  44. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.py +0 -266
  45. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.pyi +0 -84
  46. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/modal_api_grpc.py +0 -8
  47. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_grpc.py +0 -16
  48. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.py +9 -19
  49. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.pyi +0 -17
  50. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -34
  51. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -12
  52. {modal-1.4.2 → modal-1.4.2.dev0}/modal_version/__init__.py +1 -1
  53. {modal-1.4.2 → modal-1.4.2.dev0}/pyproject.toml +3 -3
  54. modal-1.4.2/modal/cli/bootstrap.py +0 -136
  55. modal-1.4.2/modal/cli/logo.py +0 -70
  56. modal-1.4.2/modal/sandbox_fs.pyi +0 -672
  57. {modal-1.4.2 → modal-1.4.2.dev0}/LICENSE +0 -0
  58. {modal-1.4.2 → modal-1.4.2.dev0}/README.md +0 -0
  59. {modal-1.4.2 → modal-1.4.2.dev0}/modal/__init__.py +0 -0
  60. {modal-1.4.2 → modal-1.4.2.dev0}/modal/__main__.py +0 -0
  61. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_billing.py +0 -0
  62. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_clustered_functions.py +0 -0
  63. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_clustered_functions.pyi +0 -0
  64. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_container_entrypoint.py +0 -0
  65. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_grpc_client.py +0 -0
  66. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_ipython.py +0 -0
  67. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_load_context.py +0 -0
  68. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_location.py +0 -0
  69. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_logs.py +0 -0
  70. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_object.py +0 -0
  71. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/__init__.py +0 -0
  72. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/manager.py +0 -0
  73. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/pty.py +0 -0
  74. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/rich.py +0 -0
  75. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/status.py +0 -0
  76. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_partial_function.py +0 -0
  77. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_resolver.py +0 -0
  78. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_resources.py +0 -0
  79. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/__init__.py +0 -0
  80. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
  81. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  82. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/execution_context.py +0 -0
  83. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
  84. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  85. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/telemetry.py +0 -0
  86. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  87. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
  88. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_serialization.py +0 -0
  89. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_server.py +0 -0
  90. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_traceback.py +0 -0
  91. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_tunnel.py +0 -0
  92. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_tunnel.pyi +0 -0
  93. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_type_manager.py +0 -0
  94. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/__init__.py +0 -0
  95. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/app_utils.py +0 -0
  96. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/async_utils.py +0 -0
  97. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
  98. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/blob_utils.py +0 -0
  99. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/browser_utils.py +0 -0
  100. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  101. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/deprecation.py +0 -0
  102. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/docker_utils.py +0 -0
  103. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/function_utils.py +0 -0
  104. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/git_utils.py +0 -0
  105. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/grpc_testing.py +0 -0
  106. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/hash_utils.py +0 -0
  107. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/http_utils.py +0 -0
  108. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/jwt_utils.py +0 -0
  109. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/logger.py +0 -0
  110. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/mount_utils.py +0 -0
  111. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/name_utils.py +0 -0
  112. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/package_utils.py +0 -0
  113. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/pattern_utils.py +0 -0
  114. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/shell_utils.py +0 -0
  115. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/time_utils.py +0 -0
  116. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/__init__.py +0 -0
  117. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  118. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
  119. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/tblib.py +0 -0
  120. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/version.py +0 -0
  121. {modal-1.4.2 → modal-1.4.2.dev0}/modal/_watcher.py +0 -0
  122. {modal-1.4.2 → modal-1.4.2.dev0}/modal/billing.py +0 -0
  123. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2023.12.312.txt +0 -0
  124. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2023.12.txt +0 -0
  125. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2024.04.txt +0 -0
  126. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2024.10.txt +0 -0
  127. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2025.06.txt +0 -0
  128. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/PREVIEW.txt +0 -0
  129. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/README.md +0 -0
  130. {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/base-images.json +0 -0
  131. {modal-1.4.2 → modal-1.4.2.dev0}/modal/call_graph.py +0 -0
  132. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/__init__.py +0 -0
  133. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/_download.py +0 -0
  134. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/_traceback.py +0 -0
  135. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/billing.py +0 -0
  136. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/changelog.py +0 -0
  137. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/cluster.py +0 -0
  138. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/config.py +0 -0
  139. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/dashboard.py +0 -0
  140. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/dict.py +0 -0
  141. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/environment.py +0 -0
  142. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/launch.py +0 -0
  143. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/network_file_system.py +0 -0
  144. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/profile.py +0 -0
  145. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/__init__.py +0 -0
  146. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  147. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/vscode.py +0 -0
  148. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/queues.py +0 -0
  149. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/secret.py +0 -0
  150. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/token.py +0 -0
  151. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/volume.py +0 -0
  152. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.py +0 -0
  153. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  154. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cls.py +0 -0
  155. {modal-1.4.2 → modal-1.4.2.dev0}/modal/cls.pyi +0 -0
  156. {modal-1.4.2 → modal-1.4.2.dev0}/modal/container_process.py +0 -0
  157. {modal-1.4.2 → modal-1.4.2.dev0}/modal/container_process.pyi +0 -0
  158. {modal-1.4.2 → modal-1.4.2.dev0}/modal/environments.py +0 -0
  159. {modal-1.4.2 → modal-1.4.2.dev0}/modal/environments.pyi +0 -0
  160. {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/__init__.py +0 -0
  161. {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/flash.py +0 -0
  162. {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/flash.pyi +0 -0
  163. {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/ipython.py +0 -0
  164. {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_io.pyi +0 -0
  165. {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_pattern_matcher.py +0 -0
  166. {modal-1.4.2 → modal-1.4.2.dev0}/modal/functions.py +0 -0
  167. {modal-1.4.2 → modal-1.4.2.dev0}/modal/io_streams.py +0 -0
  168. {modal-1.4.2 → modal-1.4.2.dev0}/modal/io_streams.pyi +0 -0
  169. {modal-1.4.2 → modal-1.4.2.dev0}/modal/mount.py +0 -0
  170. {modal-1.4.2 → modal-1.4.2.dev0}/modal/mount.pyi +0 -0
  171. {modal-1.4.2 → modal-1.4.2.dev0}/modal/network_file_system.py +0 -0
  172. {modal-1.4.2 → modal-1.4.2.dev0}/modal/network_file_system.pyi +0 -0
  173. {modal-1.4.2 → modal-1.4.2.dev0}/modal/object.py +0 -0
  174. {modal-1.4.2 → modal-1.4.2.dev0}/modal/object.pyi +0 -0
  175. {modal-1.4.2 → modal-1.4.2.dev0}/modal/output.py +0 -0
  176. {modal-1.4.2 → modal-1.4.2.dev0}/modal/parallel_map.py +0 -0
  177. {modal-1.4.2 → modal-1.4.2.dev0}/modal/parallel_map.pyi +0 -0
  178. {modal-1.4.2 → modal-1.4.2.dev0}/modal/partial_function.py +0 -0
  179. {modal-1.4.2 → modal-1.4.2.dev0}/modal/partial_function.pyi +0 -0
  180. {modal-1.4.2 → modal-1.4.2.dev0}/modal/proxy.py +0 -0
  181. {modal-1.4.2 → modal-1.4.2.dev0}/modal/proxy.pyi +0 -0
  182. {modal-1.4.2 → modal-1.4.2.dev0}/modal/py.typed +0 -0
  183. {modal-1.4.2 → modal-1.4.2.dev0}/modal/retries.py +0 -0
  184. {modal-1.4.2 → modal-1.4.2.dev0}/modal/runner.py +0 -0
  185. {modal-1.4.2 → modal-1.4.2.dev0}/modal/runner.pyi +0 -0
  186. {modal-1.4.2 → modal-1.4.2.dev0}/modal/running_app.py +0 -0
  187. {modal-1.4.2 → modal-1.4.2.dev0}/modal/schedule.py +0 -0
  188. {modal-1.4.2 → modal-1.4.2.dev0}/modal/scheduler_placement.py +0 -0
  189. {modal-1.4.2 → modal-1.4.2.dev0}/modal/server.py +0 -0
  190. {modal-1.4.2 → modal-1.4.2.dev0}/modal/server.pyi +0 -0
  191. {modal-1.4.2 → modal-1.4.2.dev0}/modal/serving.py +0 -0
  192. {modal-1.4.2 → modal-1.4.2.dev0}/modal/serving.pyi +0 -0
  193. {modal-1.4.2 → modal-1.4.2.dev0}/modal/snapshot.py +0 -0
  194. {modal-1.4.2 → modal-1.4.2.dev0}/modal/snapshot.pyi +0 -0
  195. {modal-1.4.2 → modal-1.4.2.dev0}/modal/stream_type.py +0 -0
  196. {modal-1.4.2 → modal-1.4.2.dev0}/modal/token_flow.py +0 -0
  197. {modal-1.4.2 → modal-1.4.2.dev0}/modal/token_flow.pyi +0 -0
  198. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
  199. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/entry_points.txt +0 -0
  200. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/requires.txt +0 -0
  201. {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/top_level.txt +0 -0
  202. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/__init__.py +0 -0
  203. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
  204. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  205. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
  206. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  207. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
  208. {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
  209. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/__init__.py +0 -0
  210. {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/py.typed +0 -0
  211. {modal-1.4.2 → modal-1.4.2.dev0}/modal_version/__main__.py +0 -0
  212. {modal-1.4.2 → modal-1.4.2.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.2
3
+ Version: 1.4.2.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -700,6 +700,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
700
700
  ephemeral_disk: Optional[int] = None,
701
701
  include_source: bool = True,
702
702
  experimental_options: Optional[dict[str, str]] = None,
703
+ _experimental_proxy_ip: Optional[str] = None,
703
704
  restrict_output: bool = False,
704
705
  http_config: Optional[api_pb2.HTTPConfig] = None,
705
706
  ) -> "_Function":
@@ -1040,6 +1041,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1040
1041
  # ---
1041
1042
  _experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
1042
1043
  _experimental_concurrent_cancellations=True,
1044
+ _experimental_proxy_ip=_experimental_proxy_ip,
1043
1045
  # --- These are deprecated in favor of autoscaler_settings
1044
1046
  warm_pool_size=min_containers or 0,
1045
1047
  concurrency_limit=max_containers or 0,
@@ -1082,6 +1084,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1082
1084
  _experimental_group_size=function_definition._experimental_group_size,
1083
1085
  _experimental_buffer_containers=function_definition._experimental_buffer_containers,
1084
1086
  _experimental_custom_scaling=function_definition._experimental_custom_scaling,
1087
+ _experimental_proxy_ip=function_definition._experimental_proxy_ip,
1085
1088
  snapshot_debug=function_definition.snapshot_debug,
1086
1089
  runtime_perf_record=function_definition.runtime_perf_record,
1087
1090
  function_schema=function_schema,
@@ -474,11 +474,8 @@ async def _proxy_lifespan_request(base_url, scope, receive, send) -> None:
474
474
  auto_decompress=False,
475
475
  read_bufsize=1024 * 1024, # 1 MiB
476
476
  connector=aiohttp.TCPConnector(
477
- limit=1000, # 100 is the default max, but 1000 is the max for `@modal.concurrent`.
478
- # Disable keep-alive connection reuse to avoid ServerDisconnectedError
479
- # from stale pooled connections.
480
- force_close=True,
481
- ),
477
+ limit=1000
478
+ ), # 100 is the default max, but 1000 is the max for `@modal.concurrent`.
482
479
  # Note: these values will need to be kept in sync.
483
480
  **(
484
481
  # These options were introduced in aiohttp 3.9, and we can remove the
@@ -216,11 +216,7 @@ def create_channel(
216
216
  for k, v in metadata.items():
217
217
  event.metadata[k] = v
218
218
 
219
- idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
220
- if idempotency_key is None:
221
- logger.debug(f"Sending request to {event.method_name}")
222
- else:
223
- logger.debug(f"Sending request to {event.method_name} ({idempotency_key[:8]})")
219
+ logger.debug(f"Sending request to {event.method_name}")
224
220
 
225
221
  grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
226
222
 
@@ -292,14 +288,12 @@ def process_exception_before_retry(
292
288
  n_retries: int,
293
289
  delay: float,
294
290
  idempotency_key: str,
295
- rpc_elapsed: float,
296
291
  ):
297
292
  """Process exception before retry, used by `_retry_transient_errors`."""
298
293
  with suppress_tb_frame():
299
294
  if final_attempt:
300
295
  logger.debug(
301
- f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} {rpc_elapsed=:0.2f}s "
302
- f"for {fn_name} ({idempotency_key[:8]})"
296
+ f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})"
303
297
  )
304
298
  if isinstance(exc, OSError):
305
299
  raise ConnectionError(str(exc))
@@ -316,10 +310,7 @@ def process_exception_before_retry(
316
310
  # we handle in the retry logic once we drop this check!
317
311
  raise exc
318
312
 
319
- logger.debug(
320
- f"Retryable failure {repr(exc)} {n_retries=} {delay=} {rpc_elapsed=:0.2f}s "
321
- f"for {fn_name} ({idempotency_key[:8]})"
322
- )
313
+ logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
323
314
 
324
315
 
325
316
  async def _retry_transient_errors(
@@ -382,7 +373,6 @@ async def _retry_transient_errors(
382
373
  else:
383
374
  timeout = None
384
375
 
385
- attempt_started_at = time.monotonic()
386
376
  try:
387
377
  with suppress_tb_frame():
388
378
  return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
@@ -419,13 +409,7 @@ async def _retry_transient_errors(
419
409
 
420
410
  with suppress_tb_frame():
421
411
  process_exception_before_retry(
422
- exc,
423
- final_attempt,
424
- fn.name,
425
- n_retries,
426
- server_delay,
427
- idempotency_key,
428
- time.monotonic() - attempt_started_at,
412
+ exc, final_attempt, fn.name, n_retries, server_delay, idempotency_key
429
413
  )
430
414
 
431
415
  now = time.time()
@@ -454,15 +438,7 @@ async def _retry_transient_errors(
454
438
  final_attempt = False
455
439
 
456
440
  with suppress_tb_frame():
457
- process_exception_before_retry(
458
- exc,
459
- final_attempt,
460
- fn.name,
461
- n_retries,
462
- delay,
463
- idempotency_key,
464
- time.monotonic() - attempt_started_at,
465
- )
441
+ process_exception_before_retry(exc, final_attempt, fn.name, n_retries, delay, idempotency_key)
466
442
 
467
443
  n_retries += 1
468
444
 
@@ -52,9 +52,8 @@ def _fill(msg, desc: Descriptor, rand: Random) -> None:
52
52
  if hasattr(field, "is_repeated"):
53
53
  is_repeated = field.is_repeated # type: ignore
54
54
  else:
55
- is_repeated = field.label == FieldDescriptor.LABEL_REPEATED # type: ignore[attr-defined]
55
+ is_repeated = field.label == FieldDescriptor.LABEL_REPEATED
56
56
  if is_message:
57
- assert field.message_type is not None
58
57
  msg_field = getattr(msg, field.name)
59
58
  if is_repeated:
60
59
  num = rand.randint(0, 2)
@@ -65,7 +64,6 @@ def _fill(msg, desc: Descriptor, rand: Random) -> None:
65
64
  _fill(msg_field, field.message_type, rand)
66
65
  else:
67
66
  if field.type == FieldDescriptor.TYPE_ENUM:
68
- assert field.enum_type is not None
69
67
  enum_values = [x.number for x in field.enum_type.values]
70
68
  generator = lambda rand: rand.choice(enum_values) # noqa: E731
71
69
 
@@ -18,7 +18,6 @@ from ..exception import (
18
18
  SandboxFilesystemIsADirectoryError,
19
19
  SandboxFilesystemNotADirectoryError,
20
20
  SandboxFilesystemNotFoundError,
21
- SandboxFilesystemPathAlreadyExistsError,
22
21
  SandboxFilesystemPermissionError,
23
22
  ServiceError,
24
23
  )
@@ -189,42 +188,6 @@ def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path:
189
188
  raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
190
189
 
191
190
 
192
- def make_make_directory_command(remote_path: str, create_parents: bool) -> str:
193
- """Build the JSON command string for a MakeDirectory operation.
194
-
195
- The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
196
- Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
197
- this schema like protobuf changes: fields must not be removed or renamed,
198
- only added with backwards-compatible defaults.
199
- """
200
- return json.dumps({"MakeDirectory": {"path": remote_path, "parents": create_parents}})
201
-
202
-
203
- def raise_make_directory_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
204
- if payload := try_parse_error_payload(stderr):
205
- logger.debug(
206
- f"sandbox-fs-tools make_directory error: path={remote_path}, "
207
- f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
208
- )
209
- if payload.error_kind == "NotFound":
210
- raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
211
- if payload.error_kind == "PathAlreadyExists":
212
- raise SandboxFilesystemPathAlreadyExistsError(f"{payload.message}: {remote_path}")
213
- if payload.error_kind == "NotDirectory":
214
- raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
215
- if payload.error_kind == "PermissionDenied":
216
- raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
217
- if payload.error_kind == "NotSupported":
218
- raise InvalidError(
219
- f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
220
- )
221
- raise SandboxFilesystemError(payload.message)
222
-
223
- if stderr_text := _stderr_to_text(stderr):
224
- logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
225
- raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
226
-
227
-
228
191
  def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
229
192
  if not PurePosixPath(remote_path).is_absolute():
230
193
  raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
@@ -122,18 +122,6 @@ async def fetch_command_router_access(server_client, task_id: str) -> api_pb2.Ta
122
122
  )
123
123
 
124
124
 
125
- async def fetch_command_router_access_v2(
126
- server_client, sandbox_id: str
127
- ) -> api_pb2.SandboxGetCommandRouterAccessResponse:
128
- """Fetch direct command router access info from Modal server for a V2 sandbox."""
129
- assert server_client._auth_token_manager
130
- auth_token = await server_client._auth_token_manager.get_token()
131
- return await server_client.stub.SandboxGetCommandRouterAccess(
132
- api_pb2.SandboxGetCommandRouterAccessRequest(sandbox_id=sandbox_id),
133
- metadata=[("x-modal-auth-token", auth_token)],
134
- )
135
-
136
-
137
125
  def _finalize_channel(loop, channel):
138
126
  if not loop.is_closed():
139
127
  # only run if loop has not shut down
@@ -151,19 +139,27 @@ class TaskCommandRouterClient:
151
139
  """
152
140
 
153
141
  @classmethod
154
- async def _connect(
142
+ async def try_init(
155
143
  cls,
156
144
  server_client,
157
145
  task_id: str,
158
- url: str,
159
- jwt: str,
160
- *,
161
- sandbox_id: Optional[str] = None,
162
- ) -> "TaskCommandRouterClient":
163
- """Build a connected client from a jwt and url."""
164
- o = urllib.parse.urlparse(url)
146
+ ) -> Optional["TaskCommandRouterClient"]:
147
+ """Attempt to initialize a TaskCommandRouterClient by fetching direct access.
148
+
149
+ Returns None if command router access is not enabled (FAILED_PRECONDITION).
150
+ """
151
+ try:
152
+ resp = await fetch_command_router_access(server_client, task_id)
153
+ except ConflictError:
154
+ logger.debug(f"Command router access is not enabled for task {task_id}")
155
+ return None
156
+
157
+ logger.debug(f"Using command router access for task {task_id}")
158
+
159
+ # Build and connect a channel to the task command router now that we have access info.
160
+ o = urllib.parse.urlparse(resp.url)
165
161
  if o.scheme != "https":
166
- raise ValueError(f"Task router URL must be https, got: {url}")
162
+ raise ValueError(f"Task router URL must be https, got: {resp.url}")
167
163
 
168
164
  host, _, port_str = o.netloc.partition(":")
169
165
  port = int(port_str) if port_str else 443
@@ -193,38 +189,7 @@ class TaskCommandRouterClient:
193
189
  loop = asyncio.get_running_loop()
194
190
  jwt_refresh_lock = asyncio.Lock()
195
191
 
196
- return cls(server_client, task_id, url, jwt, channel, loop, jwt_refresh_lock, sandbox_id=sandbox_id)
197
-
198
- @classmethod
199
- async def try_init(
200
- cls,
201
- server_client,
202
- task_id: str,
203
- ) -> Optional["TaskCommandRouterClient"]:
204
- """Attempt to initialize a TaskCommandRouterClient by fetching direct access.
205
-
206
- Returns None if command router access is not enabled (FAILED_PRECONDITION).
207
- """
208
- try:
209
- resp = await fetch_command_router_access(server_client, task_id)
210
- except ConflictError:
211
- logger.debug(f"Command router access is not enabled for task {task_id}")
212
- return None
213
-
214
- logger.debug(f"Using command router access for task {task_id}")
215
- return await cls._connect(server_client, task_id, resp.url, resp.jwt)
216
-
217
- @classmethod
218
- async def init_v2(
219
- cls,
220
- server_client,
221
- sandbox_id: str,
222
- task_id: str,
223
- ) -> "TaskCommandRouterClient":
224
- """Initialize a TaskCommandRouterClient for a V2 sandbox."""
225
- resp = await fetch_command_router_access_v2(server_client, sandbox_id)
226
- logger.debug(f"Using command router access for sandbox {sandbox_id}")
227
- return await cls._connect(server_client, task_id, resp.url, resp.jwt, sandbox_id=sandbox_id)
192
+ return cls(server_client, task_id, resp.url, resp.jwt, channel, loop, jwt_refresh_lock)
228
193
 
229
194
  def __init__(
230
195
  self,
@@ -236,7 +201,6 @@ class TaskCommandRouterClient:
236
201
  loop: asyncio.AbstractEventLoop,
237
202
  jwt_refresh_lock: asyncio.Lock,
238
203
  *,
239
- sandbox_id: Optional[str] = None,
240
204
  stream_stdio_retry_delay_secs: float = 0.01,
241
205
  stream_stdio_retry_delay_factor: float = 2,
242
206
  stream_stdio_max_retries: int = 10,
@@ -249,7 +213,6 @@ class TaskCommandRouterClient:
249
213
  # Attach bearer token on all requests to the worker-side router service.
250
214
  self._server_client = server_client
251
215
  self._task_id = task_id
252
- self._sandbox_id = sandbox_id
253
216
  self._server_url = server_url
254
217
  self._jwt = jwt
255
218
  self._channel = channel
@@ -274,10 +237,6 @@ class TaskCommandRouterClient:
274
237
 
275
238
  self._stub = TaskCommandRouterStub(self._channel)
276
239
 
277
- @property
278
- def _is_v2_sandbox(self) -> bool:
279
- return self._sandbox_id is not None
280
-
281
240
  def _get_metadata(self):
282
241
  return {"authorization": f"Bearer {self._jwt}"}
283
242
 
@@ -493,22 +452,14 @@ class TaskCommandRouterClient:
493
452
  )
494
453
  return
495
454
 
496
- if self._is_v2_sandbox:
497
- logger.debug(f"Refreshing JWT for exec with sandbox ID {self._sandbox_id}")
498
- v2_resp = await fetch_command_router_access_v2(self._server_client, self._sandbox_id)
499
- logger.debug(f"Finished refreshing JWT for exec with sandbox ID {self._sandbox_id}")
500
- jwt, url = v2_resp.jwt, v2_resp.url
501
- else:
502
- logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
503
- v1_resp = await fetch_command_router_access(self._server_client, self._task_id)
504
- logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
505
- jwt, url = v1_resp.jwt, v1_resp.url
455
+ logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
456
+ resp = await fetch_command_router_access(self._server_client, self._task_id)
457
+ logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
506
458
 
507
459
  # Ensure the server URL remains stable for the lifetime of this client.
508
- if url != self._server_url:
509
- logger.warning("Task router URL changed during session")
510
- self._jwt = jwt
511
- self._jwt_exp = _parse_jwt_expiration(jwt)
460
+ assert resp.url == self._server_url, "Task router URL changed during session"
461
+ self._jwt = resp.jwt
462
+ self._jwt_exp = _parse_jwt_expiration(resp.jwt)
512
463
 
513
464
  async def _call_with_auth_retry(self, func, *args, **kwargs):
514
465
  try:
@@ -620,12 +571,6 @@ class TaskCommandRouterClient:
620
571
  lambda: self._call_with_auth_retry(self._stub.TaskMountDirectory, request)
621
572
  )
622
573
 
623
- async def unmount_image(self, request: sr_pb2.TaskUnmountDirectoryRequest):
624
- with grpc_error_converter():
625
- return await call_with_retries_on_transient_errors(
626
- lambda: self._call_with_auth_retry(self._stub.TaskUnmountDirectory, request)
627
- )
628
-
629
574
  async def snapshot_directory(
630
575
  self, request: sr_pb2.TaskSnapshotDirectoryRequest
631
576
  ) -> sr_pb2.TaskSnapshotDirectoryResponse:
@@ -753,6 +753,7 @@ class _App:
753
753
  include_source: Optional[bool] = None,
754
754
  experimental_options: Optional[dict[str, Any]] = None,
755
755
  # Parameters below here are experimental. Use with caution!
756
+ _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
756
757
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
757
758
  # Parameters below here are deprecated. Please update your code as suggested
758
759
  max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
@@ -913,6 +914,7 @@ class _App:
913
914
  rdma=rdma,
914
915
  include_source=include_source if include_source is not None else local_state.include_source_default,
915
916
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
917
+ _experimental_proxy_ip=_experimental_proxy_ip,
916
918
  restrict_output=_experimental_restrict_output,
917
919
  )
918
920
 
@@ -966,6 +968,7 @@ class _App:
966
968
  include_source: Optional[bool] = None, # When `False`, don't automatically add the App source to the container.
967
969
  experimental_options: Optional[dict[str, Any]] = None,
968
970
  # Parameters below here are experimental. Use with caution!
971
+ _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
969
972
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
970
973
  # Parameters below here are deprecated. Please update your code as suggested
971
974
  max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
@@ -1112,6 +1115,7 @@ class _App:
1112
1115
  rdma=rdma,
1113
1116
  include_source=include_source if include_source is not None else local_state.include_source_default,
1114
1117
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1118
+ _experimental_proxy_ip=_experimental_proxy_ip,
1115
1119
  restrict_output=_experimental_restrict_output,
1116
1120
  )
1117
1121
 
@@ -1286,12 +1290,12 @@ class _App:
1286
1290
 
1287
1291
  ```python
1288
1292
  app_a = modal.App("a")
1289
- @app_a.function()
1293
+ @app.function()
1290
1294
  def foo():
1291
1295
  ...
1292
1296
 
1293
1297
  app_b = modal.App("b")
1294
- @app_b.function()
1298
+ @app.function()
1295
1299
  def bar():
1296
1300
  ...
1297
1301
 
@@ -497,6 +497,7 @@ class _App:
497
497
  i6pn: typing.Optional[bool] = None,
498
498
  include_source: typing.Optional[bool] = None,
499
499
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
500
+ _experimental_proxy_ip: typing.Optional[str] = None,
500
501
  _experimental_restrict_output: bool = False,
501
502
  max_inputs: typing.Optional[int] = None,
502
503
  ) -> _FunctionDecoratorType:
@@ -544,6 +545,7 @@ class _App:
544
545
  i6pn: typing.Optional[bool] = None,
545
546
  include_source: typing.Optional[bool] = None,
546
547
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
548
+ _experimental_proxy_ip: typing.Optional[str] = None,
547
549
  _experimental_restrict_output: bool = False,
548
550
  max_inputs: typing.Optional[int] = None,
549
551
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
@@ -616,12 +618,12 @@ class _App:
616
618
 
617
619
  ```python
618
620
  app_a = modal.App("a")
619
- @app_a.function()
621
+ @app.function()
620
622
  def foo():
621
623
  ...
622
624
 
623
625
  app_b = modal.App("b")
624
- @app_b.function()
626
+ @app.function()
625
627
  def bar():
626
628
  ...
627
629
 
@@ -1213,6 +1215,7 @@ class App:
1213
1215
  i6pn: typing.Optional[bool] = None,
1214
1216
  include_source: typing.Optional[bool] = None,
1215
1217
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1218
+ _experimental_proxy_ip: typing.Optional[str] = None,
1216
1219
  _experimental_restrict_output: bool = False,
1217
1220
  max_inputs: typing.Optional[int] = None,
1218
1221
  ) -> _FunctionDecoratorType:
@@ -1260,6 +1263,7 @@ class App:
1260
1263
  i6pn: typing.Optional[bool] = None,
1261
1264
  include_source: typing.Optional[bool] = None,
1262
1265
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1266
+ _experimental_proxy_ip: typing.Optional[str] = None,
1263
1267
  _experimental_restrict_output: bool = False,
1264
1268
  max_inputs: typing.Optional[int] = None,
1265
1269
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
@@ -1330,12 +1334,12 @@ class App:
1330
1334
 
1331
1335
  ```python
1332
1336
  app_a = modal.App("a")
1333
- @app_a.function()
1337
+ @app.function()
1334
1338
  def foo():
1335
1339
  ...
1336
1340
 
1337
1341
  app_b = modal.App("b")
1338
- @app_b.function()
1342
+ @app.function()
1339
1343
  def bar():
1340
1344
  ...
1341
1345