modal 1.2.6.dev3__tar.gz → 1.2.7.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 (196) hide show
  1. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/PKG-INFO +1 -1
  2. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/task_command_router_client.py +21 -13
  3. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/launch.py +0 -74
  4. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/client.pyi +2 -2
  5. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/PKG-INFO +1 -1
  6. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/SOURCES.txt +0 -2
  7. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_version/__init__.py +1 -1
  8. modal-1.2.6.dev3/modal/cli/programs/launch_instance_ssh.py +0 -94
  9. modal-1.2.6.dev3/modal/cli/programs/run_marimo.py +0 -95
  10. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/LICENSE +0 -0
  11. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/README.md +0 -0
  12. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/__init__.py +0 -0
  13. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/__main__.py +0 -0
  14. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_billing.py +0 -0
  15. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_clustered_functions.py +0 -0
  16. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_clustered_functions.pyi +0 -0
  17. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_container_entrypoint.py +0 -0
  18. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_functions.py +0 -0
  19. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_grpc_client.py +0 -0
  20. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_ipython.py +0 -0
  21. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_load_context.py +0 -0
  22. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_location.py +0 -0
  23. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_object.py +0 -0
  24. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_output.py +0 -0
  25. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_partial_function.py +0 -0
  26. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_pty.py +0 -0
  27. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_resolver.py +0 -0
  28. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_resources.py +0 -0
  29. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/__init__.py +0 -0
  30. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/asgi.py +0 -0
  31. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/container_io_manager.py +0 -0
  32. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  33. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/execution_context.py +0 -0
  34. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/execution_context.pyi +0 -0
  35. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  36. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/telemetry.py +0 -0
  37. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  38. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/user_code_imports.py +0 -0
  39. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_serialization.py +0 -0
  40. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_traceback.py +0 -0
  41. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_tunnel.py +0 -0
  42. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_tunnel.pyi +0 -0
  43. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_type_manager.py +0 -0
  44. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/__init__.py +0 -0
  45. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/app_utils.py +0 -0
  46. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/async_utils.py +0 -0
  47. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/auth_token_manager.py +0 -0
  48. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/blob_utils.py +0 -0
  49. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  50. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/deprecation.py +0 -0
  51. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/docker_utils.py +0 -0
  52. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/function_utils.py +0 -0
  53. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/git_utils.py +0 -0
  54. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/grpc_testing.py +0 -0
  55. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/grpc_utils.py +0 -0
  56. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/hash_utils.py +0 -0
  57. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/http_utils.py +0 -0
  58. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/jwt_utils.py +0 -0
  59. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/logger.py +0 -0
  60. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/mount_utils.py +0 -0
  61. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/name_utils.py +0 -0
  62. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/package_utils.py +0 -0
  63. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/pattern_utils.py +0 -0
  64. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  65. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/shell_utils.py +0 -0
  66. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/time_utils.py +0 -0
  67. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/__init__.py +0 -0
  68. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  69. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/cloudpickle.py +0 -0
  70. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/tblib.py +0 -0
  71. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_watcher.py +0 -0
  72. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/app.py +0 -0
  73. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/app.pyi +0 -0
  74. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/billing.py +0 -0
  75. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2023.12.312.txt +0 -0
  76. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2023.12.txt +0 -0
  77. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2024.04.txt +0 -0
  78. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2024.10.txt +0 -0
  79. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2025.06.txt +0 -0
  80. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/PREVIEW.txt +0 -0
  81. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/README.md +0 -0
  82. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/base-images.json +0 -0
  83. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/call_graph.py +0 -0
  84. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/__init__.py +0 -0
  85. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/_download.py +0 -0
  86. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/_traceback.py +0 -0
  87. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/app.py +0 -0
  88. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/cluster.py +0 -0
  89. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/config.py +0 -0
  90. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/container.py +0 -0
  91. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/dict.py +0 -0
  92. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/entry_point.py +0 -0
  93. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/environment.py +0 -0
  94. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/import_refs.py +0 -0
  95. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/network_file_system.py +0 -0
  96. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/profile.py +0 -0
  97. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/__init__.py +0 -0
  98. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  99. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/vscode.py +0 -0
  100. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/queues.py +0 -0
  101. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/run.py +0 -0
  102. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/secret.py +0 -0
  103. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/shell.py +0 -0
  104. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/token.py +0 -0
  105. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/utils.py +0 -0
  106. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/volume.py +0 -0
  107. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/client.py +0 -0
  108. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cloud_bucket_mount.py +0 -0
  109. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  110. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cls.py +0 -0
  111. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cls.pyi +0 -0
  112. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/config.py +0 -0
  113. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/container_process.py +0 -0
  114. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/container_process.pyi +0 -0
  115. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/dict.py +0 -0
  116. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/dict.pyi +0 -0
  117. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/environments.py +0 -0
  118. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/environments.pyi +0 -0
  119. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/exception.py +0 -0
  120. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/__init__.py +0 -0
  121. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/flash.py +0 -0
  122. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/flash.pyi +0 -0
  123. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/ipython.py +0 -0
  124. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_io.py +0 -0
  125. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_io.pyi +0 -0
  126. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_pattern_matcher.py +0 -0
  127. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/functions.py +0 -0
  128. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/functions.pyi +0 -0
  129. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/gpu.py +0 -0
  130. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/image.py +0 -0
  131. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/image.pyi +0 -0
  132. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/io_streams.py +0 -0
  133. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/io_streams.pyi +0 -0
  134. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/mount.py +0 -0
  135. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/mount.pyi +0 -0
  136. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/network_file_system.py +0 -0
  137. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/object.py +0 -0
  139. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/object.pyi +0 -0
  140. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/output.py +0 -0
  141. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/parallel_map.py +0 -0
  142. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/partial_function.py +0 -0
  144. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/proxy.py +0 -0
  146. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/proxy.pyi +0 -0
  147. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/py.typed +0 -0
  148. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/queue.py +0 -0
  149. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/queue.pyi +0 -0
  150. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/retries.py +0 -0
  151. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/runner.py +0 -0
  152. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/runner.pyi +0 -0
  153. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/running_app.py +0 -0
  154. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/sandbox.py +0 -0
  155. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/schedule.py +0 -0
  157. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/secret.py +0 -0
  159. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/secret.pyi +0 -0
  160. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/serving.py +0 -0
  161. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/serving.pyi +0 -0
  162. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/snapshot.py +0 -0
  163. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/snapshot.pyi +0 -0
  164. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/stream_type.py +0 -0
  165. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/token_flow.py +0 -0
  166. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/token_flow.pyi +0 -0
  167. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/volume.py +0 -0
  168. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/volume.pyi +0 -0
  169. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/dependency_links.txt +0 -0
  170. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/entry_points.txt +0 -0
  171. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/requires.txt +0 -0
  172. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/top_level.txt +0 -0
  173. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/__init__.py +0 -0
  174. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/gen_cli_docs.py +0 -0
  175. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/gen_reference_docs.py +0 -0
  176. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/__init__.py +0 -0
  177. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  178. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/signatures.py +0 -0
  179. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/__init__.py +0 -0
  180. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api.proto +0 -0
  181. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_grpc.py +0 -0
  182. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2.py +0 -0
  183. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2.pyi +0 -0
  184. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2_grpc.py +0 -0
  185. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
  186. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/modal_api_grpc.py +0 -0
  187. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/py.typed +0 -0
  188. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router.proto +0 -0
  189. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  190. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  191. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  192. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  193. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  194. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_version/__main__.py +0 -0
  195. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/pyproject.toml +0 -0
  196. {modal-1.2.6.dev3 → modal-1.2.7.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.6.dev3
3
+ Version: 1.2.7.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -403,7 +403,10 @@ class TaskCommandRouterClient:
403
403
  )
