modal 1.4.1.dev4__tar.gz → 1.4.2.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 (209) hide show
  1. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/PKG-INFO +2 -2
  2. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/sandbox_fs_utils.py +35 -0
  3. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/task_command_router_client.py +5 -1
  4. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/time_utils.py +55 -0
  5. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/import_refs.py +13 -4
  6. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/shell.py +5 -1
  7. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/client.py +1 -2
  8. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/client.pyi +2 -2
  9. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/dict.py +2 -1
  10. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/dict.pyi +4 -2
  11. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/exception.py +6 -0
  12. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/queue.py +2 -1
  13. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/queue.pyi +4 -2
  14. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox.py +19 -18
  15. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox.pyi +19 -18
  16. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox_fs.py +48 -0
  17. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox_fs.pyi +120 -0
  18. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/secret.py +2 -1
  19. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/secret.pyi +4 -2
  20. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/volume.py +2 -1
  21. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/volume.pyi +4 -2
  22. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/PKG-INFO +2 -2
  23. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/requires.txt +1 -1
  24. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_grpc.py +16 -0
  25. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2.py +375 -355
  26. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2.pyi +35 -0
  27. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.py +33 -0
  28. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.pyi +10 -0
  29. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/modal_api_grpc.py +1 -0
  30. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.py +43 -30
  31. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.pyi +21 -1
  32. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_version/__init__.py +1 -1
  33. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/pyproject.toml +2 -2
  34. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/LICENSE +0 -0
  35. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/README.md +0 -0
  36. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/__init__.py +0 -0
  37. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/__main__.py +0 -0
  38. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_billing.py +0 -0
  39. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_clustered_functions.py +0 -0
  40. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_clustered_functions.pyi +0 -0
  41. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_container_entrypoint.py +0 -0
  42. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_functions.py +0 -0
  43. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_grpc_client.py +0 -0
  44. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_ipython.py +0 -0
  45. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_load_context.py +0 -0
  46. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_location.py +0 -0
  47. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_logs.py +0 -0
  48. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_object.py +0 -0
  49. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/__init__.py +0 -0
  50. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/manager.py +0 -0
  51. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/pty.py +0 -0
  52. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/rich.py +0 -0
  53. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/status.py +0 -0
  54. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_partial_function.py +0 -0
  55. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_resolver.py +0 -0
  56. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_resources.py +0 -0
  57. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/__init__.py +0 -0
  58. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/asgi.py +0 -0
  59. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
  60. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  61. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/execution_context.py +0 -0
  62. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
  63. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  64. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/telemetry.py +0 -0
  65. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  66. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
  67. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_serialization.py +0 -0
  68. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_server.py +0 -0
  69. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_traceback.py +0 -0
  70. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_tunnel.py +0 -0
  71. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_tunnel.pyi +0 -0
  72. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_type_manager.py +0 -0
  73. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/__init__.py +0 -0
  74. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/app_utils.py +0 -0
  75. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/async_utils.py +0 -0
  76. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
  77. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/blob_utils.py +0 -0
  78. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/browser_utils.py +0 -0
  79. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  80. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/deprecation.py +0 -0
  81. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/docker_utils.py +0 -0
  82. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/function_utils.py +0 -0
  83. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/git_utils.py +0 -0
  84. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/grpc_testing.py +0 -0
  85. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/grpc_utils.py +0 -0
  86. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/hash_utils.py +0 -0
  87. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/http_utils.py +0 -0
  88. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/jwt_utils.py +0 -0
  89. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/logger.py +0 -0
  90. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/mount_utils.py +0 -0
  91. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/name_utils.py +0 -0
  92. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/package_utils.py +0 -0
  93. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/pattern_utils.py +0 -0
  94. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  95. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/shell_utils.py +0 -0
  96. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/__init__.py +0 -0
  97. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  98. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
  99. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/tblib.py +0 -0
  100. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/version.py +0 -0
  101. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_watcher.py +0 -0
  102. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/app.py +0 -0
  103. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/app.pyi +0 -0
  104. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/billing.py +0 -0
  105. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2023.12.312.txt +0 -0
  106. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2023.12.txt +0 -0
  107. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2024.04.txt +0 -0
  108. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2024.10.txt +0 -0
  109. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2025.06.txt +0 -0
  110. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/PREVIEW.txt +0 -0
  111. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/README.md +0 -0
  112. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/base-images.json +0 -0
  113. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/call_graph.py +0 -0
  114. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/__init__.py +0 -0
  115. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/_download.py +0 -0
  116. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/_traceback.py +0 -0
  117. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/app.py +0 -0
  118. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/billing.py +0 -0
  119. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/changelog.py +0 -0
  120. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/cluster.py +0 -0
  121. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/config.py +0 -0
  122. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/container.py +0 -0
  123. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/dashboard.py +0 -0
  124. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/dict.py +0 -0
  125. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/entry_point.py +0 -0
  126. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/environment.py +0 -0
  127. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/launch.py +0 -0
  128. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/network_file_system.py +0 -0
  129. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/profile.py +0 -0
  130. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/__init__.py +0 -0
  131. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  132. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/vscode.py +0 -0
  133. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/queues.py +0 -0
  134. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/run.py +0 -0
  135. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/secret.py +0 -0
  136. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/selector.py +0 -0
  137. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/token.py +0 -0
  138. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/utils.py +0 -0
  139. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/volume.py +0 -0
  140. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.py +0 -0
  141. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  142. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cls.py +0 -0
  143. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cls.pyi +0 -0
  144. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/config.py +0 -0
  145. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/container_process.py +0 -0
  146. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/container_process.pyi +0 -0
  147. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/environments.py +0 -0
  148. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/environments.pyi +0 -0
  149. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/__init__.py +0 -0
  150. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/flash.py +0 -0
  151. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/flash.pyi +0 -0
  152. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/ipython.py +0 -0
  153. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_io.py +0 -0
  154. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_io.pyi +0 -0
  155. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_pattern_matcher.py +0 -0
  156. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/functions.py +0 -0
  157. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/functions.pyi +0 -0
  158. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/image.py +0 -0
  159. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/image.pyi +0 -0
  160. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/io_streams.py +0 -0
  161. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/io_streams.pyi +0 -0
  162. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/mount.py +0 -0
  163. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/mount.pyi +0 -0
  164. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/network_file_system.py +0 -0
  165. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/network_file_system.pyi +0 -0
  166. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/object.py +0 -0
  167. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/object.pyi +0 -0
  168. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/output.py +0 -0
  169. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/parallel_map.py +0 -0
  170. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/parallel_map.pyi +0 -0
  171. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/partial_function.py +0 -0
  172. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/partial_function.pyi +0 -0
  173. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/proxy.py +0 -0
  174. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/proxy.pyi +0 -0
  175. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/py.typed +0 -0
  176. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/retries.py +0 -0
  177. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/runner.py +0 -0
  178. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/runner.pyi +0 -0
  179. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/running_app.py +0 -0
  180. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/schedule.py +0 -0
  181. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/scheduler_placement.py +0 -0
  182. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/server.py +0 -0
  183. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/server.pyi +0 -0
  184. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/serving.py +0 -0
  185. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/serving.pyi +0 -0
  186. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/snapshot.py +0 -0
  187. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/snapshot.pyi +0 -0
  188. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/stream_type.py +0 -0
  189. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/token_flow.py +0 -0
  190. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/token_flow.pyi +0 -0
  191. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/SOURCES.txt +0 -0
  192. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
  193. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/entry_points.txt +0 -0
  194. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/top_level.txt +0 -0
  195. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/__init__.py +0 -0
  196. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
  197. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  198. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
  199. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  200. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
  201. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  202. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
  203. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/__init__.py +0 -0
  204. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/py.typed +0 -0
  205. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  206. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  207. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  208. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_version/__main__.py +0 -0
  209. {modal-1.4.1.dev4 → modal-1.4.2.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.1.dev4
3
+ Version: 1.4.2.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -23,7 +23,7 @@ Requires-Dist: grpclib<0.4.10,>=0.4.7; python_version < "3.14"
23
23
  Requires-Dist: grpclib<0.4.10,>=0.4.9; python_version >= "3.14"
24
24
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
25
25
  Requires-Dist: rich>=12.0.0
26
- Requires-Dist: synchronicity~=0.11.1
26
+ Requires-Dist: synchronicity~=0.12.1
27
27
  Requires-Dist: toml
28
28
  Requires-Dist: typer>=0.9
29
29
  Requires-Dist: types-certifi
@@ -12,6 +12,7 @@ from ..exception import (
12
12
  Error as ModalError,
13
13
  InvalidError,
14
14
  NotFoundError,
15
+ SandboxFilesystemDirectoryNotEmptyError,
15
16
  SandboxFilesystemError,
16
17
  SandboxFilesystemFileTooLargeError,
17
18
  SandboxFilesystemIsADirectoryError,
@@ -153,6 +154,40 @@ def make_read_file_command(remote_path: str) -> str:
153
154
  return json.dumps({"ReadFile": {"path": remote_path}})
154
155
 
155
156
 
157
+ def make_remove_command(remote_path: str, recursive: bool) -> str:
158
+ """Build the JSON command string for a Remove operation.
159
+
160
+ The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
161
+ Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
162
+ this schema like protobuf changes: fields must not be removed or renamed,
163
+ only added with backwards-compatible defaults.
164
+ """
165
+ return json.dumps({"Remove": {"path": remote_path, "recursive": recursive}})
166
+
167
+
168
+ def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
169
+ if payload := try_parse_error_payload(stderr):
170
+ logger.debug(
171
+ f"sandbox-fs-tools remove error: path={remote_path}, "
172
+ f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
173
+ )
174
+ if payload.error_kind == "NotFound":
175
+ raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
176
+ if payload.error_kind == "DirectoryNotEmpty":
177
+ raise SandboxFilesystemDirectoryNotEmptyError(f"{payload.message}: {remote_path}")
178
+ if payload.error_kind == "NotSupported":
179
+ raise InvalidError(
180
+ f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
181
+ )
182
+ if payload.error_kind == "PermissionDenied":
183
+ raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
184
+ raise SandboxFilesystemError(payload.message)
185
+
186
+ if stderr_text := _stderr_to_text(stderr):
187
+ logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
188
+ raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
189
+
190
+
156
191
  def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
157
192
  if not PurePosixPath(remote_path).is_absolute():
158
193
  raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
@@ -2,6 +2,7 @@
2
2
  import asyncio
3
3
  import base64
4
4
  import json
5
+ import socket
5
6
  import ssl
6
7
  import time
7
8
  import urllib.parse
@@ -181,7 +182,10 @@ class TaskCommandRouterClient:
181
182
  closed_error_message="Unable to perform operation on a detached sandbox",
182
183
  )
183
184
 
184
- await _connect_channel(channel)
185
+ try:
186
+ await _connect_channel(channel)
187
+ except socket.gaierror as exc:
188
+ raise ConnectionError(f"Could not resolve hostname '{host}': {exc}") from exc
185
189
  loop = asyncio.get_running_loop()
186
190
  jwt_refresh_lock = asyncio.Lock()
187
191
 
@@ -164,6 +164,61 @@ def parse_date_range(s: str, tz: Optional[tzinfo] = None) -> tuple[datetime, dat
164
164
  raise ValueError(f"Unrecognized range: '{s}'. Accepted values: {accepted}")
165
165
 
166
166
 
167
+ def relative_timestamp(dt: datetime) -> str:
168
+ """Convert a tz-aware datetime to a human-readable relative time string.
169
+
170
+ Examples: "just now", "30 seconds ago", "5 minutes ago", "2 hours ago",
171
+ "yesterday", "3 days ago", "2 weeks ago", "3 months ago", "1 year ago".
172
+
173
+ Raises ValueError if the datetime is naive (no tzinfo).
174
+ """
175
+ if dt.tzinfo is None:
176
+ raise ValueError("datetime must be timezone-aware")
177
+
178
+ now = datetime.now(timezone.utc)
179
+ delta = now - dt
180
+ total_seconds = int(delta.total_seconds())
181
+
182
+ if total_seconds < 0:
183
+ return "just now"
184
+
185
+ if total_seconds < 10:
186
+ return "just now"
187
+ if total_seconds < 60:
188
+ return f"{total_seconds} seconds ago"
189
+ if total_seconds < 120:
190
+ return "1 minute ago"
191
+
192
+ minutes = total_seconds // 60
193
+ if minutes < 60:
194
+ return f"{minutes} minutes ago"
195
+ if minutes < 120:
196
+ return "1 hour ago"
197
+
198
+ hours = minutes // 60
199
+ if hours < 24:
200
+ return f"{hours} hours ago"
201
+ if hours < 48:
202
+ return "yesterday"
203
+
204
+ days = hours // 24
205
+ if days < 14:
206
+ return f"{days} days ago"
207
+
208
+ weeks = days // 7
209
+ if days < 60:
210
+ return f"{weeks} weeks ago"
211
+
212
+ months = days // 30
213
+ if days < 365:
214
+ return f"{months} months ago"
215
+
216
+ years = days // 365
217
+ if years == 1:
218
+ return "1 year ago"
219
+ return f"{years} years ago"
220
+
221
+
167
222
  def locale_tz() -> tzinfo:
168
223
  return datetime.now().astimezone().tzinfo
169
224
 
@@ -127,6 +127,7 @@ class AutoRunPriority:
127
127
 
128
128
  def list_cli_commands(
129
129
  module_members: dict[str, typing.Any],
130
+ include_service_functions: bool = False,
130
131
  ) -> list[CLICommand]:
131
132
  """
132
133
  Extracts all runnables found either directly in the input module, or in any of the Apps listed in that module
@@ -149,8 +150,8 @@ def list_cli_commands(
149
150
  all_runnables[local_entrypoint].append(f"{app_name}.{name}")
150
151
  priorities[local_entrypoint] = AutoRunPriority.APP_LOCAL_ENTRYPOINT
151
152
  for name, function in app.registered_functions.items():
152
- if function.info.is_service_class():
153
- continue # Skip class and server service functions since they don't have associated cli commands
153
+ if function.info.is_service_class() and not include_service_functions:
154
+ continue # Skip class and server service functions for all commands except `modal shell`.
154
155
  all_runnables[function].append(f"{app_name}.{name}")
155
156
  priorities[function] = AutoRunPriority.APP_FUNCTION
156
157
  for cls_name, cls in app.registered_classes.items():
@@ -322,7 +323,12 @@ def _get_runnable_app(runnable: Runnable) -> App:
322
323
 
323
324
 
324
325
  def import_and_filter(
325
- import_ref: ImportRef, *, base_cmd: str, accept_local_entrypoint=True, accept_webhook=False
326
+ import_ref: ImportRef,
327
+ *,
328
+ base_cmd: str,
329
+ accept_local_entrypoint=True,
330
+ accept_webhook=False,
331
+ accept_service_functions=False,
326
332
  ) -> tuple[Optional[Runnable], list[CLICommand]]:
327
333
  """Takes a function ref string and returns a single determined "runnable" to use, and a list of all available
328
334
  runnables.
@@ -339,7 +345,10 @@ def import_and_filter(
339
345
  """
340
346
  # all commands:
341
347
  module = import_file_or_module(import_ref, base_cmd)
342
- cli_commands = list_cli_commands(dict(inspect.getmembers(module)))
348
+ cli_commands = list_cli_commands(
349
+ dict(inspect.getmembers(module)),
350
+ include_service_functions=accept_service_functions,
351
+ )
343
352
 
344
353
  # all commands that satisfy local entrypoint/accept webhook limitations AND object path prefix
345
354
 
@@ -142,7 +142,11 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
142
142
  def _function_spec_from_ref(ref: str, use_module_mode: bool) -> _FunctionSpec:
143
143
  import_ref = parse_import_ref(ref, use_module_mode=use_module_mode)
144
144
  runnable, all_usable_commands = import_and_filter(
145
- import_ref, base_cmd="modal shell", accept_local_entrypoint=False, accept_webhook=True
145
+ import_ref,
146
+ base_cmd="modal shell",
147
+ accept_local_entrypoint=False,
148
+ accept_webhook=True,
149
+ accept_service_functions=True,
146
150
  )
147
151
  if not runnable:
148
152
  help_header = (
@@ -56,8 +56,7 @@ def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], vers
56
56
  "x-modal-token-secret": token_secret,
57
57
  }
58
58
  )
59
- agent_env = _agent_environment()
60
- if agent_env:
59
+ if agent_env := _agent_environment():
61
60
  metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
62
61
  return metadata
63
62
 
@@ -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.1.dev4",
38
+ version: str = "1.4.2.dev0",
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.1.dev4",
174
+ version: str = "1.4.2.dev0",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -298,7 +298,8 @@ class _Dict(_Object, type_prefix="di"):
298
298
  )
299
299
 
300
300
  @classproperty
301
- def objects(cls) -> _DictManager:
301
+ @classmethod
302
+ def objects(cls) -> type[_DictManager]:
302
303
  return _DictManager
303
304
 
304
305
  @property
@@ -412,7 +412,8 @@ class _Dict(modal._object._Object):
412
412
  ...
413
413
 
414
414
  @synchronicity.classproperty
415
- def objects(cls) -> _DictManager: ...
415
+ @classmethod
416
+ def objects(cls) -> type[_DictManager]: ...
416
417
  @property
417
418
  def name(self) -> typing.Optional[str]: ...
418
419
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -653,7 +654,8 @@ class Dict(modal.object.Object):
653
654
  ...
654
655
 
655
656
  @synchronicity.classproperty
656
- def objects(cls) -> DictManager: ...
657
+ @classmethod
658
+ def objects(cls) -> type[DictManager]: ...
657
659
  @property
658
660
  def name(self) -> typing.Optional[str]: ...
659
661
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -351,6 +351,12 @@ class SandboxFilesystemNotFoundError(SandboxFilesystemError):
351
351
  pass
352
352
 
353
353
 
354
+ class SandboxFilesystemDirectoryNotEmptyError(SandboxFilesystemError):
355
+ """Raised when a directory is not empty."""
356
+
357
+ pass
358
+
359
+
354
360
  class SandboxFilesystemIsADirectoryError(SandboxFilesystemError):
355
361
  """Raised when a file operation in the sandbox targets a directory when it should target a non-directory file."""
356
362
 
@@ -291,7 +291,8 @@ class _Queue(_Object, type_prefix="qu"):
291
291
  raise RuntimeError("Queue() is not allowed. Please use `Queue.from_name(...)` or `Queue.ephemeral()` instead.")
292
292
 
293
293
  @classproperty
294
- def objects(cls) -> _QueueManager:
294
+ @classmethod
295
+ def objects(cls) -> type[_QueueManager]:
295
296
  return _QueueManager
296
297
 
297
298
  @property
@@ -431,7 +431,8 @@ class _Queue(modal._object._Object):
431
431
  ...
432
432
 
433
433
  @synchronicity.classproperty
434
- def objects(cls) -> _QueueManager: ...
434
+ @classmethod
435
+ def objects(cls) -> type[_QueueManager]: ...
435
436
  @property
436
437
  def name(self) -> typing.Optional[str]: ...
437
438
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -726,7 +727,8 @@ class Queue(modal.object.Object):
726
727
  ...
727
728
 
728
729
  @synchronicity.classproperty
729
- def objects(cls) -> QueueManager: ...
730
+ @classmethod
731
+ def objects(cls) -> type[QueueManager]: ...
730
732
  @property
731
733
  def name(self) -> typing.Optional[str]: ...
732
734
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -123,24 +123,25 @@ class SandboxConnectCredentials:
123
123
  class Probe:
124
124
  """Probe configuration for the Sandbox Readiness Probe.
125
125
 
126
- Usage:
127
- ```py notest
128
- # Wait until a file exists.
129
- readiness_probe = modal.Probe.with_exec(
130
- "sh", "-c", "test -f /tmp/ready",
131
- )
132
-
133
- # Wait until a TCP port is accepting connections.
134
- readiness_probe = modal.Probe.with_tcp(8080)
135
-
136
- app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
137
- sandbox = modal.Sandbox.create(
138
- "python3", "-m", "http.server", "8080",
139
- readiness_probe=readiness_probe,
140
- app=app,
141
- )
142
- sandbox.wait_until_ready()
143
- ```
126
+ **Usage**
127
+
128
+ ```python notest
129
+ # Wait until a file exists.
130
+ readiness_probe = modal.Probe.with_exec(
131
+ "sh", "-c", "test -f /tmp/ready",
132
+ )
133
+
134
+ # Wait until a TCP port is accepting connections.
135
+ readiness_probe = modal.Probe.with_tcp(8080)
136
+
137
+ app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
138
+ sandbox = modal.Sandbox.create(
139
+ "python3", "-m", "http.server", "8080",
140
+ readiness_probe=readiness_probe,
141
+ app=app,
142
+ )
143
+ sandbox.wait_until_ready()
144
+ ```
144
145
  """
145
146
 
146
147
  tcp_port: Optional[int] = None
@@ -74,24 +74,25 @@ class SandboxConnectCredentials:
74
74
  class Probe:
75
75
  """Probe configuration for the Sandbox Readiness Probe.
76
76
 
77
- Usage:
78
- ```py notest
79
- # Wait until a file exists.
80
- readiness_probe = modal.Probe.with_exec(
81
- "sh", "-c", "test -f /tmp/ready",
82
- )
83
-
84
- # Wait until a TCP port is accepting connections.
85
- readiness_probe = modal.Probe.with_tcp(8080)
86
-
87
- app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
88
- sandbox = modal.Sandbox.create(
89
- "python3", "-m", "http.server", "8080",
90
- readiness_probe=readiness_probe,
91
- app=app,
92
- )
93
- sandbox.wait_until_ready()
94
- ```
77
+ **Usage**
78
+
79
+ ```python notest
80
+ # Wait until a file exists.
81
+ readiness_probe = modal.Probe.with_exec(
82
+ "sh", "-c", "test -f /tmp/ready",
83
+ )
84
+
85
+ # Wait until a TCP port is accepting connections.
86
+ readiness_probe = modal.Probe.with_tcp(8080)
87
+
88
+ app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
89
+ sandbox = modal.Sandbox.create(
90
+ "python3", "-m", "http.server", "8080",
91
+ readiness_probe=readiness_probe,
92
+ app=app,
93
+ )
94
+ sandbox.wait_until_ready()
95
+ ```
95
96
  """
96
97
 
97
98
  tcp_port: typing.Optional[int]
@@ -12,8 +12,10 @@ from ._utils.async_utils import synchronize_api
12
12
  from ._utils.logger import logger
13
13
  from ._utils.sandbox_fs_utils import (
14
14
  make_read_file_command,
15
+ make_remove_command,
15
16
  make_write_file_command,
16
17
  raise_read_file_error,
18
+ raise_remove_error,
17
19
  raise_write_file_error,
18
20
  translate_exec_errors,
19
21
  validate_absolute_remote_path,
@@ -303,5 +305,51 @@ class _SandboxFilesystem:
303
305
  dur_s = max(time.monotonic() - t0, 0.001)
304
306
  _log_throughput(f"copy_from_local {local_path} -> {remote_path}", total_bytes, dur_s)
305
307
 
308
+ async def remove(self, remote_path: str, *, recursive: bool = False) -> None:
309
+ """Remove a file or directory in the Sandbox.
310
+
311
+ `remote_path` must be an absolute path in the Sandbox.
312
+
313
+ When `remote_path` is a directory and `recursive` is `False` (the
314
+ default), removes it only if it is empty. When `recursive` is `True`,
315
+ removes the directory and all its contents.
316
+
317
+ Recursive directory removal is not supported on all mounts.
318
+ In particular, `CloudBucketMount` does not support it. An
319
+ `InvalidError` is raised in that case.
320
+
321
+ **Raises**
322
+
323
+ - `SandboxFilesystemNotFoundError`: the path does not exist.
324
+ - `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
325
+ - `SandboxFilesystemPermissionError`: removal is not permitted.
326
+ - `InvalidError`: the operation is not supported by the mount.
327
+ - `SandboxFilesystemError`: the command fails for any other reason.
328
+
329
+ **Usage**
330
+
331
+ To remove a file:
332
+
333
+ ```python fixture:sandbox
334
+ sandbox.filesystem.write_bytes(b"Hello, world!\\n", "/tmp/hello.bin")
335
+ sandbox.filesystem.remove("/tmp/hello.bin")
336
+ ```
337
+
338
+ To remove a directory and all its contents:
339
+
340
+ ```python fixture:sandbox
341
+ sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
342
+ sandbox.filesystem.remove("/tmp/mydir", recursive=True)
343
+ ```
344
+ """
345
+ validate_absolute_remote_path(remote_path, "remove")
346
+
347
+ with translate_exec_errors("remove", remote_path):
348
+ process = await self._sandbox.exec(_SANDBOX_FS_TOOLS_PATH, make_remove_command(remote_path, recursive))
349
+ stderr, returncode = await asyncio.gather(process.stderr.read(), process.wait())
350
+
351
+ if returncode != 0:
352
+ raise_remove_error(returncode, stderr, remote_path)
353
+
306
354
 
307
355
  SandboxFilesystem = synchronize_api(_SandboxFilesystem)
@@ -168,6 +168,45 @@ class _SandboxFilesystem:
168
168
  """
169
169
  ...
170
170
 
171
+ async def remove(self, remote_path: str, *, recursive: bool = False) -> None:
172
+ """Remove a file or directory in the Sandbox.
173
+
174
+ `remote_path` must be an absolute path in the Sandbox.
175
+
176
+ When `remote_path` is a directory and `recursive` is `False` (the
177
+ default), removes it only if it is empty. When `recursive` is `True`,
178
+ removes the directory and all its contents.
179
+
180
+ Recursive directory removal is not supported on all mounts.
181
+ In particular, `CloudBucketMount` does not support it. An
182
+ `InvalidError` is raised in that case.
183
+
184
+ **Raises**
185
+
186
+ - `SandboxFilesystemNotFoundError`: the path does not exist.
187
+ - `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
188
+ - `SandboxFilesystemPermissionError`: removal is not permitted.
189
+ - `InvalidError`: the operation is not supported by the mount.
190
+ - `SandboxFilesystemError`: the command fails for any other reason.
191
+
192
+ **Usage**
193
+
194
+ To remove a file:
195
+
196
+ ```python fixture:sandbox
197
+ sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
198
+ sandbox.filesystem.remove("/tmp/hello.bin")
199
+ ```
200
+
201
+ To remove a directory and all its contents:
202
+
203
+ ```python fixture:sandbox
204
+ sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
205
+ sandbox.filesystem.remove("/tmp/mydir", recursive=True)
206
+ ```
207
+ """
208
+ ...
209
+
171
210
  class SandboxFilesystem:
172
211
  """mdmd:namespace
173
212
  Namespace for Sandbox filesystem APIs.
@@ -505,3 +544,84 @@ class SandboxFilesystem:
505
544
  ...
506
545
 
507
546
  copy_from_local: __copy_from_local_spec
547
+
548
+ class __remove_spec(typing_extensions.Protocol):
549
+ def __call__(self, /, remote_path: str, *, recursive: bool = False) -> None:
550
+ """Remove a file or directory in the Sandbox.
551
+
552
+ `remote_path` must be an absolute path in the Sandbox.
553
+
554
+ When `remote_path` is a directory and `recursive` is `False` (the
555
+ default), removes it only if it is empty. When `recursive` is `True`,
556
+ removes the directory and all its contents.
557
+
558
+ Recursive directory removal is not supported on all mounts.
559
+ In particular, `CloudBucketMount` does not support it. An
560
+ `InvalidError` is raised in that case.
561
+
562
+ **Raises**
563
+
564
+ - `SandboxFilesystemNotFoundError`: the path does not exist.
565
+ - `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
566
+ - `SandboxFilesystemPermissionError`: removal is not permitted.
567
+ - `InvalidError`: the operation is not supported by the mount.
568
+ - `SandboxFilesystemError`: the command fails for any other reason.
569
+
570
+ **Usage**
571
+
572
+ To remove a file:
573
+
574
+ ```python fixture:sandbox
575
+ sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
576
+ sandbox.filesystem.remove("/tmp/hello.bin")
577
+ ```
578
+
579
+ To remove a directory and all its contents:
580
+
581
+ ```python fixture:sandbox
582
+ sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
583
+ sandbox.filesystem.remove("/tmp/mydir", recursive=True)
584
+ ```
585
+ """
586
+ ...
587
+
588
+ async def aio(self, /, remote_path: str, *, recursive: bool = False) -> None:
589
+ """Remove a file or directory in the Sandbox.
590
+
591
+ `remote_path` must be an absolute path in the Sandbox.
592
+
593
+ When `remote_path` is a directory and `recursive` is `False` (the
594
+ default), removes it only if it is empty. When `recursive` is `True`,
595
+ removes the directory and all its contents.
596
+
597
+ Recursive directory removal is not supported on all mounts.
598
+ In particular, `CloudBucketMount` does not support it. An
599
+ `InvalidError` is raised in that case.
600
+
601
+ **Raises**
602
+
603
+ - `SandboxFilesystemNotFoundError`: the path does not exist.
604
+ - `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
605
+ - `SandboxFilesystemPermissionError`: removal is not permitted.
606
+ - `InvalidError`: the operation is not supported by the mount.
607
+ - `SandboxFilesystemError`: the command fails for any other reason.
608
+
609
+ **Usage**
610
+
611
+ To remove a file:
612
+
613
+ ```python fixture:sandbox
614
+ sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
615
+ sandbox.filesystem.remove("/tmp/hello.bin")
616
+ ```
617
+
618
+ To remove a directory and all its contents:
619
+
620
+ ```python fixture:sandbox
621
+ sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
622
+ sandbox.filesystem.remove("/tmp/mydir", recursive=True)
623
+ ```
624
+ """
625
+ ...
626
+
627
+ remove: __remove_spec
@@ -237,7 +237,8 @@ class _Secret(_Object, type_prefix="st"):
237
237
  _metadata: Optional[api_pb2.SecretMetadata] = None
238
238
 
239
239
  @classproperty
240
- def objects(cls) -> _SecretManager:
240
+ @classmethod
241
+ def objects(cls) -> type[_SecretManager]:
241
242
  return _SecretManager
242
243
 
243
244
  @property
@@ -375,7 +375,8 @@ class _Secret(modal._object._Object):
375
375
  _metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
376
376
 
377
377
  @synchronicity.classproperty
378
- def objects(cls) -> _SecretManager: ...
378
+ @classmethod
379
+ def objects(cls) -> type[_SecretManager]: ...
379
380
  @property
380
381
  def name(self) -> typing.Optional[str]: ...
381
382
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -500,7 +501,8 @@ class Secret(modal.object.Object):
500
501
  ...
501
502
 
502
503
  @synchronicity.classproperty
503
- def objects(cls) -> SecretManager: ...
504
+ @classmethod
505
+ def objects(cls) -> type[SecretManager]: ...
504
506
  @property
505
507
  def name(self) -> typing.Optional[str]: ...
506
508
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
@@ -358,7 +358,8 @@ class _Volume(_Object, type_prefix="vo"):
358
358
  _read_only: bool = False
359
359
 
360
360
  @classproperty
361
- def objects(cls) -> _VolumeManager:
361
+ @classmethod
362
+ def objects(cls) -> type[_VolumeManager]:
362
363
  return _VolumeManager
363
364
 
364
365
  @property
@@ -454,7 +454,8 @@ class _Volume(modal._object._Object):
454
454
  _read_only: bool
455
455
 
456
456
  @synchronicity.classproperty
457
- def objects(cls) -> _VolumeManager: ...
457
+ @classmethod
458
+ def objects(cls) -> type[_VolumeManager]: ...
458
459
  @property
459
460
  def name(self) -> typing.Optional[str]: ...
460
461
  def read_only(self) -> _Volume:
@@ -789,7 +790,8 @@ class Volume(modal.object.Object):
789
790
  ...
790
791
 
791
792
  @synchronicity.classproperty
792
- def objects(cls) -> VolumeManager: ...
793
+ @classmethod
794
+ def objects(cls) -> type[VolumeManager]: ...
793
795
  @property
794
796
  def name(self) -> typing.Optional[str]: ...
795
797
  def read_only(self) -> Volume: