modal 1.4.2.dev4__tar.gz → 1.4.2.dev6__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 (210) hide show
  1. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/PKG-INFO +1 -1
  2. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/grpc_utils.py +29 -5
  3. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/task_command_router_client.py +2 -1
  4. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/app.py +59 -59
  5. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/client.pyi +2 -2
  6. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/dict.py +5 -5
  7. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/dict.pyi +8 -8
  8. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/functions.pyi +6 -6
  9. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/queue.py +5 -5
  10. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/queue.pyi +8 -8
  11. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/sandbox_fs.py +6 -6
  12. modal-1.4.2.dev6/modal/sandbox_fs.pyi +591 -0
  13. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/secret.py +5 -5
  14. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/secret.pyi +8 -8
  15. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/volume.py +5 -5
  16. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/volume.pyi +8 -8
  17. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/PKG-INFO +1 -1
  18. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/mdmd/mdmd.py +30 -13
  19. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/api_pb2.py +656 -656
  20. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/api_pb2.pyi +6 -2
  21. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_version/__init__.py +1 -1
  22. modal-1.4.2.dev4/modal/sandbox_fs.pyi +0 -627
  23. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/LICENSE +0 -0
  24. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/README.md +0 -0
  25. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/__init__.py +0 -0
  26. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/__main__.py +0 -0
  27. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_billing.py +0 -0
  28. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_clustered_functions.py +0 -0
  29. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_clustered_functions.pyi +0 -0
  30. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_container_entrypoint.py +0 -0
  31. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_functions.py +0 -0
  32. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_grpc_client.py +0 -0
  33. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_ipython.py +0 -0
  34. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_load_context.py +0 -0
  35. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_location.py +0 -0
  36. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_logs.py +0 -0
  37. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_object.py +0 -0
  38. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_output/__init__.py +0 -0
  39. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_output/manager.py +0 -0
  40. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_output/pty.py +0 -0
  41. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_output/rich.py +0 -0
  42. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_output/status.py +0 -0
  43. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_partial_function.py +0 -0
  44. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_resolver.py +0 -0
  45. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_resources.py +0 -0
  46. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/__init__.py +0 -0
  47. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/asgi.py +0 -0
  48. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/container_io_manager.py +0 -0
  49. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/container_io_manager.pyi +0 -0
  50. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/execution_context.py +0 -0
  51. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/execution_context.pyi +0 -0
  52. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  53. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/telemetry.py +0 -0
  54. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/user_code_event_loop.py +0 -0
  55. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_runtime/user_code_imports.py +0 -0
  56. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_serialization.py +0 -0
  57. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_server.py +0 -0
  58. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_traceback.py +0 -0
  59. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_tunnel.py +0 -0
  60. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_tunnel.pyi +0 -0
  61. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_type_manager.py +0 -0
  62. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/__init__.py +0 -0
  63. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/app_utils.py +0 -0
  64. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/async_utils.py +0 -0
  65. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/auth_token_manager.py +0 -0
  66. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/blob_utils.py +0 -0
  67. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/browser_utils.py +0 -0
  68. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/bytes_io_segment_payload.py +0 -0
  69. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/deprecation.py +0 -0
  70. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/docker_utils.py +0 -0
  71. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/function_utils.py +0 -0
  72. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/git_utils.py +0 -0
  73. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/grpc_testing.py +0 -0
  74. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/hash_utils.py +0 -0
  75. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/http_utils.py +0 -0
  76. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/jwt_utils.py +0 -0
  77. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/logger.py +0 -0
  78. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/mount_utils.py +0 -0
  79. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/name_utils.py +0 -0
  80. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/package_utils.py +0 -0
  81. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/pattern_utils.py +0 -0
  82. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/rand_pb_testing.py +0 -0
  83. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/sandbox_fs_utils.py +0 -0
  84. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/shell_utils.py +0 -0
  85. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_utils/time_utils.py +0 -0
  86. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_vendor/__init__.py +0 -0
  87. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  88. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_vendor/cloudpickle.py +0 -0
  89. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_vendor/tblib.py +0 -0
  90. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_vendor/version.py +0 -0
  91. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/_watcher.py +0 -0
  92. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/app.py +0 -0
  93. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/app.pyi +0 -0
  94. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/billing.py +0 -0
  95. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/2023.12.312.txt +0 -0
  96. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/2023.12.txt +0 -0
  97. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/2024.04.txt +0 -0
  98. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/2024.10.txt +0 -0
  99. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/2025.06.txt +0 -0
  100. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/PREVIEW.txt +0 -0
  101. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/README.md +0 -0
  102. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/builder/base-images.json +0 -0
  103. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/call_graph.py +0 -0
  104. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/__init__.py +0 -0
  105. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/_download.py +0 -0
  106. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/_traceback.py +0 -0
  107. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/billing.py +0 -0
  108. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/changelog.py +0 -0
  109. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/cluster.py +0 -0
  110. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/config.py +0 -0
  111. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/container.py +0 -0
  112. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/dashboard.py +0 -0
  113. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/dict.py +0 -0
  114. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/entry_point.py +0 -0
  115. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/environment.py +0 -0
  116. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/import_refs.py +0 -0
  117. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/launch.py +0 -0
  118. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/network_file_system.py +0 -0
  119. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/profile.py +0 -0
  120. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/programs/__init__.py +0 -0
  121. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/programs/run_jupyter.py +0 -0
  122. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/programs/vscode.py +0 -0
  123. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/queues.py +0 -0
  124. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/run.py +0 -0
  125. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/secret.py +0 -0
  126. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/selector.py +0 -0
  127. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/shell.py +0 -0
  128. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/token.py +0 -0
  129. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/utils.py +0 -0
  130. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cli/volume.py +0 -0
  131. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/client.py +0 -0
  132. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cloud_bucket_mount.py +0 -0
  133. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cloud_bucket_mount.pyi +0 -0
  134. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cls.py +0 -0
  135. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/cls.pyi +0 -0
  136. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/config.py +0 -0
  137. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/container_process.py +0 -0
  138. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/container_process.pyi +0 -0
  139. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/environments.py +0 -0
  140. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/environments.pyi +0 -0
  141. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/exception.py +0 -0
  142. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/experimental/__init__.py +0 -0
  143. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/experimental/flash.py +0 -0
  144. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/experimental/flash.pyi +0 -0
  145. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/experimental/ipython.py +0 -0
  146. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/file_io.py +0 -0
  147. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/file_io.pyi +0 -0
  148. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/file_pattern_matcher.py +0 -0
  149. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/functions.py +0 -0
  150. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/image.py +0 -0
  151. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/image.pyi +0 -0
  152. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/io_streams.py +0 -0
  153. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/io_streams.pyi +0 -0
  154. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/mount.py +0 -0
  155. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/mount.pyi +0 -0
  156. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/network_file_system.py +0 -0
  157. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/network_file_system.pyi +0 -0
  158. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/object.py +0 -0
  159. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/object.pyi +0 -0
  160. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/output.py +0 -0
  161. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/parallel_map.py +0 -0
  162. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/parallel_map.pyi +0 -0
  163. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/partial_function.py +0 -0
  164. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/partial_function.pyi +0 -0
  165. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/proxy.py +0 -0
  166. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/proxy.pyi +0 -0
  167. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/py.typed +0 -0
  168. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/retries.py +0 -0
  169. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/runner.py +0 -0
  170. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/runner.pyi +0 -0
  171. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/running_app.py +0 -0
  172. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/sandbox.py +0 -0
  173. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/sandbox.pyi +0 -0
  174. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/schedule.py +0 -0
  175. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/scheduler_placement.py +0 -0
  176. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/server.py +0 -0
  177. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/server.pyi +0 -0
  178. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/serving.py +0 -0
  179. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/serving.pyi +0 -0
  180. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/snapshot.py +0 -0
  181. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/snapshot.pyi +0 -0
  182. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/stream_type.py +0 -0
  183. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/token_flow.py +0 -0
  184. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal/token_flow.pyi +0 -0
  185. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/SOURCES.txt +0 -0
  186. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/dependency_links.txt +0 -0
  187. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/entry_points.txt +0 -0
  188. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/requires.txt +0 -0
  189. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal.egg-info/top_level.txt +0 -0
  190. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/__init__.py +0 -0
  191. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/gen_cli_docs.py +0 -0
  192. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/gen_cli_docs_main.py +0 -0
  193. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/gen_reference_docs.py +0 -0
  194. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/gen_reference_docs_main.py +0 -0
  195. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/mdmd/__init__.py +0 -0
  196. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_docs/mdmd/signatures.py +0 -0
  197. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/__init__.py +0 -0
  198. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/api_grpc.py +0 -0
  199. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/api_pb2_grpc.py +0 -0
  200. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/api_pb2_grpc.pyi +0 -0
  201. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/modal_api_grpc.py +0 -0
  202. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/py.typed +0 -0
  203. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/task_command_router_grpc.py +0 -0
  204. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/task_command_router_pb2.py +0 -0
  205. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/task_command_router_pb2.pyi +0 -0
  206. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  207. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  208. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/modal_version/__main__.py +0 -0
  209. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/pyproject.toml +0 -0
  210. {modal-1.4.2.dev4 → modal-1.4.2.dev6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.2.dev4
3
+ Version: 1.4.2.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -216,7 +216,11 @@ def create_channel(
216
216
  for k, v in metadata.items():
217
217
  event.metadata[k] = v
218
218
 
219
- logger.debug(f"Sending request to {event.method_name}")
219
+ idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
220
+ if idempotency_key is None:
221
+ logger.debug(f"Sending request to {event.method_name}")
222
+ else:
223
+ logger.debug(f"Sending request to {event.method_name} ({idempotency_key[:8]})")
220
224
 
221
225
  grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
222
226
 
@@ -288,12 +292,14 @@ def process_exception_before_retry(
288
292
  n_retries: int,
289
293
  delay: float,
290
294
  idempotency_key: str,
295
+ rpc_elapsed: float,
291
296
  ):
292
297
  """Process exception before retry, used by `_retry_transient_errors`."""
293
298
  with suppress_tb_frame():
294
299
  if final_attempt:
295
300
  logger.debug(
296
- f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})"
301
+ f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} {rpc_elapsed=:0.2f}s "
302
+ f"for {fn_name} ({idempotency_key[:8]})"
297
303
  )
