modAL 1.3.6.dev14__tar.gz → 1.4.0__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 (209) hide show
  1. {modal-1.3.6.dev14 → modal-1.4.0}/PKG-INFO +1 -1
  2. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_functions.py +0 -3
  3. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/pty.py +5 -0
  4. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/async_utils.py +27 -9
  5. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/grpc_utils.py +2 -3
  6. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/task_command_router_client.py +16 -4
  7. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/app.py +7 -1
  8. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/container.py +11 -1
  9. {modal-1.3.6.dev14 → modal-1.4.0}/modal/client.pyi +2 -10
  10. {modal-1.3.6.dev14 → modal-1.4.0}/modal/functions.pyi +7 -17
  11. {modal-1.3.6.dev14 → modal-1.4.0}/modal/output.py +2 -5
  12. {modal-1.3.6.dev14 → modal-1.4.0}/modal/parallel_map.py +18 -59
  13. {modal-1.3.6.dev14 → modal-1.4.0}/modal/parallel_map.pyi +6 -13
  14. {modal-1.3.6.dev14 → modal-1.4.0}/modal/runner.py +14 -4
  15. {modal-1.3.6.dev14 → modal-1.4.0}/modal/runner.pyi +1 -1
  16. {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox.py +201 -10
  17. {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox.pyi +105 -0
  18. {modal-1.3.6.dev14 → modal-1.4.0}/modal/serving.py +2 -2
  19. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.3.6.dev14 → modal-1.4.0}/modal_version/__init__.py +1 -1
  21. {modal-1.3.6.dev14 → modal-1.4.0}/LICENSE +0 -0
  22. {modal-1.3.6.dev14 → modal-1.4.0}/README.md +0 -0
  23. {modal-1.3.6.dev14 → modal-1.4.0}/modal/__init__.py +0 -0
  24. {modal-1.3.6.dev14 → modal-1.4.0}/modal/__main__.py +0 -0
  25. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_billing.py +0 -0
  26. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_clustered_functions.py +0 -0
  27. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_clustered_functions.pyi +0 -0
  28. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_container_entrypoint.py +0 -0
  29. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_grpc_client.py +0 -0
  30. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_ipython.py +0 -0
  31. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_load_context.py +0 -0
  32. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_location.py +0 -0
  33. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_logs.py +0 -0
  34. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_object.py +0 -0
  35. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/__init__.py +0 -0
  36. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/manager.py +0 -0
  37. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/rich.py +0 -0
  38. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/status.py +0 -0
  39. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_partial_function.py +0 -0
  40. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_resolver.py +0 -0
  41. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_resources.py +0 -0
  42. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/__init__.py +0 -0
  43. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/asgi.py +0 -0
  44. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/container_io_manager.py +0 -0
  45. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/container_io_manager.pyi +0 -0
  46. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/execution_context.py +0 -0
  47. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/execution_context.pyi +0 -0
  48. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  49. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/telemetry.py +0 -0
  50. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/user_code_event_loop.py +0 -0
  51. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/user_code_imports.py +0 -0
  52. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_serialization.py +0 -0
  53. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_server.py +0 -0
  54. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_traceback.py +0 -0
  55. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_tunnel.py +0 -0
  56. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_tunnel.pyi +0 -0
  57. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_type_manager.py +0 -0
  58. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/__init__.py +0 -0
  59. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/app_utils.py +0 -0
  60. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/auth_token_manager.py +0 -0
  61. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/blob_utils.py +0 -0
  62. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/browser_utils.py +0 -0
  63. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  64. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/deprecation.py +0 -0
  65. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/docker_utils.py +0 -0
  66. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/function_utils.py +0 -0
  67. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/git_utils.py +0 -0
  68. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/grpc_testing.py +0 -0
  69. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/hash_utils.py +0 -0
  70. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/http_utils.py +0 -0
  71. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/jwt_utils.py +0 -0
  72. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/logger.py +0 -0
  73. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/mount_utils.py +0 -0
  74. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/name_utils.py +0 -0
  75. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/package_utils.py +0 -0
  76. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/pattern_utils.py +0 -0
  77. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/rand_pb_testing.py +0 -0
  78. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/sandbox_fs_utils.py +0 -0
  79. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/shell_utils.py +0 -0
  80. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/time_utils.py +0 -0
  81. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/__init__.py +0 -0
  82. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  83. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/cloudpickle.py +0 -0
  84. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/tblib.py +0 -0
  85. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/version.py +0 -0
  86. {modal-1.3.6.dev14 → modal-1.4.0}/modal/_watcher.py +0 -0
  87. {modal-1.3.6.dev14 → modal-1.4.0}/modal/app.py +0 -0
  88. {modal-1.3.6.dev14 → modal-1.4.0}/modal/app.pyi +0 -0
  89. {modal-1.3.6.dev14 → modal-1.4.0}/modal/billing.py +0 -0
  90. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2023.12.312.txt +0 -0
  91. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2023.12.txt +0 -0
  92. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2024.04.txt +0 -0
  93. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2024.10.txt +0 -0
  94. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2025.06.txt +0 -0
  95. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/PREVIEW.txt +0 -0
  96. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/README.md +0 -0
  97. {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/base-images.json +0 -0
  98. {modal-1.3.6.dev14 → modal-1.4.0}/modal/call_graph.py +0 -0
  99. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/__init__.py +0 -0
  100. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/_download.py +0 -0
  101. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/_traceback.py +0 -0
  102. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/billing.py +0 -0
  103. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/changelog.py +0 -0
  104. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/cluster.py +0 -0
  105. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/config.py +0 -0
  106. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/dashboard.py +0 -0
  107. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/dict.py +0 -0
  108. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/entry_point.py +0 -0
  109. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/environment.py +0 -0
  110. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/import_refs.py +0 -0
  111. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/launch.py +0 -0
  112. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/network_file_system.py +0 -0
  113. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/profile.py +0 -0
  114. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/__init__.py +0 -0
  115. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/run_jupyter.py +0 -0
  116. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/vscode.py +0 -0
  117. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/queues.py +0 -0
  118. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/run.py +0 -0
  119. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/secret.py +0 -0
  120. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/selector.py +0 -0
  121. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/shell.py +0 -0
  122. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/token.py +0 -0
  123. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/utils.py +0 -0
  124. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/volume.py +0 -0
  125. {modal-1.3.6.dev14 → modal-1.4.0}/modal/client.py +0 -0
  126. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cloud_bucket_mount.py +0 -0
  127. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cloud_bucket_mount.pyi +0 -0
  128. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cls.py +0 -0
  129. {modal-1.3.6.dev14 → modal-1.4.0}/modal/cls.pyi +0 -0
  130. {modal-1.3.6.dev14 → modal-1.4.0}/modal/config.py +0 -0
  131. {modal-1.3.6.dev14 → modal-1.4.0}/modal/container_process.py +0 -0
  132. {modal-1.3.6.dev14 → modal-1.4.0}/modal/container_process.pyi +0 -0
  133. {modal-1.3.6.dev14 → modal-1.4.0}/modal/dict.py +0 -0
  134. {modal-1.3.6.dev14 → modal-1.4.0}/modal/dict.pyi +0 -0
  135. {modal-1.3.6.dev14 → modal-1.4.0}/modal/environments.py +0 -0
  136. {modal-1.3.6.dev14 → modal-1.4.0}/modal/environments.pyi +0 -0
  137. {modal-1.3.6.dev14 → modal-1.4.0}/modal/exception.py +0 -0
  138. {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/__init__.py +0 -0
  139. {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/flash.py +0 -0
  140. {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/flash.pyi +0 -0
  141. {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/ipython.py +0 -0
  142. {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_io.py +0 -0
  143. {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_io.pyi +0 -0
  144. {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_pattern_matcher.py +0 -0
  145. {modal-1.3.6.dev14 → modal-1.4.0}/modal/functions.py +0 -0
  146. {modal-1.3.6.dev14 → modal-1.4.0}/modal/image.py +0 -0
  147. {modal-1.3.6.dev14 → modal-1.4.0}/modal/image.pyi +0 -0
  148. {modal-1.3.6.dev14 → modal-1.4.0}/modal/io_streams.py +0 -0
  149. {modal-1.3.6.dev14 → modal-1.4.0}/modal/io_streams.pyi +0 -0
  150. {modal-1.3.6.dev14 → modal-1.4.0}/modal/mount.py +0 -0
  151. {modal-1.3.6.dev14 → modal-1.4.0}/modal/mount.pyi +0 -0
  152. {modal-1.3.6.dev14 → modal-1.4.0}/modal/network_file_system.py +0 -0
  153. {modal-1.3.6.dev14 → modal-1.4.0}/modal/network_file_system.pyi +0 -0
  154. {modal-1.3.6.dev14 → modal-1.4.0}/modal/object.py +0 -0
  155. {modal-1.3.6.dev14 → modal-1.4.0}/modal/object.pyi +0 -0
  156. {modal-1.3.6.dev14 → modal-1.4.0}/modal/partial_function.py +0 -0
  157. {modal-1.3.6.dev14 → modal-1.4.0}/modal/partial_function.pyi +0 -0
  158. {modal-1.3.6.dev14 → modal-1.4.0}/modal/proxy.py +0 -0
  159. {modal-1.3.6.dev14 → modal-1.4.0}/modal/proxy.pyi +0 -0
  160. {modal-1.3.6.dev14 → modal-1.4.0}/modal/py.typed +0 -0
  161. {modal-1.3.6.dev14 → modal-1.4.0}/modal/queue.py +0 -0
  162. {modal-1.3.6.dev14 → modal-1.4.0}/modal/queue.pyi +0 -0
  163. {modal-1.3.6.dev14 → modal-1.4.0}/modal/retries.py +0 -0
  164. {modal-1.3.6.dev14 → modal-1.4.0}/modal/running_app.py +0 -0
  165. {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox_fs.py +0 -0
  166. {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox_fs.pyi +0 -0
  167. {modal-1.3.6.dev14 → modal-1.4.0}/modal/schedule.py +0 -0
  168. {modal-1.3.6.dev14 → modal-1.4.0}/modal/scheduler_placement.py +0 -0
  169. {modal-1.3.6.dev14 → modal-1.4.0}/modal/secret.py +0 -0
  170. {modal-1.3.6.dev14 → modal-1.4.0}/modal/secret.pyi +0 -0
  171. {modal-1.3.6.dev14 → modal-1.4.0}/modal/server.py +0 -0
  172. {modal-1.3.6.dev14 → modal-1.4.0}/modal/server.pyi +0 -0
  173. {modal-1.3.6.dev14 → modal-1.4.0}/modal/serving.pyi +0 -0
  174. {modal-1.3.6.dev14 → modal-1.4.0}/modal/snapshot.py +0 -0
  175. {modal-1.3.6.dev14 → modal-1.4.0}/modal/snapshot.pyi +0 -0
  176. {modal-1.3.6.dev14 → modal-1.4.0}/modal/stream_type.py +0 -0
  177. {modal-1.3.6.dev14 → modal-1.4.0}/modal/token_flow.py +0 -0
  178. {modal-1.3.6.dev14 → modal-1.4.0}/modal/token_flow.pyi +0 -0
  179. {modal-1.3.6.dev14 → modal-1.4.0}/modal/volume.py +0 -0
  180. {modal-1.3.6.dev14 → modal-1.4.0}/modal/volume.pyi +0 -0
  181. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/SOURCES.txt +0 -0
  182. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/dependency_links.txt +0 -0
  183. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/entry_points.txt +0 -0
  184. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/requires.txt +0 -0
  185. {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/top_level.txt +0 -0
  186. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/__init__.py +0 -0
  187. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_cli_docs.py +0 -0
  188. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_cli_docs_main.py +0 -0
  189. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_reference_docs.py +0 -0
  190. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_reference_docs_main.py +0 -0
  191. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/__init__.py +0 -0
  192. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/mdmd.py +0 -0
  193. {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/signatures.py +0 -0
  194. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/__init__.py +0 -0
  195. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_grpc.py +0 -0
  196. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2.py +0 -0
  197. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2.pyi +0 -0
  198. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2_grpc.py +0 -0
  199. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2_grpc.pyi +0 -0
  200. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/modal_api_grpc.py +0 -0
  201. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/py.typed +0 -0
  202. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_grpc.py +0 -0
  203. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2.py +0 -0
  204. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2.pyi +0 -0
  205. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  206. {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  207. {modal-1.3.6.dev14 → modal-1.4.0}/modal_version/__main__.py +0 -0
  208. {modal-1.3.6.dev14 → modal-1.4.0}/pyproject.toml +0 -0
  209. {modal-1.3.6.dev14 → modal-1.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.6.dev14
3
+ Version: 1.4.0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -1562,7 +1562,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1562
1562
  input_queue: _SynchronizedQueue,
1563
1563
  order_outputs: bool,
1564
1564
  return_exceptions: bool,
1565
- wrap_returned_exceptions: bool,
1566
1565
  ) -> AsyncGenerator[Any, None]:
1567
1566
  """mdmd:hidden
1568
1567
 
@@ -1588,7 +1587,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1588
1587
  self.client,
1589
1588
  order_outputs,
1590
1589
  return_exceptions,
1591
- wrap_returned_exceptions,
1592
1590
  count_update_callback,
1593
1591
  )
1594
1592
  ) as stream:
@@ -1602,7 +1600,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1602
1600
  self.client,
1603
1601
  order_outputs,
1604
1602
  return_exceptions,
1605
- wrap_returned_exceptions,
1606
1603
  count_update_callback,
1607
1604
  api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
1608
1605
  )
@@ -147,6 +147,11 @@ async def get_app_logs_loop(
147
147
  # doesn't support server-side search)
148
148
  if search_text and search_text not in log.data:
149
149
  return
150
+ # Search results may lack trailing newlines; ensure each
151
+ # match is a complete line so the LineBufferedOutput buffer
152
+ # doesn't merge it with the next match.
153
+ if search_text and not log.data.endswith("\n"):
154
+ log.data += "\n"
150
155
  if pty_shell_finish_event:
151
156
  await output_mgr.put_pty_content(log)
152
157
  else:
@@ -349,19 +349,18 @@ def retry(
349
349
  direct_fn=None,
350
350
  *,
351
351
  n_attempts=3,
352
- base_delay=0,
352
+ base_delay=0.0,
353
353
  delay_factor=2,
354
+ max_delay: Optional[float] = None,
354
355
  attempt_timeout: Optional[float] = 90,
355
- attempt_timeout_factor=1,
356
+ total_timeout: Optional[float] = None,
356
357
  ):
357
358
  """Decorator that calls an async function multiple times, with a given timeout.
358
359
 
359
360
  If a `base_delay` is provided, the function is given an exponentially
360
361
  increasing delay on each run, up until the maximum number of attempts.
361
362
 
362
- If `attempt_timeout_factor` is set to a value > 1, the per-attempt timeout
363
- increases exponentially (starting from `attempt_timeout`, multiplied by
364
- `attempt_timeout_factor` after each failed attempt).
363
+ If `max_delay` is set, the delay between attempts is capped at this value.
365
364
 
366
365
  Usage:
367
366
 
@@ -377,6 +376,7 @@ def retry(
377
376
  pass
378
377
  ```
379
378
  """
379
+ ATTEMPT_TIMEOUT_FLOOR = 2.0
380
380
 
381
381
  def decorator(fn):
382
382
  @functools.wraps(fn)
@@ -387,17 +387,35 @@ def retry(
387
387
  local_n_attempts = n_attempts
388
388
 
389
389
  delay = base_delay
390
- current_attempt_timeout = attempt_timeout
390
+ if total_timeout is not None:
391
+ total_deadline = time.time() + total_timeout
392
+ else:
393
+ total_deadline = None
394
+
391
395
  for i in range(local_n_attempts):
392
396
  t0 = time.time()
397
+
398
+ timeouts = []
399
+ if attempt_timeout is not None:
400
+ timeouts.append(attempt_timeout)
401
+ if total_deadline is not None:
402
+ timeouts.append(max(total_deadline - time.time(), ATTEMPT_TIMEOUT_FLOOR))
403
+ timeout = min(timeouts) if timeouts else None
404
+
393
405
  try:
394
- return await asyncio.wait_for(fn(*args, **kwargs), timeout=current_attempt_timeout)
406
+ return await asyncio.wait_for(fn(*args, **kwargs), timeout=timeout)
395
407
  except asyncio.CancelledError:
396
408
  logger.debug(f"Function {fn} was cancelled")
397
409
  raise
398
410
  except Exception as e:
399
411
  if i >= local_n_attempts - 1:
400
412
  raise
413
+
414
+ # We check if the timeout will be reached **after** the sleep, so we can raise an error early
415
+ # without needing to actually sleep.
416
+ if total_deadline is not None and time.time() + delay + ATTEMPT_TIMEOUT_FLOOR >= total_deadline:
417
+ raise
418
+
401
419
  logger.debug(
402
420
  f"Failed invoking function {fn}: {e}"
403
421
  f" (took {time.time() - t0}s, sleeping {delay}s"
@@ -405,8 +423,8 @@ def retry(
405
423
  )
406
424
  await asyncio.sleep(delay)
407
425
  delay *= delay_factor
408
- if current_attempt_timeout is not None:
409
- current_attempt_timeout *= attempt_timeout_factor
426
+ if max_delay is not None:
427
+ delay = min(delay, max_delay)
410
428
 
411
429
  return f_wrapped
412
430
 
@@ -90,8 +90,6 @@ RETRYABLE_GRPC_STATUS_CODES = [
90
90
  ]
91
91
  SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
92
92
  # Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
93
- # Doubles each attempt (attempt_timeout_factor=2): 0.5 + 1 + 2 + 4 + 8 = 15.5s + ~1.5s inter-attempt delays.
94
- CONNECT_TIMEOUT = 0.5
95
93
  DEFAULT_MAX_RETRIES = 3
96
94
 
97
95
 
@@ -225,7 +223,8 @@ def create_channel(
225
223
  return channel
226
224
 
227
225
 
228
- @retry(n_attempts=5, base_delay=0.1, attempt_timeout=CONNECT_TIMEOUT, attempt_timeout_factor=2)
226
+ # With 18 attempts, the max delays between calls is: 0.1 + 0.2 + 0.4 + 0.8 + 1.6 + 3.2 + 5*11 ~ 62
227
+ @retry(n_attempts=18, base_delay=0.1, attempt_timeout=10.0, max_delay=5.0, total_timeout=63.0)
229
228
  async def connect_channel(channel: grpclib.client.Channel):
230
229
  """Connect to socket and raise exceptions when there is a connection issue."""
231
230
  await channel.__connect__()
@@ -11,7 +11,6 @@ from typing import AsyncGenerator, Optional
11
11
 
12
12
  import grpclib.client
13
13
  import grpclib.config
14
- import grpclib.events
15
14
  from grpclib import GRPCError, Status
16
15
  from grpclib.exceptions import StreamTerminatedError
17
16
 
@@ -22,8 +21,21 @@ from modal_proto.task_command_router_grpc import TaskCommandRouterStub
22
21
 
23
22
  from .._grpc_client import grpc_error_converter
24
23
  from .._utils.grpc_utils import PermanentCloseableChannel
25
- from .async_utils import aclosing
26
- from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES, connect_channel
24
+ from .async_utils import aclosing, retry
25
+ from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
26
+
27
+
28
+ @retry(n_attempts=34, base_delay=1, max_delay=10, attempt_timeout=10, total_timeout=310)
29
+ async def _connect_channel(channel: grpclib.client.Channel):
30
+ """Connect to the command router channel.
31
+
32
+ Uses a longer retry budget than grpc_utils.connect_channel. In rare cases the sandbox
33
+ may take a long time to start on the worker after scheduling.
34
+
35
+ Retries with exponential backoff (1, 2, 4, 8, 10, 10, ...) capped at 10s per delay.
36
+ Total sleep between attempts: 1 + 2 + 4 + 8 + 10*29 = 305s (~5 min).
37
+ """
38
+ await channel.__connect__()
27
39
 
28
40
 
29
41
  def _b64url_decode(data: str) -> bytes:
@@ -169,7 +181,7 @@ class TaskCommandRouterClient:
169
181
  closed_error_message="Unable to perform operation on a detached sandbox",
170
182
  )
171
183
 
172
- await connect_channel(channel)
184
+ await _connect_channel(channel)
173
185
  loop = asyncio.get_running_loop()
174
186
  jwt_refresh_lock = asyncio.Lock()
175
187
 
@@ -164,7 +164,7 @@ def logs(
164
164
  Get recent logs based on an app ID:
165
165
 
166
166
  ```
167
- modal app logs ap-123456 --tail 100
167
+ modal app logs ap-123456
168
168
  ```
169
169
 
170
170
  Get recent logs for a currently deployed App based on its name:
@@ -179,6 +179,12 @@ def logs(
179
179
  modal app logs my-app -f
180
180
  ```
181
181
 
182
+ Fetch the last 1000 entries:
183
+
184
+ ```
185
+ modal app logs my-app --tail 1000
186
+ ```
187
+
182
188
  Fetch logs from the last 2 hours:
183
189
 
184
190
  ```
@@ -27,7 +27,11 @@ container_cli = typer.Typer(name="container", help="Manage and connect to runnin
27
27
 
28
28
  @container_cli.command("list")
29
29
  @synchronizer.create_blocking
30
- async def list_(env: Optional[str] = ENV_OPTION, json: bool = False, app_id: str = ""):
30
+ async def list_(
31
+ app_id: str = typer.Option("", "--app-id", help="List containers running for a specific App."),
32
+ env: Optional[str] = ENV_OPTION,
33
+ json: bool = False,
34
+ ):
31
35
  """List all containers that are currently running."""
32
36
  env = ensure_env(env)
33
37
  client = await _Client.from_env()
@@ -113,6 +117,12 @@ async def logs(
113
117
  modal container logs ta-123456 --since 2026-03-01T05:00:00 --until 2026-03-01T08:00:00
114
118
  ```
115
119
 
120
+ Fetch the last 1000 entries:
121
+
122
+ ```
123
+ modal container logs ta-123456 --tail 1000
124
+ ```
125
+
116
126
  Fetch all container logs:
117
127
 
118
128
  ```
@@ -31,11 +31,7 @@ class _Client:
31
31
  client_type: int
32
32
 
33
33
  def __init__(
34
- self,
35
- server_url: str,
36
- client_type: int,
37
- credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.3.6.dev14",
34
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.4.0"
39
35
  ):
40
36
  """mdmd:hidden
41
37
  The Modal client object is not intended to be instantiated directly by users.
@@ -167,11 +163,7 @@ class Client:
167
163
  client_type: int
168
164
 
169
165
  def __init__(
170
- self,
171
- server_url: str,
172
- client_type: int,
173
- credentials: typing.Optional[tuple[str, str]],
174
- version: str = "1.3.6.dev14",
166
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.4.0"
175
167
  ):
176
168
  """mdmd:hidden
177
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -291,12 +291,7 @@ class Function(
291
291
 
292
292
  class ___map_spec(typing_extensions.Protocol):
293
293
  def __call__(
294
- self,
295
- /,
296
- input_queue: modal.parallel_map.SynchronizedQueue,
297
- order_outputs: bool,
298
- return_exceptions: bool,
299
- wrap_returned_exceptions: bool,
294
+ self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
300
295
  ) -> typing.Generator[typing.Any, None, None]:
301
296
  """mdmd:hidden
302
297
 
@@ -310,12 +305,7 @@ class Function(
310
305
  ...
311
306
 
312
307
  def aio(
313
- self,
314
- /,
315
- input_queue: modal.parallel_map.SynchronizedQueue,
316
- order_outputs: bool,
317
- return_exceptions: bool,
318
- wrap_returned_exceptions: bool,
308
+ self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
319
309
  ) -> collections.abc.AsyncGenerator[typing.Any, None]:
320
310
  """mdmd:hidden
321
311
 
@@ -482,7 +472,7 @@ class Function(
482
472
  kwargs={},
483
473
  order_outputs: bool = True,
484
474
  return_exceptions: bool = False,
485
- wrap_returned_exceptions: bool = True,
475
+ wrap_returned_exceptions: typing.Optional[bool] = None,
486
476
  ) -> modal._utils.async_utils.AsyncOrSyncIterable:
487
477
  """Parallel map over a set of inputs.
488
478
 
@@ -516,7 +506,7 @@ class Function(
516
506
 
517
507
  @app.local_entrypoint()
518
508
  def main():
519
- # [0, 1, UserCodeException(Exception('ohno'))]
509
+ # [0, 1, Exception('ohno')]
520
510
  print(list(my_func.map(range(3), return_exceptions=True)))
521
511
  ```
522
512
  """
@@ -529,7 +519,7 @@ class Function(
529
519
  kwargs={},
530
520
  order_outputs: bool = True,
531
521
  return_exceptions: bool = False,
532
- wrap_returned_exceptions: bool = True,
522
+ wrap_returned_exceptions: typing.Optional[bool] = None,
533
523
  ) -> typing.AsyncGenerator[typing.Any, None]: ...
534
524
 
535
525
  map: __map_spec
@@ -543,7 +533,7 @@ class Function(
543
533
  kwargs={},
544
534
  order_outputs: bool = True,
545
535
  return_exceptions: bool = False,
546
- wrap_returned_exceptions: bool = True,
536
+ wrap_returned_exceptions: typing.Optional[bool] = None,
547
537
  ) -> modal._utils.async_utils.AsyncOrSyncIterable:
548
538
  """Like `map`, but spreads arguments over multiple function arguments.
549
539
 
@@ -573,7 +563,7 @@ class Function(
573
563
  kwargs={},
574
564
  order_outputs: bool = True,
575
565
  return_exceptions: bool = False,
576
- wrap_returned_exceptions: bool = True,
566
+ wrap_returned_exceptions: typing.Optional[bool] = None,
577
567
  ) -> typing.AsyncIterable[typing.Any]: ...
578
568
 
579
569
  starmap: __starmap_spec
@@ -16,7 +16,7 @@ __all__ = ["enable_output", "OutputManager"]
16
16
 
17
17
 
18
18
  @contextlib.contextmanager
19
- def enable_output() -> Generator[OutputManager, None, None]:
19
+ def enable_output() -> Generator[None, None, None]:
20
20
  """Context manager that enable output when using the Python SDK.
21
21
 
22
22
  This will print to stdout and stderr things such as
@@ -31,9 +31,6 @@ def enable_output() -> Generator[OutputManager, None, None]:
31
31
  with app.run():
32
32
  ...
33
33
  ```
34
-
35
- To suppress progress indicators, use `output_manager.set_quiet_mode(True)`.
36
- To enable timestamps, use `output_manager.set_timestamps(True)`.
37
34
  """
38
35
  previous_output_manager = OutputManager.get()
39
36
 
@@ -42,6 +39,6 @@ def enable_output() -> Generator[OutputManager, None, None]:
42
39
  new_manager = RichOutputManager()
43
40
  OutputManager._set(new_manager)
44
41
  try:
45
- yield new_manager
42
+ yield
46
43
  finally:
47
44
  OutputManager._set(previous_output_manager)
@@ -1,7 +1,6 @@
1
1
  # Copyright Modal Labs 2024
2
2
  import asyncio
3
3
  import enum
4
- import inspect
5
4
  import time
6
5
  import typing
7
6
  from asyncio import FIRST_COMPLETED
@@ -10,7 +9,6 @@ from typing import Any, Callable, Optional, Union
10
9
 
11
10
  from grpclib import Status
12
11
 
13
- import modal.exception
14
12
  from modal._runtime.execution_context import current_input_id
15
13
  from modal._utils.async_utils import (
16
14
  AsyncOrSyncIterable,
@@ -365,7 +363,6 @@ async def _map_invocation(
365
363
  client: "modal.client._Client",
366
364
  order_outputs: bool,
367
365
  return_exceptions: bool,
368
- wrap_returned_exceptions: bool,
369
366
  count_update_callback: Optional[Callable[[int, int], None]],
370
367
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
371
368
  ):
@@ -545,13 +542,7 @@ async def _map_invocation(
545
542
  output = await _process_result(item.result, item.data_format, client.stub, client)
546
543
  except Exception as e:
547
544
  if return_exceptions:
548
- if wrap_returned_exceptions:
549
- # Prior to client 1.0.4 there was a bug where return_exceptions would wrap
550
- # any returned exceptions in a synchronicity.UserCodeException. This adds
551
- # deprecated non-breaking compatibility bandaid for migrating away from that:
552
- output = modal.exception.UserCodeException(e)
553
- else:
554
- output = e
545
+ output = e
555
546
  else:
556
547
  raise e
557
548
  return (item.idx, output)
@@ -631,7 +622,6 @@ async def _map_invocation_inputplane(
631
622
  client: "modal.client._Client",
632
623
  order_outputs: bool,
633
624
  return_exceptions: bool,
634
- wrap_returned_exceptions: bool,
635
625
  count_update_callback: Optional[Callable[[int, int], None]],
636
626
  ) -> typing.AsyncGenerator[Any, None]:
637
627
  """Input-plane implementation of a function map invocation.
@@ -916,13 +906,7 @@ async def _map_invocation_inputplane(
916
906
  output = await _process_result(item.result, item.data_format, input_plane_stub, client)
917
907
  except Exception as e:
918
908
  if return_exceptions:
919
- if wrap_returned_exceptions:
920
- # Prior to client 1.0.4 there was a bug where return_exceptions would wrap
921
- # any returned exceptions in a synchronicity.UserCodeException. This adds
922
- # deprecated non-breaking compatibility bandaid for migrating away from that:
923
- output = modal.exception.UserCodeException(e)
924
- else:
925
- output = e
909
+ output = e
926
910
  else:
927
911
  raise e
928
912
  return (item.idx, output)
@@ -992,7 +976,6 @@ async def _map_helper(
992
976
  kwargs={}, # any extra keyword arguments for the function
993
977
  order_outputs: bool = True, # return outputs in order
994
978
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
995
- wrap_returned_exceptions: bool = True,
996
979
  ) -> typing.AsyncGenerator[Any, None]:
997
980
  """Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
998
981
 
@@ -1022,41 +1005,24 @@ async def _map_helper(
1022
1005
  # synchronicity-wrapped, since they accept executable code in the form of iterators that we don't want to run inside
1023
1006
  # the synchronicity thread. Instead, we delegate to `._map()` with a safer Queue as input.
1024
1007
  async with aclosing(
1025
- async_merge(
1026
- self._map.aio(raw_input_queue, order_outputs, return_exceptions, wrap_returned_exceptions), feed_queue()
1027
- )
1008
+ async_merge(self._map.aio(raw_input_queue, order_outputs, return_exceptions), feed_queue())
1028
1009
  ) as map_output_stream:
1029
1010
  async for output in map_output_stream:
1030
1011
  yield output
1031
1012
 
1032
1013
 
1033
- def _maybe_warn_about_exceptions(func_name: str, return_exceptions: bool, wrap_returned_exceptions: bool):
1034
- if return_exceptions and wrap_returned_exceptions:
1014
+ def _maybe_warn_about_wrap_exceptions(func_name: str, wrap_returned_exceptions: Optional[bool]):
1015
+ if wrap_returned_exceptions is not None:
1035
1016
  deprecation_warning(
1036
- (2025, 6, 27),
1017
+ (2026, 3, 24),
1037
1018
  (
1038
- f"Function.{func_name} currently leaks an internal exception wrapping type "
1039
- "(modal.exceptions.UserCodeException) when `return_exceptions=True` is set. "
1040
- "In the future, this will change, and the underlying exception will be returned directly.\n"
1041
- "To opt into the future behavior and silence this warning, add `wrap_returned_exceptions=False`:\n\n"
1042
- f" f.{func_name}(..., return_exceptions=True, wrap_returned_exceptions=False)"
1019
+ f"The `wrap_returned_exceptions` parameter of `Function.{func_name}` no longer has any "
1020
+ "effect and can be removed from your code. Exceptions are no longer wrapped before being "
1021
+ f"returned from `Function.{func_name}`. "
1043
1022
  ),
1044
1023
  )
1045
1024
 
1046
1025
 
1047
- def _invoked_from_sync_wrapper() -> bool:
1048
- """Check whether the calling function was called from a sync wrapper."""
1049
- # This is temporary: we only need it to avoind double-firing the wrap_returned_exceptions warning.
1050
- # (We don't want to push the warning lower in the stack beacuse then we can't attribute to the user's code.)
1051
- try:
1052
- frame = inspect.currentframe()
1053
- caller_function_name = frame.f_back.f_back.f_code.co_name
1054
- # Embeds some assumptions about how the current calling stack works, but this is just temporary.
1055
- return caller_function_name == "asend"
1056
- except Exception:
1057
- return False
1058
-
1059
-
1060
1026
  @warn_if_generator_is_not_consumed(function_name="Function.map.aio")
1061
1027
  async def _map_async(
1062
1028
  self: "modal.functions.Function",
@@ -1066,10 +1032,9 @@ async def _map_async(
1066
1032
  kwargs={}, # any extra keyword arguments for the function
1067
1033
  order_outputs: bool = True, # return outputs in order
1068
1034
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
1069
- wrap_returned_exceptions: bool = True, # wrap returned exceptions in modal.exception.UserCodeException
1035
+ wrap_returned_exceptions: Optional[bool] = None,
1070
1036
  ) -> typing.AsyncGenerator[Any, None]:
1071
- if not _invoked_from_sync_wrapper():
1072
- _maybe_warn_about_exceptions("map.aio", return_exceptions, wrap_returned_exceptions)
1037
+ _maybe_warn_about_wrap_exceptions("map.aio", wrap_returned_exceptions)
1073
1038
  async_input_gen = async_zip(*[sync_or_async_iter(it) for it in input_iterators])
1074
1039
  async for output in _map_helper(
1075
1040
  self,
@@ -1077,7 +1042,6 @@ async def _map_async(
1077
1042
  kwargs=kwargs,
1078
1043
  order_outputs=order_outputs,
1079
1044
  return_exceptions=return_exceptions,
1080
- wrap_returned_exceptions=wrap_returned_exceptions,
1081
1045
  ):
1082
1046
  yield output
1083
1047
 
@@ -1090,17 +1054,15 @@ async def _starmap_async(
1090
1054
  kwargs={},
1091
1055
  order_outputs: bool = True,
1092
1056
  return_exceptions: bool = False,
1093
- wrap_returned_exceptions: bool = True,
1057
+ wrap_returned_exceptions: Optional[bool] = None,
1094
1058
  ) -> typing.AsyncIterable[Any]:
1095
- if not _invoked_from_sync_wrapper():
1096
- _maybe_warn_about_exceptions("starmap.aio", return_exceptions, wrap_returned_exceptions)
1059
+ _maybe_warn_about_wrap_exceptions("starmap.aio", wrap_returned_exceptions)
1097
1060
  async for output in _map_helper(
1098
1061
  self,
1099
1062
  sync_or_async_iter(input_iterator),
1100
1063
  kwargs=kwargs,
1101
1064
  order_outputs=order_outputs,
1102
1065
  return_exceptions=return_exceptions,
1103
- wrap_returned_exceptions=wrap_returned_exceptions,
1104
1066
  ):
1105
1067
  yield output
1106
1068
 
@@ -1115,7 +1077,6 @@ async def _for_each_async(self, *input_iterators, kwargs={}, ignore_exceptions:
1115
1077
  kwargs=kwargs,
1116
1078
  order_outputs=False,
1117
1079
  return_exceptions=ignore_exceptions,
1118
- wrap_returned_exceptions=False,
1119
1080
  ):
1120
1081
  pass
1121
1082
 
@@ -1127,7 +1088,7 @@ def _map_sync(
1127
1088
  kwargs={}, # any extra keyword arguments for the function
1128
1089
  order_outputs: bool = True, # return outputs in order
1129
1090
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
1130
- wrap_returned_exceptions: bool = True,
1091
+ wrap_returned_exceptions: Optional[bool] = None,
1131
1092
  ) -> AsyncOrSyncIterable:
1132
1093
  """Parallel map over a set of inputs.
1133
1094
 
@@ -1161,11 +1122,11 @@ def _map_sync(
1161
1122
 
1162
1123
  @app.local_entrypoint()
1163
1124
  def main():
1164
- # [0, 1, UserCodeException(Exception('ohno'))]
1125
+ # [0, 1, Exception('ohno')]
1165
1126
  print(list(my_func.map(range(3), return_exceptions=True)))
1166
1127
  ```
1167
1128
  """
1168
- _maybe_warn_about_exceptions("map", return_exceptions, wrap_returned_exceptions)
1129
+ _maybe_warn_about_wrap_exceptions("map", wrap_returned_exceptions)
1169
1130
 
1170
1131
  return AsyncOrSyncIterable(
1171
1132
  _map_async(
@@ -1174,7 +1135,6 @@ def _map_sync(
1174
1135
  kwargs=kwargs,
1175
1136
  order_outputs=order_outputs,
1176
1137
  return_exceptions=return_exceptions,
1177
- wrap_returned_exceptions=wrap_returned_exceptions,
1178
1138
  ),
1179
1139
  nested_async_message=(
1180
1140
  "You can't iter(Function.map()) from an async function. Use async for ... in Function.map.aio() instead."
@@ -1304,7 +1264,7 @@ def _starmap_sync(
1304
1264
  kwargs={},
1305
1265
  order_outputs: bool = True,
1306
1266
  return_exceptions: bool = False,
1307
- wrap_returned_exceptions: bool = True,
1267
+ wrap_returned_exceptions: Optional[bool] = None,
1308
1268
  ) -> AsyncOrSyncIterable:
1309
1269
  """Like `map`, but spreads arguments over multiple function arguments.
1310
1270
 
@@ -1322,7 +1282,7 @@ def _starmap_sync(
1322
1282
  assert list(my_func.starmap([(1, 2), (3, 4)])) == [3, 7]
1323
1283
  ```
1324
1284
  """
1325
- _maybe_warn_about_exceptions("starmap", return_exceptions, wrap_returned_exceptions)
1285
+ _maybe_warn_about_wrap_exceptions("starmap", wrap_returned_exceptions)
1326
1286
  return AsyncOrSyncIterable(
1327
1287
  _starmap_async(
1328
1288
  self,
@@ -1330,7 +1290,6 @@ def _starmap_sync(
1330
1290
  kwargs=kwargs,
1331
1291
  order_outputs=order_outputs,
1332
1292
  return_exceptions=return_exceptions,
1333
- wrap_returned_exceptions=wrap_returned_exceptions,
1334
1293
  ),
1335
1294
  nested_async_message=(
1336
1295
  "You can't `iter(Function.starmap())` from an async function. "
@@ -140,7 +140,6 @@ def _map_invocation(
140
140
  client: modal.client._Client,
141
141
  order_outputs: bool,
142
142
  return_exceptions: bool,
143
- wrap_returned_exceptions: bool,
144
143
  count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
145
144
  function_call_invocation_type: int,
146
145
  ): ...
@@ -150,7 +149,6 @@ def _map_invocation_inputplane(
150
149
  client: modal.client._Client,
151
150
  order_outputs: bool,
152
151
  return_exceptions: bool,
153
- wrap_returned_exceptions: bool,
154
152
  count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
155
153
  ) -> typing.AsyncGenerator[typing.Any, None]:
156
154
  """Input-plane implementation of a function map invocation.
@@ -167,7 +165,6 @@ def _map_helper(
167
165
  kwargs={},
168
166
  order_outputs: bool = True,
169
167
  return_exceptions: bool = False,
170
- wrap_returned_exceptions: bool = True,
171
168
  ) -> typing.AsyncGenerator[typing.Any, None]:
172
169
  """Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
173
170
 
@@ -182,18 +179,14 @@ def _map_helper(
182
179
  """
183
180
  ...
184
181
 
185
- def _maybe_warn_about_exceptions(func_name: str, return_exceptions: bool, wrap_returned_exceptions: bool): ...
186
- def _invoked_from_sync_wrapper() -> bool:
187
- """Check whether the calling function was called from a sync wrapper."""
188
- ...
189
-
182
+ def _maybe_warn_about_wrap_exceptions(func_name: str, wrap_returned_exceptions: typing.Optional[bool]): ...
190
183
  def _map_async(
191
184
  self: modal.functions.Function,
192
185
  *input_iterators: typing.Union[typing.Iterable[typing.Any], typing.AsyncIterable[typing.Any]],
193
186
  kwargs={},
194
187
  order_outputs: bool = True,
195
188
  return_exceptions: bool = False,
196
- wrap_returned_exceptions: bool = True,
189
+ wrap_returned_exceptions: typing.Optional[bool] = None,
197
190
  ) -> typing.AsyncGenerator[typing.Any, None]: ...
198
191
  def _starmap_async(
199
192
  self,
@@ -204,7 +197,7 @@ def _starmap_async(
204
197
  kwargs={},
205
198
  order_outputs: bool = True,
206
199
  return_exceptions: bool = False,
207
- wrap_returned_exceptions: bool = True,
200
+ wrap_returned_exceptions: typing.Optional[bool] = None,
208
201
  ) -> typing.AsyncIterable[typing.Any]: ...
209
202
  async def _for_each_async(self, *input_iterators, kwargs={}, ignore_exceptions: bool = False) -> None: ...
210
203
  def _map_sync(
@@ -213,7 +206,7 @@ def _map_sync(
213
206
  kwargs={},
214
207
  order_outputs: bool = True,
215
208
  return_exceptions: bool = False,
216
- wrap_returned_exceptions: bool = True,
209
+ wrap_returned_exceptions: typing.Optional[bool] = None,
217
210
  ) -> modal._utils.async_utils.AsyncOrSyncIterable:
218
211
  """Parallel map over a set of inputs.
219
212
 
@@ -247,7 +240,7 @@ def _map_sync(
247
240
 
248
241
  @app.local_entrypoint()
249
242
  def main():
250
- # [0, 1, UserCodeException(Exception('ohno'))]
243
+ # [0, 1, Exception('ohno')]
251
244
  print(list(my_func.map(range(3), return_exceptions=True)))
252
245
  ```
253
246
  """
@@ -323,7 +316,7 @@ def _starmap_sync(
323
316
  kwargs={},
324
317
  order_outputs: bool = True,
325
318
  return_exceptions: bool = False,
326
- wrap_returned_exceptions: bool = True,
319
+ wrap_returned_exceptions: typing.Optional[bool] = None,
327
320
  ) -> modal._utils.async_utils.AsyncOrSyncIterable:
328
321
  """Like `map`, but spreads arguments over multiple function arguments.
329
322