modal 1.2.5.dev23__tar.gz → 1.2.5.dev25__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 (203) hide show
  1. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/PKG-INFO +1 -1
  2. modal-1.2.5.dev25/modal/cli/shell.py +375 -0
  3. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/client.pyi +2 -2
  4. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/experimental/__init__.py +2 -1
  5. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/PKG-INFO +1 -1
  6. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_version/__init__.py +1 -1
  7. modal-1.2.5.dev23/modal/cli/shell.py +0 -237
  8. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/LICENSE +0 -0
  9. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/README.md +0 -0
  10. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/__init__.py +0 -0
  11. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/__main__.py +0 -0
  12. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_billing.py +0 -0
  13. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_clustered_functions.py +0 -0
  14. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_clustered_functions.pyi +0 -0
  15. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_container_entrypoint.py +0 -0
  16. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_functions.py +0 -0
  17. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_grpc_client.py +0 -0
  18. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_ipython.py +0 -0
  19. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_load_context.py +0 -0
  20. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_location.py +0 -0
  21. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_object.py +0 -0
  22. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_output.py +0 -0
  23. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_partial_function.py +0 -0
  24. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_pty.py +0 -0
  25. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_resolver.py +0 -0
  26. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_resources.py +0 -0
  27. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/__init__.py +0 -0
  28. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/asgi.py +0 -0
  29. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/container_io_manager.py +0 -0
  30. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/container_io_manager.pyi +0 -0
  31. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/execution_context.py +0 -0
  32. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/execution_context.pyi +0 -0
  33. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  34. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/telemetry.py +0 -0
  35. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/user_code_event_loop.py +0 -0
  36. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_runtime/user_code_imports.py +0 -0
  37. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_serialization.py +0 -0
  38. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_traceback.py +0 -0
  39. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_tunnel.py +0 -0
  40. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_tunnel.pyi +0 -0
  41. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_type_manager.py +0 -0
  42. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/__init__.py +0 -0
  43. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/app_utils.py +0 -0
  44. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/async_utils.py +0 -0
  45. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/auth_token_manager.py +0 -0
  46. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/blob_utils.py +0 -0
  47. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/bytes_io_segment_payload.py +0 -0
  48. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/deprecation.py +0 -0
  49. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/docker_utils.py +0 -0
  50. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/function_utils.py +0 -0
  51. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/git_utils.py +0 -0
  52. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/grpc_testing.py +0 -0
  53. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/grpc_utils.py +0 -0
  54. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/hash_utils.py +0 -0
  55. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/http_utils.py +0 -0
  56. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/jwt_utils.py +0 -0
  57. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/logger.py +0 -0
  58. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/mount_utils.py +0 -0
  59. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/name_utils.py +0 -0
  60. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/package_utils.py +0 -0
  61. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/pattern_utils.py +0 -0
  62. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/rand_pb_testing.py +0 -0
  63. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/shell_utils.py +0 -0
  64. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/task_command_router_client.py +0 -0
  65. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_utils/time_utils.py +0 -0
  66. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_vendor/__init__.py +0 -0
  67. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  68. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_vendor/cloudpickle.py +0 -0
  69. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_vendor/tblib.py +0 -0
  70. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/_watcher.py +0 -0
  71. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/app.py +0 -0
  72. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/app.pyi +0 -0
  73. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/billing.py +0 -0
  74. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/2023.12.312.txt +0 -0
  75. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/2023.12.txt +0 -0
  76. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/2024.04.txt +0 -0
  77. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/2024.10.txt +0 -0
  78. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/2025.06.txt +0 -0
  79. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/PREVIEW.txt +0 -0
  80. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/README.md +0 -0
  81. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/builder/base-images.json +0 -0
  82. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/call_graph.py +0 -0
  83. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/__init__.py +0 -0
  84. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/_download.py +0 -0
  85. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/_traceback.py +0 -0
  86. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/app.py +0 -0
  87. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/cluster.py +0 -0
  88. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/config.py +0 -0
  89. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/container.py +0 -0
  90. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/dict.py +0 -0
  91. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/entry_point.py +0 -0
  92. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/environment.py +0 -0
  93. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/import_refs.py +0 -0
  94. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/launch.py +0 -0
  95. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/network_file_system.py +0 -0
  96. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/profile.py +0 -0
  97. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/programs/__init__.py +0 -0
  98. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/programs/launch_instance_ssh.py +0 -0
  99. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/programs/run_marimo.py +0 -0
  101. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/programs/vscode.py +0 -0
  102. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/queues.py +0 -0
  103. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/run.py +0 -0
  104. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/secret.py +0 -0
  105. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/token.py +0 -0
  106. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/utils.py +0 -0
  107. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cli/volume.py +0 -0
  108. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/client.py +0 -0
  109. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cloud_bucket_mount.py +0 -0
  110. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cloud_bucket_mount.pyi +0 -0
  111. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cls.py +0 -0
  112. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/cls.pyi +0 -0
  113. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/config.py +0 -0
  114. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/container_process.py +0 -0
  115. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/container_process.pyi +0 -0
  116. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/dict.py +0 -0
  117. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/dict.pyi +0 -0
  118. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/environments.py +0 -0
  119. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/environments.pyi +0 -0
  120. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/exception.py +0 -0
  121. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/experimental/flash.py +0 -0
  122. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/experimental/flash.pyi +0 -0
  123. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/experimental/ipython.py +0 -0
  124. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/file_io.py +0 -0
  125. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/file_io.pyi +0 -0
  126. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/file_pattern_matcher.py +0 -0
  127. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/functions.py +0 -0
  128. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/functions.pyi +0 -0
  129. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/gpu.py +0 -0
  130. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/image.py +0 -0
  131. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/image.pyi +0 -0
  132. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/io_streams.py +0 -0
  133. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/io_streams.pyi +0 -0
  134. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/mount.py +0 -0
  135. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/mount.pyi +0 -0
  136. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/network_file_system.py +0 -0
  137. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/object.py +0 -0
  139. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/object.pyi +0 -0
  140. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/output.py +0 -0
  141. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/parallel_map.py +0 -0
  142. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/partial_function.py +0 -0
  144. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/proxy.py +0 -0
  146. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/proxy.pyi +0 -0
  147. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/py.typed +0 -0
  148. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/queue.py +0 -0
  149. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/queue.pyi +0 -0
  150. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/retries.py +0 -0
  151. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/runner.py +0 -0
  152. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/runner.pyi +0 -0
  153. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/running_app.py +0 -0
  154. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/sandbox.py +0 -0
  155. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/schedule.py +0 -0
  157. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/secret.py +0 -0
  159. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/secret.pyi +0 -0
  160. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/serving.py +0 -0
  161. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/serving.pyi +0 -0
  162. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/snapshot.py +0 -0
  163. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/snapshot.pyi +0 -0
  164. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/stream_type.py +0 -0
  165. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/token_flow.py +0 -0
  166. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/token_flow.pyi +0 -0
  167. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/volume.py +0 -0
  168. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal/volume.pyi +0 -0
  169. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/SOURCES.txt +0 -0
  170. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/dependency_links.txt +0 -0
  171. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/entry_points.txt +0 -0
  172. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/requires.txt +0 -0
  173. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal.egg-info/top_level.txt +0 -0
  174. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/__init__.py +0 -0
  175. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/gen_cli_docs.py +0 -0
  176. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/gen_reference_docs.py +0 -0
  177. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/mdmd/__init__.py +0 -0
  178. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/mdmd/mdmd.py +0 -0
  179. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_docs/mdmd/signatures.py +0 -0
  180. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/__init__.py +0 -0
  181. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api.proto +0 -0
  182. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api_grpc.py +0 -0
  183. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api_pb2.py +0 -0
  184. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api_pb2.pyi +0 -0
  185. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api_pb2_grpc.py +0 -0
  186. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/api_pb2_grpc.pyi +0 -0
  187. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/modal_api_grpc.py +0 -0
  188. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/py.typed +0 -0
  189. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router.proto +0 -0
  190. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router_grpc.py +0 -0
  191. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router_pb2.py +0 -0
  192. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router_pb2.pyi +0 -0
  193. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  194. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  195. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router.proto +0 -0
  196. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router_grpc.py +0 -0
  197. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router_pb2.py +0 -0
  198. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router_pb2.pyi +0 -0
  199. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  200. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  201. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/modal_version/__main__.py +0 -0
  202. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/pyproject.toml +0 -0
  203. {modal-1.2.5.dev23 → modal-1.2.5.dev25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.5.dev23
3
+ Version: 1.2.5.dev25
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -0,0 +1,375 @@
1
+ # Copyright Modal Labs 2022
2
+ import inspect
3
+ import platform
4
+ import shlex
5
+ from pathlib import Path, PurePosixPath
6
+ from typing import Any, Callable, Optional
7
+
8
+ import typer
9
+ from click import ClickException
10
+
11
+ from .._functions import _FunctionSpec
12
+ from ..app import App
13
+ from ..environments import ensure_env
14
+ from ..exception import InvalidError, NotFoundError
15
+ from ..functions import Function
16
+ from ..image import Image
17
+ from ..mount import _Mount
18
+ from ..runner import interactive_shell
19
+ from ..sandbox import Sandbox
20
+ from ..secret import Secret
21
+ from ..volume import Volume
22
+ from .container import exec
23
+ from .import_refs import (
24
+ MethodReference,
25
+ import_and_filter,
26
+ parse_import_ref,
27
+ )
28
+ from .run import _get_runnable_list
29
+ from .utils import ENV_OPTION, is_tty
30
+
31
+
32
+ def _params_from_signature(
33
+ func: Callable[..., Any],
34
+ ) -> dict[str, typer.models.ParameterInfo]:
35
+ sig = inspect.signature(func)
36
+ params = {param_name: param.default for param_name, param in sig.parameters.items()}
37
+ assert all(isinstance(param, typer.models.ParameterInfo) for param in params.values()), (
38
+ f"All params to {func.__name__} must be of type typer.models.ParameterInfo."
39
+ )
40
+ return params
41
+
42
+
43
+ def _passed_forbidden_args(
44
+ param_objs: dict[str, typer.models.ParameterInfo],
45
+ passed_args: dict[str, Any],
46
+ allowed: Callable[[str], bool],
47
+ ) -> list[str]:
48
+ """Check which forbidden arguments were passed with non-default values."""
49
+ passed_forbidden: list[str] = []
50
+ for param_name, param_obj in param_objs.items():
51
+ if allowed(param_name):
52
+ continue
53
+
54
+ assert param_obj.param_decls is not None, "All params must be typer.models.ParameterInfo, and have param_decls."
55
+
56
+ if passed_args.get(param_name) != param_obj.default:
57
+ passed_forbidden.append("/".join(param_obj.param_decls))
58
+
59
+ return passed_forbidden
60
+
61
+
62
+ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
63
+ assert prefix.endswith("-")
64
+ return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
65
+
66
+
67
+ def _is_running_container_ref(ref: Optional[str]) -> bool:
68
+ if ref is None:
69
+ return False
70
+ return _is_valid_modal_id(ref, "sb-") or _is_valid_modal_id(ref, "ta-")
71
+
72
+
73
+ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
74
+ if _is_valid_modal_id(ref, "sb-"):
75
+ try:
76
+ sandbox = Sandbox.from_id(ref)
77
+ ref = sandbox._get_task_id()
78
+ except NotFoundError as e:
79
+ raise ClickException(f"Sandbox '{ref}' not found (is it still running?)")
80
+ except Exception as e:
81
+ raise ClickException(f"Error connecting to Sandbox '{ref}': {str(e)}")
82
+
83
+ assert _is_valid_modal_id(ref, "ta-")
84
+ try:
85
+ exec(container_id=ref, command=shlex.split(cmd), pty=pty)
86
+ except NotFoundError as e:
87
+ raise ClickException(f"Container '{ref}' not found (is it still running?)")
88
+ except Exception as e:
89
+ raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
90
+
91
+
92
+ def _function_spec_from_ref(ref: str, use_module_mode: bool) -> _FunctionSpec:
93
+ import_ref = parse_import_ref(ref, use_module_mode=use_module_mode)
94
+ runnable, all_usable_commands = import_and_filter(
95
+ import_ref, base_cmd="modal shell", accept_local_entrypoint=False, accept_webhook=True
96
+ )
97
+ if not runnable:
98
+ help_header = (
99
+ "Specify a Modal function to start a shell session for. E.g.\n"
100
+ f"> modal shell {import_ref.file_or_module}::my_function"
101
+ )
102
+
103
+ if all_usable_commands:
104
+ help_footer = f"The selected module '{import_ref.file_or_module}' has the following choices:\n\n"
105
+ help_footer += _get_runnable_list(all_usable_commands)
106
+ else:
107
+ help_footer = f"The selected module '{import_ref.file_or_module}' has no Modal functions or classes."
108
+
109
+ raise ClickException(f"{help_header}\n\n{help_footer}")
110
+
111
+ if isinstance(runnable, MethodReference):
112
+ # TODO: let users specify a class instead of a method, since they use the same environment
113
+ class_service_function = runnable.cls._get_class_service_function()
114
+ return class_service_function.spec
115
+ elif isinstance(runnable, Function):
116
+ return runnable.spec
117
+
118
+ raise ValueError("Referenced entity is not a Modal Function or Cls")
119
+
120
+
121
+ def _start_shell_from_function_spec(
122
+ app: App,
123
+ cmds: list[str],
124
+ env: str,
125
+ timeout: int,
126
+ function_spec: _FunctionSpec,
127
+ pty: bool,
128
+ ) -> None:
129
+ interactive_shell(
130
+ app,
131
+ cmds=cmds,
132
+ environment_name=env,
133
+ timeout=timeout,
134
+ image=function_spec.image,
135
+ mounts=function_spec.mounts,
136
+ secrets=function_spec.secrets,
137
+ network_file_systems=function_spec.network_file_systems,
138
+ gpu=function_spec.gpus,
139
+ cloud=function_spec.cloud,
140
+ cpu=function_spec.cpu,
141
+ memory=function_spec.memory,
142
+ volumes=function_spec.volumes,
143
+ region=function_spec.scheduler_placement.regions if function_spec.scheduler_placement else None,
144
+ pty=pty,
145
+ proxy=function_spec.proxy,
146
+ )
147
+
148
+
149
+ def _start_shell_from_image(
150
+ app: App,
151
+ cmds: list[str],
152
+ env: str,
153
+ timeout: int,
154
+ modal_image: Optional[Image],
155
+ volume: list[str],
156
+ secret: list[str],
157
+ add_local: list[str],
158
+ cpu: Optional[int],
159
+ memory: Optional[int],
160
+ gpu: Optional[str],
161
+ cloud: Optional[str],
162
+ region: Optional[str],
163
+ pty: bool,
164
+ ) -> None:
165
+ volumes = {f"/mnt/{vol}": Volume.from_name(vol) for vol in volume}
166
+ secrets = [Secret.from_name(s) for s in secret]
167
+
168
+ mounts = []
169
+ for local_path_str in add_local:
170
+ local_path = Path(local_path_str).expanduser().resolve()
171
+ remote_path = PurePosixPath(f"/mnt/{local_path.name}")
172
+
173
+ if local_path.is_dir():
174
+ m = _Mount._from_local_dir(local_path, remote_path=remote_path)
175
+ else:
176
+ m = _Mount._from_local_file(local_path, remote_path=remote_path)
177
+ mounts.append(m)
178
+
179
+ interactive_shell(
180
+ app,
181
+ cmds=cmds,
182
+ environment_name=env,
183
+ timeout=timeout,
184
+ image=modal_image,
185
+ mounts=mounts,
186
+ cpu=cpu,
187
+ memory=memory,
188
+ gpu=gpu,
189
+ cloud=cloud,
190
+ volumes=volumes,
191
+ secrets=secrets,
192
+ region=region.split(",") if region else [],
193
+ pty=pty,
194
+ )
195
+
196
+
197
+ def shell(
198
+ ref: Optional[str] = typer.Argument(
199
+ default=None,
200
+ help=(
201
+ "ID of running container or Sandbox, or path to a Python file containing an App."
202
+ " Can also include a Function specifier, like `module.py::func`, if the file defines multiple Functions."
203
+ ),
204
+ ),
205
+ cmd: str = typer.Option("/bin/bash", "-c", "--cmd", help="Command to run inside the Modal image."),
206
+ env: Optional[str] = ENV_OPTION,
207
+ image: Optional[str] = typer.Option(
208
+ None, "--image", help="Container image tag for inside the shell (if not using REF)."
209
+ ),
210
+ add_python: Optional[str] = typer.Option(None, "--add-python", help="Add Python to the image (if not using REF)."),
211
+ volume: Optional[list[str]] = typer.Option(
212
+ None,
213
+ "--volume",
214
+ help=(
215
+ "Name of a `modal.Volume` to mount inside the shell at `/mnt/{name}` (if not using REF)."
216
+ " Can be used multiple times."
217
+ ),
218
+ ),
219
+ add_local: Optional[list[str]] = typer.Option(
220
+ None,
221
+ "--add-local",
222
+ help=(
223
+ "Local file or directory to mount inside the shell at `/mnt/{basename}` (if not using REF)."
224
+ " Can be used multiple times."
225
+ ),
226
+ ),
227
+ secret: Optional[list[str]] = typer.Option(
228
+ None,
229
+ "--secret",
230
+ help=("Name of a `modal.Secret` to mount inside the shell (if not using REF). Can be used multiple times."),
231
+ ),
232
+ cpu: Optional[int] = typer.Option(
233
+ None, "--cpu", help="Number of CPUs to allocate to the shell (if not using REF)."
234
+ ),
235
+ memory: Optional[int] = typer.Option(
236
+ None, "--memory", help="Memory to allocate for the shell, in MiB (if not using REF)."
237
+ ),
238
+ gpu: Optional[str] = typer.Option(
239
+ None,
240
+ "--gpu",
241
+ help="GPUs to request for the shell, if any. Examples are `any`, `a10g`, `a100:4` (if not using REF).",
242
+ ),
243
+ cloud: Optional[str] = typer.Option(
244
+ None,
245
+ "--cloud",
246
+ help=(
247
+ "Cloud provider to run the shell on. Possible values are `aws`, `gcp`, `oci`, `auto` (if not using REF)."
248
+ ),
249
+ ),
250
+ region: Optional[str] = typer.Option(
251
+ None,
252
+ "--region",
253
+ help=(
254
+ "Region(s) to run the container on. "
255
+ "Can be a single region or a comma-separated list to choose from (if not using REF)."
256
+ ),
257
+ ),
258
+ pty: Optional[bool] = typer.Option(None, "--pty", help="Run the command using a PTY."),
259
+ use_module_mode: bool = typer.Option(
260
+ False, "-m", help="Interpret argument as a Python module path instead of a file/script path"
261
+ ),
262
+ ):
263
+ """Run a command or interactive shell inside a Modal container.
264
+
265
+ **Examples:**
266
+
267
+ Start an interactive shell inside the default Debian-based image:
268
+
269
+ ```
270
+ modal shell
271
+ ```
272
+
273
+ Start an interactive shell with the spec for `my_function` in your App
274
+ (uses the same image, volumes, mounts, etc.):
275
+
276
+ ```
277
+ modal shell hello_world.py::my_function
278
+ ```
279
+
280
+ Or, if you're using a [modal.Cls](https://modal.com/docs/reference/modal.Cls)
281
+ you can refer to a `@modal.method` directly:
282
+
283
+ ```
284
+ modal shell hello_world.py::MyClass.my_method
285
+ ```
286
+
287
+ Start a `python` shell:
288
+
289
+ ```
290
+ modal shell hello_world.py --cmd=python
291
+ ```
292
+
293
+ Run a command with your function's spec and pipe the output to a file:
294
+
295
+ ```
296
+ modal shell hello_world.py -c 'uv pip list' > env.txt
297
+ ```
298
+
299
+ Connect to a running Sandbox by ID:
300
+
301
+ ```
302
+ modal shell sb-abc123xyz
303
+ ```
304
+ """
305
+ if pty is None:
306
+ pty = is_tty()
307
+
308
+ if platform.system() == "Windows":
309
+ raise InvalidError("`modal shell` is currently not supported on Windows")
310
+
311
+ param_objs = _params_from_signature(shell)
312
+
313
+ if ref is not None and _is_running_container_ref(ref):
314
+ # We're attaching to an already running container or Sandbox.
315
+ if passed_forbidden := _passed_forbidden_args(
316
+ param_objs, locals(), allowed=lambda p: p in {"cmd", "pty", "ref"}
317
+ ):
318
+ raise ClickException(
319
+ f"Cannot specify container configuration arguments ({', '.join(passed_forbidden)}) "
320
+ f"when attaching to an already running container or Sandbox ('{ref}')."
321
+ )
322
+
323
+ _start_shell_in_running_container(ref, cmd, pty)
324
+ return
325
+
326
+ # We're not attaching to an existing container, so we need to create a new one.
327
+ env = ensure_env(env)
328
+ app = App("modal shell")
329
+
330
+ # NB: invoking under bash makes --cmd a lot more flexible.
331
+ cmds = shlex.split(f'/bin/bash -c "{cmd}"')
332
+ timeout = 3600
333
+
334
+ if ref is not None and not _is_valid_modal_id(ref, "im-"):
335
+ # If ref it not a Modal Image ID, then it's a function reference, and we'll start a new container from its spec.
336
+ if passed_forbidden := _passed_forbidden_args(
337
+ param_objs, locals(), allowed=lambda p: p in {"cmd", "env", "pty", "ref", "use_module_mode"}
338
+ ):
339
+ raise ClickException(
340
+ f"Cannot specify container configuration arguments ({', '.join(passed_forbidden)}) "
341
+ f"when starting a new container from a function reference ('{ref}')."
342
+ )
343
+
344
+ function_spec = _function_spec_from_ref(ref, use_module_mode)
345
+ _start_shell_from_function_spec(app, cmds, env, timeout, function_spec, pty)
346
+ return
347
+
348
+ if ref is not None and _is_valid_modal_id(ref, "im-"):
349
+ if passed_forbidden := _passed_forbidden_args(
350
+ param_objs, locals(), allowed=lambda p: p not in {"add_python", "image"}
351
+ ):
352
+ raise ClickException(
353
+ f"Cannot specify {', '.join(passed_forbidden)} argument(s) "
354
+ f"when starting a new container from a Modal Image ID ('{ref}')."
355
+ )
356
+ modal_image = Image.from_id(ref)
357
+ else:
358
+ modal_image = Image.from_registry(image, add_python=add_python) if image else None
359
+
360
+ _start_shell_from_image(
361
+ app,
362
+ cmds,
363
+ env,
364
+ timeout,
365
+ modal_image,
366
+ volume or [],
367
+ secret or [],
368
+ add_local or [],
369
+ cpu,
370
+ memory,
371
+ gpu,
372
+ cloud,
373
+ region,
374
+ pty,
375
+ )
@@ -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.5.dev23",
35
+ version: str = "1.2.5.dev25",
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.5.dev23",
166
+ version: str = "1.2.5.dev25",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -364,7 +364,8 @@ async def image_delete(
364
364
  ) -> None:
365
365
  """Delete an Image by its ID.
366
366
 
367
- Deletion is irreversible and will prevent Apps from using the Image.
367
+ Deletion is irreversible and will prevent Functions/Sandboxes from using
368
+ the Image.
368
369
 
369
370
  This is an experimental interface for a feature that we will be adding to
370
371
  the main Image class. The stable form of this interface may look different.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.5.dev23
3
+ Version: 1.2.5.dev25
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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.5.dev23"
4
+ __version__ = "1.2.5.dev25"
@@ -1,237 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import platform
3
- import shlex
4
- from functools import partial
5
- from pathlib import Path, PurePosixPath
6
- from typing import Optional
7
-
8
- import typer
9
- from click import ClickException
10
-
11
- from .._functions import _FunctionSpec
12
- from ..app import App
13
- from ..environments import ensure_env
14
- from ..exception import InvalidError, NotFoundError
15
- from ..functions import Function
16
- from ..image import Image
17
- from ..mount import _Mount
18
- from ..runner import interactive_shell
19
- from ..secret import Secret
20
- from ..volume import Volume
21
- from .import_refs import (
22
- MethodReference,
23
- import_and_filter,
24
- parse_import_ref,
25
- )
26
- from .run import _get_runnable_list
27
- from .utils import ENV_OPTION, is_tty
28
-
29
-
30
- def shell(
31
- ref: Optional[str] = typer.Argument(
32
- default=None,
33
- help=(
34
- "ID of running container or Sandbox, or path to a Python file containing an App."
35
- " Can also include a Function specifier, like `module.py::func`, if the file defines multiple Functions."
36
- ),
37
- ),
38
- cmd: str = typer.Option("/bin/bash", "-c", "--cmd", help="Command to run inside the Modal image."),
39
- env: str = ENV_OPTION,
40
- image: Optional[str] = typer.Option(
41
- default=None, help="Container image tag for inside the shell (if not using REF)."
42
- ),
43
- add_python: Optional[str] = typer.Option(default=None, help="Add Python to the image (if not using REF)."),
44
- volume: Optional[list[str]] = typer.Option(
45
- default=None,
46
- help=(
47
- "Name of a `modal.Volume` to mount inside the shell at `/mnt/{name}` (if not using REF)."
48
- " Can be used multiple times."
49
- ),
50
- ),
51
- add_local: Optional[list[str]] = typer.Option(
52
- default=None,
53
- help=(
54
- "Local file or directory to mount inside the shell at `/mnt/{basename}` (if not using REF)."
55
- " Can be used multiple times."
56
- ),
57
- ),
58
- secret: Optional[list[str]] = typer.Option(
59
- default=None,
60
- help=("Name of a `modal.Secret` to mount inside the shell (if not using REF). Can be used multiple times."),
61
- ),
62
- cpu: Optional[int] = typer.Option(default=None, help="Number of CPUs to allocate to the shell (if not using REF)."),
63
- memory: Optional[int] = typer.Option(
64
- default=None, help="Memory to allocate for the shell, in MiB (if not using REF)."
65
- ),
66
- gpu: Optional[str] = typer.Option(
67
- default=None,
68
- help="GPUs to request for the shell, if any. Examples are `any`, `a10g`, `a100:4` (if not using REF).",
69
- ),
70
- cloud: Optional[str] = typer.Option(
71
- default=None,
72
- help=(
73
- "Cloud provider to run the shell on. Possible values are `aws`, `gcp`, `oci`, `auto` (if not using REF)."
74
- ),
75
- ),
76
- region: Optional[str] = typer.Option(
77
- default=None,
78
- help=(
79
- "Region(s) to run the container on. "
80
- "Can be a single region or a comma-separated list to choose from (if not using REF)."
81
- ),
82
- ),
83
- pty: Optional[bool] = typer.Option(default=None, help="Run the command using a PTY."),
84
- use_module_mode: bool = typer.Option(
85
- False, "-m", help="Interpret argument as a Python module path instead of a file/script path"
86
- ),
87
- ):
88
- """Run a command or interactive shell inside a Modal container.
89
-
90
- **Examples:**
91
-
92
- Start an interactive shell inside the default Debian-based image:
93
-
94
- ```
95
- modal shell
96
- ```
97
-
98
- Start an interactive shell with the spec for `my_function` in your App
99
- (uses the same image, volumes, mounts, etc.):
100
-
101
- ```
102
- modal shell hello_world.py::my_function
103
- ```
104
-
105
- Or, if you're using a [modal.Cls](https://modal.com/docs/reference/modal.Cls)
106
- you can refer to a `@modal.method` directly:
107
-
108
- ```
109
- modal shell hello_world.py::MyClass.my_method
110
- ```
111
-
112
- Start a `python` shell:
113
-
114
- ```
115
- modal shell hello_world.py --cmd=python
116
- ```
117
-
118
- Run a command with your function's spec and pipe the output to a file:
119
-
120
- ```
121
- modal shell hello_world.py -c 'uv pip list' > env.txt
122
- ```
123
-
124
- Connect to a running Sandbox by ID:
125
-
126
- ```
127
- modal shell sb-abc123xyz
128
- ```
129
- """
130
- env = ensure_env(env)
131
-
132
- if pty is None:
133
- pty = is_tty()
134
-
135
- if platform.system() == "Windows":
136
- raise InvalidError("`modal shell` is currently not supported on Windows")
137
-
138
- app = App("modal shell")
139
-
140
- if ref is not None:
141
- # `modal shell` with a sandbox ID gets the task_id, that's then handled by the `ta-*` flow below.
142
- if ref.startswith("sb-") and len(ref[3:]) > 0 and ref[3:].isalnum():
143
- from ..sandbox import Sandbox
144
-
145
- try:
146
- sandbox = Sandbox.from_id(ref)
147
- task_id = sandbox._get_task_id()
148
- ref = task_id
149
- except NotFoundError as e:
150
- raise ClickException(f"Sandbox '{ref}' not found")
151
- except Exception as e:
152
- raise ClickException(f"Error connecting to sandbox '{ref}': {str(e)}")
153
-
154
- # `modal shell` with a container ID is a special case, alias for `modal container exec`.
155
- if ref.startswith("ta-") and len(ref[3:]) > 0 and ref[3:].isalnum():
156
- from .container import exec
157
-
158
- exec(container_id=ref, command=shlex.split(cmd), pty=pty)
159
- return
160
-
161
- import_ref = parse_import_ref(ref, use_module_mode=use_module_mode)
162
- runnable, all_usable_commands = import_and_filter(
163
- import_ref, base_cmd="modal shell", accept_local_entrypoint=False, accept_webhook=True
164
- )
165
- if not runnable:
166
- help_header = (
167
- "Specify a Modal function to start a shell session for. E.g.\n"
168
- f"> modal shell {import_ref.file_or_module}::my_function"
169
- )
170
-
171
- if all_usable_commands:
172
- help_footer = f"The selected module '{import_ref.file_or_module}' has the following choices:\n\n"
173
- help_footer += _get_runnable_list(all_usable_commands)
174
- else:
175
- help_footer = f"The selected module '{import_ref.file_or_module}' has no Modal functions or classes."
176
-
177
- raise ClickException(f"{help_header}\n\n{help_footer}")
178
-
179
- function_spec: _FunctionSpec
180
- if isinstance(runnable, MethodReference):
181
- # TODO: let users specify a class instead of a method, since they use the same environment
182
- class_service_function = runnable.cls._get_class_service_function()
183
- function_spec = class_service_function.spec
184
- elif isinstance(runnable, Function):
185
- function_spec = runnable.spec
186
- else:
187
- raise ValueError("Referenced entity is not a Modal function or class")
188
-
189
- start_shell = partial(
190
- interactive_shell,
191
- image=function_spec.image,
192
- mounts=function_spec.mounts,
193
- secrets=function_spec.secrets,
194
- network_file_systems=function_spec.network_file_systems,
195
- gpu=function_spec.gpus,
196
- cloud=function_spec.cloud,
197
- cpu=function_spec.cpu,
198
- memory=function_spec.memory,
199
- volumes=function_spec.volumes,
200
- region=function_spec.scheduler_placement.regions if function_spec.scheduler_placement else None,
201
- pty=pty,
202
- proxy=function_spec.proxy,
203
- )
204
- else:
205
- modal_image = Image.from_registry(image, add_python=add_python) if image else None
206
- volumes = {} if volume is None else {f"/mnt/{vol}": Volume.from_name(vol) for vol in volume}
207
- secrets = [] if secret is None else [Secret.from_name(s) for s in secret]
208
-
209
- mounts = []
210
- if add_local:
211
- for local_path_str in add_local:
212
- local_path = Path(local_path_str).expanduser().resolve()
213
- remote_path = PurePosixPath(f"/mnt/{local_path.name}")
214
-
215
- if local_path.is_dir():
216
- m = _Mount._from_local_dir(local_path, remote_path=remote_path)
217
- else:
218
- m = _Mount._from_local_file(local_path, remote_path=remote_path)
219
- mounts.append(m)
220
-
221
- start_shell = partial(
222
- interactive_shell,
223
- image=modal_image,
224
- mounts=mounts,
225
- cpu=cpu,
226
- memory=memory,
227
- gpu=gpu,
228
- cloud=cloud,
229
- volumes=volumes,
230
- secrets=secrets,
231
- region=region.split(",") if region else [],
232
- pty=pty,
233
- )
234
-
235
- # NB: invoking under bash makes --cmd a lot more flexible.
236
- cmds = shlex.split(f'/bin/bash -c "{cmd}"')
237
- start_shell(app, cmds=cmds, environment_name=env, timeout=3600)
File without changes
File without changes