298
304
  if isinstance(exc, OSError):
299
305
  raise ConnectionError(str(exc))
@@ -310,7 +316,10 @@ def process_exception_before_retry(
310
316
  # we handle in the retry logic once we drop this check!
311
317
  raise exc
312
318
 
313
- logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
319
+ logger.debug(
320
+ f"Retryable failure {repr(exc)} {n_retries=} {delay=} {rpc_elapsed=:0.2f}s "
321
+ f"for {fn_name} ({idempotency_key[:8]})"
322
+ )
314
323
 
315
324
 
316
325
  async def _retry_transient_errors(
@@ -373,6 +382,7 @@ async def _retry_transient_errors(
373
382
  else:
374
383
  timeout = None
375
384
 
385
+ attempt_started_at = time.monotonic()
376
386
  try:
377
387
  with suppress_tb_frame():
378
388
  return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
@@ -409,7 +419,13 @@ async def _retry_transient_errors(
409
419
 
410
420
  with suppress_tb_frame():
411
421
  process_exception_before_retry(
412
- exc, final_attempt, fn.name, n_retries, server_delay, idempotency_key
422
+ exc,
423
+ final_attempt,
424
+ fn.name,
425
+ n_retries,
426
+ server_delay,
427
+ idempotency_key,
428
+ time.monotonic() - attempt_started_at,
413
429
  )
414
430
 
415
431
  now = time.time()
@@ -438,7 +454,15 @@ async def _retry_transient_errors(
438
454
  final_attempt = False
439
455
 
440
456
  with suppress_tb_frame():
441
- process_exception_before_retry(exc, final_attempt, fn.name, n_retries, delay, idempotency_key)
457
+ process_exception_before_retry(
458
+ exc,
459
+ final_attempt,
460
+ fn.name,
461
+ n_retries,
462
+ delay,
463
+ idempotency_key,
464
+ time.monotonic() - attempt_started_at,
465
+ )
442
466
 
443
467
  n_retries += 1
444
468
 
@@ -502,7 +502,8 @@ class TaskCommandRouterClient:
502
502
  jwt, url = v1_resp.jwt, v1_resp.url
503
503
 
504
504
  # Ensure the server URL remains stable for the lifetime of this client.
505
- assert url == self._server_url, "Task router URL changed during session"
505
+ if url != self._server_url:
506
+ logger.warning("Task router URL changed during session")
506
507
  self._jwt = jwt
507
508
  self._jwt_exp = _parse_jwt_expiration(jwt)
508
509
 
@@ -351,6 +351,65 @@ async def rollback(
351
351
  rich.print("[green]✓[/green] Deployment rollback successful!")
352
352
 
353
353
 
354
+ @app_cli.command("rollover", no_args_is_help=True)
355
+ @synchronizer.create_blocking
356
+ async def rollover(
357
+ app_identifier: str = APP_IDENTIFIER,
358
+ *,
359
+ strategy: str = typer.Option(
360
+ "rolling",
361
+ help="Strategy for rollover",
362
+ click_type=click.Choice(get_args(DEPLOYMENT_STRATEGY_TYPE)),
363
+ ),
364
+ env: Optional[str] = ENV_OPTION,
365
+ ):
366
+ """Rollover an App.
367
+
368
+ A rollover replaces existing containers with fresh ones built from the same
369
+ App version — useful for refreshing containers without changing your code.
370
+ The rollover appears as a new entry in the App's deployment history.
371
+
372
+ **Examples:**
373
+
374
+ Rollover an App using a rolling deployment. Running containers are now considered
375
+ outdated and new containers will replace them.
376
+
377
+ ```
378
+ modal app rollover my-app
379
+ ```
380
+
381
+ Rollover an App by termatining all running containers. Inputs on the queue will
382
+ start new containers.
383
+
384
+ ```
385
+ modal app rollover my-app --strategy recreate
386
+ ```
387
+ """
388
+ env = ensure_env(env)
389
+ output_mgr = OutputManager.get()
390
+ output_mgr.print(f"🔨 Starting app rollover with {strategy} strategy")
391
+ t0 = time.monotonic()
392
+
393
+ client = await _Client.from_env()
394
+ app_id = await get_app_id.aio(app_identifier, env, client)
395
+
396
+ req = api_pb2.AppRolloverRequest(app_id=app_id)
397
+ response = await client.stub.AppRollover(req)
398
+ print_server_warnings(response.server_warnings)
399
+
400
+ if strategy == "recreate":
401
+ try:
402
+ await _stop_and_wait_for_containers(client, app_id, response.deployed_at, env)
403
+ except Exception as exc:
404
+ warnings.warn(f"App updated successfully, but containers did not all terminate. {exc}", UserWarning)
405
+ output_mgr.print(f"\nView Deployment: [magenta]{response.url}[/magenta]")
406
+ sys.exit(1)
407
+
408
+ duration = time.monotonic() - t0
409
+ output_mgr.step_completed(f"Rollover completed in {duration:.3f}s with {strategy} strategy! 🎉")
410
+ output_mgr.print(f"\nView Deployment: [magenta]{response.url}[/magenta]")
411
+
412
+
354
413
  @app_cli.command("stop", no_args_is_help=True)
355
414
  @synchronizer.create_blocking
356
415
  async def stop(
@@ -471,62 +530,3 @@ async def dashboard(
471
530
 
472
531
  url = f"https://modal.com/id/{app_id}"
473
532
  open_url_and_display(url, "App dashboard")
474
-
475
-
476
- @app_cli.command("rollover", no_args_is_help=True, context_settings={"ignore_unknown_options": True})
477
- @synchronizer.create_blocking
478
- async def rollover(
479
- app_identifier: str = APP_IDENTIFIER,
480
- *,
481
- strategy: str = typer.Option(
482
- "rolling",
483
- help="Strategy for rollover",
484
- click_type=click.Choice(get_args(DEPLOYMENT_STRATEGY_TYPE)),
485
- ),
486
- env: Optional[str] = ENV_OPTION,
487
- ):
488
- """Rollover an App.
489
-
490
- A rollover replaces existing containers with fresh ones built from the same
491
- App version — useful for refreshing containers without changing your code.
492
- The rollover appears as a new entry in the App's deployment history.
493
-
494
- **Examples:**
495
-
496
- Rollover an App using a rolling deployment. Running containers are now considered
497
- outdated and new containers will replace them.
498
-
499
- ```
500
- modal app rollover my-app
501
- ```
502
-
503
- Rollover an App by termatining all running containers. Inputs on the queue will
504
- start new containers.
505
-
506
- ```
507
- modal app rollover my-app --strategy recreate
508
- ```
509
- """
510
- env = ensure_env(env)
511
- output_mgr = OutputManager.get()
512
- output_mgr.print(f"🔨 Starting app rollover with {strategy} strategy")
513
- t0 = time.monotonic()
514
-
515
- client = await _Client.from_env()
516
- app_id = await get_app_id.aio(app_identifier, env, client)
517
-
518
- req = api_pb2.AppRolloverRequest(app_id=app_id)
519
- response = await client.stub.AppRollover(req)
520
- print_server_warnings(response.server_warnings)
521
-
522
- if strategy == "recreate":
523
- try:
524
- await _stop_and_wait_for_containers(client, app_id, response.deployed_at, env)
525
- except Exception as exc:
526
- warnings.warn(f"App updated successfully, but containers did not all terminate. {exc}", UserWarning)
527
- output_mgr.print(f"\nView Deployment: [magenta]{response.url}[/magenta]")
528
- sys.exit(1)
529
-
530
- duration = time.monotonic() - t0
531
- output_mgr.step_completed(f"Rollover completed in {duration:.3f}s with {strategy} strategy! 🎉")
532
- output_mgr.print(f"\nView Deployment: [magenta]{response.url}[/magenta]")
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.4.2.dev4",
38
+ version: str = "1.4.2.dev6",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -171,7 +171,7 @@ class Client:
171
171
  server_url: str,
172
172
  client_type: int,
173
173
  credentials: typing.Optional[tuple[str, str]],
174
- version: str = "1.4.2.dev4",
174
+ version: str = "1.4.2.dev6",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -84,8 +84,8 @@ class DictInfo:
84
84
  class _DictManager:
85
85
  """Namespace with methods for managing named Dict objects."""
86
86
 
87
- @staticmethod
88
87
  async def create(
88
+ self,
89
89
  name: str, # Name to use for the new Dict
90
90
  *,
91
91
  allow_existing: bool = False, # If True, no-op when the Dict already exists
@@ -137,8 +137,8 @@ class _DictManager:
137
137
  if not allow_existing:
138
138
  raise
139
139
 
140
- @staticmethod
141
140
  async def list(
141
+ self,
142
142
  *,
143
143
  max_objects: Optional[int] = None, # Limit results to this size
144
144
  created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
@@ -205,8 +205,8 @@ class _DictManager:
205
205
  ]
206
206
  return dicts[:max_objects] if max_objects is not None else dicts
207
207
 
208
- @staticmethod
209
208
  async def delete(
209
+ self,
210
210
  name: str, # Name of the Dict to delete
211
211
  *,
212
212
  allow_missing: bool = False, # If True, don't raise an error if the Dict doesn't exist
@@ -299,8 +299,8 @@ class _Dict(_Object, type_prefix="di"):
299
299
 
300
300
  @classproperty
301
301
  @classmethod
302
- def objects(cls) -> type[_DictManager]:
303
- return _DictManager
302
+ def objects(cls) -> _DictManager:
303
+ return _DictManager()
304
304
 
305
305
  @property
306
306
  def name(self) -> Optional[str]:
@@ -44,8 +44,8 @@ class DictInfo:
44
44
 
45
45
  class _DictManager:
46
46
  """Namespace with methods for managing named Dict objects."""
47
- @staticmethod
48
47
  async def create(
48
+ self,
49
49
  name: str,
50
50
  *,
51
51
  allow_existing: bool = False,
@@ -80,8 +80,8 @@ class _DictManager:
80
80
  """
81
81
  ...
82
82
 
83
- @staticmethod
84
83
  async def list(
84
+ self,
85
85
  *,
86
86
  max_objects: typing.Optional[int] = None,
87
87
  created_before: typing.Union[datetime.datetime, str, None] = None,
@@ -114,8 +114,8 @@ class _DictManager:
114
114
  """
115
115
  ...
116
116
 
117
- @staticmethod
118
117
  async def delete(
118
+ self,
119
119
  name: str,
120
120
  *,
121
121
  allow_missing: bool = False,
@@ -224,7 +224,7 @@ class DictManager:
224
224
  """
225
225
  ...
226
226
 
227
- create: typing.ClassVar[__create_spec]
227
+ create: __create_spec
228
228
 
229
229
  class __list_spec(typing_extensions.Protocol):
230
230
  def __call__(
@@ -297,7 +297,7 @@ class DictManager:
297
297
  """
298
298
  ...
299
299
 
300
- list: typing.ClassVar[__list_spec]
300
+ list: __list_spec
301
301
 
302
302
  class __delete_spec(typing_extensions.Protocol):
303
303
  def __call__(
@@ -360,7 +360,7 @@ class DictManager:
360
360
  """
361
361
  ...
362
362
 
363
- delete: typing.ClassVar[__delete_spec]
363
+ delete: __delete_spec
364
364
 
365
365
  class _Dict(modal._object._Object):
366
366
  """Distributed dictionary for storage in Modal apps.
@@ -413,7 +413,7 @@ class _Dict(modal._object._Object):
413
413
 
414
414
  @synchronicity.classproperty
415
415
  @classmethod
416
- def objects(cls) -> type[_DictManager]: ...
416
+ def objects(cls) -> _DictManager: ...
417
417
  @property
418
418
  def name(self) -> typing.Optional[str]: ...
419
419
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -655,7 +655,7 @@ class Dict(modal.object.Object):
655
655
 
656
656
  @synchronicity.classproperty
657
657
  @classmethod
658
- def objects(cls) -> type[DictManager]: ...
658
+ def objects(cls) -> DictManager: ...
659
659
  @property
660
660
  def name(self) -> typing.Optional[str]: ...
661
661
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -348,7 +348,7 @@ class Function(
348
348
 
349
349
  _call_generator: ___call_generator_spec
350
350
 
351
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
351
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
352
352
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
353
353
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
354
354
  ...
@@ -357,7 +357,7 @@ class Function(
357
357
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
358
358
  ...
359
359
 
360
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
360
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
361
361
 
362
362
  class __remote_gen_spec(typing_extensions.Protocol):
363
363
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -384,7 +384,7 @@ class Function(
384
384
  """
385
385
  ...
386
386
 
387
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
387
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
388
388
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
389
389
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
390
390
 
@@ -407,7 +407,7 @@ class Function(
407
407
  """
408
408
  ...
409
409
 
410
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
410
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
411
411
 
412
412
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
413
413
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -415,7 +415,7 @@ class Function(
415
415
 
416
416
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
417
417
 
418
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
418
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
419
419
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
420
420
  """Calls the function with the given arguments, without waiting for the results.
421
421
 
@@ -436,7 +436,7 @@ class Function(
436
436
  """
437
437
  ...
438
438
 
439
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
439
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
440
440
 
441
441
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
442
442
  """Return the inner Python object wrapped by this Modal Function."""
@@ -49,8 +49,8 @@ class QueueInfo:
49
49
  class _QueueManager:
50
50
  """Namespace with methods for managing named Queue objects."""
51
51
 
52
- @staticmethod
53
52
  async def create(
53
+ self,
54
54
  name: str, # Name to use for the new Queue
55
55
  *,
56
56
  allow_existing: bool = False, # If True, no-op when the Queue already exists
@@ -102,8 +102,8 @@ class _QueueManager:
102
102
  if not allow_existing:
103
103
  raise
104
104
 
105
- @staticmethod
106
105
  async def list(
106
+ self,
107
107
  *,
108
108
  max_objects: Optional[int] = None, # Limit results to this size
109
109
  created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
@@ -170,8 +170,8 @@ class _QueueManager:
170
170
  ]
171
171
  return queues[:max_objects] if max_objects is not None else queues
172
172
 
173
- @staticmethod
174
173
  async def delete(
174
+ self,
175
175
  name: str, # Name of the Queue to delete
176
176
  *,
177
177
  allow_missing: bool = False, # If True, don't raise an error if the Queue doesn't exist
@@ -292,8 +292,8 @@ class _Queue(_Object, type_prefix="qu"):
292
292
 
293
293
  @classproperty
294
294
  @classmethod
295
- def objects(cls) -> type[_QueueManager]:
296
- return _QueueManager
295
+ def objects(cls) -> _QueueManager:
296
+ return _QueueManager()
297
297
 
298
298
  @property
299
299
  def name(self) -> Optional[str]:
@@ -33,8 +33,8 @@ class QueueInfo:
33
33
 
34
34
  class _QueueManager:
35
35
  """Namespace with methods for managing named Queue objects."""
36
- @staticmethod
37
36
  async def create(
37
+ self,
38
38
  name: str,
39
39
  *,
40
40
  allow_existing: bool = False,
@@ -69,8 +69,8 @@ class _QueueManager:
69
69
  """
70
70
  ...
71
71
 
72
- @staticmethod
73
72
  async def list(
73
+ self,
74
74
  *,
75
75
  max_objects: typing.Optional[int] = None,
76
76
  created_before: typing.Union[datetime.datetime, str, None] = None,
@@ -103,8 +103,8 @@ class _QueueManager:
103
103
  """
104
104
  ...
105
105
 
106
- @staticmethod
107
106
  async def delete(
107
+ self,
108
108
  name: str,
109
109
  *,
110
110
  allow_missing: bool = False,
@@ -213,7 +213,7 @@ class QueueManager:
213
213
  """
214
214
  ...
215
215
 
216
- create: typing.ClassVar[__create_spec]
216
+ create: __create_spec
217
217
 
218
218
  class __list_spec(typing_extensions.Protocol):
219
219
  def __call__(
@@ -286,7 +286,7 @@ class QueueManager:
286
286
  """
287
287
  ...
288
288
 
289
- list: typing.ClassVar[__list_spec]
289
+ list: __list_spec
290
290
 
291
291
  class __delete_spec(typing_extensions.Protocol):
292
292
  def __call__(
@@ -349,7 +349,7 @@ class QueueManager:
349
349
  """
350
350
  ...
351
351
 
352
- delete: typing.ClassVar[__delete_spec]
352
+ delete: __delete_spec
353
353
 
354
354
  class _Queue(modal._object._Object):
355
355
  """Distributed, FIFO queue for data flow in Modal apps.
@@ -432,7 +432,7 @@ class _Queue(modal._object._Object):
432
432
 
433
433
  @synchronicity.classproperty
434
434
  @classmethod
435
- def objects(cls) -> type[_QueueManager]: ...
435
+ def objects(cls) -> _QueueManager: ...
436
436
  @property
437
437
  def name(self) -> typing.Optional[str]: ...
438
438
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -728,7 +728,7 @@ class Queue(modal.object.Object):
728
728
 
729
729
  @synchronicity.classproperty
730
730
  @classmethod
731
- def objects(cls) -> type[QueueManager]: ...
731
+ def objects(cls) -> QueueManager: ...
732
732
  @property
733
733
  def name(self) -> typing.Optional[str]: ...
734
734
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -61,7 +61,7 @@ class _SandboxFilesystem:
61
61
  **Usage**
62
62
 
63
63
  ```python fixture:sandbox
64
- sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
64
+ sandbox.filesystem.write_bytes(b"Hello, world!\\n", "/tmp/hello.bin")
65
65
  contents = sandbox.filesystem.read_bytes("/tmp/hello.bin")
66
66
  print(contents.decode("utf-8"))
67
67
  ```
@@ -97,7 +97,7 @@ class _SandboxFilesystem:
97
97
  **Usage**
98
98
 
99
99
  ```python fixture:sandbox
100
- sandbox.filesystem.write_text("Hello, world!\n", "/tmp/hello.txt")
100
+ sandbox.filesystem.write_text("Hello, world!\\n", "/tmp/hello.txt")
101
101
  contents = sandbox.filesystem.read_text("/tmp/hello.txt")
102
102
  print(contents)
103
103
  ```
@@ -140,7 +140,7 @@ class _SandboxFilesystem:
140
140
  **Usage**
141
141
 
142
142
  ```python fixture:sandbox
143
- sandbox.filesystem.write_text("Hello, world!\n", "/tmp/hello.txt")
143
+ sandbox.filesystem.write_text("Hello, world!\\n", "/tmp/hello.txt")
144
144
  sandbox.filesystem.copy_to_local("/tmp/hello.txt", "/tmp/local-hello.txt")
145
145
  ```
146
146
  """
@@ -201,7 +201,7 @@ class _SandboxFilesystem:
201
201
  **Usage**
202
202
 
203
203
  ```python fixture:sandbox
204
- sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
204
+ sandbox.filesystem.write_bytes(b"Hello, world!\\n", "/tmp/hello.bin")
205
205
  ```
206
206
  """
207
207
  validate_absolute_remote_path(remote_path, "write_bytes")
@@ -241,7 +241,7 @@ class _SandboxFilesystem:
241
241
  **Usage**
242
242
 
243
243
  ```python fixture:sandbox
244
- sandbox.filesystem.write_text("Hello, world!\n", "/tmp/hello.txt")
244
+ sandbox.filesystem.write_text("Hello, world!\\n", "/tmp/hello.txt")
245
245
  ```
246
246
  """
247
247
  validate_absolute_remote_path(remote_path, "write_text")
@@ -273,7 +273,7 @@ class _SandboxFilesystem:
273
273
  from pathlib import Path
274
274
 
275
275
  local_path = Path(tempfile.mktemp())
276
- local_path.write_text("Hello, world!\n")
276
+ local_path.write_text("Hello, world!\\n")
277
277
  sandbox.filesystem.copy_from_local(local_path, "/tmp/hello.txt")
278
278
  ```
279
279
  """