404
404
  return
405
405
 
406
+ logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
406
407
  resp = await fetch_command_router_access(self._server_client, self._task_id)
408
+ logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
409
+
407
410
  # Ensure the server URL remains stable for the lifetime of this client.
408
411
  assert resp.url == self._server_url, "Task router URL changed during session"
409
412
  self._jwt = resp.jwt
@@ -434,7 +437,9 @@ class TaskCommandRouterClient:
434
437
  delay_secs = self.stream_stdio_retry_delay_secs
435
438
  delay_factor = self.stream_stdio_retry_delay_factor
436
439
  num_retries_remaining = self.stream_stdio_max_retries
437
- num_auth_retries = 0
440
+ # Flag to prevent infinite auth retries in the event that the JWT
441
+ # refresh yields an invalid JWT somehow or that the JWT is otherwise invalid.
442
+ did_auth_retry = False
438
443
 
439
444
  async def sleep_and_update_delay_and_num_retries_remaining(e: Exception):
440
445
  nonlocal delay_secs, num_retries_remaining
@@ -458,25 +463,28 @@ class TaskCommandRouterClient:
458
463
  file_descriptor=file_descriptor,
459
464
  )
460
465
 
461
- # Scope auth retry strictly to the initial send (where headers/auth are sent).
466
+ # Auth retry is scoped to a single refresh per streaming attempt. While auth metadata is
467
+ # sent on request start, UNAUTHENTICATED may sometimes surface during iteration,
468
+ # so we handle it at both send and receive boundaries.
462
469
  try:
