modal 1.5.6.dev12__tar.gz → 1.5.6.dev14__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 (242) hide show
  1. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_functions.py +57 -9
  3. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_output/status.py +5 -6
  4. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/auth_token_manager.py +28 -17
  5. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/grpc_utils.py +20 -6
  6. modal-1.5.6.dev14/modal/_utils/idle_countdown.py +64 -0
  7. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/task_command_router_client.py +192 -82
  8. modal-1.5.6.dev14/modal/cli/_logs.py +124 -0
  9. modal-1.5.6.dev14/modal/cli/_stats.py +141 -0
  10. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/app.py +16 -111
  11. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/container.py +1 -1
  12. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/entry_point.py +2 -0
  13. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/function.py +145 -119
  14. modal-1.5.6.dev14/modal/cli/server.py +519 -0
  15. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/utils.py +89 -1
  16. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/client.py +0 -1
  17. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/client.pyi +2 -2
  18. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/config.py +8 -0
  19. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/exception.py +0 -4
  20. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/functions.pyi +58 -6
  21. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/io_streams.py +18 -0
  22. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/io_streams.pyi +18 -0
  23. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/sandbox.py +50 -152
  24. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/sandbox.pyi +15 -188
  25. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/sandbox_fs.py +62 -54
  26. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/sandbox_fs.pyi +182 -158
  27. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/types.py +61 -2
  28. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/volume.py +101 -48
  29. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/volume.pyi +16 -2
  30. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/PKG-INFO +1 -1
  31. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/SOURCES.txt +3 -2
  32. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs.py +0 -1
  33. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/reference_intro.md +0 -1
  34. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/api_grpc.py +64 -0
  35. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/api_pb2.py +1595 -1374
  36. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/api_pb2.pyi +494 -27
  37. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.py +133 -0
  38. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.pyi +42 -0
  39. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/modal_api_grpc.py +4 -0
  40. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_version/__init__.py +1 -1
  41. modal-1.5.6.dev12/modal/cli/_stats.py +0 -50
  42. modal-1.5.6.dev12/modal/file_io.py +0 -564
  43. modal-1.5.6.dev12/modal/file_io.pyi +0 -447
  44. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/LICENSE +0 -0
  45. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/README.md +0 -0
  46. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/__init__.py +0 -0
  47. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/__main__.py +0 -0
  48. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_billing.py +0 -0
  49. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_clustered_functions.py +0 -0
  50. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_clustered_functions.pyi +0 -0
  51. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_container_entrypoint.py +0 -0
  52. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_environments.py +0 -0
  53. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_function_variants.py +0 -0
  54. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_grpc_client.py +0 -0
  55. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_image.py +0 -0
  56. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_ipython.py +0 -0
  57. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_load_context.py +0 -0
  58. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_location.py +0 -0
  59. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_logs.py +0 -0
  60. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_logs_manager.py +0 -0
  61. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_logs_manager.pyi +0 -0
  62. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_object.py +0 -0
  63. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_output/__init__.py +0 -0
  64. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_output/manager.py +0 -0
  65. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_output/pty.py +0 -0
  66. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_output/rich.py +0 -0
  67. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_partial_function.py +0 -0
  68. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_resolver.py +0 -0
  69. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_resources.py +0 -0
  70. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/__init__.py +0 -0
  71. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/asgi.py +0 -0
  72. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
  73. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  74. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/execution_context.py +0 -0
  75. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
  76. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  77. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.py +0 -0
  78. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  79. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/telemetry.py +0 -0
  80. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
  81. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
  82. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_serialization.py +0 -0
  83. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_server.py +0 -0
  84. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_supports_logs.py +0 -0
  85. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_traceback.py +0 -0
  86. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_tunnel.py +0 -0
  87. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_tunnel.pyi +0 -0
  88. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_type_manager.py +0 -0
  89. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/__init__.py +0 -0
  90. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/app_utils.py +0 -0
  91. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/async_utils.py +0 -0
  92. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/blob_utils.py +0 -0
  93. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/browser_utils.py +0 -0
  94. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  95. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/curl_utils.py +0 -0
  96. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/deprecation.py +0 -0
  97. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/display_utils.py +0 -0
  98. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/docker_utils.py +0 -0
  99. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/function_utils.py +0 -0
  100. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/git_utils.py +0 -0
  101. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/grpc_testing.py +0 -0
  102. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/hash_utils.py +0 -0
  103. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/http_utils.py +0 -0
  104. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/jwt_utils.py +0 -0
  105. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/logger.py +0 -0
  106. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/mount_utils.py +0 -0
  107. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/name_utils.py +0 -0
  108. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/package_utils.py +0 -0
  109. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/pattern_utils.py +0 -0
  110. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/proxy_support.py +0 -0
  111. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  112. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
  113. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/shell_utils.py +0 -0
  114. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_utils/time_utils.py +0 -0
  115. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_vendor/__init__.py +0 -0
  116. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  117. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
  118. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_vendor/tblib.py +0 -0
  119. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_vendor/version.py +0 -0
  120. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_watcher.py +0 -0
  121. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/_workspace.py +0 -0
  122. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/app.py +0 -0
  123. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/app.pyi +0 -0
  124. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/billing.py +0 -0
  125. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/billing.pyi +0 -0
  126. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/2023.12.312.txt +0 -0
  127. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/2023.12.txt +0 -0
  128. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/2024.04.txt +0 -0
  129. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/2024.10.txt +0 -0
  130. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/2025.06.txt +0 -0
  131. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/PREVIEW.txt +0 -0
  132. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/README.md +0 -0
  133. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/builder/base-images.json +0 -0
  134. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/call_graph.py +0 -0
  135. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/__init__.py +0 -0
  136. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/_download.py +0 -0
  137. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/_help.py +0 -0
  138. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/_traceback.py +0 -0
  139. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/billing.py +0 -0
  140. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/bootstrap.py +0 -0
  141. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/changelog.py +0 -0
  142. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/cluster.py +0 -0
  143. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/config.py +0 -0
  144. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/curl.py +0 -0
  145. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/dashboard.py +0 -0
  146. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/dict.py +0 -0
  147. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/endpoint.py +0 -0
  148. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/environment.py +0 -0
  149. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/image.py +0 -0
  150. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/import_refs.py +0 -0
  151. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/launch.py +0 -0
  152. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/logo.py +0 -0
  153. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/network_file_system.py +0 -0
  154. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/profile.py +0 -0
  155. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/programs/__init__.py +0 -0
  156. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  157. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/programs/vscode.py +0 -0
  158. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/queues.py +0 -0
  159. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/run.py +0 -0
  160. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/secret.py +0 -0
  161. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/selector.py +0 -0
  162. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/shell.py +0 -0
  163. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/skills.py +0 -0
  164. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/token.py +0 -0
  165. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/volume.py +0 -0
  166. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cli/workspace.py +0 -0
  167. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.py +0 -0
  168. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  169. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cls.py +0 -0
  170. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/cls.pyi +0 -0
  171. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/container_process.py +0 -0
  172. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/container_process.pyi +0 -0
  173. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/dict.py +0 -0
  174. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/dict.pyi +0 -0
  175. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/environments.py +0 -0
  176. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/environments.pyi +0 -0
  177. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/experimental/__init__.py +0 -0
  178. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/experimental/flash.py +0 -0
  179. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/experimental/flash.pyi +0 -0
  180. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/experimental/ipython.py +0 -0
  181. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/file_pattern_matcher.py +0 -0
  182. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/functions.py +0 -0
  183. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/image.py +0 -0
  184. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/image.pyi +0 -0
  185. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/mount.py +0 -0
  186. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/mount.pyi +0 -0
  187. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/network_file_system.py +0 -0
  188. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/network_file_system.pyi +0 -0
  189. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/object.py +0 -0
  190. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/object.pyi +0 -0
  191. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/output.py +0 -0
  192. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/parallel_map.py +0 -0
  193. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/parallel_map.pyi +0 -0
  194. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/partial_function.py +0 -0
  195. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/partial_function.pyi +0 -0
  196. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/proxy.py +0 -0
  197. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/proxy.pyi +0 -0
  198. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/py.typed +0 -0
  199. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/queue.py +0 -0
  200. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/queue.pyi +0 -0
  201. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/retries.py +0 -0
  202. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/runner.py +0 -0
  203. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/runner.pyi +0 -0
  204. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/running_app.py +0 -0
  205. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/schedule.py +0 -0
  206. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/scheduler_placement.py +0 -0
  207. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/secret.py +0 -0
  208. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/secret.pyi +0 -0
  209. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/server.py +0 -0
  210. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/server.pyi +0 -0
  211. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/serving.py +0 -0
  212. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/serving.pyi +0 -0
  213. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/skills/modal/SKILL.md +0 -0
  214. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/snapshot.py +0 -0
  215. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/snapshot.pyi +0 -0
  216. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/stream_type.py +0 -0
  217. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/token_flow.py +0 -0
  218. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/token_flow.pyi +0 -0
  219. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/workspace.py +0 -0
  220. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal/workspace.pyi +0 -0
  221. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
  222. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/entry_points.txt +0 -0
  223. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/requires.txt +0 -0
  224. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal.egg-info/top_level.txt +0 -0
  225. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/__init__.py +0 -0
  226. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
  227. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
  228. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
  229. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
  230. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  231. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
  232. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_docs/mdmd/types.py +0 -0
  233. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/__init__.py +0 -0
  234. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/py.typed +0 -0
  235. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  236. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  237. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  238. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  239. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  240. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/modal_version/__main__.py +0 -0
  241. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/pyproject.toml +0 -0
  242. {modal-1.5.6.dev12 → modal-1.5.6.dev14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev12
3
+ Version: 1.5.6.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -8,6 +8,7 @@ import warnings
8
8
  from collections import OrderedDict
9
9
  from collections.abc import AsyncGenerator, AsyncIterator, Callable, Collection, Sequence
10
10
  from dataclasses import dataclass
11
+ from datetime import datetime, timedelta, timezone
11
12
  from pathlib import PurePosixPath
12
13
  from typing import TYPE_CHECKING, Any
13
14
 
@@ -64,7 +65,7 @@ from ._utils.function_utils import (
64
65
  parse_gpu_config,
65
66
  validate_target_concurrency,
66
67
  )
67
- from ._utils.grpc_utils import Retry, RetryWarningMessage
68
+ from ._utils.grpc_utils import Retry, RetryWarningMessage, is_class_function_lookup_error
68
69
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
69
70
  from .call_graph import InputInfo, _reconstruct_call_graph
70
71
  from .client import _Client
@@ -99,7 +100,12 @@ from .proxy import _Proxy
99
100
  from .retries import Retries, RetryManager
100
101
  from .schedule import Schedule
101
102
  from .secret import _Secret
102
- from .types import FunctionAutoscalerSettings, FunctionStats, ServerAutoscalerSettings
103
+ from .types import (
104
+ FunctionAutoscalerSettings,
105
+ FunctionCurrentStats,
106
+ FunctionStats,
107
+ ServerAutoscalerSettings,
108
+ )
103
109
  from .volume import _Volume, _volume_to_mount_proto
104
110
 
105
111
  if TYPE_CHECKING:
@@ -1361,9 +1367,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1361
1367
  env_context = (
1362
1368
  f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
1363
1369
  )
1364
- raise NotFoundError(
1365
- f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
1366
- ) from None
1370
+ msg = f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
1371
+ if is_class_function_lookup_error(exc):
1372
+ msg = (
1373
+ f"Function '{name}' from the '{app_name}' app{env_context} is a modal.Cls. "
1374
+ f"Use `modal.Cls.from_name` instead."
1375
+ )
1376
+ raise NotFoundError(msg) from None
1367
1377
 
1368
1378
  print_server_warnings(response.server_warnings)
1369
1379
 
@@ -2144,23 +2154,61 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2144
2154
  return self._raw_f
2145
2155
 
2146
2156
  @live_method
2147
- async def get_current_stats(self) -> FunctionStats:
2148
- """Return a `FunctionStats` object describing the current function's queue and runner counts.
2157
+ async def get_current_stats(self) -> FunctionCurrentStats:
2158
+ """Return a snapshot of the Function's current input and container state.
2149
2159
 
2150
2160
  Returns:
2151
- Snapshot counts for backlog, runners, and running inputs.
2161
+ A `FunctionCurrentStats` object containing live input and container counts.
2152
2162
  """
2153
2163
  resp = await self.client.stub.FunctionGetCurrentStats(
2154
2164
  api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
2155
2165
  retry=Retry(total_timeout=10.0),
2156
2166
  )
2157
- return FunctionStats(
2167
+ return FunctionCurrentStats(
2158
2168
  backlog=resp.backlog,
2159
2169
  num_total_runners=resp.num_total_tasks,
2160
2170
  num_running_inputs=resp.num_running_inputs,
2161
2171
  input_headroom=resp.input_headroom,
2162
2172
  )
2163
2173
 
2174
+ @live_method
2175
+ async def stats(
2176
+ self, *, since: datetime | None = None, until: datetime | None = None, container: str | None = None
2177
+ ) -> FunctionStats:
2178
+ """Return statistics for a modal Function.
2179
+
2180
+ The default time range is the most recent hour. The maximum time range is 7 days.
2181
+
2182
+ Args:
2183
+ since: The beginning of the time range, inclusive. If omitted, this defaults to an hour before `until`.
2184
+ Values without a timezone are interpeted as local time.
2185
+ until: The end of the time range, exclusive. If omitted, this defaults to current time.
2186
+ Values without a timezone are interpeted as local time.
2187
+ container: If passed in, the stats are computed for only this container. Default None.
2188
+
2189
+ Returns:
2190
+ A `FunctionStats` object
2191
+ """
2192
+ until = until or datetime.now(timezone.utc)
2193
+ if until.tzinfo is None:
2194
+ until = until.astimezone()
2195
+ until = until.astimezone(timezone.utc)
2196
+
2197
+ since = since or until - timedelta(hours=1)
2198
+ if since.tzinfo is None:
2199
+ since = since.astimezone()
2200
+ since = since.astimezone(timezone.utc)
2201
+ if since >= until:
2202
+ raise InvalidError("`since` must be before `until`.")
2203
+
2204
+ historical_request = api_pb2.FunctionGetTimeRangeStatsRequest(function_id=self.object_id)
2205
+ historical_request.since.FromDatetime(since)
2206
+ historical_request.until.FromDatetime(until)
2207
+ if container:
2208
+ historical_request.container_id = container
2209
+ stats = await self.client.stub.FunctionGetTimeRangeStats(historical_request)
2210
+ return FunctionStats._from_proto(stats)
2211
+
2164
2212
  @live_method
2165
2213
  async def _get_schema(self) -> api_pb2.FunctionSchema:
2166
2214
  """Returns recorded schema for function, internal use only for now"""
@@ -46,16 +46,17 @@ class FunctionCreationStatus:
46
46
 
47
47
  if not self.response:
48
48
  self.status_row.finish(f"Unknown error when creating function {self.tag}")
49
+ return
49
50
 
50
- elif web_url := self.response.handle_metadata.web_url:
51
+ for warning in self.response.server_warnings:
52
+ self.status_row.warn(warning)
53
+
54
+ if web_url := self.response.handle_metadata.web_url:
51
55
  url_info = self.response.function.web_url_info
52
56
  requires_proxy_auth = self.response.function.webhook_config.requires_proxy_auth
53
57
  proxy_auth_suffix = " 🔑" if requires_proxy_auth else ""
54
58
  # Ensure terms used here match terms used in modal.com/docs/guide/webhook-urls doc.
55
59
  suffix = _get_suffix_from_web_url_info(url_info)
56
- # TODO: this is only printed when we're showing progress. Maybe move this somewhere else.
57
- for warning in self.response.server_warnings:
58
- self.status_row.warn(warning)
59
60
  self.status_row.finish(
60
61
  f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]"
61
62
  f"{proxy_auth_suffix}{suffix}"
@@ -78,8 +79,6 @@ class FunctionCreationStatus:
78
79
  )
79
80
 
80
81
  else:
81
- for warning in self.response.server_warnings:
82
- self.status_row.warn(warning)
83
82
  self.status_row.finish(f"Created function {self.tag}.")
84
83
  if self.response.function.method_definitions_set:
85
84
  for method_definition in self.response.function.method_definitions.values():
@@ -4,6 +4,7 @@ import base64
4
4
  import json
5
5
  import random
6
6
  import time
7
+ from collections.abc import Awaitable, Callable
7
8
  from typing import Any
8
9
 
9
10
  from modal._utils.grpc_utils import DEFAULT_MAX_RETRIES, Retry
@@ -15,8 +16,15 @@ from .logger import logger
15
16
  AUTH_DENIED_EXCEPTIONS = (AuthError, PermissionDeniedError)
16
17
 
17
18
 
19
+ TokenFetcher = Callable[[Retry], Awaitable[str]]
20
+
21
+
18
22
  class _AuthTokenManager:
19
- """Handles fetching and refreshing of the input plane auth token."""
23
+ """Handles fetching and refreshing of a short-lived JWT.
24
+
25
+ Defaults to the input plane auth token (`AuthTokenGet`); pass `fetch` to manage a different token with the
26
+ same caching and refresh behavior.
27
+ """
20
28
 
21
29
  # Fraction of the token's lifetime to use before refreshing.
22
30
  REFRESH_FRACTION = 0.5
@@ -26,7 +34,7 @@ class _AuthTokenManager:
26
34
  # only ever delays a refresh, so the margin before expiry stays at no less than
27
35
  # 1 - REFRESH_FRACTION - REFRESH_JITTER of the lifetime.
28
36
  REFRESH_JITTER = 0.1
29
- # Bound each AuthTokenGet attempt so a refresh can't hang.
37
+ # Bound each fetch attempt so a refresh can't hang.
30
38
  AUTH_TOKEN_TIMEOUT = 5.0
31
39
  AUTH_TOKEN_RETRY_TOTAL_TIMEOUT = 3 * AUTH_TOKEN_TIMEOUT
32
40
  # After a failed refresh, wait before hitting the server again, growing exponentially with
@@ -36,8 +44,9 @@ class _AuthTokenManager:
36
44
  # If the token doesn't have an expiry field, default to current time plus this value (not expected).
37
45
  DEFAULT_EXPIRY_OFFSET = 20 * 60
38
46
 
39
- def __init__(self, stub: "modal_api_grpc.ModalClientModal"):
47
+ def __init__(self, stub: "modal_api_grpc.ModalClientModal", fetch: TokenFetcher | None = None):
40
48
  self._stub = stub
49
+ self._fetch = fetch or self._fetch_auth_token
41
50
  self._token = ""
42
51
  self._expiry = 0.0
43
52
  self._refresh_at = 0.0
@@ -105,31 +114,33 @@ class _AuthTokenManager:
105
114
  # Retrieve the exception, if any, so a failure isn't logged as unhandled when no caller is waiting.
106
115
  task.exception()
107
116
 
117
+ async def _fetch_auth_token(self, retry: Retry) -> str:
118
+ resp: api_pb2.AuthTokenGetResponse = await self._stub.AuthTokenGet(api_pb2.AuthTokenGetRequest(), retry=retry)
119
+ return resp.token
120
+
108
121
  async def _fetch_token(self):
109
- """Make the AuthTokenGet request and cache its token, or arm the failure backoff."""
122
+ """Fetch a new token and cache it, or arm the failure backoff."""
110
123
  try:
111
- resp: api_pb2.AuthTokenGetResponse = await self._stub.AuthTokenGet(
112
- api_pb2.AuthTokenGetRequest(),
113
- # No cached token to fall back on, so a failure is user-visible: retry transient errors.
114
- # Otherwise one attempt, and _retry_after handles the cooldown.
115
- retry=Retry(
116
- attempt_timeout=self.AUTH_TOKEN_TIMEOUT,
117
- total_timeout=self.AUTH_TOKEN_RETRY_TOTAL_TIMEOUT,
118
- max_retries=0 if self._token else DEFAULT_MAX_RETRIES,
119
- ),
124
+ # No cached token to fall back on, so a failure is user-visible: retry transient errors.
125
+ # Otherwise one attempt, and _retry_after handles the cooldown.
126
+ retry = Retry(
127
+ attempt_timeout=self.AUTH_TOKEN_TIMEOUT,
128
+ total_timeout=self.AUTH_TOKEN_RETRY_TOTAL_TIMEOUT,
129
+ max_retries=0 if self._token else DEFAULT_MAX_RETRIES,
120
130
  )
121
- if not resp.token:
131
+ token = await self._fetch(retry)
132
+ if not token:
122
133
  # Not expected
123
134
  raise ExecutionError(
124
135
  "Internal error: Did not receive auth token from server. Please contact Modal support."
125
136
  )
126
- if exp := self._decode_jwt(resp.token).get("exp"):
137
+ if exp := self._decode_jwt(token).get("exp"):
127
138
  expiry = float(exp)
128
139
  else:
129
140
  # This should never happen.
130
- logger.warning("x-modal-auth-token does not contain exp field")
141
+ logger.warning("Auth token does not contain exp field")
131
142
  expiry = time.time() + self.DEFAULT_EXPIRY_OFFSET
132
- self._token = resp.token
143
+ self._token = token
133
144
  self._set_expiry(expiry)
134
145
  self._retry_after = 0.0
135
146
  self._backoff = self.FAILURE_BACKOFF_BASE
@@ -25,7 +25,7 @@ from grpclib.encoding.base import CodecBase, StatusDetailsCodecBase
25
25
  from grpclib.exceptions import StreamTerminatedError
26
26
  from grpclib.protocol import H2Protocol
27
27
 
28
- from modal.exception import ClientClosed, ConnectionError
28
+ from modal.exception import ClientClosed, ConnectionError, NotFoundError
29
29
  from modal_proto import api_pb2
30
30
  from modal_version import __version__
31
31
 
@@ -121,12 +121,12 @@ class ModalChannel(grpclib.client.Channel):
121
121
  self.__target_host = host
122
122
  self.__target_port = port
123
123
  self.__ssl_context = ssl_context
124
- self.__closed = False
125
- self.__closed_error_message = closed_error_message
124
+ self._permanently_closed = False
125
+ self._closed_error_message = closed_error_message
126
126
 
127
127
  async def __connect__(self):
128
- if self.__closed and self.__closed_error_message is not None:
129
- raise ClientClosed(self.__closed_error_message)
128
+ if self._permanently_closed and self._closed_error_message is not None:
129
+ raise ClientClosed(self._closed_error_message)
130
130
  return await super().__connect__()
131
131
 
132
132
  async def _create_connection(self) -> H2Protocol:
@@ -161,8 +161,13 @@ class ModalChannel(grpclib.client.Channel):
161
161
  and not self._protocol.connection.is_closing()
162
162
  )
163
163
 
164
+ def release_connection(self):
165
+ """Release the connection while leaving the channel reusable."""
166
+ return super().close()
167
+
164
168
  def close(self):
165
- self.__closed = True
169
+ """Close the channel permanently."""
170
+ self._permanently_closed = True
166
171
  return super().close()
167
172
 
168
173
 
@@ -450,6 +455,15 @@ async def retry_transient_errors(
450
455
  return await _retry_transient_errors(fn, req, retry=Retry(max_retries=max_retries))
451
456
 
452
457
 
458
+ def is_class_function_lookup_error(exc: NotFoundError) -> bool:
459
+ """Whether a function lookup failed because the requested name identifies a class."""
460
+ return any(
461
+ isinstance(detail, api_pb2.FunctionLookupError)
462
+ and detail.reason == api_pb2.FunctionLookupError.REASON_CLASS_NAME_USED
463
+ for detail in (getattr(exc, "_grpc_details", None) or [])
464
+ )
465
+
466
+
453
467
  def get_server_retry_policy(exc: Exception) -> api_pb2.RPCRetryPolicy | None:
454
468
  """Get server retry policy."""
455
469
  if not isinstance(exc, GRPCError) or not exc.details:
@@ -0,0 +1,64 @@
1
+ # Copyright Modal Labs 2025
2
+ import asyncio
3
+ from collections.abc import Callable
4
+
5
+
6
+ class IdleCountdown:
7
+ """An extendable idle timeout tracker."""
8
+
9
+ def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
10
+ self._loop = loop
11
+ self._handle: asyncio.TimerHandle | None = None
12
+ self._retired = False
13
+ self._expire_on_idle = False
14
+ self._release: Callable[[], None] | None = None
15
+
16
+ def _assert_owning_loop(self) -> None:
17
+ assert asyncio.get_running_loop() is self._loop, "Idle bookkeeping must run on its owning event loop"
18
+
19
+ def expire_when_idle(self) -> None:
20
+ """Release at the current or next idle boundary, even if the timeout is disabled.
21
+
22
+ Must run on the owning event loop without yielding control.
23
+ """
24
+ self._assert_owning_loop()
25
+ self._expire_on_idle = True
26
+ if self._release is not None and not self._retired:
27
+ release = self._release
28
+ self.stop()
29
+ release()
30
+
31
+ def stop(self) -> None:
32
+ """Cancel the countdown on the owning event loop without yielding control."""
33
+ self._assert_owning_loop()
34
+ self._release = None
35
+ if self._handle is not None:
36
+ self._handle.cancel()
37
+ self._handle = None
38
+
39
+ def retire(self) -> None:
40
+ """Stop the countdown and refuse every subsequent arm.
41
+
42
+ Must run on the owning event loop without yielding control.
43
+ """
44
+ self._assert_owning_loop()
45
+ self._retired = True
46
+ self.stop()
47
+
48
+ def arm(self, timeout_secs: float, release: Callable[[], None]) -> None:
49
+ """Start the countdown again, calling `release` when it elapses.
50
+
51
+ Must run on the owning event loop without yielding control.
52
+ A timeout of zero or less never fires unless `expire_when_idle` was
53
+ requested. `release` runs as a plain callback on the event loop, so it
54
+ must not block.
55
+ """
56
+ self.stop()
57
+ if self._retired:
58
+ return
59
+ self._release = release
60
+ if self._expire_on_idle:
61
+ timeout_secs = 0
62
+ elif timeout_secs <= 0:
63
+ return
64
+ self._handle = self._loop.call_later(timeout_secs, release)