modal 1.5.4.dev22__tar.gz → 1.5.5.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 (236) hide show
  1. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/PKG-INFO +1 -1
  2. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_functions.py +12 -2
  3. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_image.py +4 -1
  4. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs_manager.py +124 -9
  5. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs_manager.pyi +267 -1
  6. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/asgi.py +1 -1
  7. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/container_io_manager.py +9 -6
  8. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/container_io_manager.pyi +6 -5
  9. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_supports_logs.py +1 -3
  10. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/app.py +29 -0
  11. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/app.pyi +47 -0
  12. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/volume.py +7 -7
  13. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/client.pyi +2 -2
  14. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/__init__.py +3 -2
  15. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/functions.pyi +6 -6
  16. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox.py +3 -4
  17. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox.pyi +9 -12
  18. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/types.py +4 -3
  19. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2.py +931 -924
  21. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2.pyi +22 -1
  22. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_version/__init__.py +1 -1
  23. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/pyproject.toml +1 -1
  24. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/LICENSE +0 -0
  25. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/README.md +0 -0
  26. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/__init__.py +0 -0
  27. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/__main__.py +0 -0
  28. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_billing.py +0 -0
  29. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_clustered_functions.py +0 -0
  30. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_clustered_functions.pyi +0 -0
  31. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_container_entrypoint.py +0 -0
  32. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_environments.py +0 -0
  33. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_function_variants.py +0 -0
  34. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_grpc_client.py +0 -0
  35. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_ipython.py +0 -0
  36. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_load_context.py +0 -0
  37. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_location.py +0 -0
  38. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs.py +0 -0
  39. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_object.py +0 -0
  40. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/__init__.py +0 -0
  41. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/manager.py +0 -0
  42. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/pty.py +0 -0
  43. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/rich.py +0 -0
  44. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/status.py +0 -0
  45. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_partial_function.py +0 -0
  46. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_resolver.py +0 -0
  47. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_resources.py +0 -0
  48. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/__init__.py +0 -0
  49. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/execution_context.py +0 -0
  50. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/execution_context.pyi +0 -0
  51. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  52. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
  53. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  54. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/telemetry.py +0 -0
  55. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  56. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/user_code_imports.py +0 -0
  57. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_serialization.py +0 -0
  58. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_server.py +0 -0
  59. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_traceback.py +0 -0
  60. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_tunnel.py +0 -0
  61. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_tunnel.pyi +0 -0
  62. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_type_manager.py +0 -0
  63. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/__init__.py +0 -0
  64. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/app_utils.py +0 -0
  65. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/async_utils.py +0 -0
  66. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/auth_token_manager.py +0 -0
  67. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/blob_utils.py +0 -0
  68. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/browser_utils.py +0 -0
  69. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  70. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/curl_utils.py +0 -0
  71. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/deprecation.py +0 -0
  72. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/display_utils.py +0 -0
  73. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/docker_utils.py +0 -0
  74. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/function_utils.py +0 -0
  75. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/git_utils.py +0 -0
  76. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/grpc_testing.py +0 -0
  77. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/grpc_utils.py +0 -0
  78. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/hash_utils.py +0 -0
  79. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/http_utils.py +0 -0
  80. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/jwt_utils.py +0 -0
  81. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/logger.py +0 -0
  82. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/mount_utils.py +0 -0
  83. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/name_utils.py +0 -0
  84. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/package_utils.py +0 -0
  85. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/pattern_utils.py +0 -0
  86. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/proxy_support.py +0 -0
  87. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  88. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
  89. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/shell_utils.py +0 -0
  90. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/task_command_router_client.py +0 -0
  91. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/time_utils.py +0 -0
  92. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/__init__.py +0 -0
  93. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  94. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/cloudpickle.py +0 -0
  95. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/tblib.py +0 -0
  96. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/version.py +0 -0
  97. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_watcher.py +0 -0
  98. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_workspace.py +0 -0
  99. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/billing.py +0 -0
  100. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/billing.pyi +0 -0
  101. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2023.12.312.txt +0 -0
  102. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2023.12.txt +0 -0
  103. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2024.04.txt +0 -0
  104. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2024.10.txt +0 -0
  105. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2025.06.txt +0 -0
  106. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/PREVIEW.txt +0 -0
  107. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/README.md +0 -0
  108. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/base-images.json +0 -0
  109. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/call_graph.py +0 -0
  110. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/__init__.py +0 -0
  111. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_download.py +0 -0
  112. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_help.py +0 -0
  113. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_traceback.py +0 -0
  114. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/app.py +0 -0
  115. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/billing.py +0 -0
  116. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/bootstrap.py +0 -0
  117. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/changelog.py +0 -0
  118. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/cluster.py +0 -0
  119. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/config.py +0 -0
  120. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/container.py +0 -0
  121. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/curl.py +0 -0
  122. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/dashboard.py +0 -0
  123. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/dict.py +0 -0
  124. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/endpoint.py +0 -0
  125. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/entry_point.py +0 -0
  126. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/environment.py +0 -0
  127. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/image.py +0 -0
  128. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/import_refs.py +0 -0
  129. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/launch.py +0 -0
  130. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/logo.py +0 -0
  131. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/network_file_system.py +0 -0
  132. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/profile.py +0 -0
  133. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/__init__.py +0 -0
  134. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  135. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/vscode.py +0 -0
  136. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/queues.py +0 -0
  137. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/run.py +0 -0
  138. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/secret.py +0 -0
  139. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/selector.py +0 -0
  140. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/shell.py +0 -0
  141. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/skills.py +0 -0
  142. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/token.py +0 -0
  143. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/utils.py +0 -0
  144. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/workspace.py +0 -0
  145. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/client.py +0 -0
  146. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cloud_bucket_mount.py +0 -0
  147. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  148. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cls.py +0 -0
  149. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cls.pyi +0 -0
  150. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/config.py +0 -0
  151. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/container_process.py +0 -0
  152. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/container_process.pyi +0 -0
  153. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/dict.py +0 -0
  154. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/dict.pyi +0 -0
  155. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/environments.py +0 -0
  156. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/environments.pyi +0 -0
  157. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/exception.py +0 -0
  158. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/flash.py +0 -0
  159. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/flash.pyi +0 -0
  160. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/ipython.py +0 -0
  161. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_io.py +0 -0
  162. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_io.pyi +0 -0
  163. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_pattern_matcher.py +0 -0
  164. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/functions.py +0 -0
  165. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/image.py +0 -0
  166. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/image.pyi +0 -0
  167. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/io_streams.py +0 -0
  168. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/io_streams.pyi +0 -0
  169. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/mount.py +0 -0
  170. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/mount.pyi +0 -0
  171. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/network_file_system.py +0 -0
  172. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/network_file_system.pyi +0 -0
  173. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/object.py +0 -0
  174. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/object.pyi +0 -0
  175. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/output.py +0 -0
  176. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/parallel_map.py +0 -0
  177. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/parallel_map.pyi +0 -0
  178. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/partial_function.py +0 -0
  179. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/partial_function.pyi +0 -0
  180. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/proxy.py +0 -0
  181. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/proxy.pyi +0 -0
  182. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/py.typed +0 -0
  183. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/queue.py +0 -0
  184. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/queue.pyi +0 -0
  185. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/retries.py +0 -0
  186. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/runner.py +0 -0
  187. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/runner.pyi +0 -0
  188. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/running_app.py +0 -0
  189. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox_fs.py +0 -0
  190. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox_fs.pyi +0 -0
  191. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/schedule.py +0 -0
  192. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/scheduler_placement.py +0 -0
  193. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/secret.py +0 -0
  194. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/secret.pyi +0 -0
  195. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/server.py +0 -0
  196. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/server.pyi +0 -0
  197. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/serving.py +0 -0
  198. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/serving.pyi +0 -0
  199. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/skills/modal/SKILL.md +0 -0
  200. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/snapshot.py +0 -0
  201. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/snapshot.pyi +0 -0
  202. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/stream_type.py +0 -0
  203. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/token_flow.py +0 -0
  204. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/token_flow.pyi +0 -0
  205. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/volume.py +0 -0
  206. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/volume.pyi +0 -0
  207. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/workspace.py +0 -0
  208. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/workspace.pyi +0 -0
  209. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/SOURCES.txt +0 -0
  210. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/dependency_links.txt +0 -0
  211. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/entry_points.txt +0 -0
  212. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/requires.txt +0 -0
  213. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/top_level.txt +0 -0
  214. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/__init__.py +0 -0
  215. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_cli_docs.py +0 -0
  216. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  217. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_reference_docs.py +0 -0
  218. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  219. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/__init__.py +0 -0
  220. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  221. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/signatures.py +0 -0
  222. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/types.py +0 -0
  223. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/reference_intro.md +0 -0
  224. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/__init__.py +0 -0
  225. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_grpc.py +0 -0
  226. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2_grpc.py +0 -0
  227. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
  228. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/modal_api_grpc.py +0 -0
  229. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/py.typed +0 -0
  230. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  231. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  232. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  233. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  234. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  235. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_version/__main__.py +0 -0
  236. {modal-1.5.4.dev22 → modal-1.5.5.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.4.dev22
3
+ Version: 1.5.5.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
@@ -640,7 +640,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
640
640
  await self.hydrate()
641
641
  if not self._app_id:
642
642
  raise ExecutionError("app_id should have been set during function hydration")
643
- return _LogQueryData(self.client, self._app_id, LogsFilters(function_id=self.object_id))
643
+ return _LogQueryData(
644
+ self.client,
645
+ self._app_id,
646
+ LogsFilters(function_id=self.object_id),
647
+ source_object_id=self.object_id,
648
+ )
644
649
 
645
650
  @property
646
651
  def logs(self) -> _FunctionLogsManager:
@@ -2077,7 +2082,12 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2077
2082
  await self._hydrate_from_id_metadata()
2078
2083
  if not self._app_id:
2079
2084
  raise ExecutionError("app_id should have been set during function call hydration")
2080
- return _LogQueryData(self.client, self._app_id, LogsFilters(function_call_id=self.object_id))
2085
+ return _LogQueryData(
2086
+ self.client,
2087
+ self._app_id,
2088
+ LogsFilters(function_call_id=self.object_id),
2089
+ source_object_id=self.object_id,
2090
+ )
2081
2091
 
2082
2092
  @property
2083
2093
  def logs(self) -> _FunctionCallLogsManager:
@@ -764,7 +764,10 @@ class _Image(_Object, type_prefix="im"):
764
764
  if build_resp.HasField("metadata"):
765
765
  metadata = build_resp.metadata
766
766
 
767
- if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
767
+ if result.status in (
768
+ api_pb2.GenericResult.GENERIC_STATUS_FAILURE,
769
+ api_pb2.GenericResult.GENERIC_STATUS_MEMORY_MANAGER_EVICTION,
770
+ ):
768
771
  if result.exception:
769
772
  raise ImageBuildError(
770
773
  f"Image build for {image_id} failed with the exception:\n{result.exception}", image_id
@@ -78,6 +78,13 @@ def _resolve_source(source: LogSource | None) -> api_pb2.FileDescriptor.ValueTyp
78
78
 
79
79
  def _entry_context_ids(object_id: str, item: api_pb2.TaskLogs, batch: api_pb2.TaskLogsBatch) -> list[str]:
80
80
  match object_id[:2]:
81
+ case "ap":
82
+ context_ids = [
83
+ batch.function_id,
84
+ item.function_call_id,
85
+ item.input_id or batch.input_id,
86
+ item.container_id or batch.task_id,
87
+ ]
81
88
  case "fu":
82
89
  context_ids = [
83
90
  item.function_call_id,
@@ -140,10 +147,14 @@ class _LogsManager:
140
147
  since = _normalize_utc_datetime(since, "since")
141
148
  until = _normalize_utc_datetime(until or datetime.now(timezone.utc), "until")
142
149
  target = await self._params()
143
- filters = dataclasses.replace(target.filters, source=_resolve_source(source), search_text=search_text)
150
+ filters = dataclasses.replace(
151
+ target.filters,
152
+ source=_resolve_source(source),
153
+ search_text=search_text,
154
+ )
144
155
  async for batch in fetch_logs(target.client, target.app_id, since=since, until=until, filters=filters):
145
156
  for item in batch.items:
146
- yield _entry_from_item(self._source.object_id, item, batch)
157
+ yield _entry_from_item(target.source_object_id, item, batch)
147
158
 
148
159
  async def tail(
149
160
  self,
@@ -161,7 +172,7 @@ class _LogsManager:
161
172
  filters=filters,
162
173
  ):
163
174
  for item in batch.items:
164
- yield _entry_from_item(self._source.object_id, item, batch)
175
+ yield _entry_from_item(params.source_object_id, item, batch)
165
176
 
166
177
  def _create_log_stream(
167
178
  self, params: _LogQueryData, last_entry_id: str, timeout: float
@@ -179,10 +190,10 @@ class _LogsManager:
179
190
  )
180
191
  return params.client.stub.AppGetLogs.unary_stream(request)
181
192
 
182
- def _stream_entries(self, batch: api_pb2.TaskLogsBatch):
193
+ def _stream_entries(self, batch: api_pb2.TaskLogsBatch, source_object_id: str):
183
194
  for item in batch.items:
184
195
  if item.data:
185
- yield _entry_from_item(self._source.object_id, item, batch)
196
+ yield _entry_from_item(source_object_id, item, batch)
186
197
 
187
198
  @staticmethod
188
199
  def _advance_batch(batch: api_pb2.TaskLogsBatch, last_entry_id: str) -> tuple[str, bool]:
@@ -205,7 +216,7 @@ class _LogsManager:
205
216
  log_stream = self._create_log_stream(params, last_entry_id, timeout)
206
217
  try:
207
218
  async for batch in log_stream:
208
- for entry in self._stream_entries(batch):
219
+ for entry in self._stream_entries(batch, params.source_object_id):
209
220
  yield entry
210
221
  if batch.app_done:
211
222
  return
@@ -243,7 +254,7 @@ class _LogsManager:
243
254
  except StopAsyncIteration:
244
255
  break
245
256
  last_entry_id, app_done = self._advance_batch(batch, last_entry_id)
246
- for log_entry in self._stream_entries(batch):
257
+ for log_entry in self._stream_entries(batch, params.source_object_id):
247
258
  deadline.reset(timeout)
248
259
  yield log_entry
249
260
  if app_done:
@@ -262,7 +273,7 @@ class _LogsManager:
262
273
  except StopAsyncIteration:
263
274
  break
264
275
  last_entry_id, app_done = self._advance_batch(batch, last_entry_id)
265
- for log_entry in self._stream_entries(batch):
276
+ for log_entry in self._stream_entries(batch, params.source_object_id):
266
277
  deadline.reset(timeout)
267
278
  yield log_entry
268
279
  if app_done:
@@ -538,7 +549,7 @@ class _FunctionCallLogsManager:
538
549
  await asyncio.sleep(1)
539
550
  continue
540
551
  raise
541
- raise NotFoundError(f"Function call {self._source.object_id} not found after retries.")
552
+ raise AssertionError("unreachable")
542
553
 
543
554
  async def _determine_function_call_stop(self) -> bool:
544
555
  try:
@@ -789,3 +800,107 @@ class _ImageLogsManager:
789
800
 
790
801
 
791
802
  ImageLogsManager = synchronize_api(_ImageLogsManager, target_module=__name__)
803
+
804
+
805
+ class _AppLogsManager:
806
+ """mdmd:namespace"""
807
+
808
+ def __init__(self, source: _SupportsLogs):
809
+ """mdmd:hidden"""
810
+ self._manager = _LogsManager(source)
811
+
812
+ async def fetch(
813
+ self,
814
+ *,
815
+ since: datetime,
816
+ until: datetime | None = None,
817
+ source: LogSource | None = None,
818
+ search_text: str = "",
819
+ ) -> AsyncGenerator[LogEntry, None]:
820
+ """Fetch App logs corresponding to the date range and filters.
821
+
822
+ Args:
823
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
824
+ which is interpreted as local time.
825
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
826
+ as local time.
827
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
828
+ search_text: Filter by search text.
829
+
830
+ Yields:
831
+ `LogEntry` objects in chronological order.
832
+
833
+ Examples:
834
+
835
+ ```python notest
836
+ app = modal.App.lookup("my-app")
837
+
838
+ for entry in app.logs.fetch(
839
+ since=datetime.now() - timedelta(hours=4),
840
+ source="stdout",
841
+ ):
842
+ print(entry.message, end="")
843
+ ```
844
+ """
845
+ async for log_entry in self._manager.fetch(
846
+ since=since,
847
+ until=until,
848
+ source=source,
849
+ search_text=search_text,
850
+ ):
851
+ yield log_entry
852
+
853
+ async def tail(
854
+ self,
855
+ entries: int = 100,
856
+ *,
857
+ source: LogSource | None = None,
858
+ ) -> AsyncGenerator[LogEntry, None]:
859
+ """Fetch the most recent App logs.
860
+
861
+ Args:
862
+ entries: The number of log entries to return.
863
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
864
+
865
+ Yields:
866
+ `LogEntry` objects in chronological order.
867
+
868
+ Examples:
869
+
870
+ ```python notest
871
+ app = modal.App.lookup("my-app")
872
+
873
+ for entry in app.logs.tail(20):
874
+ print(entry.message, end="")
875
+ ```
876
+ """
877
+ async for log_entry in self._manager.tail(
878
+ entries,
879
+ source=source,
880
+ ):
881
+ yield log_entry
882
+
883
+ async def stream(self, timeout: float | None = None) -> AsyncGenerator[LogEntry, None]:
884
+ """Stream new App logs until the timeout is reached.
885
+
886
+ Args:
887
+ timeout: Number of seconds to wait between log entries before terminating the stream.
888
+ By default, this will block until it is interrupted.
889
+
890
+ Yields:
891
+ `LogEntry` objects as they arrive.
892
+
893
+ Examples:
894
+
895
+ ```python notest
896
+ app = modal.App.lookup("my-app")
897
+
898
+ for entry in app.logs.stream(timeout=60):
899
+ print(entry.message, end="")
900
+ ```
901
+ """
902
+ async for log_entry in self._manager.stream(timeout=timeout):
903
+ yield log_entry
904
+
905
+
906
+ AppLogsManager = synchronize_api(_AppLogsManager, target_module=__name__)
@@ -78,7 +78,7 @@ class _LogsManager:
78
78
  """Helper for creating the log stream RPC."""
79
79
  ...
80
80
 
81
- def _stream_entries(self, batch: modal_proto.api_pb2.TaskLogsBatch): ...
81
+ def _stream_entries(self, batch: modal_proto.api_pb2.TaskLogsBatch, source_object_id: str): ...
82
82
  @staticmethod
83
83
  def _advance_batch(batch: modal_proto.api_pb2.TaskLogsBatch, last_entry_id: str) -> tuple[str, bool]: ...
84
84
  @staticmethod
@@ -995,3 +995,269 @@ class ImageLogsManager:
995
995
  ...
996
996
 
997
997
  tail: __tail_spec
998
+
999
+ class _AppLogsManager:
1000
+ """mdmd:namespace"""
1001
+ def __init__(self, source: modal._supports_logs._SupportsLogs):
1002
+ """mdmd:hidden"""
1003
+ ...
1004
+
1005
+ def fetch(
1006
+ self,
1007
+ *,
1008
+ since: datetime.datetime,
1009
+ until: typing.Optional[datetime.datetime] = None,
1010
+ source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
1011
+ search_text: str = "",
1012
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1013
+ """Fetch App logs corresponding to the date range and filters.
1014
+
1015
+ Args:
1016
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
1017
+ which is interpreted as local time.
1018
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
1019
+ as local time.
1020
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1021
+ search_text: Filter by search text.
1022
+
1023
+ Yields:
1024
+ `LogEntry` objects in chronological order.
1025
+
1026
+ Examples:
1027
+
1028
+ ```python notest
1029
+ app = modal.App.lookup("my-app")
1030
+
1031
+ for entry in app.logs.fetch(
1032
+ since=datetime.now() - timedelta(hours=4),
1033
+ source="stdout",
1034
+ ):
1035
+ print(entry.message, end="")
1036
+ ```
1037
+ """
1038
+ ...
1039
+
1040
+ def tail(
1041
+ self, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
1042
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1043
+ """Fetch the most recent App logs.
1044
+
1045
+ Args:
1046
+ entries: The number of log entries to return.
1047
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1048
+
1049
+ Yields:
1050
+ `LogEntry` objects in chronological order.
1051
+
1052
+ Examples:
1053
+
1054
+ ```python notest
1055
+ app = modal.App.lookup("my-app")
1056
+
1057
+ for entry in app.logs.tail(20):
1058
+ print(entry.message, end="")
1059
+ ```
1060
+ """
1061
+ ...
1062
+
1063
+ def stream(
1064
+ self, timeout: typing.Optional[float] = None
1065
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1066
+ """Stream new App logs until the timeout is reached.
1067
+
1068
+ Args:
1069
+ timeout: Number of seconds to wait between log entries before terminating the stream.
1070
+ By default, this will block until it is interrupted.
1071
+
1072
+ Yields:
1073
+ `LogEntry` objects as they arrive.
1074
+
1075
+ Examples:
1076
+
1077
+ ```python notest
1078
+ app = modal.App.lookup("my-app")
1079
+
1080
+ for entry in app.logs.stream(timeout=60):
1081
+ print(entry.message, end="")
1082
+ ```
1083
+ """
1084
+ ...
1085
+
1086
+ class AppLogsManager:
1087
+ """mdmd:namespace"""
1088
+ def __init__(self, source: modal._supports_logs._SupportsLogs):
1089
+ """mdmd:hidden"""
1090
+ ...
1091
+
1092
+ class __fetch_spec(typing_extensions.Protocol):
1093
+ def __call__(
1094
+ self,
1095
+ /,
1096
+ *,
1097
+ since: datetime.datetime,
1098
+ until: typing.Optional[datetime.datetime] = None,
1099
+ source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
1100
+ search_text: str = "",
1101
+ ) -> typing.Generator[modal.types.LogEntry, None, None]:
1102
+ """Fetch App logs corresponding to the date range and filters.
1103
+
1104
+ Args:
1105
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
1106
+ which is interpreted as local time.
1107
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
1108
+ as local time.
1109
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1110
+ search_text: Filter by search text.
1111
+
1112
+ Yields:
1113
+ `LogEntry` objects in chronological order.
1114
+
1115
+ Examples:
1116
+
1117
+ ```python notest
1118
+ app = modal.App.lookup("my-app")
1119
+
1120
+ for entry in app.logs.fetch(
1121
+ since=datetime.now() - timedelta(hours=4),
1122
+ source="stdout",
1123
+ ):
1124
+ print(entry.message, end="")
1125
+ ```
1126
+ """
1127
+ ...
1128
+
1129
+ def aio(
1130
+ self,
1131
+ /,
1132
+ *,
1133
+ since: datetime.datetime,
1134
+ until: typing.Optional[datetime.datetime] = None,
1135
+ source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
1136
+ search_text: str = "",
1137
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1138
+ """Fetch App logs corresponding to the date range and filters.
1139
+
1140
+ Args:
1141
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
1142
+ which is interpreted as local time.
1143
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
1144
+ as local time.
1145
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1146
+ search_text: Filter by search text.
1147
+
1148
+ Yields:
1149
+ `LogEntry` objects in chronological order.
1150
+
1151
+ Examples:
1152
+
1153
+ ```python notest
1154
+ app = modal.App.lookup("my-app")
1155
+
1156
+ for entry in app.logs.fetch(
1157
+ since=datetime.now() - timedelta(hours=4),
1158
+ source="stdout",
1159
+ ):
1160
+ print(entry.message, end="")
1161
+ ```
1162
+ """
1163
+ ...
1164
+
1165
+ fetch: __fetch_spec
1166
+
1167
+ class __tail_spec(typing_extensions.Protocol):
1168
+ def __call__(
1169
+ self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
1170
+ ) -> typing.Generator[modal.types.LogEntry, None, None]:
1171
+ """Fetch the most recent App logs.
1172
+
1173
+ Args:
1174
+ entries: The number of log entries to return.
1175
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1176
+
1177
+ Yields:
1178
+ `LogEntry` objects in chronological order.
1179
+
1180
+ Examples:
1181
+
1182
+ ```python notest
1183
+ app = modal.App.lookup("my-app")
1184
+
1185
+ for entry in app.logs.tail(20):
1186
+ print(entry.message, end="")
1187
+ ```
1188
+ """
1189
+ ...
1190
+
1191
+ def aio(
1192
+ self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
1193
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1194
+ """Fetch the most recent App logs.
1195
+
1196
+ Args:
1197
+ entries: The number of log entries to return.
1198
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1199
+
1200
+ Yields:
1201
+ `LogEntry` objects in chronological order.
1202
+
1203
+ Examples:
1204
+
1205
+ ```python notest
1206
+ app = modal.App.lookup("my-app")
1207
+
1208
+ for entry in app.logs.tail(20):
1209
+ print(entry.message, end="")
1210
+ ```
1211
+ """
1212
+ ...
1213
+
1214
+ tail: __tail_spec
1215
+
1216
+ class __stream_spec(typing_extensions.Protocol):
1217
+ def __call__(
1218
+ self, /, timeout: typing.Optional[float] = None
1219
+ ) -> typing.Generator[modal.types.LogEntry, None, None]:
1220
+ """Stream new App logs until the timeout is reached.
1221
+
1222
+ Args:
1223
+ timeout: Number of seconds to wait between log entries before terminating the stream.
1224
+ By default, this will block until it is interrupted.
1225
+
1226
+ Yields:
1227
+ `LogEntry` objects as they arrive.
1228
+
1229
+ Examples:
1230
+
1231
+ ```python notest
1232
+ app = modal.App.lookup("my-app")
1233
+
1234
+ for entry in app.logs.stream(timeout=60):
1235
+ print(entry.message, end="")
1236
+ ```
1237
+ """
1238
+ ...
1239
+
1240
+ def aio(
1241
+ self, /, timeout: typing.Optional[float] = None
1242
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1243
+ """Stream new App logs until the timeout is reached.
1244
+
1245
+ Args:
1246
+ timeout: Number of seconds to wait between log entries before terminating the stream.
1247
+ By default, this will block until it is interrupted.
1248
+
1249
+ Yields:
1250
+ `LogEntry` objects as they arrive.
1251
+
1252
+ Examples:
1253
+
1254
+ ```python notest
1255
+ app = modal.App.lookup("my-app")
1256
+
1257
+ for entry in app.logs.stream(timeout=60):
1258
+ print(entry.message, end="")
1259
+ ```
1260
+ """
1261
+ ...
1262
+
1263
+ stream: __stream_spec
@@ -523,6 +523,6 @@ def web_server_proxy(host: str, port: int):
523
523
  except aiohttp.ClientConnectorError as exc:
524
524
  # If the server is not running or not reachable, we should stop fetching new inputs.
525
525
  logger.warning(f"Terminating runner due to @web_server connection issue: {exc}")
526
- stop_fetching_inputs()
526
+ await stop_fetching_inputs.aio() # type: ignore
527
527
 
528
528
  return web_server_proxy_app
@@ -554,7 +554,6 @@ class _ContainerIOManager:
554
554
  _waiting_for_memory_snapshot: bool
555
555
 
556
556
  _is_interactivity_enabled: bool
557
- _fetching_inputs: bool
558
557
 
559
558
  _singleton: ClassVar["_ContainerIOManager | None"] = None
560
559
 
@@ -595,7 +594,6 @@ class _ContainerIOManager:
595
594
  self._waiting_for_memory_snapshot = False
596
595
 
597
596
  self._is_interactivity_enabled = False
598
- self._fetching_inputs = True
599
597
 
600
598
  self._max_object_size_bytes, self._max_async_object_size_bytes = _resolve_output_size_limits(container_args)
601
599
 
@@ -863,7 +861,7 @@ class _ContainerIOManager:
863
861
  ]:
864
862
  request = api_pb2.FunctionGetInputsRequest(function_id=self.function_id)
865
863
  iteration = 0
866
- while self._fetching_inputs:
864
+ while True:
867
865
  await self._input_slots.acquire()
868
866
 
869
867
  request.input_concurrency = self.get_input_concurrency()
@@ -1112,10 +1110,15 @@ class _ContainerIOManager:
1112
1110
  io_manager._input_slots.set_value(concurrency)
1113
1111
 
1114
1112
  @classmethod
1115
- def stop_fetching_inputs(cls):
1116
- if not cls._singleton:
1113
+ async def stop_fetching_inputs(cls):
1114
+ io_manager = cls._singleton
1115
+ if not io_manager:
1117
1116
  raise RuntimeError("Must be called from within a Modal container.")
1118
- cls._singleton._fetching_inputs = False
1117
+ request = api_pb2.ContainerStopRequest(task_id=io_manager.task_id, graceful=True)
1118
+ await io_manager._client.stub.ContainerStop(
1119
+ request,
1120
+ retry=Retry(total_timeout=5.0),
1121
+ )
1119
1122
 
1120
1123
 
1121
1124
  ContainerIOManager = synchronize_api(_ContainerIOManager)
@@ -124,7 +124,6 @@ class _ContainerIOManager:
124
124
  _heartbeat_condition: typing.Optional[asyncio.locks.Condition]
125
125
  _waiting_for_memory_snapshot: bool
126
126
  _is_interactivity_enabled: bool
127
- _fetching_inputs: bool
128
127
  _singleton: typing.ClassVar[typing.Optional[_ContainerIOManager]]
129
128
 
130
129
  def _init(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client._Client): ...
@@ -241,7 +240,7 @@ class _ContainerIOManager:
241
240
  ...
242
241
 
243
242
  @classmethod
244
- def stop_fetching_inputs(cls): ...
243
+ async def stop_fetching_inputs(cls): ...
245
244
 
246
245
  class ContainerIOManager:
247
246
  """Synchronizes all RPC calls and network operations for a running container.
@@ -266,7 +265,6 @@ class ContainerIOManager:
266
265
  _heartbeat_condition: typing.Optional[asyncio.locks.Condition]
267
266
  _waiting_for_memory_snapshot: bool
268
267
  _is_interactivity_enabled: bool
269
- _fetching_inputs: bool
270
268
  _singleton: typing.ClassVar[typing.Optional[ContainerIOManager]]
271
269
 
272
270
  def __init__(self, /, *args, **kwargs):
@@ -515,7 +513,10 @@ class ContainerIOManager:
515
513
  """
516
514
  ...
517
515
 
518
- @classmethod
519
- def stop_fetching_inputs(cls): ...
516
+ class __stop_fetching_inputs_spec(typing_extensions.Protocol):
517
+ def __call__(self, /): ...
518
+ async def aio(self, /): ...
519
+
520
+ stop_fetching_inputs: typing.ClassVar[__stop_fetching_inputs_spec]
520
521
 
521
522
  MAX_OUTPUT_BATCH_SIZE: int
@@ -19,6 +19,7 @@ class _LogQueryData:
19
19
  client: _Client
20
20
  app_id: str
21
21
  filters: LogsFilters
22
+ source_object_id: str
22
23
 
23
24
 
24
25
  class _SupportsLogs(Protocol):
@@ -29,9 +30,6 @@ class _SupportsLogs(Protocol):
29
30
 
30
31
  async def _get_log_query_data(self) -> _LogQueryData: ...
31
32
 
32
- @property
33
- def object_id(self) -> str: ...
34
-
35
33
 
36
34
  @dataclasses.dataclass(frozen=True)
37
35
  class _ImageLogQueryData:
@@ -15,12 +15,14 @@ import typing_extensions
15
15
  from google.protobuf.message import Message
16
16
  from synchronicity.async_wrap import asynccontextmanager
17
17
 
18
+ from modal._logs import LogsFilters
18
19
  from modal_proto import api_pb2
19
20
 
20
21
  from ._functions import _Function
21
22
  from ._image import _Image
22
23
  from ._ipython import is_interactive_ipython
23
24
  from ._load_context import LoadContext
25
+ from ._logs_manager import _AppLogsManager
24
26
  from ._object import _get_environment_name, _Object
25
27
  from ._partial_function import (
26
28
  _find_partial_methods_for_user_cls,
@@ -29,6 +31,7 @@ from ._partial_function import (
29
31
  verify_concurrent_params,
30
32
  )
31
33
  from ._server import _Server, validate_http_server_config
34
+ from ._supports_logs import _LogQueryData
32
35
  from ._utils.async_utils import synchronize_api
33
36
  from ._utils.deprecation import (
34
37
  deprecation_warning,
@@ -1615,5 +1618,31 @@ class _App:
1615
1618
  """Only used for tests."""
1616
1619
  cls._container_app = None
1617
1620
 
1621
+ async def _get_log_query_data(self) -> _LogQueryData:
1622
+ """Get the data needed to query logs for this app."""
1623
+ if not self._app_id:
1624
+ raise InvalidError("`app._logs` requires a running/stopped app.")
1625
+ client = self._client or await _Client.from_env()
1626
+ filters = LogsFilters()
1627
+ return _LogQueryData(client=client, app_id=self._app_id, filters=filters, source_object_id=self._app_id)
1628
+
1629
+ @property
1630
+ def logs(self) -> _AppLogsManager:
1631
+ """Access logs for an `App`.
1632
+
1633
+ Use [`fetch()`](#logsfetch)
1634
+ to read logs from a UTC time range, [`tail()`](#logstail)
1635
+ to read the most recent logs, and [`stream()`](#logsstream)
1636
+ to follow new logs as they arrive.
1637
+
1638
+ See also:
1639
+ - [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
1640
+ CLI access to logs for an App.
1641
+ """
1642
+ if not self._app_id:
1643
+ raise InvalidError("`app.logs` requires a running/stopped app.")
1644
+
1645
+ return _AppLogsManager(self)
1646
+
1618
1647
 
1619
1648
  App = synchronize_api(_App)