modal 1.5.4.dev2__tar.gz → 1.5.4.dev4__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.dev2 → modal-1.5.4.dev4}/PKG-INFO +1 -1
  2. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_tunnel.py +2 -2
  3. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_watcher.py +4 -2
  4. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/app.py +2 -2
  5. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/app.pyi +4 -2
  6. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_download.py +3 -6
  7. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/app.py +16 -9
  8. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/container.py +16 -80
  9. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/run.py +14 -4
  10. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/shell.py +3 -4
  11. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/utils.py +8 -6
  12. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/volume.py +7 -10
  13. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/client.pyi +2 -2
  14. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/functions.pyi +6 -6
  15. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/mount.py +2 -2
  16. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/mount.pyi +2 -2
  17. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox.py +58 -1
  18. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox.pyi +33 -0
  19. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_version/__init__.py +1 -1
  21. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/pyproject.toml +1 -8
  22. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/LICENSE +0 -0
  23. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/README.md +0 -0
  24. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/__init__.py +0 -0
  25. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/__main__.py +0 -0
  26. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_billing.py +0 -0
  27. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_clustered_functions.py +0 -0
  28. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_clustered_functions.pyi +0 -0
  29. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_container_entrypoint.py +0 -0
  30. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_environments.py +0 -0
  31. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_function_variants.py +0 -0
  32. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_functions.py +0 -0
  33. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_grpc_client.py +0 -0
  34. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_image.py +0 -0
  35. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_ipython.py +0 -0
  36. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_load_context.py +0 -0
  37. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_location.py +0 -0
  38. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs.py +0 -0
  39. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs_manager.py +0 -0
  40. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs_manager.pyi +0 -0
  41. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_object.py +0 -0
  42. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/__init__.py +0 -0
  43. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/manager.py +0 -0
  44. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/pty.py +0 -0
  45. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/rich.py +0 -0
  46. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/status.py +0 -0
  47. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_partial_function.py +0 -0
  48. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_resolver.py +0 -0
  49. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_resources.py +0 -0
  50. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/__init__.py +0 -0
  51. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/asgi.py +0 -0
  52. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/container_io_manager.py +0 -0
  53. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/container_io_manager.pyi +0 -0
  54. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/execution_context.py +0 -0
  55. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/execution_context.pyi +0 -0
  56. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  57. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/task_lifecycle_manager.py +0 -0
  58. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  59. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/telemetry.py +0 -0
  60. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/user_code_event_loop.py +0 -0
  61. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/user_code_imports.py +0 -0
  62. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_serialization.py +0 -0
  63. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_server.py +0 -0
  64. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_supports_logs.py +0 -0
  65. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_traceback.py +0 -0
  66. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_tunnel.pyi +0 -0
  67. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_type_manager.py +0 -0
  68. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/__init__.py +0 -0
  69. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/app_utils.py +0 -0
  70. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/async_utils.py +0 -0
  71. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/auth_token_manager.py +0 -0
  72. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/blob_utils.py +0 -0
  73. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/browser_utils.py +0 -0
  74. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
  75. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/curl_utils.py +0 -0
  76. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/deprecation.py +0 -0
  77. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/display_utils.py +0 -0
  78. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/docker_utils.py +0 -0
  79. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/function_utils.py +0 -0
  80. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/git_utils.py +0 -0
  81. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/grpc_testing.py +0 -0
  82. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/grpc_utils.py +0 -0
  83. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/hash_utils.py +0 -0
  84. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/http_utils.py +0 -0
  85. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/jwt_utils.py +0 -0
  86. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/logger.py +0 -0
  87. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/mount_utils.py +0 -0
  88. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/name_utils.py +0 -0
  89. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/package_utils.py +0 -0
  90. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/pattern_utils.py +0 -0
  91. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/proxy_support.py +0 -0
  92. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/rand_pb_testing.py +0 -0
  93. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/sandbox_fs_utils.py +0 -0
  94. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/shell_utils.py +0 -0
  95. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/task_command_router_client.py +0 -0
  96. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/time_utils.py +0 -0
  97. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/__init__.py +0 -0
  98. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  99. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/cloudpickle.py +0 -0
  100. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/tblib.py +0 -0
  101. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/version.py +0 -0
  102. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_workspace.py +0 -0
  103. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/billing.py +0 -0
  104. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/billing.pyi +0 -0
  105. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2023.12.312.txt +0 -0
  106. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2023.12.txt +0 -0
  107. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2024.04.txt +0 -0
  108. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2024.10.txt +0 -0
  109. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2025.06.txt +0 -0
  110. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/PREVIEW.txt +0 -0
  111. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/README.md +0 -0
  112. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/base-images.json +0 -0
  113. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/call_graph.py +0 -0
  114. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/__init__.py +0 -0
  115. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_help.py +0 -0
  116. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_traceback.py +0 -0
  117. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/billing.py +0 -0
  118. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/bootstrap.py +0 -0
  119. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/changelog.py +0 -0
  120. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/cluster.py +0 -0
  121. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/config.py +0 -0
  122. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/curl.py +0 -0
  123. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/dashboard.py +0 -0
  124. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/dict.py +0 -0
  125. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/endpoint.py +0 -0
  126. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/entry_point.py +0 -0
  127. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/environment.py +0 -0
  128. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/image.py +0 -0
  129. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/import_refs.py +0 -0
  130. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/launch.py +0 -0
  131. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/logo.py +0 -0
  132. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/network_file_system.py +0 -0
  133. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/profile.py +0 -0
  134. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/__init__.py +0 -0
  135. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/run_jupyter.py +0 -0
  136. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/vscode.py +0 -0
  137. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/queues.py +0 -0
  138. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/secret.py +0 -0
  139. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/selector.py +0 -0
  140. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/skills.py +0 -0
  141. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/token.py +0 -0
  142. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/workspace.py +0 -0
  143. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/client.py +0 -0
  144. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cloud_bucket_mount.py +0 -0
  145. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cloud_bucket_mount.pyi +0 -0
  146. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cls.py +0 -0
  147. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cls.pyi +0 -0
  148. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/config.py +0 -0
  149. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/container_process.py +0 -0
  150. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/container_process.pyi +0 -0
  151. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/dict.py +0 -0
  152. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/dict.pyi +0 -0
  153. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/environments.py +0 -0
  154. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/environments.pyi +0 -0
  155. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/exception.py +0 -0
  156. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/__init__.py +0 -0
  157. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/flash.py +0 -0
  158. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/flash.pyi +0 -0
  159. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/ipython.py +0 -0
  160. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_io.py +0 -0
  161. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_io.pyi +0 -0
  162. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_pattern_matcher.py +0 -0
  163. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/functions.py +0 -0
  164. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/image.py +0 -0
  165. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/image.pyi +0 -0
  166. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/io_streams.py +0 -0
  167. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/io_streams.pyi +0 -0
  168. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/network_file_system.py +0 -0
  169. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/network_file_system.pyi +0 -0
  170. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/object.py +0 -0
  171. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/object.pyi +0 -0
  172. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/output.py +0 -0
  173. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/parallel_map.py +0 -0
  174. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/parallel_map.pyi +0 -0
  175. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/partial_function.py +0 -0
  176. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/partial_function.pyi +0 -0
  177. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/proxy.py +0 -0
  178. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/proxy.pyi +0 -0
  179. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/py.typed +0 -0
  180. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/queue.py +0 -0
  181. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/queue.pyi +0 -0
  182. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/retries.py +0 -0
  183. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/runner.py +0 -0
  184. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/runner.pyi +0 -0
  185. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/running_app.py +0 -0
  186. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox_fs.py +0 -0
  187. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox_fs.pyi +0 -0
  188. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/schedule.py +0 -0
  189. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/scheduler_placement.py +0 -0
  190. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/secret.py +0 -0
  191. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/secret.pyi +0 -0
  192. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/server.py +0 -0
  193. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/server.pyi +0 -0
  194. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/serving.py +0 -0
  195. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/serving.pyi +0 -0
  196. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/skills/modal/SKILL.md +0 -0
  197. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/snapshot.py +0 -0
  198. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/snapshot.pyi +0 -0
  199. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/stream_type.py +0 -0
  200. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/token_flow.py +0 -0
  201. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/token_flow.pyi +0 -0
  202. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/types.py +0 -0
  203. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/volume.py +0 -0
  204. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/volume.pyi +0 -0
  205. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/workspace.py +0 -0
  206. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/workspace.pyi +0 -0
  207. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/SOURCES.txt +0 -0
  208. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/dependency_links.txt +0 -0
  209. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/entry_points.txt +0 -0
  210. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/requires.txt +0 -0
  211. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/top_level.txt +0 -0
  212. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/__init__.py +0 -0
  213. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_cli_docs.py +0 -0
  214. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_cli_docs_main.py +0 -0
  215. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_reference_docs.py +0 -0
  216. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_reference_docs_main.py +0 -0
  217. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/__init__.py +0 -0
  218. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/mdmd.py +0 -0
  219. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/signatures.py +0 -0
  220. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/types.py +0 -0
  221. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/reference_intro.md +0 -0
  222. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/__init__.py +0 -0
  223. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_grpc.py +0 -0
  224. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2.py +0 -0
  225. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2.pyi +0 -0
  226. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2_grpc.py +0 -0
  227. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2_grpc.pyi +0 -0
  228. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/modal_api_grpc.py +0 -0
  229. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/py.typed +0 -0
  230. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_grpc.py +0 -0
  231. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2.py +0 -0
  232. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2.pyi +0 -0
  233. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  234. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  235. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_version/__main__.py +0 -0
  236. {modal-1.5.4.dev2 → modal-1.5.4.dev4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.4.dev2
3
+ Version: 1.5.4.dev4
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,7 +1,7 @@
1
1
  # Copyright Modal Labs 2023
2
2
  """Client for Modal relay servers, allowing users to expose TLS."""
3
3
 
4
- from collections.abc import AsyncIterator
4
+ from collections.abc import AsyncGenerator
5
5
  from dataclasses import dataclass
6
6
 
7
7
  from synchronicity.async_wrap import asynccontextmanager
@@ -60,7 +60,7 @@ class Tunnel:
60
60
  @asynccontextmanager
61
61
  async def _forward(
62
62
  port: int, *, unencrypted: bool = False, h2_enabled: bool = False, client: _Client | None = None
63
- ) -> AsyncIterator[Tunnel]:
63
+ ) -> AsyncGenerator[Tunnel, None]:
64
64
  """Expose a port publicly from inside a running Modal container, with TLS.
65
65
 
66
66
  If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
@@ -79,6 +79,8 @@ def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFi
79
79
  paths = set()
80
80
  dir_filters: dict[Path, set[Path] | None] = defaultdict(set)
81
81
  for mount in mounts:
82
+ if mount._entries is None:
83
+ continue
82
84
  # TODO(elias): Make this part of the mount class instead, since it uses so much internals
83
85
  for entry in mount._entries:
84
86
  path, filter_file = entry.watch_entry()
@@ -86,8 +88,8 @@ def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFi
86
88
  paths.add(path)
87
89
  if filter_file is None:
88
90
  dir_filters[path] = None
89
- elif dir_filters[path] is not None:
90
- dir_filters[path].add(filter_file.absolute().resolve())
91
+ elif (filters := dir_filters[path]) is not None:
92
+ filters.add(filter_file.absolute().resolve())
91
93
 
92
94
  watch_filter = AppFilesFilter(dir_filters=dict(dir_filters))
93
95
  return paths, watch_filter
@@ -1310,7 +1310,7 @@ class _App:
1310
1310
  include_source: bool | None = None, # Whether to add source to container
1311
1311
  # Experimental options
1312
1312
  experimental_options: dict[str, Any] | None = None,
1313
- ) -> Callable[[CLS_T | _PartialFunction], _Server]:
1313
+ ) -> Callable[[type[Any] | _PartialFunction], _Server]:
1314
1314
  """