463
470
  await s.send_message(req, end=True)
471
+ async for item in s:
472
+ # We successfully authenticated after a JWT refresh, reset the auth retry flag.
473
+ if did_auth_retry:
474
+ did_auth_retry = False
475
+ # Reset retry backoff after any successful chunk.
476
+ delay_secs = self.stream_stdio_retry_delay_secs
477
+ offset += len(item.data)
478
+ yield item
464
479
  except GRPCError as exc:
465
- if exc.status == Status.UNAUTHENTICATED and num_auth_retries < 1:
480
+ if exc.status == Status.UNAUTHENTICATED and not did_auth_retry:
466
481
  await self._refresh_jwt()
467
- num_auth_retries += 1
482
+ # Mark that we've retried authentication for this streaming attempt, to
483
+ # prevent subsequent retries.
484
+ did_auth_retry = True
468
485
  continue
469
486
  raise
470
487
 
471
- # We successfully authenticated, reset the auth retry count.
472
- num_auth_retries = 0
473
-
474
- async for item in s:
475
- # Reset retry backoff after any successful chunk.
476
- delay_secs = self.stream_stdio_retry_delay_secs
477
- offset += len(item.data)
478
- yield item
479
-
480
488
  # We successfully streamed all output.
481
489
  return
482
490
  except GRPCError as e:
@@ -3,8 +3,6 @@ import asyncio
3
3
  import inspect
4
4
  import json
5
5
  import os
6
- import subprocess
7
- import tempfile
8
6
  from pathlib import Path
9
7
  from typing import Any, Optional
10
8
 
@@ -120,75 +118,3 @@ def vscode(
120
118
  "volume": volume,
121
119
  }
122
120
  _launch_program("vscode", "vscode.py", detach, args)
123
-
124
-
125
- @launch_cli.command(name="machine", help="Start an instance on Modal, with direct SSH access.", hidden=True)
126
- def machine(
127
- name: str, # Name of the machine App.
128
- cpu: int = 8, # Reservation of CPU cores (can burst above this value).
129
- memory: int = 32768, # Reservation of memory in MiB (can burst above this value).
130
- gpu: Optional[str] = None, # GPU type and count, e.g. "t4" or "h100:2".
131
- image: Optional[str] = None, # Image tag to use from registry. Defaults to the notebook base image.
132
- timeout: int = 3600 * 24, # Timeout in seconds for the instance.
133
- volume: str = "machine-vol", # Attach a persisted `modal.Volume` at /workspace (created if missing).
134
- ):
135
- tempdir = Path(tempfile.gettempdir())
136
- key_path = tempdir / "modal-machine-keyfile.pem"
137
- # Generate a new SSH key pair for this machine instance.
138
- if not key_path.exists():
139
- subprocess.run(
140
- ["ssh-keygen", "-t", "ed25519", "-f", str(key_path), "-N", ""],
141
- check=True,
142
- stdout=subprocess.DEVNULL,
143
- )
144
- # Add the key with expiry 1d to ssh agent.
145
- subprocess.run(
146
- ["ssh-add", "-t", "1d", str(key_path)],
147
- check=True,
148
- stdout=subprocess.DEVNULL,
149
- stderr=subprocess.DEVNULL,
150
- )
151
-
152
- os.environ["SSH_PUBLIC_KEY"] = Path(str(key_path) + ".pub").read_text()
153
- os.environ["MODAL_LOGS_TIMEOUT"] = "0" # hack to work with --detach
154
-
155
- args = {
156
- "cpu": cpu,
157
- "memory": memory,
158
- "gpu": gpu,
159
- "image": image,
160
- "timeout": timeout,
161
- "volume": volume,
162
- }
163
- _launch_program(
164
- "machine",
165
- "launch_instance_ssh.py",
166
- True,
167
- args,
168
- description=name,
169
- )
170
-
171
-
172
- @launch_cli.command(name="marimo", help="Start a remote Marimo notebook on Modal.", hidden=True)
173
- def marimo(
174
- cpu: int = 8,
175
- memory: int = 32768,
176
- gpu: Optional[str] = None,
177
- image: str = "debian:12",
178
- timeout: int = 3600,
179
- add_python: Optional[str] = "3.12",
180
- mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
181
- volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
182
- detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
183
- ):
184
- args = {
185
- "cpu": cpu,
186
- "memory": memory,
187
- "gpu": gpu,
188
- "timeout": timeout,
189
- "image": image,
190
- "add_python": add_python,
191
- "mount": mount,
192
- "volume": volume,
193
- }
194
- _launch_program("marimo", "run_marimo.py", detach, args)
@@ -32,7 +32,7 @@ class _Client:
32
32
  server_url: str,
