modAL 1.6.2__tar.gz → 1.6.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 (250) hide show
  1. {modal-1.6.2 → modal-1.6.2.dev0}/PKG-INFO +3 -4
  2. {modal-1.6.2 → modal-1.6.2.dev0}/README.md +1 -1
  3. {modal-1.6.2 → modal-1.6.2.dev0}/modal/__init__.py +3 -3
  4. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_environments.py +2 -2
  5. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_functions.py +2 -148
  6. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_image.py +2 -2
  7. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_logs.py +54 -82
  8. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_outbound_policy.py +3 -3
  9. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_outbound_policy.pyi +2 -2
  10. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_output/status.py +7 -10
  11. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_server.py +0 -185
  12. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_server.pyi +0 -89
  13. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/async_utils.py +1 -7
  14. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/blob_utils.py +0 -2
  15. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/name_utils.py +7 -9
  16. modal-1.6.2/modal/sandbox/_task_command_router_client.py → modal-1.6.2.dev0/modal/_utils/task_command_router_client.py +46 -103
  17. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/time_utils.py +39 -22
  18. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_workspace.py +3 -183
  19. {modal-1.6.2 → modal-1.6.2.dev0}/modal/app.py +1 -1
  20. {modal-1.6.2 → modal-1.6.2.dev0}/modal/app.pyi +2 -2
  21. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/2025.06.txt +4 -8
  22. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/PREVIEW.txt +2 -4
  23. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/base-images.json +2 -4
  24. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/_logs.py +5 -33
  25. modal-1.6.2.dev0/modal/cli/_stats.py +141 -0
  26. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/app.py +5 -5
  27. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/container.py +3 -5
  28. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/dict.py +7 -3
  29. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/endpoint.py +1 -6
  30. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/environment.py +3 -3
  31. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/function.py +34 -477
  32. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/import_refs.py +13 -93
  33. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/network_file_system.py +4 -2
  34. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/queues.py +7 -3
  35. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/run.py +2 -55
  36. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/secret.py +4 -2
  37. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/server.py +27 -493
  38. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/shell.py +2 -8
  39. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/utils.py +1 -29
  40. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/volume.py +7 -3
  41. modal-1.6.2.dev0/modal/cli/workspace.py +269 -0
  42. {modal-1.6.2 → modal-1.6.2.dev0}/modal/client.pyi +2 -2
  43. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cls.py +1 -1
  44. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cls.pyi +1 -1
  45. {modal-1.6.2 → modal-1.6.2.dev0}/modal/config.py +0 -4
  46. {modal-1.6.2 → modal-1.6.2.dev0}/modal/container_process.py +1 -1
  47. {modal-1.6.2 → modal-1.6.2.dev0}/modal/container_process.pyi +3 -3
  48. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/__init__.py +0 -66
  49. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/_flash_container_entry.py +0 -1
  50. {modal-1.6.2 → modal-1.6.2.dev0}/modal/functions.py +1 -2
  51. {modal-1.6.2 → modal-1.6.2.dev0}/modal/functions.pyi +0 -155
  52. {modal-1.6.2 → modal-1.6.2.dev0}/modal/io_streams.py +1 -1
  53. {modal-1.6.2 → modal-1.6.2.dev0}/modal/io_streams.pyi +9 -9
  54. {modal-1.6.2 → modal-1.6.2.dev0}/modal/mount.py +1 -14
  55. {modal-1.6.2 → modal-1.6.2.dev0}/modal/mount.pyi +3 -3
  56. {modal-1.6.2 → modal-1.6.2.dev0}/modal/queue.py +1 -1
  57. {modal-1.6.2 → modal-1.6.2.dev0}/modal/queue.pyi +2 -2
  58. {modal-1.6.2 → modal-1.6.2.dev0}/modal/runner.py +2 -2
  59. modal-1.6.2/modal/sandbox/_sandbox.py → modal-1.6.2.dev0/modal/sandbox.py +897 -94
  60. {modal-1.6.2/modal/sandbox → modal-1.6.2.dev0/modal}/sandbox.pyi +1238 -691
  61. modal-1.6.2/modal/sandbox/_filesystem.py → modal-1.6.2.dev0/modal/sandbox_fs.py +28 -35
  62. modal-1.6.2.dev0/modal/sandbox_fs.pyi +1087 -0
  63. {modal-1.6.2 → modal-1.6.2.dev0}/modal/secret.py +4 -13
  64. {modal-1.6.2 → modal-1.6.2.dev0}/modal/secret.pyi +0 -4
  65. modal-1.6.2.dev0/modal/server.py +5 -0
  66. {modal-1.6.2 → modal-1.6.2.dev0}/modal/server.pyi +0 -170
  67. modal-1.6.2/modal/sandbox/_snapshot.py → modal-1.6.2.dev0/modal/snapshot.py +8 -5
  68. modal-1.6.2.dev0/modal/snapshot.pyi +85 -0
  69. {modal-1.6.2 → modal-1.6.2.dev0}/modal/types.py +1 -90
  70. {modal-1.6.2 → modal-1.6.2.dev0}/modal/volume.py +0 -10
  71. {modal-1.6.2 → modal-1.6.2.dev0}/modal/volume.pyi +0 -3
  72. {modal-1.6.2 → modal-1.6.2.dev0}/modal/workspace.py +0 -3
  73. {modal-1.6.2 → modal-1.6.2.dev0}/modal/workspace.pyi +1 -239
  74. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/PKG-INFO +3 -4
  75. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/SOURCES.txt +7 -11
  76. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/requires.txt +0 -3
  77. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/api_grpc.py +16 -224
  78. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/api_pb2.py +1273 -1774
  79. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/api_pb2.pyi +231 -1385
  80. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/api_pb2_grpc.py +35 -466
  81. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/api_pb2_grpc.pyi +16 -150
  82. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/modal_api_grpc.py +1 -14
  83. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/task_command_router_grpc.py +5 -5
  84. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/task_command_router_pb2.py +49 -49
  85. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/task_command_router_pb2.pyi +22 -22
  86. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +7 -8
  87. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +6 -10
  88. {modal-1.6.2 → modal-1.6.2.dev0}/modal_version/__init__.py +1 -1
  89. {modal-1.6.2 → modal-1.6.2.dev0}/pyproject.toml +5 -13
  90. modal-1.6.2/modal/_metrics.py +0 -212
  91. modal-1.6.2/modal/_metrics.pyi +0 -312
  92. modal-1.6.2/modal/cli/_stats.py +0 -334
  93. modal-1.6.2/modal/cli/workspace.py +0 -641
  94. modal-1.6.2/modal/sandbox/__init__.py +0 -9
  95. modal-1.6.2/modal/sandbox/_common.py +0 -248
  96. modal-1.6.2/modal/sandbox/_sidecar.py +0 -646
  97. modal-1.6.2/modal/sandbox/sandbox.py +0 -11
  98. modal-1.6.2/modal/server.py +0 -6
  99. {modal-1.6.2 → modal-1.6.2.dev0}/LICENSE +0 -0
  100. {modal-1.6.2 → modal-1.6.2.dev0}/modal/__main__.py +0 -0
  101. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_billing.py +0 -0
  102. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_cluster.py +0 -0
  103. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_cluster.pyi +0 -0
  104. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_container_entrypoint.py +0 -0
  105. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_function_variants.py +0 -0
  106. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_grpc_client.py +0 -0
  107. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_ipython.py +0 -0
  108. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_load_context.py +0 -0
  109. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_location.py +0 -0
  110. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_logs_manager.py +0 -0
  111. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_logs_manager.pyi +0 -0
  112. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_object.py +0 -0
  113. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_output/__init__.py +0 -0
  114. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_output/manager.py +0 -0
  115. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_output/pty.py +0 -0
  116. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_output/rich.py +0 -0
  117. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_partial_function.py +0 -0
  118. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_resolver.py +0 -0
  119. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_resources.py +0 -0
  120. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/__init__.py +0 -0
  121. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/asgi.py +0 -0
  122. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
  123. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  124. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/execution_context.py +0 -0
  125. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
  126. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  127. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
  128. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  129. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/telemetry.py +0 -0
  130. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  131. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
  132. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_serialization.py +0 -0
  133. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_supports_logs.py +0 -0
  134. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_traceback.py +0 -0
  135. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_tunnel.py +0 -0
  136. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_tunnel.pyi +0 -0
  137. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_type_manager.py +0 -0
  138. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/__init__.py +0 -0
  139. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/app_utils.py +0 -0
  140. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
  141. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/browser_utils.py +0 -0
  142. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  143. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/curl_utils.py +0 -0
  144. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/deprecation.py +0 -0
  145. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/display_utils.py +0 -0
  146. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/docker_utils.py +0 -0
  147. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/function_utils.py +0 -0
  148. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/git_utils.py +0 -0
  149. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/grpc_testing.py +0 -0
  150. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/grpc_utils.py +0 -0
  151. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/hash_utils.py +0 -0
  152. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/http_utils.py +0 -0
  153. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/idle_countdown.py +0 -0
  154. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/jwt_utils.py +0 -0
  155. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/logger.py +0 -0
  156. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/mount_utils.py +0 -0
  157. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/package_utils.py +0 -0
  158. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/pattern_utils.py +0 -0
  159. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/proxy_support.py +0 -0
  160. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  161. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
  162. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_utils/shell_utils.py +0 -0
  163. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_vendor/__init__.py +0 -0
  164. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  165. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
  166. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_vendor/tblib.py +0 -0
  167. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_vendor/version.py +0 -0
  168. {modal-1.6.2 → modal-1.6.2.dev0}/modal/_watcher.py +0 -0
  169. {modal-1.6.2 → modal-1.6.2.dev0}/modal/billing.py +0 -0
  170. {modal-1.6.2 → modal-1.6.2.dev0}/modal/billing.pyi +0 -0
  171. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/2023.12.312.txt +0 -0
  172. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/2023.12.txt +0 -0
  173. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/2024.04.txt +0 -0
  174. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/2024.10.txt +0 -0
  175. {modal-1.6.2 → modal-1.6.2.dev0}/modal/builder/README.md +0 -0
  176. {modal-1.6.2 → modal-1.6.2.dev0}/modal/call_graph.py +0 -0
  177. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/__init__.py +0 -0
  178. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/_download.py +0 -0
  179. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/_help.py +0 -0
  180. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/_traceback.py +0 -0
  181. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/billing.py +0 -0
  182. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/changelog.py +0 -0
  183. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/config.py +0 -0
  184. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/curl.py +0 -0
  185. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/dashboard.py +0 -0
  186. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/entry_point.py +0 -0
  187. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/image.py +0 -0
  188. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/logo.py +0 -0
  189. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/profile.py +0 -0
  190. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/selector.py +0 -0
  191. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/skills.py +0 -0
  192. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cli/token.py +0 -0
  193. {modal-1.6.2 → modal-1.6.2.dev0}/modal/client.py +0 -0
  194. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cloud_bucket_mount.py +0 -0
  195. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  196. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cluster.py +0 -0
  197. {modal-1.6.2 → modal-1.6.2.dev0}/modal/cluster.pyi +0 -0
  198. {modal-1.6.2 → modal-1.6.2.dev0}/modal/dict.py +0 -0
  199. {modal-1.6.2 → modal-1.6.2.dev0}/modal/dict.pyi +0 -0
  200. {modal-1.6.2 → modal-1.6.2.dev0}/modal/environments.py +0 -0
  201. {modal-1.6.2 → modal-1.6.2.dev0}/modal/environments.pyi +0 -0
  202. {modal-1.6.2 → modal-1.6.2.dev0}/modal/exception.py +0 -0
  203. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/flash.py +0 -0
  204. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/flash.pyi +0 -0
  205. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/flash_server.py +0 -0
  206. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/flash_server.pyi +0 -0
  207. {modal-1.6.2 → modal-1.6.2.dev0}/modal/experimental/ipython.py +0 -0
  208. {modal-1.6.2 → modal-1.6.2.dev0}/modal/file_pattern_matcher.py +0 -0
  209. {modal-1.6.2 → modal-1.6.2.dev0}/modal/image.py +0 -0
  210. {modal-1.6.2 → modal-1.6.2.dev0}/modal/image.pyi +0 -0
  211. {modal-1.6.2 → modal-1.6.2.dev0}/modal/network_file_system.py +0 -0
  212. {modal-1.6.2 → modal-1.6.2.dev0}/modal/network_file_system.pyi +0 -0
  213. {modal-1.6.2 → modal-1.6.2.dev0}/modal/object.py +0 -0
  214. {modal-1.6.2 → modal-1.6.2.dev0}/modal/object.pyi +0 -0
  215. {modal-1.6.2 → modal-1.6.2.dev0}/modal/output.py +0 -0
  216. {modal-1.6.2 → modal-1.6.2.dev0}/modal/parallel_map.py +0 -0
  217. {modal-1.6.2 → modal-1.6.2.dev0}/modal/parallel_map.pyi +0 -0
  218. {modal-1.6.2 → modal-1.6.2.dev0}/modal/partial_function.py +0 -0
  219. {modal-1.6.2 → modal-1.6.2.dev0}/modal/partial_function.pyi +0 -0
  220. {modal-1.6.2 → modal-1.6.2.dev0}/modal/proxy.py +0 -0
  221. {modal-1.6.2 → modal-1.6.2.dev0}/modal/proxy.pyi +0 -0
  222. {modal-1.6.2 → modal-1.6.2.dev0}/modal/py.typed +0 -0
  223. {modal-1.6.2 → modal-1.6.2.dev0}/modal/retries.py +0 -0
  224. {modal-1.6.2 → modal-1.6.2.dev0}/modal/runner.pyi +0 -0
  225. {modal-1.6.2 → modal-1.6.2.dev0}/modal/running_app.py +0 -0
  226. {modal-1.6.2 → modal-1.6.2.dev0}/modal/schedule.py +0 -0
  227. {modal-1.6.2 → modal-1.6.2.dev0}/modal/scheduler_placement.py +0 -0
  228. {modal-1.6.2 → modal-1.6.2.dev0}/modal/serving.py +0 -0
  229. {modal-1.6.2 → modal-1.6.2.dev0}/modal/serving.pyi +0 -0
  230. {modal-1.6.2 → modal-1.6.2.dev0}/modal/skills/modal/SKILL.md +0 -0
  231. {modal-1.6.2 → modal-1.6.2.dev0}/modal/stream_type.py +0 -0
  232. {modal-1.6.2 → modal-1.6.2.dev0}/modal/token_flow.py +0 -0
  233. {modal-1.6.2 → modal-1.6.2.dev0}/modal/token_flow.pyi +0 -0
  234. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
  235. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/entry_points.txt +0 -0
  236. {modal-1.6.2 → modal-1.6.2.dev0}/modal.egg-info/top_level.txt +0 -0
  237. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/__init__.py +0 -0
  238. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
  239. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  240. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
  241. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  242. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
  243. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  244. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
  245. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/mdmd/types.py +0 -0
  246. {modal-1.6.2 → modal-1.6.2.dev0}/modal_docs/reference_intro.md +0 -0
  247. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/__init__.py +0 -0
  248. {modal-1.6.2 → modal-1.6.2.dev0}/modal_proto/py.typed +0 -0
  249. {modal-1.6.2 → modal-1.6.2.dev0}/modal_version/__main__.py +0 -0
  250. {modal-1.6.2 → modal-1.6.2.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.6.2
3
+ Version: 1.6.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
@@ -12,7 +12,7 @@ Keywords: modal,client,cloud,serverless,infrastructure
12
12
  Classifier: Topic :: System :: Distributed Computing
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Requires-Python: <3.16,>=3.10
15
+ Requires-Python: <3.15,>=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: aiohttp
@@ -29,7 +29,6 @@ Requires-Dist: types-certifi
29
29
  Requires-Dist: types-toml
30
30
  Requires-Dist: watchfiles
31
31
  Requires-Dist: typing_extensions~=4.6
32
- Requires-Dist: typing_extensions>=4.14; python_version >= "3.15"
33
32
  Provides-Extra: api-proxy-support
34
33
  Requires-Dist: python-socks[asyncio]>=2.5; extra == "api-proxy-support"
35
34
  Requires-Dist: aiohttp-socks>=0.8; extra == "api-proxy-support"
@@ -53,7 +52,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
53
52
 
54
53
  ## Installation
55
54
 
56
- **This library requires Python 3.10 – 3.15.**
55
+ **This library requires Python 3.10 – 3.14.**
57
56
 
58
57
  Install the package with `uv` or `pip`:
59
58
 
@@ -16,7 +16,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
16
16
 
17
17
  ## Installation
18
18
 
19
- **This library requires Python 3.10 – 3.15.**
19
+ **This library requires Python 3.10 – 3.14.**
20
20
 
21
21
  Install the package with `uv` or `pip`:
22
22
 
@@ -3,8 +3,8 @@ import sys
3
3
 
4
4
  if sys.version_info[:2] < (3, 10):
5
5
  raise RuntimeError("This version of Modal requires at least Python 3.10")
6
- if sys.version_info[:2] >= (3, 16):
7
- raise RuntimeError("This version of Modal does not support Python 3.16+")
6
+ if sys.version_info[:2] >= (3, 15):
7
+ raise RuntimeError("This version of Modal does not support Python 3.15+")
8
8
 
9
9
  from modal_version import __version__
10
10
 
@@ -43,11 +43,11 @@ try:
43
43
  from .queue import Queue
44
44
  from .retries import Retries
45
45
  from .sandbox import Probe, Sandbox
46
- from .sandbox.sandbox import SandboxSnapshot
47
46
  from .schedule import Cron, Period
48
47
  from .scheduler_placement import SchedulerPlacement
49
48
  from .secret import Secret
50
49
  from .server import Server
50
+ from .snapshot import SandboxSnapshot
51
51
  from .volume import Volume
52
52
  from .workspace import Workspace
53
53
  except Exception:
@@ -54,7 +54,7 @@ class _EnvironmentManager:
54
54
  modal.Environment.objects.create("my-environment")
55
55
  ```
56
56
  """
57
- check_environment_name(name, allow_periods=False)
57
+ check_environment_name(name)
58
58
 
59
59
  client = await _Client.from_env() if client is None else client
60
60
  request = api_pb2.EnvironmentCreateRequest(name=name, is_managed=restricted)
@@ -464,7 +464,7 @@ class _Environment(_Object, type_prefix="en"):
464
464
  client: _Client | None = None,
465
465
  ) -> "_Environment":
466
466
  """Look up an Environment object using its name."""
467
- check_environment_name(name, allow_periods=True)
467
+ check_environment_name(name)
468
468
  return _Environment._get_or_create(
469
469
  name=name,
470
470
  repr=f"Environment.from_name({name!r})",
@@ -27,7 +27,6 @@ from ._image import _Image
27
27
  from ._load_context import LoadContext
28
28
  from ._logs import LogsFilters
29
29
  from ._logs_manager import _FunctionCallLogsManager, _FunctionLogsManager
30
- from ._metrics import _FunctionMetricsManager
31
30
  from ._object import _Object, live_method, live_method_gen
32
31
  from ._output.pty import get_pty_info
33
32
  from ._output.status import FunctionCreationStatus
@@ -51,7 +50,6 @@ from ._utils.async_utils import (
51
50
  )
52
51
  from ._utils.auth_token_manager import _AuthTokenManager
53
52
  from ._utils.blob_utils import MAX_ASYNC_OBJECT_SIZE_BYTES, MAX_OBJECT_SIZE_BYTES
54
- from ._utils.deprecation import deprecation_warning
55
53
  from ._utils.function_utils import (
56
54
  ATTEMPT_TIMEOUT_GRACE_PERIOD,
57
55
  OUTPUTS_TIMEOUT,
@@ -663,11 +661,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
663
661
  source_object_id=self.object_id,
664
662
  )
665
663
 
666
- @property
667
- def metrics(self) -> "_FunctionMetricsManager":
668
- """Access metric samples with `export()` and series definitions with `schema()`."""
669
- return _FunctionMetricsManager(self)
670
-
671
664
  @property
672
665
  def logs(self) -> _FunctionLogsManager:
673
666
  """Access logs for a `Function`.
@@ -826,8 +819,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
826
819
  raise InvalidError(
827
820
  f"`min_containers` ({min_containers}) cannot be greater than `max_containers` ({max_containers})"
828
821
  )
829
- if scaledown_window is not None and not (2 <= scaledown_window <= 3600):
830
- raise InvalidError("`scaledown_window` must be between 2 and 3600 (inclusive)")
822
+ if scaledown_window is not None and scaledown_window <= 0:
823
+ raise InvalidError("`scaledown_window` must be > 0")
831
824
 
832
825
  autoscaler_settings = api_pb2.AutoscalerSettings(
833
826
  min_containers=min_containers,
@@ -1397,13 +1390,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1397
1390
  ```
1398
1391
 
1399
1392
  """
1400
-
1401
- deprecation_warning(
1402
- (2026, 10, 16),
1403
- "`Function.update_autoscaler()` is deprecated and will be removed in a future version "
1404
- "of `modal`. Use `Function.autoscaler.update()` instead.",
1405
- )
1406
-
1407
1393
  # Assert .update_autoscaler() is not called on a method as opposed to the Object. Applicable for Cls only.
1408
1394
  if self._is_method:
1409
1395
  raise InvalidError("Cannot call .update_autoscaler() on a method. Call it on the class instance instead.")
@@ -2320,11 +2306,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2320
2306
  assert self._metadata
2321
2307
  return self._metadata.function_schema
2322
2308
 
2323
- @property
2324
- def autoscaler(self) -> "_FunctionAutoscalerManager":
2325
- """Namespace for Function autoscaler APIs."""
2326
- return _FunctionAutoscalerManager(self)
2327
-
2328
2309
  # A bit hacky - but the map-style functions need to not be synchronicity-wrapped
2329
2310
  # in order to not execute their input iterators on the synchronicity event loop.
2330
2311
  # We still need to wrap them using MethodWithAio to maintain a synchronicity-like
@@ -2578,130 +2559,3 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2578
2559
  except Exception as exc:
2579
2560
  # TODO: kill all running function calls
2580
2561
  raise exc
2581
-
2582
-
2583
- UNSET_PARAMETER = -1
2584
-
2585
-
2586
- class _FunctionAutoscalerManager:
2587
- """mdmd:namespace"""
2588
-
2589
- def __init__(self, function: _Function):
2590
- """mdmd:hidden"""
2591
- self._function = function
2592
-
2593
- async def settings(self) -> FunctionAutoscalerSettings:
2594
- """View the current Autoscaler settings for this Function.
2595
-
2596
- Returns:
2597
- A `FunctionAutoscalerSettings` dataclass.
2598
-
2599
- Examples:
2600
-
2601
- ```python notest
2602
- fn = modal.Function.from_name("my-app", "my_function")
2603
- settings = fn.autoscaler.settings()
2604
- ```
2605
- """
2606
-
2607
- if not self._function._is_hydrated:
2608
- await self._function.hydrate()
2609
-
2610
- response = await self._function.client._stub.FunctionGetSchedulingParams(
2611
- api_pb2.FunctionGetSchedulingParamsRequest(function_id=self._function.object_id)
2612
- )
2613
-
2614
- ret = FunctionAutoscalerSettings._from_proto(response.autoscaler_configuration.settings)
2615
- if ret.max_containers == 0:
2616
- ret = dataclasses.replace(ret, max_containers=None)
2617
-
2618
- return ret
2619
-
2620
- async def update(
2621
- self,
2622
- *,
2623
- min_containers: int | None = UNSET_PARAMETER,
2624
- max_containers: int | None = UNSET_PARAMETER,
2625
- buffer_containers: int | None = UNSET_PARAMETER,
2626
- scaledown_window: int = UNSET_PARAMETER,
2627
- ) -> FunctionAutoscalerSettings:
2628
- """Override the current Autoscaler settings for this Function.
2629
-
2630
- All parameters to this function are optional; not passing an argument for a given parameter
2631
- will not update its value.
2632
-
2633
- Note that re-deploying the App containing this Function will wipe any overrides and revert
2634
- its Autoscaler settings back to those defined on the Function at creation time.
2635
-
2636
- Args:
2637
- min_containers: Minimum number of containers to keep running. Must be a nonnegative
2638
- integer, or `None`. Passing `None` is equivalent to setting the minimum to 0.
2639
- max_containers: Maximum concurrent containers. Must be a positive integer, or `None`.
2640
- Passing `None` is equivalent to disabling the upper bound on container concurrency at
2641
- the Function level (note that Environment and Workspace level container upper limits
2642
- still apply.)
2643
- buffer_containers: Extra containers to keep warm beyond current demand. Must be a
2644
- nonnegative integer, or `None`. Passing `None` is equivalent to setting the buffer to
2645
- 0.
2646
- scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
2647
- Must be a positive integer between 2 and 3600, inclusive.
2648
-
2649
- Returns:
2650
- A `FunctionAutoscalerSettings` dataclass.
2651
-
2652
- Examples:
2653
-
2654
- Update `my_function` so that 2 containers are always active.
2655
-
2656
- ```python notest
2657
- fn = modal.Function.from_name("my-app", "my_function")
2658
- fn.autoscaler.update(min_containers=2)
2659
- ```
2660
-
2661
- Update `my_function` so that there is no Function-level limit on `max_containers`:
2662
-
2663
- ```python notest
2664
- fn = modal.Function.from_name("my-app", "my_function")
2665
- fn.autoscaler.update(max_containers=None)
2666
- ```
2667
- """
2668
-
2669
- if not self._function._is_hydrated:
2670
- await self._function.hydrate()
2671
-
2672
- settings: api_pb2.AutoscalerSettings = api_pb2.AutoscalerSettings()
2673
-
2674
- if min_containers != UNSET_PARAMETER:
2675
- min_containers = min_containers if min_containers is not None else 0
2676
- if min_containers < 0:
2677
- raise InvalidError("Invalid argument: `min_containers` must be a nonnegative integer or `None`.")
2678
-
2679
- settings.min_containers = min_containers
2680
-
2681
- if buffer_containers != UNSET_PARAMETER:
2682
- buffer_containers = buffer_containers if buffer_containers is not None else 0
2683
- if buffer_containers < 0:
2684
- raise InvalidError("Invalid argument: `buffer_containers` must be a nonnegative integer or `None`.")
2685
-
2686
- settings.buffer_containers = buffer_containers
2687
-
2688
- if max_containers != UNSET_PARAMETER:
2689
- if max_containers is not None and max_containers <= 0:
2690
- raise InvalidError("Invalid argument: `max_containers` must be a postive integer or `None`.")
2691
-
2692
- settings.max_containers = max_containers if max_containers is not None else 0
2693
-
2694
- if scaledown_window != UNSET_PARAMETER:
2695
- if not (2 <= scaledown_window <= 3600):
2696
- raise InvalidError("Invalid argument: `scaledown_window` must be between 2 and 3600 (inclusive)")
2697
-
2698
- settings.scaledown_window = scaledown_window
2699
-
2700
- request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self._function.object_id, settings=settings)
2701
- response = await self._function.client._stub.FunctionUpdateSchedulingParams(request)
2702
-
2703
- ret = FunctionAutoscalerSettings._from_proto(response.current_settings)
2704
- if ret.max_containers == 0:
2705
- ret = dataclasses.replace(ret, max_containers=None)
2706
-
2707
- return ret
@@ -76,8 +76,8 @@ ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVI
76
76
  # Python versions in mount.py where we specify the "standalone Python versions" we create mounts for.
77
77
  # Consider consolidating these multiple sources of truth?
78
78
  SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
79
- "PREVIEW": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
80
- "2025.06": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
79
+ "PREVIEW": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"],
80
+ "2025.06": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"],
81
81
  "2024.10": ["3.10", "3.11", "3.12", "3.13"],
82
82
  "2024.04": ["3.10", "3.11", "3.12"],
83
83
  "2023.12": ["3.10", "3.11", "3.12"],
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
 
4
4
  import asyncio
5
5
  import dataclasses
6
- from collections.abc import AsyncGenerator, Awaitable, Callable
6
+ from collections.abc import AsyncGenerator
7
7
  from datetime import datetime, timedelta, timezone
8
- from typing import TYPE_CHECKING, Sequence, TypeVar
8
+ from typing import TYPE_CHECKING
9
9
 
10
10
  from google.protobuf.timestamp_pb2 import Timestamp
11
11
 
@@ -13,9 +13,6 @@ from modal._utils.async_utils import TaskContext
13
13
  from modal.exception import LogsFetchError
14
14
  from modal_proto import api_pb2
15
15
 
16
- _T = TypeVar("_T")
17
- _CountRanges = Callable[[float, float, int], Awaitable[list[tuple[float, float, int]]]]
18
-
19
16
  if TYPE_CHECKING:
20
17
  from modal.client import _Client
21
18
 
@@ -197,32 +194,6 @@ def _next_smaller_bucket_secs(current_bucket_secs: int) -> int | None:
197
194
  return None
198
195
 
199
196
 
200
- async def _count_log_ranges(
201
- client: _Client,
202
- app_id: str,
203
- since: float,
204
- until: float,
205
- bucket_secs: int,
206
- filters: LogsFilters,
207
- ) -> list[tuple[float, float, int]]:
208
- count_req = api_pb2.AppCountLogsRequest(
209
- app_id=app_id,
210
- since=_seconds_to_timestamp(since),
211
- until=_seconds_to_timestamp(until),
212
- bucket_secs=bucket_secs,
213
- source=filters.source,
214
- function_id=filters.function_id,
215
- parametrized_function_id=filters.parametrized_function_id,
216
- function_call_id=filters.function_call_id,
217
- task_id=filters.task_id,
218
- sandbox_id=filters.sandbox_id,
219
- search_text=filters.search_text,
220
- )
221
- count_resp: api_pb2.AppCountLogsResponse = await client._stub.AppCountLogs(count_req)
222
-
223
- return _buckets_to_ranges(list(count_resp.buckets), bucket_secs)
224
-
225
-
226
197
  async def _refine_dense_ranges(
227
198
  client: _Client,
228
199
  app_id: str,
@@ -230,19 +201,6 @@ async def _refine_dense_ranges(
230
201
  filters: LogsFilters,
231
202
  max_ranges: int,
232
203
  max_iterations: int,
233
- ) -> list[tuple[float, float, int]]:
234
- async def count_ranges(start: float, end: float, bucket_secs: int) -> list[tuple[float, float, int]]:
235
- return await _count_log_ranges(client, app_id, start, end, bucket_secs, filters)
236
-
237
- return await _refine_ranges(ranges, count_ranges, max_ranges, max_iterations)
238
-
239
-
240
- async def _refine_ranges(
241
- ranges: list[tuple[float, float, int]],
242
- count_ranges: _CountRanges,
243
- max_ranges: int,
244
- max_iterations: int,
245
- max_concurrent_counts: int = _MAX_CONCURRENT_COUNTS,
246
204
  ) -> list[tuple[float, float, int]]:
247
205
  """Refine ranges exceeding _FETCH_LIMIT by subdividing with smaller buckets.
248
206
 
@@ -281,11 +239,25 @@ async def _refine_ranges(
281
239
  break
282
240
 
283
241
  # Fire off all re-count RPCs in parallel with bounded concurrency
284
- semaphore = asyncio.Semaphore(max_concurrent_counts)
242
+ semaphore = asyncio.Semaphore(_MAX_CONCURRENT_COUNTS)
285
243
 
286
244
  async def _recount(start: float, end: float, smaller_secs: int) -> list[tuple[float, float, int]]:
287
245
  async with semaphore:
288
- sub_ranges = await count_ranges(start, end, smaller_secs)
246
+ sub_req = api_pb2.AppCountLogsRequest(
247
+ app_id=app_id,
248
+ since=_seconds_to_timestamp(start),
249
+ until=_seconds_to_timestamp(end),
250
+ bucket_secs=smaller_secs,
251
+ source=filters.source,
252
+ function_id=filters.function_id,
253
+ parametrized_function_id=filters.parametrized_function_id,
254
+ function_call_id=filters.function_call_id,
255
+ task_id=filters.task_id,
256
+ sandbox_id=filters.sandbox_id,
257
+ search_text=filters.search_text,
258
+ )
259
+ sub_resp = await client._stub.AppCountLogs(sub_req)
260
+ sub_ranges = _buckets_to_ranges(list(sub_resp.buckets), smaller_secs)
289
261
  # Clamp the edge sub-ranges to the parent boundaries. Bucket
290
262
  # alignment can make the first bucket begin before the parent
291
263
  # (e.g. parent (6,12) with 4s buckets produces (4,8)) or make
@@ -446,34 +418,26 @@ async def fetch_logs(
446
418
 
447
419
  if until - since > _MAX_FETCH_RANGE:
448
420
  raise LogsFetchError(f"Time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
421
+ # Phase 1: Count logs per bucket
422
+ bucket_secs = _pick_bucket_secs(since, until)
423
+ count_req = api_pb2.AppCountLogsRequest(
424
+ app_id=app_id,
425
+ since=_datetime_to_timestamp(since),
426
+ until=_datetime_to_timestamp(until),
427
+ bucket_secs=bucket_secs,
428
+ source=filters.source,
429
+ function_id=filters.function_id,
430
+ parametrized_function_id=filters.parametrized_function_id,
431
+ function_call_id=filters.function_call_id,
432
+ task_id=filters.task_id,
433
+ sandbox_id=filters.sandbox_id,
434
+ search_text=filters.search_text,
435
+ )
436
+ count_resp: api_pb2.AppCountLogsResponse = await client._stub.AppCountLogs(count_req)
449
437
 
450
- async def count_ranges(start: float, end: float, bucket_secs: int) -> list[tuple[float, float, int]]:
451
- return await _count_log_ranges(client, app_id, start, end, bucket_secs, filters)
452
-
453
- async def fetch_interval(start: float, end: float, limit: int) -> list[api_pb2.TaskLogsBatch]:
454
- return await _fetch_interval(client, app_id, start, end, limit, filters)
455
-
456
- async for batch in _fetch_time_range(
457
- since,
458
- until,
459
- count_ranges,
460
- fetch_interval,
461
- ):
462
- yield batch
463
-
464
-
465
- async def _fetch_time_range(
466
- since: datetime,
467
- until: datetime,
468
- count_ranges: _CountRanges,
469
- fetch_interval: Callable[[float, float, int], Awaitable[Sequence[_T]]],
470
- *,
471
- max_concurrent_counts: int = _MAX_CONCURRENT_COUNTS,
472
- ) -> AsyncGenerator[_T]:
473
- """Count, refine, and fetch time intervals with bounded concurrency and ordered results."""
474
- ranges = await count_ranges(since.timestamp(), until.timestamp(), _pick_bucket_secs(since, until))
475
- total_records = sum(count for _, _, count in ranges)
476
- if total_records == 0:
438
+ ranges = _buckets_to_ranges(list(count_resp.buckets), bucket_secs)
439
+ total_logs = sum(count for _, _, count in ranges)
440
+ if total_logs == 0:
477
441
  return
478
442
 
479
443
  # Trim leading/trailing empty buckets so they don't consume refinement
@@ -485,17 +449,19 @@ async def _fetch_time_range(
485
449
 
486
450
  # Phase 1b: Refine any ranges that exceed the per-fetch limit.
487
451
  # Budget: at most _MAX_FETCHES intervals, each fetching up to _FETCH_LIMIT entries.
488
- ranges = await _refine_ranges(
452
+ ranges = await _refine_dense_ranges(
453
+ client,
454
+ app_id,
489
455
  ranges,
490
- count_ranges,
456
+ filters,
491
457
  max_ranges=_MAX_FETCHES,
492
458
  max_iterations=_MAX_REFINE_ITERATIONS,
493
- max_concurrent_counts=max_concurrent_counts,
494
459
  )
460
+ fetch_error_message = "Too many logs to fetch in time range. Consider narrowing the range or adding filters."
495
461
  # Check that all ranges fit within the per-fetch limit.
496
462
  over_limit = [r for r in ranges if r[2] > _FETCH_LIMIT]
497
463
  if over_limit:
498
- raise LogsFetchError("Too many logs to fetch in time range. Consider narrowing the range or adding filters.")
464
+ raise LogsFetchError(fetch_error_message)
499
465
 
500
466
  # Phase 2: Build intervals from non-empty ranges and fetch in parallel
501
467
  since_secs = since.timestamp()
@@ -510,17 +476,24 @@ async def _fetch_time_range(
510
476
  return
511
477
 
512
478
  if len(intervals) > _MAX_FETCHES:
513
- raise LogsFetchError("Too many logs to fetch in time range. Consider narrowing the range or adding filters.")
479
+ raise LogsFetchError(fetch_error_message)
514
480
 
515
481
  # Fetch all intervals concurrently (bounded by semaphore), but yield
516
- # results in interval order so records stay chronological. Awaiting each
482
+ # results in interval order so logs stay chronological. Awaiting each
517
483
  # task in sequence lets us stream batches to the caller as soon as all
518
484
  # preceding intervals have completed.
519
485
  semaphore = asyncio.Semaphore(_MAX_CONCURRENT_FETCHES)
520
486
 
521
- async def _bounded_fetch(since_s: float, until_s: float) -> Sequence[_T]:
487
+ async def _bounded_fetch(since_s: float, until_s: float) -> list[api_pb2.TaskLogsBatch]:
522
488
  async with semaphore:
523
- return await fetch_interval(since_s, until_s, _FETCH_LIMIT)
489
+ return await _fetch_interval(
490
+ client,
491
+ app_id,
492
+ since_s,
493
+ until_s,
494
+ _FETCH_LIMIT,
495
+ filters,
496
+ )
524
497
 
525
498
  tasks = [asyncio.create_task(_bounded_fetch(s, u)) for s, u in intervals]
526
499
 
@@ -531,4 +504,3 @@ async def _fetch_time_range(
531
504
  finally:
532
505
  for task in tasks:
533
506
  task.cancel()
534
- await asyncio.gather(*tasks, return_exceptions=True)
@@ -139,11 +139,11 @@ class _OutboundPolicy:
139
139
  """Deduplicated list of secrets referenced by the policy's replacements."""
140
140
  return list(dict.fromkeys(r.secret for r in self._replacements if r.secret is not None))
141
141
 
142
- def _to_proto(self) -> api_pb2.ExperimentalOutboundPolicy:
142
+ def _to_proto(self) -> api_pb2.OutboundPolicy:
143
143
  """Convert to the wire format. Referenced secrets must be hydrated first."""
144
- return api_pb2.ExperimentalOutboundPolicy(
144
+ return api_pb2.OutboundPolicy(
145
145
  header_replacements=[
146
- api_pb2.ExperimentalOutboundPolicy.HeaderReplacement(
146
+ api_pb2.OutboundPolicy.HeaderReplacement(
147
147
  domain=replacement.domain,
148
148
  secret_id=(replacement.secret.object_id if replacement.secret is not None else ""),
149
149
  headers=replacement.headers,
@@ -107,7 +107,7 @@ class _OutboundPolicy:
107
107
  """Deduplicated list of secrets referenced by the policy's replacements."""
108
108
  ...
109
109
 
110
- def _to_proto(self) -> modal_proto.api_pb2.ExperimentalOutboundPolicy:
110
+ def _to_proto(self) -> modal_proto.api_pb2.OutboundPolicy:
111
111
  """Convert to the wire format. Referenced secrets must be hydrated first."""
112
112
  ...
113
113
 
@@ -185,6 +185,6 @@ class OutboundPolicy:
185
185
  """Deduplicated list of secrets referenced by the policy's replacements."""
186
186
  ...
187
187
 
188
- def _to_proto(self) -> modal_proto.api_pb2.ExperimentalOutboundPolicy:
188
+ def _to_proto(self) -> modal_proto.api_pb2.OutboundPolicy:
189
189
  """Convert to the wire format. Referenced secrets must be hydrated first."""
190
190
  ...
@@ -20,11 +20,6 @@ def _get_annotation_for_web_url(url_info: api_pb2.WebUrlInfo) -> str:
20
20
  return suffix
21
21
 
22
22
 
23
- def _format_url(url: str) -> str:
24
- # OSC 8 hyperlinks stay clickable when the URL wraps across lines
25
- return f"[dim underline][link={url}]{url}[/link][/dim underline]"
26
-
27
-
28
23
  class FunctionCreationStatus:
29
24
  """Context manager for tracking and displaying function creation progress."""
30
25
 
@@ -63,9 +58,9 @@ class FunctionCreationStatus:
63
58
  suffix = _get_annotation_for_web_url(url_info)
64
59
 
65
60
  self.status_row.finish(f"Created {self.type} [green]{self.name}[/green]")
66
- self.status_row.details(f"{_format_url(web_url)}{auth_suffix}{suffix}")
61
+ self.status_row.details(f"[dim underline]{web_url}[/dim underline]{auth_suffix}{suffix}")
67
62
  for custom_domain in self.response.function.custom_domain_info:
68
- self.status_row.details(f"Custom domain: {_format_url(custom_domain.url)}")
63
+ self.status_row.details(f"Custom domain: [dim underline]{custom_domain.url}[/dim underline]")
69
64
 
70
65
  elif self.response.function.flash_service_urls:
71
66
  # Despite the gRPC API types, Servers only have one URL
@@ -73,7 +68,7 @@ class FunctionCreationStatus:
73
68
  auth_suffix = " 🔑" if authenticated else " [yellow](unauthenticated)[/yellow]"
74
69
  self.status_row.finish(f"Created {self.type} [green]{self.name}[/green]")
75
70
  url = self.response.function.flash_service_urls[0]
76
- self.status_row.details(f"{_format_url(url)}{auth_suffix}")
71
+ self.status_row.details(f"[dim underline]{url}[/dim underline]{auth_suffix}")
77
72
 
78
73
  else:
79
74
  self.status_row.finish(f"Created {self.type} [green]{self.name}[/green].")
@@ -86,8 +81,10 @@ class FunctionCreationStatus:
86
81
  auth_suffix = " 🔑" if authenticated else " [yellow](unauthenticated)[/yellow]"
87
82
  self.status_row.details(
88
83
  f"{method_definition.function_name} -> "
89
- f"{_format_url(method_definition.web_url)}{auth_suffix}{suffix}"
84
+ f"[dim underline]{method_definition.web_url}[/dim underline]{auth_suffix}{suffix}"
90
85
  )
91
86
  for custom_domain in method_definition.custom_domain_info:
92
87
  indent = len(method_definition.function_name) * " "
93
- self.status_row.details(f"{indent} -> {_format_url(custom_domain.url)}{auth_suffix}")
88
+ self.status_row.details(
89
+ f"{indent} -> [dim underline]{custom_domain.url}[/dim underline]{auth_suffix}"
90
+ )