1315
1315
  Decorator to register a new Modal Server with this App.
1316
1316
 
@@ -1403,7 +1403,7 @@ class _App:
1403
1403
  if env:
1404
1404
  secrets_list.append(_Secret.from_dict(env))
1405
1405
 
1406
- def wrapper(wrapped_user_cls: CLS_T | _PartialFunction | Callable) -> _Server:
1406
+ def wrapper(wrapped_user_cls: type[Any] | _PartialFunction | Callable) -> _Server:
1407
1407
  _Server._validate_wrapped_user_cls_decorators(wrapped_user_cls, enable_memory_snapshot)
1408
1408
 
1409
1409
  # Validate the server class
@@ -766,7 +766,7 @@ class _App:
766
766
  include_source: typing.Optional[bool] = None,
767
767
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
768
768
  ) -> collections.abc.Callable[
769
- [typing.Union[CLS_T, modal._partial_function._PartialFunction]], modal._server._Server
769
+ [typing.Union[type[typing.Any], modal._partial_function._PartialFunction]], modal._server._Server
770
770
  ]:
771
771
  """Decorator to register a new Modal Server with this App.
772
772
 
@@ -1771,7 +1771,9 @@ class App:
1771
1771
  enable_memory_snapshot: bool = False,
1772
1772
  include_source: typing.Optional[bool] = None,
1773
1773
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1774
- ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], modal.server.Server]:
1774
+ ) -> collections.abc.Callable[
1775
+ [typing.Union[type[typing.Any], modal.partial_function.PartialFunction]], modal.server.Server
1776
+ ]:
1775
1777
  """Decorator to register a new Modal Server with this App.
1776
1778
 
1777
1779
  Servers run HTTP servers that are started in a `@modal.enter()` method.
@@ -24,14 +24,9 @@ async def _volume_download(
24
24
  remote_path: str,
25
25
  local_destination: Path,
26
26
  overwrite: bool,
27
+ progress_cb: Callable,
27
28
  concurrency: int | None = None,
28
- progress_cb: Callable | None = None,
29
29
  ):
30
- if progress_cb is None:
31
-
32
- def progress_cb(*_, **__):
33
- pass
34
-
35
30
  if concurrency is None:
36
31
  concurrency = max(128, 2 * multiprocessing.cpu_count())
37
32
 
@@ -91,6 +86,8 @@ async def _volume_download(
91
86
 
92
87
  file_progress_cb(complete=True)
93
88
  else:
89
+ # When not streaming to a pipe, the producer always enqueues a real output path.
90
+ assert output_path is not None
94
91
  if entry.type == FileEntryType.FILE:
95
92
  progress_task_id = progress_cb(name=entry.path, size=entry.size)
96
93
  output_path.parent.mkdir(parents=True, exist_ok=True)
@@ -27,12 +27,12 @@ from .._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
27
27
  from .._utils.time_utils import locale_tz, timestamp_to_localized_str
28
28
  from ._help import ModalGroup
29
29
  from .utils import (
30
+ _fetch_app_logs,
31
+ _stream_app_logs,
32
+ _tail_app_logs,
30
33
  confirm_or_suggest_yes,
31
34
  display_table,
32
35
  env_option,
33
- fetch_app_logs,
34
- stream_app_logs,
35
- tail_app_logs,
36
36
  yes_option,
37
37
  )
38
38
 
@@ -117,7 +117,7 @@ async def list_(env: str | None = None, json: bool = False):
117
117
  "Created at",
118
118
  "Stopped at",
119
119
  ]
120
- rows: list[list[Text | str]] = []
120
+ rows: list[list[Text | str | None]] = []
121
121
  for app_stats in resp.apps:
122
122
  state = APP_STATE_TO_MESSAGE.get(app_stats.state, Text("unknown", style="gray"))
123
123
  rows.append(
@@ -305,7 +305,7 @@ async def logs(
305
305
  )
306
306
 
307
307
  if follow:
308
- await stream_app_logs.aio(
308
+ await _stream_app_logs(
309
309
  app_id,
310
310
  task_id=container_id or "",
311
311
  show_timestamps=timestamps,
@@ -326,9 +326,9 @@ async def logs(
326
326
  if effective_until - since_dt > _MAX_FETCH_RANGE:
327
327
  raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
328
328
 
329
- if since and tail is None:
329
+ if since_dt and tail is None:
330
330
  # Range mode: --since without --tail fetches everything in the range.
331
- await fetch_app_logs.aio(
331
+ await _fetch_app_logs(
332
332
  app_id,
333
333
  since_dt,
334
334
  until_dt or now,
@@ -340,7 +340,7 @@ async def logs(
340
340
  # Tail mode: single fetch with limit.
341
341
  # --since is a hard floor, --until shifts the anchor.
342
342
  effective_tail = tail if tail is not None else _DEFAULT_LOGS_TAIL
343
- await tail_app_logs.aio(
343
+ await _tail_app_logs(
344
344
  app_id,
345
345
  effective_tail,
346
346
  show_timestamps=timestamps,
@@ -586,9 +586,16 @@ async def history(
586
586
  for idx, app_stats in enumerate(resp.app_deployment_histories):
587
587
  style = "bold green" if idx == 0 else ""
588
588
 
589
+ if app_stats.deployed_at:
590
+ ts = timestamp_to_localized_str(app_stats.deployed_at, json)
591
+ assert ts is not None
592
+ deployed_at = Text(ts, style=style)
593
+ else:
594
+ deployed_at = None
595
+
589
596
  row = [
590
597
  Text(f"v{app_stats.version}", style=style),
591
- Text(timestamp_to_localized_str(app_stats.deployed_at, json), style=style),
598
+ deployed_at,
592
599
  Text(app_stats.client_version, style=style),
593
600
  Text(app_stats.deployed_by, style=style),
594
601
  ]
@@ -1,5 +1,4 @@
1
1
  # Copyright Modal Labs 2022
2
- import uuid
3
2
  import warnings
4
3
  from datetime import datetime, timezone
5
4
 
@@ -11,27 +10,23 @@ from rich.text import Text
11
10
  from modal._environments import ensure_env
12
11
  from modal._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
13
12
  from modal._object import _get_environment_name
14
- from modal._output.pty import get_pty_info
15
13
  from modal._utils.async_utils import synchronizer
16
- from modal._utils.task_command_router_client import TaskCommandRouterClient
17
14
  from modal._utils.time_utils import timestamp_to_localized_str
18
15
  from modal.cli.app import _DEFAULT_LOGS_TAIL, _SOURCE_OPTIONS, _parse_time_arg
19
16
  from modal.cli.utils import (
17
+ _fetch_app_logs,
18
+ _stream_app_logs,
19
+ _tail_app_logs,
20
20
  confirm_or_suggest_yes,
21
21
  display_table,
22
22
  env_option,
23
- fetch_app_logs,
24
23
  is_tty,
25
- stream_app_logs,
26
- tail_app_logs,
27
24
  yes_option,
28
25
  )
29
26
  from modal.client import _Client
30
- from modal.config import config
31
- from modal.container_process import _ContainerProcess
32
27
  from modal.exception import InvalidError
33
- from modal.stream_type import StreamType
34
- from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
28
+ from modal.sandbox import _container_exec
29
+ from modal_proto import api_pb2
35
30
 
36
31
  from ._help import ModalGroup
37
32
 
@@ -70,7 +65,7 @@ async def list_(
70
65
  task_stats.task_id,
71
66
  task_stats.app_id,
72
67
  task_stats.app_description,
73
- timestamp_to_localized_str(task_stats.started_at, json) if task_stats.started_at else "Pending",
68
+ timestamp_to_localized_str(task_stats.started_at, json) or "Pending",
74
69
  ]
75
70
  )
76
71
 
@@ -184,7 +179,7 @@ async def logs(
184
179
  )
185
180
 
186
181
  if follow:
187
- await stream_app_logs.aio(
182
+ await _stream_app_logs(
188
183
  task_id=task_id,
189
184
  sandbox_id=sandbox_id,
190
185
  show_timestamps=timestamps,
@@ -199,6 +194,7 @@ async def logs(
199
194
  sb_resp = await client.stub.SandboxGetTaskId(api_pb2.SandboxGetTaskIdRequest(sandbox_id=sandbox_id))
200
195
  task_id = sb_resp.task_id
201
196
 
197
+ assert task_id
202
198
  task_info_resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=task_id))
203
199
  app_id = task_info_resp.app_id
204
200
 
@@ -236,14 +232,13 @@ async def logs(
236
232
  warnings.warn("--until time is before the Container started, no logs to fetch.", UserWarning)
237
233
  return
238
234
 
239
- if since_dt is not None:
240
- effective_until = until_dt or now
241
- if effective_until - since_dt > _MAX_FETCH_RANGE:
242
- raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
235
+ effective_until = until_dt or now
236
+ if effective_until - since_dt > _MAX_FETCH_RANGE:
237
+ raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
243
238
 
244
239
  if all_logs or (since and tail is None):
245
240
  # Range mode: --since without --tail fetches everything in the range.
246
- await fetch_app_logs.aio(
241
+ await _fetch_app_logs(
247
242
  app_id,
248
243
  since_dt,
249
244
  until_dt or now,
@@ -254,7 +249,7 @@ async def logs(
254
249
  # Tail mode: single fetch with limit.
255
250
  # --since is a hard floor, --until shifts the anchor.
256
251
  effective_tail = tail if tail is not None else _DEFAULT_LOGS_TAIL
257
- await tail_app_logs.aio(
252
+ await _tail_app_logs(
258
253
  app_id,
259
254
  effective_tail,
260
255
  show_timestamps=timestamps,
@@ -264,67 +259,6 @@ async def logs(
264
259
  )
265
260
 
266
261
 
267
- @synchronizer.create_blocking
268
- async def _exec_impl(
269
- pty: bool | None = None,
270
- container_id: str = "",
271
- command: tuple[str, ...] = (),
272
- object_id_for_v2: str | None = None,
273
- ):
274
- """Execute a command in a container (implementation).
275
-
276
- For tasks belonging to V2 sandboxes, `object_id_for_v2` must be set to the
277
- sandbox ID.
278
- """
279
-
280
- if pty is None:
281
- pty = is_tty()
282
-
283
- client = await _Client.from_env()
284
-
285
- if object_id_for_v2 is not None:
286
- command_router_client = await TaskCommandRouterClient.init_v2(client, object_id_for_v2, container_id)
287
- else:
288
- command_router_client = await TaskCommandRouterClient.init(client, container_id)
289
-
290
- process_id = str(uuid.uuid4())
291
-
292
- start_req = sr_pb2.TaskExecStartRequest(
293
- task_id=container_id,
294
- exec_id=process_id,
295
- command_args=command,
296
- stdout_config=sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_PIPE,
297
- stderr_config=sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_PIPE,
298
- pty_info=get_pty_info(shell=True) if pty else None,
299
- runtime_debug=config.get("function_runtime_debug"),
300
- )
301
- await command_router_client.exec_start(start_req)
302
-
303
- if pty:
304
- # PTY output is raw terminal bytes (control sequences, mouse events,
305
- # glyphs in non-UTF-8 locales, etc.) — not text. Strict UTF-8 decode on
306
- # this stream crashes the shell as soon as anything emits a byte that
307
- # isn't valid UTF-8, e.g. vim drawing a Latin-1 file under LC_CTYPE=C.
308
- # Pass bytes through unmodified; `attach()` writes them straight to the
309
- # local fd.
310
- await _ContainerProcess(
311
- process_id,
312
- container_id,
313
- client,
314
- command_router_client=command_router_client,
315
- text=False,
316
- ).attach()
317
- else:
318
- await _ContainerProcess(
319
- process_id,
320
- container_id,
321
- client,
322
- command_router_client=command_router_client,
323
- stdout=StreamType.STDOUT,
324
- stderr=StreamType.STDOUT,
325
- ).wait()
326
-
327
-
328
262
  @container_cli.command("exec", no_args_is_help=True)
329
263
  @click.option("--pty/--no-pty", default=None, help="Run the command using a PTY.")
330
264
  @click.argument("container_id")
@@ -335,7 +269,9 @@ def exec(
335
269
  command: tuple[str, ...] = (),
336
270
  ):
337
271
  """Execute a command in a container."""
338
- _exec_impl(pty=pty, container_id=container_id, command=command)
272
+ if pty is None:
273
+ pty = is_tty()
274
+ _container_exec(pty=pty, container_id=container_id, command=command)
339
275
 
340
276
 
341
277
  @container_cli.command("stop", no_args_is_help=True)
@@ -252,7 +252,7 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
252
252
  def _get_signature(func: typing.Any) -> inspect.Signature:
253
253
  """Returns signature with the original source annotations."""
254
254
  kwargs: dict[str, typing.Any] = {}
255
- if sys.version_info[:2] >= (3, 14):
255
+ if sys.version_info >= (3, 14):
256
256
  import annotationlib
257
257
 
258
258
  kwargs["annotation_format"] = annotationlib.Format.STRING
@@ -263,6 +263,7 @@ def _get_click_command_for_function(app: App, function: Function, ctx: click.Con
263
263
  if function.is_generator:
264
264
  raise InvalidError("`modal run` is not supported for generator functions")
265
265
 
266
+ assert function.info.raw_f
266
267
  sig: inspect.Signature = _get_signature(function.info.raw_f)
267
268
  type_hints = safe_get_type_hints(function.info.raw_f)
268
269
  signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
@@ -344,6 +345,8 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference, ctx: click
344
345
 
345
346
  def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoint):
346
347
  func = entrypoint.info.raw_f
348
+ assert func
349
+
347
350
  isasync = inspect.iscoroutinefunction(func)
348
351
 
349
352
  signature = _get_cli_runnable_signature(_get_signature(func), safe_get_type_hints(func))
@@ -378,7 +381,10 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
378
381
  else:
379
382
  res = func(*args, **kwargs)
380
383
  except Exception as exc:
381
- raise _CliUserExecutionError(inspect.getsourcefile(func)) from exc
384
+ user_source = inspect.getsourcefile(func)
385
+ if user_source is None:
386
+ raise exc
387
+ raise _CliUserExecutionError(user_source) from exc
382
388
 
383
389
  if result_path := ctx.obj["result_path"]:
384
390
  _write_local_result(result_path, res)
@@ -405,13 +411,15 @@ def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
405
411
  class RunGroup(ModalGroup):
406
412
  """Click group that resolves subcommands dynamically from a file/module ref."""
407
413
 
408
- def get_command(self, ctx, func_ref):
414
+ def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
409
415
  # note: get_command here is run before the "group logic" in the `run` logic below
410
416
  # so to ensure that `env` has been globally populated before user code is loaded, it
411
417
  # needs to be handled here, and not in the `run` logic below
412
418
  ctx.ensure_object(dict)
413
419
  ctx.obj["env"] = ensure_env(ctx.params["env"])
414
420
 
421
+ func_ref = cmd_name
422
+
415
423
  import_ref = parse_import_ref(func_ref, use_module_mode=ctx.params["m"], command="modal run")
416
424
  runnable, all_usable_commands = import_and_filter(
417
425
  import_ref, base_cmd="modal run", accept_local_entrypoint=True, accept_webhook=False
@@ -553,6 +561,7 @@ def deploy(
553
561
  import_ref = parse_import_ref(app_ref, use_module_mode=use_module_mode, command="modal deploy")
554
562
 
555
563
  OutputManager.get().set_timestamps(timestamps)
564
+ # Keep the app import on the main thread; user code must not run on the synchronizer thread.
556
565
  app = import_app_from_ref(import_ref, base_cmd="modal deploy")
557
566
 
558
567
  name = name or app.name or ""
@@ -569,7 +578,8 @@ def deploy(
569
578
  res = deploy_app(app, name=name, environment_name=env or "", tag=tag, deployment_strategy=strategy)
570
579
 
571
580
  if stream_logs:
572
- stream_app_logs(
581
+ # stream_app_logs is defined in cli/utils.py, which does not get type stubs
582
+ stream_app_logs( # pyright: ignore[reportCallIssue]
573
583
  app_id=res.app_id,
574
584
  app_logs_url=res.app_logs_url,
575
585
  show_timestamps=timestamps,
@@ -17,12 +17,11 @@ from ..functions import Function
17
17
  from ..image import Image
18
18
  from ..mount import _Mount
19
19
  from ..runner import interactive_shell
20
- from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox, _is_v2_sandbox_id
20
+ from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox, _container_exec, _is_v2_sandbox_id
21
21
  from ..secret import Secret
22
22
  from ..stream_type import StreamType
23
23
  from ..volume import Volume
24
24
  from ._help import ModalCommand
25
- from .container import _exec_impl
26
25
  from .import_refs import (
27
26
  MethodReference,
28
27
  import_and_filter,
@@ -115,7 +114,7 @@ def _start_shell_in_sidecar_container(sandbox_id: str, container_name: str, cmd:
115
114
  if pty:
116
115
  # PTY output is raw terminal bytes, not text; strict UTF-8 decode
117
116
  # crashes on the first non-UTF-8 byte (e.g. vim drawing a Latin-1
118
- # file under LC_CTYPE=C). See the matching call in `_exec_impl`.
117
+ # file under LC_CTYPE=C). See the matching call in `_container_exec`.
119
118
  process = sandbox_container.exec(*shlex.split(cmd), pty=True, text=False)
120
119
  process.attach()
121
120
  else:
@@ -150,7 +149,7 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
150
149
 
151
150
  assert _is_valid_modal_id(ref, "ta-")
152
151
  try:
153
- _ = _exec_impl(container_id=ref, command=tuple(shlex.split(cmd)), pty=pty, object_id_for_v2=object_id_for_v2)
152
+ _container_exec(pty, container_id=ref, command=tuple(shlex.split(cmd)), object_id_for_v2=object_id_for_v2)
154
153
  except NotFoundError:
155
154
  raise ClickException(f"Container '{ref}' not found (is it still running?)")
156
155
  except Exception as e:
@@ -21,8 +21,7 @@ from ..exception import InvalidError
21
21
  from ..output import OutputManager
22
22
 
23
23
 
24
- @synchronizer.create_blocking
25
- async def stream_app_logs(
24
+ async def _stream_app_logs(
26
25
  app_id: str | None = None,
27
26
  task_id: str | None = None,
28
27
  sandbox_id: str | None = None,
@@ -66,6 +65,11 @@ async def stream_app_logs(
66
65
  pass
67
66
 
68
67
 
68
+ # Blocking variant just used by the `modal deploy` handler,
69
+ # which must run on synchronizer thread.
70
+ stream_app_logs = synchronizer.create_blocking(_stream_app_logs)
71
+
72
+
69
73
  async def _drain_batches(output_mgr, batches, prefixes, search_text=""):
70
74
  """Iterate over log batches and display them via the output manager."""
71
75
  last_data = ""
@@ -84,8 +88,7 @@ async def _drain_batches(output_mgr, batches, prefixes, search_text=""):
84
88
  output_mgr.print("")
85
89
 
86
90
 
87
- @synchronizer.create_blocking
88
- async def tail_app_logs(
91
+ async def _tail_app_logs(
89
92
  app_id: str,
90
93
  n: int,
91
94
  show_timestamps: bool = False,
@@ -104,8 +107,7 @@ async def tail_app_logs(
104
107
  await _drain_batches(output_mgr, batches, prefix_fields or [], filters.search_text)
105
108
 
106
109
 
107
- @synchronizer.create_blocking
108
- async def fetch_app_logs(
110
+ async def _fetch_app_logs(
109
111
  app_id: str,
110
112
  since: datetime,
111
113
  until: datetime,
@@ -33,17 +33,13 @@ volume_cli = ModalGroup(
33
33
  def humanize_filesize(value: int) -> str:
34
34
  if value < 0:
35
35
  raise ValueError("value should be >= 0")
36
- suffix = (" KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB")
37
- format = "%.1f"
38
36
  base = 1024
39
- bytes_ = float(value)
40
- if bytes_ < base:
41
- return f"{bytes_:0.0f} B"
42
- for i, s in enumerate(suffix):
43
- unit = base ** (i + 2)
44
- if bytes_ < unit:
45
- break
46
- return format % (base * bytes_ / unit) + s
37
+ size = float(value)
38
+ for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
39
+ if size < base:
40
+ return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
41
+ size /= base
42
+ return f"{size:.1f} ZiB"
47
43
 
48
44
 
49
45
  @volume_cli.command("create", help="Create a named, persistent modal.Volume.", panel="Management", no_args_is_help=True)
@@ -198,6 +194,7 @@ async def put(
198
194
  """
199
195
  ensure_env(env)
200
196
  vol = await _Volume.from_name(volume_name, environment_name=env).hydrate()
197
+ assert vol._metadata
201
198
 
202
199
  if remote_path.endswith("/"):
203
200
  remote_path = remote_path + os.path.basename(local_path)
@@ -37,7 +37,7 @@ class _Client:
37
37
  server_url: str,
38
38
  client_type: int,
39
39
  credentials: typing.Optional[tuple[str, str]],
40
- version: str = "1.5.4.dev2",
40
+ version: str = "1.5.4.dev4",
41
41
  ):
42
42
  """mdmd:hidden
43
43
  The Modal client object is not intended to be instantiated directly by users.
@@ -207,7 +207,7 @@ class Client:
207
207
  server_url: str,
208
208
  client_type: int,
209
209
  credentials: typing.Optional[tuple[str, str]],
210
- version: str = "1.5.4.dev2",
210
+ version: str = "1.5.4.dev4",
211
211
  ):
212
212
  """mdmd:hidden
213
213
  The Modal client object is not intended to be instantiated directly by users.
@@ -546,7 +546,7 @@ class Function(
546
546
 
547
547
  _call_generator: ___call_generator_spec
548
548
 
549
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
549
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
550
550
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
551
551
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
552
552
 
@@ -571,7 +571,7 @@ class Function(
571
571
  """
572
572
  ...
573
573
 
574
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
574
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
575
575
 
576
576
  class __remote_gen_spec(typing_extensions.Protocol):
577
577
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -621,7 +621,7 @@ class Function(
621
621
  """
622
622
  ...
623
623
 
624
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
624
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
625
625
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
626
626
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
627
627
 
@@ -654,7 +654,7 @@ class Function(
654
654
  """
655
655
  ...
656
656
 
657
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
657
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
658
658
 
659
659
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
660
660
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -662,7 +662,7 @@ class Function(
662
662
 
663
663
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
664
664
 
665
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
665
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
666
666
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
667
667
  """Calls the function with the given arguments, without waiting for the results.
668
668
 
@@ -695,7 +695,7 @@ class Function(
695
695
  """
696
696
  ...
697
697
 
698
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
698
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
699
699
 
700
700
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
701
701
  """Return the inner Python object wrapped by this Modal Function.
@@ -94,7 +94,7 @@ class _MountEntry(metaclass=abc.ABCMeta):
94
94
  def get_files_to_upload(self) -> typing.Iterator[tuple[Path, str]]: ...
95
95
 
96
96
  @abc.abstractmethod
97
- def watch_entry(self) -> tuple[Path, Path]: ...
97
+ def watch_entry(self) -> tuple[Path, Path | None]: ...
98
98
 
99
99
  @abc.abstractmethod
100
100
  def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]: ...
@@ -270,7 +270,7 @@ class _MountedPythonModule(_MountEntry):
270
270
  for entry in self._proxy_entries():
271
271
  yield from entry.get_files_to_upload()
272
272
 
273
- def watch_entry(self) -> tuple[Path, Path]:
273
+ def watch_entry(self) -> tuple[Path, Path | None]:
274
274
  for entry in self._proxy_entries():
275
275
  # TODO: fix watch for mounts of multi-path packages
276
276
  return entry.watch_entry()
@@ -23,7 +23,7 @@ def python_standalone_mount_name(version: str) -> str:
23
23
  class _MountEntry:
24
24
  def description(self) -> str: ...
25
25
  def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
26
- def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
26
+ def watch_entry(self) -> tuple[pathlib.Path, typing.Optional[pathlib.Path]]: ...
27
27
  def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
28
28
 
29
29
  def _select_files(entries: list[_MountEntry]) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
@@ -105,7 +105,7 @@ class _MountedPythonModule(_MountEntry):
105
105
  def description(self) -> str: ...
106
106
  def _proxy_entries(self) -> list[_MountEntry]: ...
107
107
  def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
108
- def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
108
+ def watch_entry(self) -> tuple[pathlib.Path, typing.Optional[pathlib.Path]]: ...
109
109
  def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
110
110
  def __init__(
111
111
  self,