modal 1.5.5.dev1__tar.gz → 1.5.5.dev3__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.5.dev1 → modal-1.5.5.dev3}/PKG-INFO +1 -1
  2. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_container_entrypoint.py +4 -2
  3. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_logs_manager.py +67 -0
  4. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_logs_manager.pyi +167 -0
  5. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/container_io_manager.py +44 -23
  6. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/container_io_manager.pyi +10 -5
  7. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/client.pyi +2 -2
  8. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/sandbox.py +53 -0
  9. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/sandbox.pyi +72 -0
  10. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_version/__init__.py +1 -1
  12. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/pyproject.toml +1 -1
  13. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/LICENSE +0 -0
  14. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/README.md +0 -0
  15. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/__init__.py +0 -0
  16. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/__main__.py +0 -0
  17. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_billing.py +0 -0
  18. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_clustered_functions.py +0 -0
  19. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_clustered_functions.pyi +0 -0
  20. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_environments.py +0 -0
  21. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_function_variants.py +0 -0
  22. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_functions.py +0 -0
  23. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_grpc_client.py +0 -0
  24. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_image.py +0 -0
  25. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_ipython.py +0 -0
  26. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_load_context.py +0 -0
  27. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_location.py +0 -0
  28. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_logs.py +0 -0
  29. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_object.py +0 -0
  30. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_output/__init__.py +0 -0
  31. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_output/manager.py +0 -0
  32. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_output/pty.py +0 -0
  33. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_output/rich.py +0 -0
  34. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_output/status.py +0 -0
  35. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_partial_function.py +0 -0
  36. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_resolver.py +0 -0
  37. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_resources.py +0 -0
  38. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/__init__.py +0 -0
  39. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/asgi.py +0 -0
  40. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/execution_context.py +0 -0
  41. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/execution_context.pyi +0 -0
  42. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  43. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/task_lifecycle_manager.py +0 -0
  44. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  45. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/telemetry.py +0 -0
  46. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/user_code_event_loop.py +0 -0
  47. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_runtime/user_code_imports.py +0 -0
  48. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_serialization.py +0 -0
  49. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_server.py +0 -0
  50. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_supports_logs.py +0 -0
  51. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_traceback.py +0 -0
  52. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_tunnel.py +0 -0
  53. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_tunnel.pyi +0 -0
  54. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_type_manager.py +0 -0
  55. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/__init__.py +0 -0
  56. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/app_utils.py +0 -0
  57. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/async_utils.py +0 -0
  58. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/auth_token_manager.py +0 -0
  59. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/blob_utils.py +0 -0
  60. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/browser_utils.py +0 -0
  61. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/bytes_io_segment_payload.py +0 -0
  62. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/curl_utils.py +0 -0
  63. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/deprecation.py +0 -0
  64. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/display_utils.py +0 -0
  65. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/docker_utils.py +0 -0
  66. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/function_utils.py +0 -0
  67. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/git_utils.py +0 -0
  68. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/grpc_testing.py +0 -0
  69. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/grpc_utils.py +0 -0
  70. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/hash_utils.py +0 -0
  71. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/http_utils.py +0 -0
  72. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/jwt_utils.py +0 -0
  73. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/logger.py +0 -0
  74. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/mount_utils.py +0 -0
  75. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/name_utils.py +0 -0
  76. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/package_utils.py +0 -0
  77. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/pattern_utils.py +0 -0
  78. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/proxy_support.py +0 -0
  79. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/rand_pb_testing.py +0 -0
  80. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/sandbox_fs_utils.py +0 -0
  81. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/shell_utils.py +0 -0
  82. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/task_command_router_client.py +0 -0
  83. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_utils/time_utils.py +0 -0
  84. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_vendor/__init__.py +0 -0
  85. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  86. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_vendor/cloudpickle.py +0 -0
  87. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_vendor/tblib.py +0 -0
  88. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_vendor/version.py +0 -0
  89. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_watcher.py +0 -0
  90. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/_workspace.py +0 -0
  91. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/app.py +0 -0
  92. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/app.pyi +0 -0
  93. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/billing.py +0 -0
  94. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/billing.pyi +0 -0
  95. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/2023.12.312.txt +0 -0
  96. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/2023.12.txt +0 -0
  97. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/2024.04.txt +0 -0
  98. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/2024.10.txt +0 -0
  99. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/2025.06.txt +0 -0
  100. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/PREVIEW.txt +0 -0
  101. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/README.md +0 -0
  102. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/builder/base-images.json +0 -0
  103. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/call_graph.py +0 -0
  104. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/__init__.py +0 -0
  105. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/_download.py +0 -0
  106. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/_help.py +0 -0
  107. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/_traceback.py +0 -0
  108. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/app.py +0 -0
  109. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/billing.py +0 -0
  110. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/bootstrap.py +0 -0
  111. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/changelog.py +0 -0
  112. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/cluster.py +0 -0
  113. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/config.py +0 -0
  114. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/container.py +0 -0
  115. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/curl.py +0 -0
  116. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/dashboard.py +0 -0
  117. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/dict.py +0 -0
  118. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/endpoint.py +0 -0
  119. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/entry_point.py +0 -0
  120. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/environment.py +0 -0
  121. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/image.py +0 -0
  122. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/import_refs.py +0 -0
  123. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/launch.py +0 -0
  124. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/logo.py +0 -0
  125. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/network_file_system.py +0 -0
  126. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/profile.py +0 -0
  127. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/programs/__init__.py +0 -0
  128. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/programs/run_jupyter.py +0 -0
  129. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/programs/vscode.py +0 -0
  130. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/queues.py +0 -0
  131. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/run.py +0 -0
  132. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/secret.py +0 -0
  133. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/selector.py +0 -0
  134. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/shell.py +0 -0
  135. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/skills.py +0 -0
  136. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/token.py +0 -0
  137. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/utils.py +0 -0
  138. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/volume.py +0 -0
  139. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cli/workspace.py +0 -0
  140. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/client.py +0 -0
  141. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cloud_bucket_mount.py +0 -0
  142. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cloud_bucket_mount.pyi +0 -0
  143. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cls.py +0 -0
  144. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/cls.pyi +0 -0
  145. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/config.py +0 -0
  146. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/container_process.py +0 -0
  147. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/container_process.pyi +0 -0
  148. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/dict.py +0 -0
  149. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/dict.pyi +0 -0
  150. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/environments.py +0 -0
  151. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/environments.pyi +0 -0
  152. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/exception.py +0 -0
  153. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/experimental/__init__.py +0 -0
  154. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/experimental/flash.py +0 -0
  155. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/experimental/flash.pyi +0 -0
  156. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/experimental/ipython.py +0 -0
  157. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/file_io.py +0 -0
  158. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/file_io.pyi +0 -0
  159. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/file_pattern_matcher.py +0 -0
  160. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/functions.py +0 -0
  161. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/functions.pyi +0 -0
  162. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/image.py +0 -0
  163. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/image.pyi +0 -0
  164. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/io_streams.py +0 -0
  165. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/io_streams.pyi +0 -0
  166. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/mount.py +0 -0
  167. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/mount.pyi +0 -0
  168. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/network_file_system.py +0 -0
  169. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/network_file_system.pyi +0 -0
  170. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/object.py +0 -0
  171. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/object.pyi +0 -0
  172. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/output.py +0 -0
  173. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/parallel_map.py +0 -0
  174. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/parallel_map.pyi +0 -0
  175. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/partial_function.py +0 -0
  176. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/partial_function.pyi +0 -0
  177. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/proxy.py +0 -0
  178. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/proxy.pyi +0 -0
  179. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/py.typed +0 -0
  180. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/queue.py +0 -0
  181. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/queue.pyi +0 -0
  182. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/retries.py +0 -0
  183. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/runner.py +0 -0
  184. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/runner.pyi +0 -0
  185. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/running_app.py +0 -0
  186. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/sandbox_fs.py +0 -0
  187. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/sandbox_fs.pyi +0 -0
  188. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/schedule.py +0 -0
  189. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/scheduler_placement.py +0 -0
  190. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/secret.py +0 -0
  191. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/secret.pyi +0 -0
  192. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/server.py +0 -0
  193. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/server.pyi +0 -0
  194. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/serving.py +0 -0
  195. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/serving.pyi +0 -0
  196. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/skills/modal/SKILL.md +0 -0
  197. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/snapshot.py +0 -0
  198. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/snapshot.pyi +0 -0
  199. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/stream_type.py +0 -0
  200. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/token_flow.py +0 -0
  201. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/token_flow.pyi +0 -0
  202. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/types.py +0 -0
  203. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/volume.py +0 -0
  204. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/volume.pyi +0 -0
  205. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/workspace.py +0 -0
  206. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal/workspace.pyi +0 -0
  207. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/SOURCES.txt +0 -0
  208. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/dependency_links.txt +0 -0
  209. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/entry_points.txt +0 -0
  210. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/requires.txt +0 -0
  211. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal.egg-info/top_level.txt +0 -0
  212. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/__init__.py +0 -0
  213. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/gen_cli_docs.py +0 -0
  214. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/gen_cli_docs_main.py +0 -0
  215. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/gen_reference_docs.py +0 -0
  216. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/gen_reference_docs_main.py +0 -0
  217. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/mdmd/__init__.py +0 -0
  218. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/mdmd/mdmd.py +0 -0
  219. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/mdmd/signatures.py +0 -0
  220. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/mdmd/types.py +0 -0
  221. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_docs/reference_intro.md +0 -0
  222. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/__init__.py +0 -0
  223. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/api_grpc.py +0 -0
  224. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/api_pb2.py +0 -0
  225. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/api_pb2.pyi +0 -0
  226. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/api_pb2_grpc.py +0 -0
  227. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/api_pb2_grpc.pyi +0 -0
  228. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/modal_api_grpc.py +0 -0
  229. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/py.typed +0 -0
  230. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/task_command_router_grpc.py +0 -0
  231. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/task_command_router_pb2.py +0 -0
  232. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/task_command_router_pb2.pyi +0 -0
  233. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  234. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  235. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/modal_version/__main__.py +0 -0
  236. {modal-1.5.5.dev1 → modal-1.5.5.dev3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.5.dev1
3
+ Version: 1.5.5.dev3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -145,7 +145,7 @@ def call_function(
145
145
  item_count += 1
146
146
 
147
147
  await container_io_manager._send_outputs.aio(
148
- io_context.output_items_generator_done(started_at, item_count)
148
+ io_context, io_context.output_items_generator_done(started_at, item_count)
149
149
  )
150
150
  else:
151
151
  value = await io_context.call_function_async()
@@ -182,7 +182,9 @@ def call_function(
182
182
  container_io_manager._queue_put(generator_queue, value)
183
183
  item_count += 1
184
184
 
185
- container_io_manager._send_outputs(io_context.output_items_generator_done(started_at, item_count))
185
+ container_io_manager._send_outputs(
186
+ io_context, io_context.output_items_generator_done(started_at, item_count)
187
+ )
186
188
  else:
187
189
  values = io_context.call_function_sync()
188
190
  container_io_manager.push_outputs(io_context, started_at, values)
@@ -93,6 +93,8 @@ def _entry_context_ids(object_id: str, item: api_pb2.TaskLogs, batch: api_pb2.Ta
93
93
  ]
94
94
  case "fc":
95
95
  context_ids = [item.input_id or batch.input_id, item.container_id or batch.task_id]
96
+ case "sb":
97
+ context_ids = [item.container_id or batch.task_id]
96
98
  case _:
97
99
  context_ids = []
98
100
  # filter for empty strings, can happen for Server which have function_ids but no input/container
@@ -802,6 +804,71 @@ class _ImageLogsManager:
802
804
  ImageLogsManager = synchronize_api(_ImageLogsManager, target_module=__name__)
803
805
 
804
806
 
807
+ class _SandboxLogsManager:
808
+ """mdmd:namespace"""
809
+
810
+ def __init__(self, source: _SupportsLogs):
811
+ """mdmd:hidden"""
812
+ self._manager = _LogsManager(source)
813
+
814
+ async def fetch(
815
+ self,
816
+ *,
817
+ since: datetime,
818
+ until: datetime | None = None,
819
+ source: LogSource | None = None,
820
+ search_text: str = "",
821
+ ) -> AsyncGenerator[LogEntry, None]:
822
+ """Fetch Sandbox logs corresponding to the date range and filters.
823
+
824
+ Args:
825
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
826
+ which is interpreted as local time.
827
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
828
+ as local time.
829
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
830
+ search_text: Filter by search text.
831
+
832
+ Yields:
833
+ `LogEntry` objects in chronological order.
834
+
835
+ Examples:
836
+
837
+ ```python notest
838
+ sandbox = modal.Sandbox.from_name("my-app", "sandbox")
839
+
840
+ for entry in sandbox.logs.fetch(
841
+ since=datetime.now() - timedelta(minutes=25),
842
+ source="stdout",
843
+ ):
844
+ print(entry.message, end="")
845
+ ```
846
+ """
847
+ async for log_entry in self._manager.fetch(since=since, until=until, source=source, search_text=search_text):
848
+ yield log_entry
849
+
850
+ async def tail(
851
+ self,
852
+ entries: int = 100,
853
+ *,
854
+ source: LogSource | None = None,
855
+ ) -> AsyncGenerator[LogEntry, None]:
856
+ """Fetch the most recent Sandbox logs.
857
+
858
+ Args:
859
+ entries: The number of log entries to return.
860
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
861
+
862
+ Yields:
863
+ `LogEntry` objects in chronological order.
864
+ """
865
+ async for log_entry in self._manager.tail(entries, source=source):
866
+ yield log_entry
867
+
868
+
869
+ SandboxLogsManager = synchronize_api(_SandboxLogsManager, target_module=__name__)
870
+
871
+
805
872
  class _AppLogsManager:
806
873
  """mdmd:namespace"""
807
874
 
@@ -996,6 +996,173 @@ class ImageLogsManager:
996
996
 
997
997
  tail: __tail_spec
998
998
 
999
+ class _SandboxLogsManager:
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 Sandbox 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
+ sandbox = modal.Sandbox.from_name("my-app", "sandbox")
1030
+
1031
+ for entry in sandbox.logs.fetch(
1032
+ since=datetime.now() - timedelta(minutes=25),
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 Sandbox 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
+ ...
1053
+
1054
+ class SandboxLogsManager:
1055
+ """mdmd:namespace"""
1056
+ def __init__(self, source: modal._supports_logs._SupportsLogs):
1057
+ """mdmd:hidden"""
1058
+ ...
1059
+
1060
+ class __fetch_spec(typing_extensions.Protocol):
1061
+ def __call__(
1062
+ self,
1063
+ /,
1064
+ *,
1065
+ since: datetime.datetime,
1066
+ until: typing.Optional[datetime.datetime] = None,
1067
+ source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
1068
+ search_text: str = "",
1069
+ ) -> typing.Generator[modal.types.LogEntry, None, None]:
1070
+ """Fetch Sandbox logs corresponding to the date range and filters.
1071
+
1072
+ Args:
1073
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
1074
+ which is interpreted as local time.
1075
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
1076
+ as local time.
1077
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1078
+ search_text: Filter by search text.
1079
+
1080
+ Yields:
1081
+ `LogEntry` objects in chronological order.
1082
+
1083
+ Examples:
1084
+
1085
+ ```python notest
1086
+ sandbox = modal.Sandbox.from_name("my-app", "sandbox")
1087
+
1088
+ for entry in sandbox.logs.fetch(
1089
+ since=datetime.now() - timedelta(minutes=25),
1090
+ source="stdout",
1091
+ ):
1092
+ print(entry.message, end="")
1093
+ ```
1094
+ """
1095
+ ...
1096
+
1097
+ def aio(
1098
+ self,
1099
+ /,
1100
+ *,
1101
+ since: datetime.datetime,
1102
+ until: typing.Optional[datetime.datetime] = None,
1103
+ source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
1104
+ search_text: str = "",
1105
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1106
+ """Fetch Sandbox logs corresponding to the date range and filters.
1107
+
1108
+ Args:
1109
+ since: Start date to fetch logs from. Must be in UTC or timezone-naive,
1110
+ which is interpreted as local time.
1111
+ until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
1112
+ as local time.
1113
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1114
+ search_text: Filter by search text.
1115
+
1116
+ Yields:
1117
+ `LogEntry` objects in chronological order.
1118
+
1119
+ Examples:
1120
+
1121
+ ```python notest
1122
+ sandbox = modal.Sandbox.from_name("my-app", "sandbox")
1123
+
1124
+ for entry in sandbox.logs.fetch(
1125
+ since=datetime.now() - timedelta(minutes=25),
1126
+ source="stdout",
1127
+ ):
1128
+ print(entry.message, end="")
1129
+ ```
1130
+ """
1131
+ ...
1132
+
1133
+ fetch: __fetch_spec
1134
+
1135
+ class __tail_spec(typing_extensions.Protocol):
1136
+ def __call__(
1137
+ self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
1138
+ ) -> typing.Generator[modal.types.LogEntry, None, None]:
1139
+ """Fetch the most recent Sandbox logs.
1140
+
1141
+ Args:
1142
+ entries: The number of log entries to return.
1143
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1144
+
1145
+ Yields:
1146
+ `LogEntry` objects in chronological order.
1147
+ """
1148
+ ...
1149
+
1150
+ def aio(
1151
+ self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
1152
+ ) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
1153
+ """Fetch the most recent Sandbox logs.
1154
+
1155
+ Args:
1156
+ entries: The number of log entries to return.
1157
+ source: Filter by source: 'stdout', 'stderr', or 'system'.
1158
+
1159
+ Yields:
1160
+ `LogEntry` objects in chronological order.
1161
+ """
1162
+ ...
1163
+
1164
+ tail: __tail_spec
1165
+
999
1166
  class _AppLogsManager:
1000
1167
  """mdmd:namespace"""
1001
1168
  def __init__(self, source: modal._supports_logs._SupportsLogs):
@@ -96,6 +96,7 @@ class IOContext:
96
96
 
97
97
  _cancel_issued: bool = False
98
98
  _cancel_callback: Callable[[], None] | None = None
99
+ _exited: bool = False
99
100
 
100
101
  def __init__(
101
102
  self,
@@ -889,25 +890,35 @@ class _ContainerIOManager:
889
890
  if item.kill_switch:
890
891
  logger.debug(f"Task {self.task_id} input kill signal input.")
891
892
  return
892
- inputs.append(
893
- (
894
- item.input_id,
895
- item.retry_count,
896
- item.function_call_id,
897
- item.attempt_token,
898
- item.input,
899
- item.function_call_invocation_type,
893
+ live_io_context = self.current_inputs.get(item.input_id)
894
+ if live_io_context is not None and (
895
+ live_io_context.retry_counts[live_io_context.input_ids.index(item.input_id)]
896
+ == item.retry_count
897
+ ):
898
+ # An expired fetch lease redelivers a still-running attempt under the same
899
+ # input id and retry count; a retry of a failed attempt gets a new retry count.
900
+ logger.warning(f"Skipping duplicate delivery of input {item.input_id}")
901
+ else:
902
+ inputs.append(
903
+ (
904
+ item.input_id,
905
+ item.retry_count,
906
+ item.function_call_id,
907
+ item.attempt_token,
908
+ item.input,
909
+ item.function_call_invocation_type,
910
+ )
900
911
  )
901
- )
902
912
  if item.input.final_input:
903
913
  if request.batch_max_size > 0:
904
914
  logger.debug(f"Task {self.task_id} Final input not expected in batch input stream")
905
915
  final_input_received = True
906
916
  break
907
917
 
908
- # If yielded, allow input slots to be released via exit_context
909
- yield inputs
910
- yielded = True
918
+ if inputs:
919
+ # If yielded, allow input slots to be released via exit_context
920
+ yield inputs
921
+ yielded = True
911
922
 
912
923
  # TODO(michael): Remove use of max_inputs after worker rollover
913
924
  single_use_container = self.function_def.single_use_containers or self.function_def.max_inputs == 1
@@ -950,7 +961,7 @@ class _ContainerIOManager:
950
961
  # collect all active input slots, meaning all inputs have wrapped up.
951
962
  await self._input_slots.close()
952
963
 
953
- async def _send_outputs(self, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
964
+ async def _send_outputs(self, io_context: IOContext, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
954
965
  """Send pre-built output items with retry and chunking."""
955
966
  # There are multiple outputs for a single IOContext in the case of @modal.batched.
956
967
  # Limit the batch size to 20 to stay within message size limits and buffer size limits.
@@ -963,8 +974,7 @@ class _ContainerIOManager:
963
974
  max_retries=None, # Retry indefinitely, trying every 1s.
964
975
  ),
965
976
  )
966
- input_ids = [output.input_id for output in outputs]
967
- self.exit_context(input_ids)
977
+ self.exit_context(io_context)
968
978
 
969
979
  @asynccontextmanager
970
980
  async def handle_input_exception(
@@ -984,7 +994,7 @@ class _ContainerIOManager:
984
994
  raise
985
995
  except (InputCancellation, asyncio.CancelledError):
986
996
  outputs = await io_context.output_items_cancellation(started_at)
987
- await self._send_outputs(outputs)
997
+ await self._send_outputs(io_context, outputs)
988
998
  logger.warning(f"Successfully canceled input {io_context.input_ids}")
989
999
  return
990
1000
  except BaseException as exc:
@@ -995,13 +1005,24 @@ class _ContainerIOManager:
995
1005
  # print exception so it's logged
996
1006
  print_exception(*sys.exc_info())
997
1007
  outputs = await io_context.output_items_exception(started_at, self.task_id, exc)
998
- await self._send_outputs(outputs)
1008
+ await self._send_outputs(io_context, outputs)
999
1009
 
1000
- def exit_context(self, input_ids: list[str]):
1001
- for input_id in input_ids:
1002
- self.current_inputs.pop(input_id)
1003
-
1004
- self._input_slots.release()
1010
+ def exit_context(self, io_context: IOContext):
1011
+ # A cancellation can land after outputs were already sent, re-entering here for the same
1012
+ # context; exit at most once so the input slot is not released twice.
1013
+ if io_context._exited:
1014
+ return
1015
+ io_context._exited = True
1016
+ try:
1017
+ for input_id in io_context.input_ids:
1018
+ # A retry admitted before its predecessor finished exiting takes over the
1019
+ # tracking entry, so remove it only if this context still owns it.
1020
+ if self.current_inputs.get(input_id) is io_context:
1021
+ self.current_inputs.pop(input_id)
1022
+ else:
1023
+ logger.warning(f"Input {input_id} missing from active input tracking")
1024
+ finally:
1025
+ self._input_slots.release()
1005
1026
 
1006
1027
  # skip inspection of user-generated output_data for synchronicity input translation
1007
1028
  @synchronizer.no_io_translation
@@ -1013,7 +1034,7 @@ class _ContainerIOManager:
1013
1034
  ) -> None:
1014
1035
  # The standard output encoding+sending method for successful function outputs
1015
1036
  outputs = await io_context.output_items(started_at, output_data)
1016
- await self._send_outputs(outputs)
1037
+ await self._send_outputs(io_context, outputs)
1017
1038
 
1018
1039
  @asynccontextmanager
1019
1040
  async def snapshot_context_manager(self) -> AsyncGenerator[None, None]:
@@ -33,6 +33,7 @@ class IOContext:
33
33
  finalized_function: modal._runtime.user_code_imports.FinalizedFunction
34
34
  _cancel_issued: bool
35
35
  _cancel_callback: typing.Optional[collections.abc.Callable[[], None]]
36
+ _exited: bool
36
37
 
37
38
  def __init__(
38
39
  self,
@@ -202,7 +203,9 @@ class _ContainerIOManager:
202
203
  batch_max_size: int = 0,
203
204
  batch_wait_ms: int = 0,
204
205
  ) -> collections.abc.AsyncIterator[IOContext]: ...
205
- async def _send_outputs(self, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
206
+ async def _send_outputs(
207
+ self, io_context: IOContext, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]
208
+ ) -> None:
206
209
  """Send pre-built output items with retry and chunking."""
207
210
  ...
208
211
 
@@ -210,7 +213,7 @@ class _ContainerIOManager:
210
213
  """Handle an exception while processing a function input."""
211
214
  ...
212
215
 
213
- def exit_context(self, input_ids: list[str]): ...
216
+ def exit_context(self, io_context: IOContext): ...
214
217
  async def push_outputs(self, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
215
218
  def snapshot_context_manager(self) -> typing.AsyncContextManager[None]: ...
216
219
  async def interact(self, from_breakpoint: bool = False): ...
@@ -445,11 +448,13 @@ class ContainerIOManager:
445
448
  run_inputs_outputs: __run_inputs_outputs_spec
446
449
 
447
450
  class ___send_outputs_spec(typing_extensions.Protocol):
448
- def __call__(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
451
+ def __call__(self, /, io_context: IOContext, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
449
452
  """Send pre-built output items with retry and chunking."""
450
453
  ...
451
454
 
452
- async def aio(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
455
+ async def aio(
456
+ self, /, io_context: IOContext, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]
457
+ ) -> None:
453
458
  """Send pre-built output items with retry and chunking."""
454
459
  ...
455
460
 
@@ -468,7 +473,7 @@ class ContainerIOManager:
468
473
 
469
474
  handle_input_exception: __handle_input_exception_spec
470
475
 
471
- def exit_context(self, input_ids: list[str]): ...
476
+ def exit_context(self, io_context: IOContext): ...
472
477
 
473
478
  class __push_outputs_spec(typing_extensions.Protocol):
474
479
  def __call__(self, /, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
@@ -38,7 +38,7 @@ class _Client:
38
38
  server_url: str,
39
39
  client_type: int,
40
40
  credentials: typing.Optional[tuple[str, str]],
41
- version: str = "1.5.5.dev1",
41
+ version: str = "1.5.5.dev3",
42
42
  ):
43
43
  """mdmd:hidden
44
44
  The Modal client object is not intended to be instantiated directly by users.
@@ -209,7 +209,7 @@ class Client:
209
209
  server_url: str,
210
210
  client_type: int,
211
211
  credentials: typing.Optional[tuple[str, str]],
212
- version: str = "1.5.5.dev1",
212
+ version: str = "1.5.5.dev3",
213
213
  ):
214
214
  """mdmd:hidden
215
215
  The Modal client object is not intended to be instantiated directly by users.
@@ -15,6 +15,8 @@ from dataclasses import dataclass
15
15
  from pathlib import PurePosixPath
16
16
  from typing import TYPE_CHECKING, Any, Literal, Union, overload
17
17
 
18
+ from modal._logs_manager import _SandboxLogsManager
19
+ from modal._supports_logs import LogsFilters, _LogQueryData
18
20
  from modal.secret import _split_env_dict_and_resolvable_secrets
19
21
 
20
22
  from ._output.pty import get_pty_info
@@ -1931,6 +1933,27 @@ class _Sandbox(_Object, type_prefix="sb"):
1931
1933
  await asyncio.sleep(0.5)
1932
1934
  return self._task_id
1933
1935
 
1936
+ async def _resolve_task_id_for_logs(self) -> str:
1937
+ """Try to resolve the task ID in one shot for historical log fetches.
1938
+
1939
+ For a historical log api, we should not wait for the task to be created.
1940
+ If the Sandbox created logs worth fetching, the task ID should be available.
1941
+ """
1942
+ if self._task_id:
1943
+ return self._task_id
1944
+
1945
+ req = api_pb2.SandboxGetTaskIdRequest(sandbox_id=self.object_id)
1946
+ stub = self.__client.stub
1947
+ if self._is_v2:
1948
+ assert self.__client._auth_token_manager
1949
+ auth_token = await self.__client._auth_token_manager.get_token()
1950
+ resp = await stub.SandboxGetTaskIdV2(req, metadata=[("x-modal-auth-token", auth_token)])
1951
+ else:
1952
+ resp = await stub.SandboxGetTaskId(req)
1953
+ if not resp.task_id:
1954
+ raise ExecutionError("Sandbox task_id is not available yet.")
1955
+ return resp.task_id
1956
+
1934
1957
  async def _get_command_router_client(self, task_id: str) -> TaskCommandRouterClient:
1935
1958
  if self._command_router_client is None:
1936
1959
  try:
@@ -2605,6 +2628,36 @@ class _Sandbox(_Object, type_prefix="sb"):
2605
2628
  # Fetch the next batch starting from the end of the current one.
2606
2629
  before_timestamp = resp.sandboxes[-1].created_at
2607
2630
 
2631
+ async def _get_log_query_data(self) -> _LogQueryData:
2632
+ if not self._app_id:
2633
+ if not self._attached:
2634
+ raise ExecutionError("Cannot fetch logs for a detached Sandbox without hydrated app_id")
2635
+ await self.hydrate()
2636
+
2637
+ if not self._app_id:
2638
+ raise ExecutionError("app_id should have been set during sandbox hydration")
2639
+
2640
+ task_id = await self._resolve_task_id_for_logs()
2641
+ return _LogQueryData(self.__client, self._app_id, LogsFilters(task_id=task_id), self.object_id)
2642
+
2643
+ @property
2644
+ def logs(self) -> _SandboxLogsManager:
2645
+ """Access logs for a `Sandbox` entrypoint.
2646
+
2647
+ Useful for inspecting logs after a Sandbox terminates.
2648
+ Use [`fetch()`](#logsfetch)
2649
+ to read logs from a UTC time range, [`tail()`](#logstail)
2650
+ to read the most recent logs.
2651
+
2652
+ Note that the logs from executed commands in the sandbox (via `exec()`) are not included in the
2653
+ entrypoint logs.
2654
+
2655
+ See also:
2656
+ - [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
2657
+ CLI access to logs for an App.
2658
+ """
2659
+ return _SandboxLogsManager(self)
2660
+
2608
2661
 
2609
2662
  class _SidecarContainer:
2610
2663
  """Handle to an additional container running in a Sandbox."""
@@ -3,7 +3,9 @@ import collections.abc
3
3
  import enum
4
4
  import google.protobuf.message
5
5
  import modal._image
6
+ import modal._logs_manager
6
7
  import modal._object
8
+ import modal._supports_logs
7
9
  import modal._tunnel
8
10
  import modal._utils.task_command_router_client
9
11
  import modal.app
@@ -808,6 +810,14 @@ class _Sandbox(modal._object._Object):
808
810
  ...
809
811
 
810
812
  async def _get_task_id(self, raise_if_task_complete=False) -> str: ...
813
+ async def _resolve_task_id_for_logs(self) -> str:
814
+ """Try to resolve the task ID in one shot for historical log fetches.
815
+
816
+ For a historical log api, we should not wait for the task to be created.
817
+ If the Sandbox created logs worth fetching, the task ID should be available.
818
+ """
819
+ ...
820
+
811
821
  async def _get_command_router_client(
812
822
  self, task_id: str
813
823
  ) -> modal._utils.task_command_router_client.TaskCommandRouterClient: ...
@@ -1052,6 +1062,25 @@ class _Sandbox(modal._object._Object):
1052
1062
  """
1053
1063
  ...
1054
1064
 
1065
+ async def _get_log_query_data(self) -> modal._supports_logs._LogQueryData: ...
1066
+ @property
1067
+ def logs(self) -> modal._logs_manager._SandboxLogsManager:
1068
+ """Access logs for a `Sandbox` entrypoint.
1069
+
1070
+ Useful for inspecting logs after a Sandbox terminates.
1071
+ Use [`fetch()`](#logsfetch)
1072
+ to read logs from a UTC time range, [`tail()`](#logstail)
1073
+ to read the most recent logs.
1074
+
1075
+ Note that the logs from executed commands in the sandbox (via `exec()`) are not included in the
1076
+ entrypoint logs.
1077
+
1078
+ See also:
1079
+ - [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
1080
+ CLI access to logs for an App.
1081
+ """
1082
+ ...
1083
+
1055
1084
  class _SidecarContainer:
1056
1085
  """Handle to an additional container running in a Sandbox."""
1057
1086
 
@@ -2841,6 +2870,25 @@ class Sandbox(modal.object.Object):
2841
2870
 
2842
2871
  _get_task_id: ___get_task_id_spec
2843
2872
 
2873
+ class ___resolve_task_id_for_logs_spec(typing_extensions.Protocol):
2874
+ def __call__(self, /) -> str:
2875
+ """Try to resolve the task ID in one shot for historical log fetches.
2876
+
2877
+ For a historical log api, we should not wait for the task to be created.
2878
+ If the Sandbox created logs worth fetching, the task ID should be available.
2879
+ """
2880
+ ...
2881
+
2882
+ async def aio(self, /) -> str:
2883
+ """Try to resolve the task ID in one shot for historical log fetches.
2884
+
2885
+ For a historical log api, we should not wait for the task to be created.
2886
+ If the Sandbox created logs worth fetching, the task ID should be available.
2887
+ """
2888
+ ...
2889
+
2890
+ _resolve_task_id_for_logs: ___resolve_task_id_for_logs_spec
2891
+
2844
2892
  class ___get_command_router_client_spec(typing_extensions.Protocol):
2845
2893
  def __call__(self, /, task_id: str) -> modal._utils.task_command_router_client.TaskCommandRouterClient: ...
2846
2894
  async def aio(self, /, task_id: str) -> modal._utils.task_command_router_client.TaskCommandRouterClient: ...
@@ -3349,6 +3397,30 @@ class Sandbox(modal.object.Object):
3349
3397
 
3350
3398
  _experimental_list: typing.ClassVar[___experimental_list_spec]
3351
3399
 
3400
+ class ___get_log_query_data_spec(typing_extensions.Protocol):
3401
+ def __call__(self, /) -> modal._supports_logs._LogQueryData: ...
3402
+ async def aio(self, /) -> modal._supports_logs._LogQueryData: ...
3403
+
3404
+ _get_log_query_data: ___get_log_query_data_spec
3405
+
3406
+ @property
3407
+ def logs(self) -> modal._logs_manager.SandboxLogsManager:
3408
+ """Access logs for a `Sandbox` entrypoint.
3409
+
3410
+ Useful for inspecting logs after a Sandbox terminates.
3411
+ Use [`fetch()`](#logsfetch)
3412
+ to read logs from a UTC time range, [`tail()`](#logstail)
3413
+ to read the most recent logs.
3414
+
3415
+ Note that the logs from executed commands in the sandbox (via `exec()`) are not included in the
3416
+ entrypoint logs.
3417
+
3418
+ See also:
3419
+ - [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
3420
+ CLI access to logs for an App.
3421
+ """
3422
+ ...
3423
+
3352
3424
  _default_image: modal._image._Image
3353
3425
 
3354
3426
  _MAIN_CONTAINER_NAME: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.5.dev1
3
+ Version: 1.5.5.dev3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.5.5.dev1"
4
+ __version__ = "1.5.5.dev3"