modal 1.5.6.dev11__tar.gz → 1.5.6.dev13__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 (241) hide show
  1. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_functions.py +16 -5
  3. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/status.py +5 -6
  4. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/execution_context.py +1 -1
  5. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/auth_token_manager.py +28 -17
  6. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/grpc_utils.py +20 -6
  7. modal-1.5.6.dev13/modal/_utils/idle_countdown.py +64 -0
  8. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/task_command_router_client.py +192 -82
  9. modal-1.5.6.dev13/modal/cli/_logs.py +131 -0
  10. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/app.py +16 -111
  11. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/container.py +1 -1
  12. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/entry_point.py +2 -0
  13. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/function.py +148 -9
  14. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/image.py +4 -2
  15. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/import_refs.py +5 -2
  16. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/profile.py +21 -6
  17. modal-1.5.6.dev13/modal/cli/server.py +136 -0
  18. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/token.py +9 -4
  19. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/utils.py +13 -0
  20. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/client.py +0 -1
  21. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/client.pyi +2 -2
  22. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/config.py +50 -6
  23. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/exception.py +0 -4
  24. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/functions.pyi +10 -8
  25. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/io_streams.py +18 -0
  26. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/io_streams.pyi +18 -0
  27. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/runner.py +13 -3
  28. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox.py +50 -152
  29. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox.pyi +15 -188
  30. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/token_flow.py +8 -1
  31. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/volume.py +138 -8
  32. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/volume.pyi +28 -0
  33. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/PKG-INFO +1 -1
  34. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/SOURCES.txt +3 -2
  35. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_reference_docs.py +0 -1
  36. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/reference_intro.md +0 -1
  37. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_grpc.py +48 -0
  38. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2.py +1302 -1187
  39. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2.pyi +277 -10
  40. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2_grpc.py +99 -0
  41. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2_grpc.pyi +30 -0
  42. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/modal_api_grpc.py +3 -0
  43. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_grpc.py +16 -0
  44. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2.py +45 -25
  45. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2.pyi +29 -0
  46. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2_grpc.py +34 -0
  47. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2_grpc.pyi +12 -0
  48. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_version/__init__.py +1 -1
  49. modal-1.5.6.dev11/modal/file_io.py +0 -564
  50. modal-1.5.6.dev11/modal/file_io.pyi +0 -447
  51. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/LICENSE +0 -0
  52. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/README.md +0 -0
  53. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/__init__.py +0 -0
  54. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/__main__.py +0 -0
  55. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_billing.py +0 -0
  56. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_clustered_functions.py +0 -0
  57. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_clustered_functions.pyi +0 -0
  58. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_container_entrypoint.py +0 -0
  59. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_environments.py +0 -0
  60. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_function_variants.py +0 -0
  61. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_grpc_client.py +0 -0
  62. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_image.py +0 -0
  63. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_ipython.py +0 -0
  64. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_load_context.py +0 -0
  65. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_location.py +0 -0
  66. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs.py +0 -0
  67. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs_manager.py +0 -0
  68. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs_manager.pyi +0 -0
  69. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_object.py +0 -0
  70. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/__init__.py +0 -0
  71. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/manager.py +0 -0
  72. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/pty.py +0 -0
  73. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/rich.py +0 -0
  74. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_partial_function.py +0 -0
  75. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_resolver.py +0 -0
  76. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_resources.py +0 -0
  77. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/__init__.py +0 -0
  78. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/asgi.py +0 -0
  79. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/container_io_manager.py +0 -0
  80. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/container_io_manager.pyi +0 -0
  81. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/execution_context.pyi +0 -0
  82. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  83. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/task_lifecycle_manager.py +0 -0
  84. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  85. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/telemetry.py +0 -0
  86. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/user_code_event_loop.py +0 -0
  87. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/user_code_imports.py +0 -0
  88. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_serialization.py +0 -0
  89. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_server.py +0 -0
  90. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_supports_logs.py +0 -0
  91. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_traceback.py +0 -0
  92. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_tunnel.py +0 -0
  93. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_tunnel.pyi +0 -0
  94. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_type_manager.py +0 -0
  95. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/__init__.py +0 -0
  96. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/app_utils.py +0 -0
  97. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/async_utils.py +0 -0
  98. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/blob_utils.py +0 -0
  99. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/browser_utils.py +0 -0
  100. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/bytes_io_segment_payload.py +0 -0
  101. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/curl_utils.py +0 -0
  102. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/deprecation.py +0 -0
  103. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/display_utils.py +0 -0
  104. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/docker_utils.py +0 -0
  105. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/function_utils.py +0 -0
  106. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/git_utils.py +0 -0
  107. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/grpc_testing.py +0 -0
  108. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/hash_utils.py +0 -0
  109. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/http_utils.py +0 -0
  110. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/jwt_utils.py +0 -0
  111. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/logger.py +0 -0
  112. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/mount_utils.py +0 -0
  113. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/name_utils.py +0 -0
  114. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/package_utils.py +0 -0
  115. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/pattern_utils.py +0 -0
  116. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/proxy_support.py +0 -0
  117. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/rand_pb_testing.py +0 -0
  118. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/sandbox_fs_utils.py +0 -0
  119. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/shell_utils.py +0 -0
  120. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/time_utils.py +0 -0
  121. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/__init__.py +0 -0
  122. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  123. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/cloudpickle.py +0 -0
  124. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/tblib.py +0 -0
  125. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/version.py +0 -0
  126. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_watcher.py +0 -0
  127. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_workspace.py +0 -0
  128. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/app.py +0 -0
  129. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/app.pyi +0 -0
  130. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/billing.py +0 -0
  131. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/billing.pyi +0 -0
  132. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2023.12.312.txt +0 -0
  133. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2023.12.txt +0 -0
  134. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2024.04.txt +0 -0
  135. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2024.10.txt +0 -0
  136. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2025.06.txt +0 -0
  137. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/PREVIEW.txt +0 -0
  138. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/README.md +0 -0
  139. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/base-images.json +0 -0
  140. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/call_graph.py +0 -0
  141. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/__init__.py +0 -0
  142. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_download.py +0 -0
  143. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_help.py +0 -0
  144. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_stats.py +0 -0
  145. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_traceback.py +0 -0
  146. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/billing.py +0 -0
  147. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/bootstrap.py +0 -0
  148. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/changelog.py +0 -0
  149. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/cluster.py +0 -0
  150. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/config.py +0 -0
  151. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/curl.py +0 -0
  152. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/dashboard.py +0 -0
  153. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/dict.py +0 -0
  154. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/endpoint.py +0 -0
  155. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/environment.py +0 -0
  156. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/launch.py +0 -0
  157. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/logo.py +0 -0
  158. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/network_file_system.py +0 -0
  159. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/__init__.py +0 -0
  160. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/run_jupyter.py +0 -0
  161. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/vscode.py +0 -0
  162. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/queues.py +0 -0
  163. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/run.py +0 -0
  164. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/secret.py +0 -0
  165. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/selector.py +0 -0
  166. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/shell.py +0 -0
  167. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/skills.py +0 -0
  168. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/volume.py +0 -0
  169. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/workspace.py +0 -0
  170. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cloud_bucket_mount.py +0 -0
  171. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cloud_bucket_mount.pyi +0 -0
  172. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cls.py +0 -0
  173. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cls.pyi +0 -0
  174. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/container_process.py +0 -0
  175. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/container_process.pyi +0 -0
  176. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/dict.py +0 -0
  177. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/dict.pyi +0 -0
  178. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/environments.py +0 -0
  179. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/environments.pyi +0 -0
  180. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/__init__.py +0 -0
  181. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/flash.py +0 -0
  182. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/flash.pyi +0 -0
  183. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/ipython.py +0 -0
  184. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/file_pattern_matcher.py +0 -0
  185. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/functions.py +0 -0
  186. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/image.py +0 -0
  187. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/image.pyi +0 -0
  188. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/mount.py +0 -0
  189. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/mount.pyi +0 -0
  190. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/network_file_system.py +0 -0
  191. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/network_file_system.pyi +0 -0
  192. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/object.py +0 -0
  193. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/object.pyi +0 -0
  194. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/output.py +0 -0
  195. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/parallel_map.py +0 -0
  196. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/parallel_map.pyi +0 -0
  197. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/partial_function.py +0 -0
  198. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/partial_function.pyi +0 -0
  199. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/proxy.py +0 -0
  200. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/proxy.pyi +0 -0
  201. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/py.typed +0 -0
  202. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/queue.py +0 -0
  203. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/queue.pyi +0 -0
  204. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/retries.py +0 -0
  205. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/runner.pyi +0 -0
  206. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/running_app.py +0 -0
  207. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox_fs.py +0 -0
  208. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox_fs.pyi +0 -0
  209. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/schedule.py +0 -0
  210. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/scheduler_placement.py +0 -0
  211. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/secret.py +0 -0
  212. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/secret.pyi +0 -0
  213. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/server.py +0 -0
  214. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/server.pyi +0 -0
  215. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/serving.py +0 -0
  216. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/serving.pyi +0 -0
  217. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/skills/modal/SKILL.md +0 -0
  218. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/snapshot.py +0 -0
  219. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/snapshot.pyi +0 -0
  220. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/stream_type.py +0 -0
  221. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/token_flow.pyi +0 -0
  222. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/types.py +0 -0
  223. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/workspace.py +0 -0
  224. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/workspace.pyi +0 -0
  225. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/dependency_links.txt +0 -0
  226. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/entry_points.txt +0 -0
  227. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/requires.txt +0 -0
  228. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/top_level.txt +0 -0
  229. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/__init__.py +0 -0
  230. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_cli_docs.py +0 -0
  231. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_cli_docs_main.py +0 -0
  232. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_reference_docs_main.py +0 -0
  233. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/__init__.py +0 -0
  234. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/mdmd.py +0 -0
  235. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/signatures.py +0 -0
  236. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/types.py +0 -0
  237. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/__init__.py +0 -0
  238. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/py.typed +0 -0
  239. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_version/__main__.py +0 -0
  240. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/pyproject.toml +0 -0
  241. {modal-1.5.6.dev11 → modal-1.5.6.dev13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev11
3
+ Version: 1.5.6.dev13
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -16,6 +16,7 @@ from google.protobuf.message import Message
16
16
  from grpclib import Status
17
17
  from synchronicity.combined_types import MethodWithAio
18
18
 
19
+ from modal._utils.logger import logger
19
20
  from modal.types import CloudBucketMountInfo, FunctionInfo, HttpInfo, VolumeMountInfo
20
21
  from modal_proto import api_pb2
21
22
  from modal_proto.modal_api_grpc import ModalClientModal
@@ -63,7 +64,7 @@ from ._utils.function_utils import (
63
64
  parse_gpu_config,
64
65
  validate_target_concurrency,
65
66
  )
66
- from ._utils.grpc_utils import Retry, RetryWarningMessage
67
+ from ._utils.grpc_utils import Retry, RetryWarningMessage, is_class_function_lookup_error
67
68
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
68
69
  from .call_graph import InputInfo, _reconstruct_call_graph
69
70
  from .client import _Client
@@ -1360,9 +1361,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1360
1361
  env_context = (
1361
1362
  f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
1362
1363
  )
1363
- raise NotFoundError(
1364
- f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
1365
- ) from None
1364
+ msg = f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
1365
+ if is_class_function_lookup_error(exc):
1366
+ msg = (
1367
+ f"Function '{name}' from the '{app_name}' app{env_context} is a modal.Cls. "
1368
+ f"Use `modal.Cls.from_name` instead."
1369
+ )
1370
+ raise NotFoundError(msg) from None
1366
1371
 
1367
1372
  print_server_warnings(response.server_warnings)
1368
1373
 
@@ -2306,7 +2311,8 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2306
2311
  """Fetch information about the graph of Inputs this FunctionCall is part of.
2307
2312
 
2308
2313
  Note: the call graph data is not populated in real-time, and its capture is best-effort.
2309
- We do not recommend relying on this method for critical use cases.
2314
+ Large call graphs may be truncated. We do not recommend relying on this method
2315
+ for critical use cases.
2310
2316
 
2311
2317
  See the [`modal.types`](/docs/sdk/py/latest/types) reference for information
2312
2318
  on the return values.
@@ -2317,6 +2323,11 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2317
2323
  assert self._client and self._client.stub
2318
2324
  request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
2319
2325
  response = await self._client.stub.FunctionGetCallGraph(request)
2326
+ if response.truncated:
2327
+ logger.warning(
2328
+ f"Call graph for {self.object_id} was truncated; "
2329
+ f"returning the first {len(response.inputs)} inputs. The graph may be incomplete."
2330
+ )
2320
2331
  return _reconstruct_call_graph(response)
2321
2332
 
2322
2333
  @live_method
@@ -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():
@@ -109,7 +109,7 @@ _current_input_id: ContextVar = ContextVar("_current_input_id")
109
109
  _current_function_call_id: ContextVar = ContextVar("_current_function_call_id")
110
110
  _current_attempt_token: ContextVar = ContextVar("_current_attempt_token")
111
111
 
112
- _is_currently_importing = False # we set this to True while a container is importing user code
112
+ _is_currently_importing = False
113
113
 
114
114
 
115
115
  @contextmanager
@@ -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)