33
33
  client_type: int,
34
34
  credentials: typing.Optional[tuple[str, str]],
35
- version: str = "1.2.6.dev3",
35
+ version: str = "1.2.7.dev0",
36
36
  ):
37
37
  """mdmd:hidden
38
38
  The Modal client object is not intended to be instantiated directly by users.
@@ -163,7 +163,7 @@ class Client:
163
163
  server_url: str,
164
164
  client_type: int,
165
165
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.2.6.dev3",
166
+ version: str = "1.2.7.dev0",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.6.dev3
3
+ Version: 1.2.7.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -161,9 +161,7 @@ modal/cli/token.py
161
161
  modal/cli/utils.py
162
162
  modal/cli/volume.py
163
163
  modal/cli/programs/__init__.py
164
- modal/cli/programs/launch_instance_ssh.py
165
164
  modal/cli/programs/run_jupyter.py
166
- modal/cli/programs/run_marimo.py
167
165
  modal/cli/programs/vscode.py
168
166
  modal/experimental/__init__.py
169
167
  modal/experimental/flash.py
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.2.6.dev3"
4
+ __version__ = "1.2.7.dev0"
@@ -1,94 +0,0 @@
1
- # Copyright Modal Labs 2023
2
- # type: ignore
3
- import json
4
- import os
5
- import sys
6
- from typing import Any
7
-
8
- import rich
9
- import rich.panel
10
- import rich.rule
11
-
12
- import modal
13
- import modal.experimental
14
-
15
- # Passed by `modal launch` locally via CLI.
16
- args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
17
-
18
- app = modal.App()
19
-
20
- image: modal.Image
21
- if args.get("image"):
22
- image = modal.Image.from_registry(args.get("image"))
23
- else:
24
- # Must be set to the same image builder version as the notebook base image.
25
- os.environ["MODAL_IMAGE_BUILDER_VERSION"] = "2024.10"
26
- image = modal.experimental.notebook_base_image(python_version="3.12")
27
-
28
- volume = (
29
- modal.Volume.from_name(
30
- args.get("volume"),
31
- create_if_missing=True,
32
- )
33
- if args.get("volume")
34
- else None
35
- )
36
- volumes = {"/workspace": volume} if volume else {}
37
-
38
-
39
- startup_script = """
40
- set -eu
41
- mkdir -p /run/sshd
42
-
43
- # Check if sshd is installed, install if not
44
- test -x /usr/sbin/sshd || (apt-get update && apt-get install -y openssh-server)
45
-
46
- # Change default working directory to /workspace
47
- echo "cd /workspace" >> /root/.profile
48
-
49
- mkdir -p /root/.ssh
50
- echo "$SSH_PUBLIC_KEY" >> /root/.ssh/authorized_keys
51
- /usr/sbin/sshd -D -e
52
- """
53
-
54
-
55
- @app.local_entrypoint()
56
- def main():
57
- if not os.environ.get("SSH_PUBLIC_KEY"):
58
- raise ValueError("SSH_PUBLIC_KEY environment variable is not set")
59
-
60
- sb = modal.Sandbox.create(
61
- *("sh", "-c", startup_script),
62
- app=app,
63
- image=image,
64
- cpu=args.get("cpu"),
65
- memory=args.get("memory"),
66
- gpu=args.get("gpu"),
67
- timeout=args.get("timeout"),
68
- volumes=volumes,
69
- unencrypted_ports=[22], # Forward SSH port
70
- secrets=[modal.Secret.from_dict({"SSH_PUBLIC_KEY": os.environ.get("SSH_PUBLIC_KEY")})],
71
- )
72
- hostname, port = sb.tunnels()[22].tcp_socket
73
- connection_cmd = f"ssh -A -p {port} root@{hostname}"
74
-
75
- rich.print(
76
- rich.rule.Rule(style="yellow"),
77
- rich.panel.Panel(
78
- f"""Your instance is ready! You can SSH into it using the following command:
79
-
80
- [dim gray]>[/dim gray] [bold cyan]{connection_cmd}[/bold cyan]
81
-
82
- [italic]Details:[/italic]
83
- • Name: [magenta]{app.description}[/magenta]
84
- • CPU: [yellow]{args.get("cpu")} cores[/yellow]
85
- • Memory: [yellow]{args.get("memory")} MiB[/yellow]
86
- • Timeout: [yellow]{args.get("timeout")} seconds[/yellow]
87
- • GPU: [green]{(args.get("gpu") or "N/A").upper()}[/green]""",
88
- title="SSH Connection",
89
- expand=False,
90
- ),
91
- rich.rule.Rule(style="yellow"),
92
- )
93
-
94
- sys.exit(0) # Exit immediately to prevent "Timed out waiting for final apps log."
@@ -1,95 +0,0 @@
1
- # Copyright Modal Labs 2025
2
- # type: ignore
3
- import json
4
- import os
5
- import secrets
6
- import socket
7
- import subprocess
8
- import threading
9
- import time
10
- import webbrowser
11
- from typing import Any
12
-
13
- from modal import App, Image, Queue, Secret, Volume, forward
14
-
15
- # Args injected by `modal launch` CLI.
16
- args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
17
-
18
- app = App()
19
-
20
- image = Image.from_registry(args.get("image"), add_python=args.get("add_python")).uv_pip_install("marimo")
21
-
22
- # Optional host-filesystem mount (read-only snapshot of your project, useful for editing)
23
- if args.get("mount"):
24
- image = image.add_local_dir(args["mount"], remote_path="/root/marimo/mount")
25
-
26
- # Optional persistent Modal volume
27
- volume = Volume.from_name(args["volume"], create_if_missing=True) if args.get("volume") else None
28
- volumes = {"/root/marimo/volume": volume} if volume else {}
29
-
30
-
31
- def _wait_for_port(url: str, q: Queue) -> None:
32
- start = time.monotonic()
33
- while True:
34
- try:
35
- with socket.create_connection(("localhost", 8888), timeout=30):
36
- break
37
- except OSError as exc:
38
- if time.monotonic() - start > 30:
39
- raise TimeoutError("marimo server did not start within 30 s") from exc
40
- time.sleep(0.05)
41
- q.put(url)
42
-
43
-
44
- @app.function(
45
- image=image,
46
- cpu=args.get("cpu"),
47
- memory=args.get("memory"),
48
- gpu=args.get("gpu"),
49
- timeout=args.get("timeout", 3600),
50
- secrets=[Secret.from_dict({"MODAL_LAUNCH_ARGS": json.dumps(args)})],
51
- volumes=volumes,
52
- max_containers=1 if volume else None,
53
- )
54
- def run_marimo(q: Queue):
55
- os.makedirs("/root/marimo", exist_ok=True)
56
-
57
- # marimo supports token-based auth; generate one so only you can connect
58
- token = secrets.token_urlsafe(12)
59
-
60
- with forward(8888) as tunnel:
61
- url = f"{tunnel.url}/?access_token={token}"
62
- threading.Thread(target=_wait_for_port, args=(url, q), daemon=True).start()
63
-
64
- print("\nmarimo on Modal, opening in browser …")
65
- print(f" -> {url}\n")
66
-
67
- # Launch the headless edit server
68
- subprocess.run(
69
- [
70
- "marimo",
71
- "edit",
72
- "--headless", # don't open browser in container
73
- "--host",
74
- "0.0.0.0", # bind all interfaces
75
- "--port",
76
- "8888",
77
- "--token-password",
78
- token, # enable session-based auth
79
- "--skip-update-check",
80
- "/root/marimo", # workspace directory
81
- ],
82
- env={**os.environ, "SHELL": "/bin/bash"},
83
- )
84
-
85
- q.put("done")
86
-
87
-
88
- @app.local_entrypoint()
89
- def main():
90
- with Queue.ephemeral() as q:
91
- run_marimo.spawn(q)
92
- url = q.get() # first message = connect URL
93
- time.sleep(1) # give server a heartbeat
94
- webbrowser.open(url)
95
- assert q.get() == "done"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes