modAL 1.3.6.dev8__tar.gz → 1.3.6.dev10__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.3.6.dev8 → modal-1.3.6.dev10}/PKG-INFO +1 -2
  2. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/README.md +0 -1
  3. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/async_utils.py +17 -2
  4. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/blob_utils.py +2 -2
  5. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/grpc_utils.py +15 -5
  6. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/task_command_router_client.py +38 -0
  7. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/shell.py +57 -7
  8. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/client.pyi +2 -2
  9. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/functions.pyi +6 -6
  10. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox.py +252 -12
  11. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox.pyi +235 -0
  12. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/volume.py +3 -3
  13. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/PKG-INFO +1 -2
  14. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_version/__init__.py +1 -1
  15. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/pyproject.toml +47 -1
  16. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/LICENSE +0 -0
  17. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/__init__.py +0 -0
  18. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/__main__.py +0 -0
  19. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_billing.py +0 -0
  20. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_clustered_functions.py +0 -0
  21. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_clustered_functions.pyi +0 -0
  22. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_container_entrypoint.py +0 -0
  23. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_functions.py +0 -0
  24. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_grpc_client.py +0 -0
  25. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_ipython.py +0 -0
  26. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_load_context.py +0 -0
  27. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_location.py +0 -0
  28. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_logs.py +0 -0
  29. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_object.py +0 -0
  30. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/__init__.py +0 -0
  31. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/manager.py +0 -0
  32. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/pty.py +0 -0
  33. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/rich.py +0 -0
  34. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/status.py +0 -0
  35. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_partial_function.py +0 -0
  36. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_resolver.py +0 -0
  37. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_resources.py +0 -0
  38. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/__init__.py +0 -0
  39. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/asgi.py +0 -0
  40. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.py +0 -0
  41. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
  42. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/execution_context.py +0 -0
  43. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/execution_context.pyi +0 -0
  44. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  45. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/telemetry.py +0 -0
  46. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
  47. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/user_code_imports.py +0 -0
  48. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_serialization.py +0 -0
  49. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_server.py +0 -0
  50. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_traceback.py +0 -0
  51. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_tunnel.py +0 -0
  52. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_tunnel.pyi +0 -0
  53. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_type_manager.py +0 -0
  54. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/__init__.py +0 -0
  55. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/app_utils.py +0 -0
  56. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/auth_token_manager.py +0 -0
  57. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/browser_utils.py +0 -0
  58. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
  59. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/deprecation.py +0 -0
  60. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/docker_utils.py +0 -0
  61. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/function_utils.py +0 -0
  62. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/git_utils.py +0 -0
  63. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/grpc_testing.py +0 -0
  64. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/hash_utils.py +0 -0
  65. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/http_utils.py +0 -0
  66. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/jwt_utils.py +0 -0
  67. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/logger.py +0 -0
  68. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/mount_utils.py +0 -0
  69. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/name_utils.py +0 -0
  70. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/package_utils.py +0 -0
  71. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/pattern_utils.py +0 -0
  72. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/rand_pb_testing.py +0 -0
  73. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
  74. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/shell_utils.py +0 -0
  75. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/time_utils.py +0 -0
  76. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/__init__.py +0 -0
  77. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  78. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/cloudpickle.py +0 -0
  79. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/tblib.py +0 -0
  80. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/version.py +0 -0
  81. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_watcher.py +0 -0
  82. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/app.py +0 -0
  83. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/app.pyi +0 -0
  84. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/billing.py +0 -0
  85. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2023.12.312.txt +0 -0
  86. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2023.12.txt +0 -0
  87. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2024.04.txt +0 -0
  88. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2024.10.txt +0 -0
  89. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2025.06.txt +0 -0
  90. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/PREVIEW.txt +0 -0
  91. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/README.md +0 -0
  92. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/base-images.json +0 -0
  93. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/call_graph.py +0 -0
  94. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/__init__.py +0 -0
  95. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/_download.py +0 -0
  96. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/_traceback.py +0 -0
  97. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/app.py +0 -0
  98. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/billing.py +0 -0
  99. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/changelog.py +0 -0
  100. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/cluster.py +0 -0
  101. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/config.py +0 -0
  102. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/container.py +0 -0
  103. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/dashboard.py +0 -0
  104. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/dict.py +0 -0
  105. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/entry_point.py +0 -0
  106. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/environment.py +0 -0
  107. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/import_refs.py +0 -0
  108. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/launch.py +0 -0
  109. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/network_file_system.py +0 -0
  110. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/profile.py +0 -0
  111. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/__init__.py +0 -0
  112. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/run_jupyter.py +0 -0
  113. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/vscode.py +0 -0
  114. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/queues.py +0 -0
  115. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/run.py +0 -0
  116. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/secret.py +0 -0
  117. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/selector.py +0 -0
  118. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/token.py +0 -0
  119. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/utils.py +0 -0
  120. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/volume.py +0 -0
  121. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/client.py +0 -0
  122. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.py +0 -0
  123. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.pyi +0 -0
  124. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cls.py +0 -0
  125. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cls.pyi +0 -0
  126. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/config.py +0 -0
  127. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/container_process.py +0 -0
  128. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/container_process.pyi +0 -0
  129. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/dict.py +0 -0
  130. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/dict.pyi +0 -0
  131. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/environments.py +0 -0
  132. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/environments.pyi +0 -0
  133. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/exception.py +0 -0
  134. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/__init__.py +0 -0
  135. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/flash.py +0 -0
  136. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/flash.pyi +0 -0
  137. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/ipython.py +0 -0
  138. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_io.py +0 -0
  139. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_io.pyi +0 -0
  140. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_pattern_matcher.py +0 -0
  141. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/functions.py +0 -0
  142. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/gpu.py +0 -0
  143. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/image.py +0 -0
  144. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/image.pyi +0 -0
  145. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/io_streams.py +0 -0
  146. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/io_streams.pyi +0 -0
  147. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/mount.py +0 -0
  148. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/mount.pyi +0 -0
  149. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/network_file_system.py +0 -0
  150. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/network_file_system.pyi +0 -0
  151. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/object.py +0 -0
  152. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/object.pyi +0 -0
  153. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/output.py +0 -0
  154. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/parallel_map.py +0 -0
  155. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/parallel_map.pyi +0 -0
  156. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/partial_function.py +0 -0
  157. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/partial_function.pyi +0 -0
  158. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/proxy.py +0 -0
  159. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/proxy.pyi +0 -0
  160. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/py.typed +0 -0
  161. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/queue.py +0 -0
  162. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/queue.pyi +0 -0
  163. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/retries.py +0 -0
  164. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/runner.py +0 -0
  165. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/runner.pyi +0 -0
  166. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/running_app.py +0 -0
  167. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox_fs.py +0 -0
  168. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox_fs.pyi +0 -0
  169. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/schedule.py +0 -0
  170. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/scheduler_placement.py +0 -0
  171. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/secret.py +0 -0
  172. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/secret.pyi +0 -0
  173. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/server.py +0 -0
  174. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/server.pyi +0 -0
  175. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/serving.py +0 -0
  176. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/serving.pyi +0 -0
  177. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/snapshot.py +0 -0
  178. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/snapshot.pyi +0 -0
  179. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/stream_type.py +0 -0
  180. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/token_flow.py +0 -0
  181. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/token_flow.pyi +0 -0
  182. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/volume.pyi +0 -0
  183. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/SOURCES.txt +0 -0
  184. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/dependency_links.txt +0 -0
  185. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/entry_points.txt +0 -0
  186. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/requires.txt +0 -0
  187. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/top_level.txt +0 -0
  188. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/__init__.py +0 -0
  189. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs.py +0 -0
  190. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
  191. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs.py +0 -0
  192. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
  193. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/__init__.py +0 -0
  194. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/mdmd.py +0 -0
  195. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/signatures.py +0 -0
  196. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/__init__.py +0 -0
  197. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_grpc.py +0 -0
  198. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2.py +0 -0
  199. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2.pyi +0 -0
  200. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.py +0 -0
  201. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
  202. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/modal_api_grpc.py +0 -0
  203. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/py.typed +0 -0
  204. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_grpc.py +0 -0
  205. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.py +0 -0
  206. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
  207. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  208. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  209. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_version/__main__.py +0 -0
  210. {modal-1.3.6.dev8 → modal-1.3.6.dev10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.6.dev8
3
+ Version: 1.3.6.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -36,7 +36,6 @@ Dynamic: license-file
36
36
 
37
37
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
38
38
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
39
- [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
40
39
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
41
40
 
42
41
  The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
4
4
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
5
- [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
6
5
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
7
6
 
8
7
  The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
@@ -345,12 +345,24 @@ def synchronize_api(obj, target_module=None):
345
345
  RETRY_N_ATTEMPTS_OVERRIDE: Optional[int] = None
346
346
 
347
347
 
348
- def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout=90):
348
+ def retry(
349
+ direct_fn=None,
350
+ *,
351
+ n_attempts=3,
352
+ base_delay=0,
353
+ delay_factor=2,
354
+ attempt_timeout: Optional[float] = 90,
355
+ attempt_timeout_factor=1,
356
+ ):
349
357
  """Decorator that calls an async function multiple times, with a given timeout.
350
358
 
351
359
  If a `base_delay` is provided, the function is given an exponentially
352
360
  increasing delay on each run, up until the maximum number of attempts.
353
361
 
362
+ If `attempt_timeout_factor` is set to a value > 1, the per-attempt timeout
363
+ increases exponentially (starting from `attempt_timeout`, multiplied by
364
+ `attempt_timeout_factor` after each failed attempt).
365
+
354
366
  Usage:
355
367
 
356
368
  ```
@@ -375,10 +387,11 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
375
387
  local_n_attempts = n_attempts
376
388
 
377
389
  delay = base_delay
390
+ current_attempt_timeout = attempt_timeout
378
391
  for i in range(local_n_attempts):
379
392
  t0 = time.time()
380
393
  try:
381
- return await asyncio.wait_for(fn(*args, **kwargs), timeout=timeout)
394
+ return await asyncio.wait_for(fn(*args, **kwargs), timeout=current_attempt_timeout)
382
395
  except asyncio.CancelledError:
383
396
  logger.debug(f"Function {fn} was cancelled")
384
397
  raise
@@ -392,6 +405,8 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
392
405
  )
393
406
  await asyncio.sleep(delay)
394
407
  delay *= delay_factor
408
+ if current_attempt_timeout is not None:
409
+ current_attempt_timeout *= attempt_timeout_factor
395
410
 
396
411
  return f_wrapped
397
412
 
@@ -58,7 +58,7 @@ MULTIPART_UPLOAD_THRESHOLD = 1024**3
58
58
  BLOCK_SIZE: int = 8 * 1024 * 1024
59
59
 
60
60
 
61
- @retry(n_attempts=3, base_delay=0.3, timeout=None)
61
+ @retry(n_attempts=3, base_delay=0.3, attempt_timeout=None)
62
62
  async def _upload_to_s3_url(
63
63
  upload_url,
64
64
  payload: "BytesIOSegmentPayload",
@@ -310,7 +310,7 @@ async def blob_upload_file(
310
310
  return blob_id
311
311
 
312
312
 
313
- @retry(n_attempts=5, base_delay=0.1, timeout=None)
313
+ @retry(n_attempts=5, base_delay=0.1, attempt_timeout=None)
314
314
  async def _download_from_url(download_url: str) -> bytes:
315
315
  async with ClientSessionRegistry.get_session().get(download_url) as s3_resp:
316
316
  # S3 signal to slow down request rate.
@@ -89,6 +89,10 @@ RETRYABLE_GRPC_STATUS_CODES = [
89
89
  Status.UNKNOWN,
90
90
  ]
91
91
  SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
92
+ # Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
93
+ # Doubles each attempt (attempt_timeout_factor=2): 0.5 + 1 + 2 + 4 + 8 = 15.5s + ~1.5s inter-attempt delays.
94
+ CONNECT_TIMEOUT = 0.5
95
+ DEFAULT_MAX_RETRIES = 3
92
96
 
93
97
 
94
98
  @dataclass
@@ -117,7 +121,7 @@ class ConnectionManager:
117
121
  self._channels[server_url] = create_channel(server_url, self._metadata)
118
122
  try:
119
123
  await connect_channel(self._channels[server_url])
120
- except OSError as exc:
124
+ except (OSError, asyncio.TimeoutError) as exc:
121
125
  raise ConnectionError("Could not connect to the Modal server.") from exc
122
126
  return self._channels[server_url]
123
127
 
@@ -221,7 +225,7 @@ def create_channel(
221
225
  return channel
222
226
 
223
227
 
224
- @retry(n_attempts=5, base_delay=0.1)
228
+ @retry(n_attempts=5, base_delay=0.1, attempt_timeout=CONNECT_TIMEOUT, attempt_timeout_factor=2)
225
229
  async def connect_channel(channel: grpclib.client.Channel):
226
230
  """Connect to socket and raise exceptions when there is a connection issue."""
227
231
  await channel.__connect__()
@@ -246,7 +250,7 @@ class Retry:
246
250
  base_delay: float = 0.1
247
251
  max_delay: float = 1
248
252
  delay_factor: float = 2
249
- max_retries: Optional[int] = 3
253
+ max_retries: Optional[int] = DEFAULT_MAX_RETRIES
250
254
  additional_status_codes: list = field(default_factory=list)
251
255
  attempt_timeout: Optional[float] = None # timeout for each attempt
252
256
  total_timeout: Optional[float] = None # timeout for the entire function call
@@ -257,7 +261,7 @@ class Retry:
257
261
  async def retry_transient_errors(
258
262
  fn: "grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]",
259
263
  req: RequestType,
260
- max_retries: Optional[int] = 3,
264
+ max_retries: Optional[int] = DEFAULT_MAX_RETRIES,
261
265
  ) -> ResponseType:
262
266
  """Minimum API version of _retry_transient_errors that works with grpclib.client.UnaryUnaryMethod.
263
267
 
@@ -373,7 +377,13 @@ async def _retry_transient_errors(
373
377
  try:
374
378
  with suppress_tb_frame():
375
379
  return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
376
- except (StreamTerminatedError, GRPCError, OSError, asyncio.TimeoutError, AttributeError) as exc:
380
+ except (
381
+ StreamTerminatedError,
382
+ GRPCError,
383
+ OSError,
384
+ asyncio.TimeoutError,
385
+ AttributeError,
386
+ ) as exc:
377
387
  # Note that we only catch AttributeError to handle a specific case that works around a bug
378
388
  # in grpclib<=0.4.7. See above (search for `write_appdata`).
379
389
 
@@ -242,6 +242,44 @@ class TaskCommandRouterClient:
242
242
  lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
243
243
  )
244
244
 
245
+ async def container_create(self, request: sr_pb2.TaskContainerCreateRequest) -> sr_pb2.TaskContainerCreateResponse:
246
+ """Create an additional container via task command router."""
247
+ with grpc_error_converter():
248
+ return await call_with_retries_on_transient_errors(
249
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerCreate, request)
250
+ )
251
+
252
+ async def container_terminate(
253
+ self,
254
+ request: sr_pb2.TaskContainerTerminateRequest,
255
+ ) -> sr_pb2.TaskContainerTerminateResponse:
256
+ """Terminate an additional container via task command router."""
257
+ with grpc_error_converter():
258
+ return await call_with_retries_on_transient_errors(
259
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerTerminate, request)
260
+ )
261
+
262
+ async def container_wait(self, request: sr_pb2.TaskContainerWaitRequest) -> sr_pb2.TaskContainerWaitResponse:
263
+ """Wait for an additional container via task command router."""
264
+ with grpc_error_converter():
265
+ return await call_with_retries_on_transient_errors(
266
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerWait, request)
267
+ )
268
+
269
+ async def container_get(self, request: sr_pb2.TaskContainerGetRequest) -> sr_pb2.TaskContainerGetResponse:
270
+ """Get the latest tracked container for a logical name via task command router."""
271
+ with grpc_error_converter():
272
+ return await call_with_retries_on_transient_errors(
273
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerGet, request)
274
+ )
275
+
276
+ async def container_list(self, request: sr_pb2.TaskContainerListRequest) -> sr_pb2.TaskContainerListResponse:
277
+ """List sandbox containers via task command router."""
278
+ with grpc_error_converter():
279
+ return await call_with_retries_on_transient_errors(
280
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerList, request)
281
+ )
282
+
245
283
  async def exec_stdio_read(
246
284
  self,
247
285
  task_id: str,
@@ -18,6 +18,7 @@ from ..mount import _Mount
18
18
  from ..runner import interactive_shell
19
19
  from ..sandbox import Sandbox
20
20
  from ..secret import Secret
21
+ from ..stream_type import StreamType
21
22
  from ..volume import Volume
22
23
  from .container import exec
23
24
  from .import_refs import (
@@ -64,26 +65,75 @@ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
64
65
  return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
65
66
 
66
67
 
68
+ def _parse_sandbox_container_ref(ref: str) -> tuple[str, Optional[str]]:
69
+ """Parse a sandbox:container reference into (sandbox_id, container_name).
70
+
71
+ Supports the pattern `sb-xyz:container_name` for accessing sandbox containers.
72
+ Returns (ref, None) if no container name is specified.
73
+ """
74
+ if _is_valid_modal_id(ref, "sb-"):
75
+ return ref, None
76
+
77
+ if ":" in ref:
78
+ parts = ref.split(":", 1)
79
+ sandbox_id, container_name = parts[0], parts[1]
80
+ if _is_valid_modal_id(sandbox_id, "sb-") and container_name:
81
+ return sandbox_id, container_name
82
+
83
+ return ref, None
84
+
85
+
67
86
  def _is_running_container_ref(ref: Optional[str]) -> bool:
68
87
  if ref is None:
69
88
  return False
70
- return _is_valid_modal_id(ref, "sb-") or _is_valid_modal_id(ref, "ta-")
89
+ sandbox_id, _ = _parse_sandbox_container_ref(ref)
90
+ return _is_valid_modal_id(sandbox_id, "sb-") or _is_valid_modal_id(ref, "ta-")
91
+
92
+
93
+ def _start_shell_in_sandbox_container(sandbox_id: str, container_name: str, cmd: str, pty: bool) -> None:
94
+ """Shell into a named container within a sandbox."""
95
+ try:
96
+ sandbox = Sandbox.from_id(sandbox_id)
97
+ except NotFoundError:
98
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
99
+ except Exception as e:
100
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
101
+
102
+ try:
103
+ sandbox_container = sandbox._experimental_containers.get(name=container_name)
104
+ if pty:
105
+ process = sandbox_container.exec(*shlex.split(cmd), pty=pty)
106
+ process.attach()
107
+ else:
108
+ process = sandbox_container.exec(
109
+ *shlex.split(cmd), pty=pty, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
110
+ )
111
+ process.wait()
112
+ except NotFoundError:
113
+ raise ClickException(f"Container '{container_name}' not found in Sandbox '{sandbox_id}'.")
114
+ except Exception as e:
115
+ raise ClickException(f"Error exec-ing into container '{container_name}' in Sandbox '{sandbox_id}': {str(e)}")
71
116
 
72
117
 
73
118
  def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
74
- if _is_valid_modal_id(ref, "sb-"):
119
+ sandbox_id, container_name = _parse_sandbox_container_ref(ref)
120
+
121
+ if container_name is not None:
122
+ return _start_shell_in_sandbox_container(sandbox_id, container_name, cmd, pty)
123
+
124
+ if _is_valid_modal_id(sandbox_id, "sb-"):
75
125
  try:
76
- sandbox = Sandbox.from_id(ref)
126
+ sandbox = Sandbox.from_id(sandbox_id)
77
127
  ref = sandbox._get_task_id()
78
- except NotFoundError as e:
79
- raise ClickException(f"Sandbox '{ref}' not found (is it still running?)")
128
+ except NotFoundError:
129
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
80
130
  except Exception as e:
81
- raise ClickException(f"Error connecting to Sandbox '{ref}': {str(e)}")
131
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
82
132
 
83
133
  assert _is_valid_modal_id(ref, "ta-")
84
134
  try:
85
135
  exec(container_id=ref, command=shlex.split(cmd), pty=pty)
86
- except NotFoundError as e:
136
+ except NotFoundError:
87
137
  raise ClickException(f"Container '{ref}' not found (is it still running?)")
88
138
  except Exception as e:
89
139
  raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
@@ -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.3.6.dev8",
38
+ version: str = "1.3.6.dev10",
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.3.6.dev8",
174
+ version: str = "1.3.6.dev10",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -408,7 +408,7 @@ class Function(
408
408
 
409
409
  _call_generator: ___call_generator_spec
410
410
 
411
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
411
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
412
412
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
413
413
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
414
414
  ...
@@ -417,7 +417,7 @@ class Function(
417
417
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
418
418
  ...
419
419
 
420
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
420
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
421
421
 
422
422
  class __remote_gen_spec(typing_extensions.Protocol):
423
423
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -444,7 +444,7 @@ class Function(
444
444
  """
445
445
  ...
446
446
 
447
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
447
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
448
448
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
449
449
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
450
450
 
@@ -467,7 +467,7 @@ class Function(
467
467
  """
468
468
  ...
469
469
 
470
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
470
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
471
471
 
472
472
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
473
473
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -475,7 +475,7 @@ class Function(
475
475
 
476
476
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
477
477
 
478
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
478
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
479
479
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
480
480
  """Calls the function with the given arguments, without waiting for the results.
481
481
 
@@ -496,7 +496,7 @@ class Function(
496
496
  """
497
497
  ...
498
498
 
499
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
499
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
500
500
 
501
501
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
502
502
  """Return the inner Python object wrapped by this Modal Function."""