modal 1.2.1.dev22__tar.gz → 1.2.2.dev2__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (198) hide show
  1. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/PKG-INFO +1 -1
  2. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/mount_utils.py +26 -1
  3. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/client.pyi +2 -2
  4. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/container_process.py +2 -3
  5. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/functions.pyi +6 -6
  6. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/image.py +21 -0
  7. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/image.pyi +4 -0
  8. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/io_streams.py +51 -47
  9. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/io_streams.pyi +20 -6
  10. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/sandbox.py +2 -2
  11. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/volume.py +1 -1
  12. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/PKG-INFO +1 -1
  13. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_version/__init__.py +1 -1
  14. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/LICENSE +0 -0
  15. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/README.md +0 -0
  16. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/__init__.py +0 -0
  17. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/__main__.py +0 -0
  18. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_billing.py +0 -0
  19. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_clustered_functions.py +0 -0
  20. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_clustered_functions.pyi +0 -0
  21. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_container_entrypoint.py +0 -0
  22. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_functions.py +0 -0
  23. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_ipython.py +0 -0
  24. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_location.py +0 -0
  25. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_object.py +0 -0
  26. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_output.py +0 -0
  27. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_partial_function.py +0 -0
  28. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_pty.py +0 -0
  29. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_resolver.py +0 -0
  30. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_resources.py +0 -0
  31. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/__init__.py +0 -0
  32. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/asgi.py +0 -0
  33. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.py +0 -0
  34. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
  35. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/execution_context.py +0 -0
  36. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/execution_context.pyi +0 -0
  37. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  38. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/telemetry.py +0 -0
  39. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_runtime/user_code_imports.py +0 -0
  40. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_serialization.py +0 -0
  41. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_traceback.py +0 -0
  42. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_tunnel.py +0 -0
  43. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_tunnel.pyi +0 -0
  44. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_type_manager.py +0 -0
  45. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/__init__.py +0 -0
  46. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/app_utils.py +0 -0
  47. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/async_utils.py +0 -0
  48. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/auth_token_manager.py +0 -0
  49. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/blob_utils.py +0 -0
  50. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
  51. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/deprecation.py +0 -0
  52. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/docker_utils.py +0 -0
  53. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/function_utils.py +0 -0
  54. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/git_utils.py +0 -0
  55. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/grpc_testing.py +0 -0
  56. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/grpc_utils.py +0 -0
  57. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/hash_utils.py +0 -0
  58. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/http_utils.py +0 -0
  59. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/jwt_utils.py +0 -0
  60. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/logger.py +0 -0
  61. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/name_utils.py +0 -0
  62. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/package_utils.py +0 -0
  63. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/pattern_utils.py +0 -0
  64. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/rand_pb_testing.py +0 -0
  65. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/shell_utils.py +0 -0
  66. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/task_command_router_client.py +0 -0
  67. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_utils/time_utils.py +0 -0
  68. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_vendor/__init__.py +0 -0
  69. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  70. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_vendor/cloudpickle.py +0 -0
  71. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_vendor/tblib.py +0 -0
  72. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/_watcher.py +0 -0
  73. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/app.py +0 -0
  74. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/app.pyi +0 -0
  75. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/billing.py +0 -0
  76. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/2023.12.312.txt +0 -0
  77. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/2023.12.txt +0 -0
  78. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/2024.04.txt +0 -0
  79. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/2024.10.txt +0 -0
  80. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/2025.06.txt +0 -0
  81. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/PREVIEW.txt +0 -0
  82. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/README.md +0 -0
  83. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/builder/base-images.json +0 -0
  84. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/call_graph.py +0 -0
  85. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/__init__.py +0 -0
  86. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/_download.py +0 -0
  87. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/_traceback.py +0 -0
  88. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/app.py +0 -0
  89. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/cluster.py +0 -0
  90. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/config.py +0 -0
  91. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/container.py +0 -0
  92. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/dict.py +0 -0
  93. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/entry_point.py +0 -0
  94. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/environment.py +0 -0
  95. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/import_refs.py +0 -0
  96. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/launch.py +0 -0
  97. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/network_file_system.py +0 -0
  98. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/profile.py +0 -0
  99. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/programs/__init__.py +0 -0
  100. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/programs/launch_instance_ssh.py +0 -0
  101. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/programs/run_jupyter.py +0 -0
  102. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/programs/run_marimo.py +0 -0
  103. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/programs/vscode.py +0 -0
  104. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/queues.py +0 -0
  105. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/run.py +0 -0
  106. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/secret.py +0 -0
  107. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/token.py +0 -0
  108. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/utils.py +0 -0
  109. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cli/volume.py +0 -0
  110. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/client.py +0 -0
  111. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.py +0 -0
  112. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.pyi +0 -0
  113. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cls.py +0 -0
  114. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/cls.pyi +0 -0
  115. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/config.py +0 -0
  116. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/container_process.pyi +0 -0
  117. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/dict.py +0 -0
  118. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/dict.pyi +0 -0
  119. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/environments.py +0 -0
  120. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/environments.pyi +0 -0
  121. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/exception.py +0 -0
  122. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/experimental/__init__.py +0 -0
  123. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/experimental/flash.py +0 -0
  124. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/experimental/flash.pyi +0 -0
  125. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/experimental/ipython.py +0 -0
  126. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/file_io.py +0 -0
  127. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/file_io.pyi +0 -0
  128. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/file_pattern_matcher.py +0 -0
  129. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/functions.py +0 -0
  130. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/gpu.py +0 -0
  131. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/mount.py +0 -0
  132. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/mount.pyi +0 -0
  133. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/network_file_system.py +0 -0
  134. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/network_file_system.pyi +0 -0
  135. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/object.py +0 -0
  136. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/object.pyi +0 -0
  137. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/output.py +0 -0
  138. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/parallel_map.py +0 -0
  139. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/parallel_map.pyi +0 -0
  140. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/partial_function.py +0 -0
  141. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/partial_function.pyi +0 -0
  142. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/proxy.py +0 -0
  143. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/proxy.pyi +0 -0
  144. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/py.typed +0 -0
  145. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/queue.py +0 -0
  146. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/queue.pyi +0 -0
  147. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/retries.py +0 -0
  148. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/runner.py +0 -0
  149. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/runner.pyi +0 -0
  150. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/running_app.py +0 -0
  151. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/sandbox.pyi +0 -0
  152. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/schedule.py +0 -0
  153. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/scheduler_placement.py +0 -0
  154. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/secret.py +0 -0
  155. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/secret.pyi +0 -0
  156. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/serving.py +0 -0
  157. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/serving.pyi +0 -0
  158. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/snapshot.py +0 -0
  159. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/snapshot.pyi +0 -0
  160. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/stream_type.py +0 -0
  161. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/token_flow.py +0 -0
  162. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/token_flow.pyi +0 -0
  163. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal/volume.pyi +0 -0
  164. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/SOURCES.txt +0 -0
  165. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/dependency_links.txt +0 -0
  166. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/entry_points.txt +0 -0
  167. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/requires.txt +0 -0
  168. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal.egg-info/top_level.txt +0 -0
  169. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/__init__.py +0 -0
  170. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/gen_cli_docs.py +0 -0
  171. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/gen_reference_docs.py +0 -0
  172. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/mdmd/__init__.py +0 -0
  173. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/mdmd/mdmd.py +0 -0
  174. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_docs/mdmd/signatures.py +0 -0
  175. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/__init__.py +0 -0
  176. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api.proto +0 -0
  177. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api_grpc.py +0 -0
  178. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api_pb2.py +0 -0
  179. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api_pb2.pyi +0 -0
  180. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.py +0 -0
  181. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
  182. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/modal_api_grpc.py +0 -0
  183. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/py.typed +0 -0
  184. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router.proto +0 -0
  185. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router_grpc.py +0 -0
  186. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.py +0 -0
  187. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.pyi +0 -0
  188. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  189. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  190. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router.proto +0 -0
  191. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router_grpc.py +0 -0
  192. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.py +0 -0
  193. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
  194. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  195. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  196. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/modal_version/__main__.py +0 -0
  197. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/pyproject.toml +0 -0
  198. {modal-1.2.1.dev22 → modal-1.2.2.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev22
3
+ Version: 1.2.2.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -3,7 +3,9 @@ import posixpath
3
3
  import typing
4
4
  from collections.abc import Mapping, Sequence
5
5
  from pathlib import PurePath, PurePosixPath
6
- from typing import Union
6
+ from typing import Optional, Union
7
+
8
+ from typing_extensions import TypeGuard
7
9
 
8
10
  from ..cloud_bucket_mount import _CloudBucketMount
9
11
  from ..exception import InvalidError
@@ -76,3 +78,26 @@ def validate_volumes(
76
78
  )
77
79
 
78
80
  return validated_volumes
81
+
82
+
83
+ def validate_only_modal_volumes(
84
+ volumes: Optional[Optional[dict[Union[str, PurePosixPath], _Volume]]],
85
+ caller_name: str,
86
+ ) -> Sequence[tuple[str, _Volume]]:
87
+ """Validate all volumes are `modal.Volume`."""
88
+ if volumes is None:
89
+ return []
90
+
91
+ validated_volumes = validate_volumes(volumes)
92
+
93
+ # Although the typing forbids `_CloudBucketMount` for type checking, one can still pass a `_CloudBucketMount`
94
+ # during runtime, so we'll check the type here.
95
+ def all_modal_volumes(
96
+ vols: Sequence[tuple[str, Union[_Volume, _CloudBucketMount]]],
97
+ ) -> TypeGuard[Sequence[tuple[str, _Volume]]]:
98
+ return all(isinstance(v, _Volume) for _, v in vols)
99
+
100
+ if not all_modal_volumes(validated_volumes):
101
+ raise InvalidError(f"{caller_name} only supports volumes that are modal.Volume")
102
+
103
+ return validated_volumes
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.1.dev22",
36
+ version: str = "1.2.2.dev2",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.1.dev22",
167
+ version: str = "1.2.2.dev2",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -101,6 +101,7 @@ class _ContainerProcessThroughServer(Generic[T]):
101
101
 
102
102
  Returns `None` if the process is still running, else returns the exit code.
103
103
  """
104
+ assert self._process_id
104
105
  if self._returncode is not None:
105
106
  return self._returncode
106
107
  if self._exec_deadline and time.monotonic() >= self._exec_deadline:
@@ -119,6 +120,7 @@ class _ContainerProcessThroughServer(Generic[T]):
119
120
  return None
120
121
 
121
122
  async def _wait_for_completion(self) -> int:
123
+ assert self._process_id
122
124
  while True:
123
125
  req = api_pb2.ContainerExecWaitRequest(exec_id=self._process_id, timeout=10)
124
126
  resp: api_pb2.ContainerExecWaitResponse = await retry_transient_errors(
@@ -169,9 +171,6 @@ class _ContainerProcessThroughServer(Generic[T]):
169
171
  stream_impl = stream._impl
170
172
  # Don't skip empty messages so we can detect when the process has booted.
171
173
  async for chunk in stream_impl._get_logs(skip_empty_messages=False):
172
- if chunk is None:
173
- break
174
-
175
174
  if not on_connect.is_set():
176
175
  connecting_status.stop()
177
176
  on_connect.set()
@@ -401,7 +401,7 @@ class Function(
401
401
 
402
402
  _call_generator: ___call_generator_spec[typing_extensions.Self]
403
403
 
404
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
404
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
405
405
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
406
406
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
407
407
  ...
@@ -410,7 +410,7 @@ class Function(
410
410
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
411
411
  ...
412
412
 
413
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
413
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
414
414
 
415
415
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
416
416
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -437,7 +437,7 @@ class Function(
437
437
  """
438
438
  ...
439
439
 
440
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
440
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
441
441
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
442
442
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
443
443
 
@@ -461,7 +461,7 @@ class Function(
461
461
  ...
462
462
 
463
463
  _experimental_spawn: ___experimental_spawn_spec[
464
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
464
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
465
465
  ]
466
466
 
467
467
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -470,7 +470,7 @@ class Function(
470
470
 
471
471
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
472
472
 
473
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
473
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
474
474
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
475
475
  """Calls the function with the given arguments, without waiting for the results.
476
476
 
@@ -491,7 +491,7 @@ class Function(
491
491
  """
492
492
  ...
493
493
 
494
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
494
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
495
495
 
496
496
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
497
497
  """Return the inner Python object wrapped by this Modal Function."""
@@ -39,6 +39,7 @@ from ._utils.docker_utils import (
39
39
  )
40
40
  from ._utils.function_utils import FunctionInfo
41
41
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
42
+ from ._utils.mount_utils import validate_only_modal_volumes
42
43
  from .client import _Client
43
44
  from .cloud_bucket_mount import _CloudBucketMount
44
45
  from .config import config, logger, user_config_path
@@ -487,6 +488,7 @@ class _Image(_Object, type_prefix="im"):
487
488
  context_mount_function: Optional[Callable[[], Optional[_Mount]]] = None,
488
489
  force_build: bool = False,
489
490
  build_args: dict[str, str] = {},
491
+ validated_volumes: Optional[Sequence[tuple[str, _Volume]]] = None,
490
492
  # For internal use only.
491
493
  _namespace: "api_pb2.DeploymentNamespace.ValueType" = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
492
494
  _do_assert_no_mount_layers: bool = True,
@@ -494,6 +496,9 @@ class _Image(_Object, type_prefix="im"):
494
496
  if base_images is None:
495
497
  base_images = {}
496
498
 
499
+ if validated_volumes is None:
500
+ validated_volumes = []
501
+
497
502
  if secrets is None:
498
503
  secrets = []
499
504
  if gpu_config is None:
@@ -514,6 +519,8 @@ class _Image(_Object, type_prefix="im"):
514
519
  deps += (build_function,)
515
520
  if image_registry_config and image_registry_config.secret:
516
521
  deps += (image_registry_config.secret,)
522
+ for _, vol in validated_volumes:
523
+ deps += (vol,)
517
524
  return deps
518
525
 
519
526
  async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
@@ -592,6 +599,17 @@ class _Image(_Object, type_prefix="im"):
592
599
  build_function_id = ""
593
600
  _build_function = None
594
601
 
602
+ # Relies on dicts being ordered (true as of Python 3.6).
603
+ volume_mounts = [
604
+ api_pb2.VolumeMount(
605
+ mount_path=path,
606
+ volume_id=volume.object_id,
607
+ allow_background_commits=True,
608
+ read_only=volume._read_only,
609
+ )
610
+ for path, volume in validated_volumes
611
+ ]
612
+
595
613
  image_definition = api_pb2.Image(
596
614
  base_images=base_images_pb2s,
597
615
  dockerfile_commands=dockerfile.commands,
@@ -604,6 +622,7 @@ class _Image(_Object, type_prefix="im"):
604
622
  runtime_debug=config.get("function_runtime_debug"),
605
623
  build_function=_build_function,
606
624
  build_args=build_args,
625
+ volume_mounts=volume_mounts,
607
626
  )
608
627
 
609
628
  req = api_pb2.ImageGetOrCreateRequest(
@@ -1690,6 +1709,7 @@ class _Image(_Object, type_prefix="im"):
1690
1709
  *commands: Union[str, list[str]],
1691
1710
  env: Optional[dict[str, Optional[str]]] = None,
1692
1711
  secrets: Optional[Collection[_Secret]] = None,
1712
+ volumes: Optional[dict[Union[str, PurePosixPath], _Volume]] = None,
1693
1713
  gpu: GPU_T = None,
1694
1714
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1695
1715
  ) -> "_Image":
@@ -1712,6 +1732,7 @@ class _Image(_Object, type_prefix="im"):
1712
1732
  secrets=secrets,
1713
1733
  gpu_config=parse_gpu_config(gpu),
1714
1734
  force_build=self.force_build or force_build,
1735
+ validated_volumes=validate_only_modal_volumes(volumes, "Image.run_commands"),
1715
1736
  )
1716
1737
 
1717
1738
  @staticmethod
@@ -176,6 +176,7 @@ class _Image(modal._object._Object):
176
176
  ] = None,
177
177
  force_build: bool = False,
178
178
  build_args: dict[str, str] = {},
179
+ validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume._Volume]]] = None,
179
180
  _namespace: int = 1,
180
181
  _do_assert_no_mount_layers: bool = True,
181
182
  ): ...
@@ -668,6 +669,7 @@ class _Image(modal._object._Object):
668
669
  *commands: typing.Union[str, list[str]],
669
670
  env: typing.Optional[dict[str, typing.Optional[str]]] = None,
670
671
  secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
672
+ volumes: typing.Optional[dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]] = None,
671
673
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
672
674
  force_build: bool = False,
673
675
  ) -> _Image:
@@ -1091,6 +1093,7 @@ class Image(modal.object.Object):
1091
1093
  ] = None,
1092
1094
  force_build: bool = False,
1093
1095
  build_args: dict[str, str] = {},
1096
+ validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume.Volume]]] = None,
1094
1097
  _namespace: int = 1,
1095
1098
  _do_assert_no_mount_layers: bool = True,
1096
1099
  ): ...
@@ -1648,6 +1651,7 @@ class Image(modal.object.Object):
1648
1651
  *commands: typing.Union[str, list[str]],
1649
1652
  env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1650
1653
  secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1654
+ volumes: typing.Optional[dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]] = None,
1651
1655
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1652
1656
  force_build: bool = False,
1653
1657
  ) -> Image:
@@ -64,7 +64,6 @@ async def _container_process_logs_iterator(
64
64
  get_raw_bytes=True,
65
65
  last_batch_index=last_index,
66
66
  )
67
-
68
67
  stream = client.stub.ContainerExecGetOutput.unary_stream(req)
69
68
  while True:
70
69
  # Check deadline before attempting to receive the next batch
@@ -76,11 +75,13 @@ async def _container_process_logs_iterator(
76
75
  break
77
76
  except StopAsyncIteration:
78
77
  break
78
+
79
+ for item in batch.items:
80
+ yield item.message_bytes, batch.batch_index
81
+
79
82
  if batch.HasField("exit_code"):
80
83
  yield None, batch.batch_index
81
84
  break
82
- for item in batch.items:
83
- yield item.message_bytes, batch.batch_index
84
85
 
85
86
 
86
87
  T = TypeVar("T", str, bytes)
@@ -89,7 +90,7 @@ T = TypeVar("T", str, bytes)
89
90
  class _StreamReaderThroughServer(Generic[T]):
90
91
  """A StreamReader implementation that reads from the server."""
91
92
 
92
- _stream: Optional[AsyncGenerator[Optional[bytes], None]]
93
+ _stream: Optional[AsyncGenerator[T, None]]
93
94
 
94
95
  def __init__(
95
96
  self,
@@ -134,10 +135,9 @@ class _StreamReaderThroughServer(Generic[T]):
134
135
  self._stream_type = stream_type
135
136
 
136
137
  if self._object_type == "container_process":
137
- # Container process streams need to be consumed as they are produced,
138
- # otherwise the process will block. Use a buffer to store the stream
139
- # until the client consumes it.
140
- self._container_process_buffer: list[Optional[bytes]] = []
138
+ # TODO: we should not have this async code in constructors!
139
+ # it only works as long as all the construction happens inside of synchronicity code
140
+ self._container_process_buffer: list[Optional[bytes]] = [] # TODO: change this to an asyncio.Queue
141
141
  self._consume_container_process_task = asyncio.create_task(self._consume_container_process_stream())
142
142
 
143
143
  @property
@@ -147,21 +147,18 @@ class _StreamReaderThroughServer(Generic[T]):
147
147
 
148
148
  async def read(self) -> T:
149
149
  """Fetch the entire contents of the stream until EOF."""
150
- data_str = ""
151
- data_bytes = b""
152
150
  logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read starting")
153
- async for message in self._get_logs():
154
- if message is None:
155
- break
156
- if self._text:
157
- data_str += message.decode("utf-8")
158
- else:
159
- data_bytes += message
160
-
161
- logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
162
151
  if self._text:
152
+ data_str = ""
153
+ async for message in _decode_bytes_stream_to_str(self._get_logs()):
154
+ data_str += message
155
+ logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
163
156
  return cast(T, data_str)
164
157
  else:
158
+ data_bytes = b""
159
+ async for message in self._get_logs():
160
+ data_bytes += message
161
+ logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
165
162
  return cast(T, data_bytes)
166
163
 
167
164
  async def _consume_container_process_stream(self):
@@ -181,6 +178,7 @@ class _StreamReaderThroughServer(Generic[T]):
181
178
  )
182
179
  async for message, batch_index in iterator:
183
180
  if self._stream_type == StreamType.STDOUT and message:
181
+ # TODO: rearchitect this, since these bytes aren't necessarily decodable
184
182
  print(message.decode("utf-8"), end="")
185
183
  elif self._stream_type == StreamType.PIPE:
186
184
  self._container_process_buffer.append(message)
@@ -208,6 +206,9 @@ class _StreamReaderThroughServer(Generic[T]):
208
206
 
209
207
  async def _stream_container_process(self) -> AsyncGenerator[tuple[Optional[bytes], str], None]:
210
208
  """Streams the container process buffer to the reader."""
209
+ # Container process streams need to be consumed as they are produced,
210
+ # otherwise the process will block. Use a buffer to store the stream
211
+ # until the client consumes it.
211
212
  entry_id = 0
212
213
  if self._last_entry_id:
213
214
  entry_id = int(self._last_entry_id) + 1
@@ -225,7 +226,7 @@ class _StreamReaderThroughServer(Generic[T]):
225
226
 
226
227
  entry_id += 1
227
228
 
228
- async def _get_logs(self, skip_empty_messages: bool = True) -> AsyncGenerator[Optional[bytes], None]:
229
+ async def _get_logs(self, skip_empty_messages: bool = True) -> AsyncGenerator[bytes, None]:
229
230
  """Streams sandbox or process logs from the server to the reader.
230
231
 
231
232
  Logs returned by this method may contain partial or multiple lines at a time.
@@ -237,7 +238,6 @@ class _StreamReaderThroughServer(Generic[T]):
237
238
  raise InvalidError("Logs can only be retrieved using the PIPE stream type.")
238
239
 
239
240
  if self.eof:
240
- yield None
241
241
  return
242
242
 
243
243
  completed = False
@@ -262,6 +262,8 @@ class _StreamReaderThroughServer(Generic[T]):
262
262
  if message is None:
263
263
  completed = True
264
264
  self.eof = True
265
+ return
266
+
265
267
  yield message
266
268
 
267
269
  except (GRPCError, StreamTerminatedError) as exc:
@@ -275,43 +277,37 @@ class _StreamReaderThroughServer(Generic[T]):
275
277
  continue
276
278
  raise
277
279
 
278
- async def _get_logs_by_line(self) -> AsyncGenerator[Optional[bytes], None]:
280
+ async def _get_logs_by_line(self) -> AsyncGenerator[bytes, None]:
279
281
  """Process logs from the server and yield complete lines only."""
280
282
  async for message in self._get_logs():
281
- if message is None:
282
- if self._line_buffer:
283
- yield self._line_buffer
284
- self._line_buffer = b""
285
- yield None
286
- else:
287
- assert isinstance(message, bytes)
288
- self._line_buffer += message
289
- while b"\n" in self._line_buffer:
290
- line, self._line_buffer = self._line_buffer.split(b"\n", 1)
291
- yield line + b"\n"
283
+ assert isinstance(message, bytes)
284
+ self._line_buffer += message
285
+ while b"\n" in self._line_buffer:
286
+ line, self._line_buffer = self._line_buffer.split(b"\n", 1)
287
+ yield line + b"\n"
292
288
 
293
- def _ensure_stream(self) -> AsyncGenerator[Optional[bytes], None]:
289
+ if self._line_buffer:
290
+ yield self._line_buffer
291
+ self._line_buffer = b""
292
+
293
+ def _ensure_stream(self) -> AsyncGenerator[T, None]:
294
294
  if not self._stream:
295
295
  if self._by_line:
296
- self._stream = self._get_logs_by_line()
296
+ # TODO: This is quite odd - it does line buffering in binary mode
297
+ # but we then always add the buffered text decoding on top of that.
298
+ # feels a bit upside down...
299
+ stream = self._get_logs_by_line()
297
300
  else:
298
- self._stream = self._get_logs()
301
+ stream = self._get_logs()
302
+ if self._text:
303
+ stream = _decode_bytes_stream_to_str(stream)
304
+ self._stream = cast(AsyncGenerator[T, None], stream)
299
305
  return self._stream
300
306
 
301
307
  async def __anext__(self) -> T:
302
308
  """mdmd:hidden"""
303
309
  stream = self._ensure_stream()
304
-
305
- value = await stream.__anext__()
306
-
307
- # The stream yields None if it receives an EOF batch.
308
- if value is None:
309
- raise StopAsyncIteration
310
-
311
- if self._text:
312
- return cast(T, value.decode("utf-8"))
313
- else:
314
- return cast(T, value)
310
+ return cast(T, await stream.__anext__())
315
311
 
316
312
  async def aclose(self):
317
313
  """mdmd:hidden"""
@@ -330,6 +326,7 @@ async def _decode_bytes_stream_to_str(stream: AsyncGenerator[bytes, None]) -> As
330
326
  text = decoder.decode(item, final=False)
331
327
  if text:
332
328
  yield text
329
+
333
330
  # Flush any buffered partial character at end-of-stream
334
331
  tail = decoder.decode(b"", final=True)
335
332
  if tail:
@@ -512,6 +509,13 @@ class _StreamReader(Generic[T]):
512
509
  ```
513
510
  """
514
511
 
512
+ _impl: Union[
513
+ _StreamReaderThroughServer,
514
+ _DevnullStreamReader,
515
+ _TextStreamReaderThroughCommandRouter,
516
+ _BytesStreamReaderThroughCommandRouter,
517
+ ]
518
+
515
519
  def __init__(
516
520
  self,
517
521
  file_descriptor: "api_pb2.FileDescriptor.ValueType",
@@ -21,7 +21,7 @@ T = typing.TypeVar("T")
21
21
  class _StreamReaderThroughServer(typing.Generic[T]):
22
22
  """A StreamReader implementation that reads from the server."""
23
23
 
24
- _stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
24
+ _stream: typing.Optional[collections.abc.AsyncGenerator[T, None]]
25
25
 
26
26
  def __init__(
27
27
  self,
@@ -54,9 +54,7 @@ class _StreamReaderThroughServer(typing.Generic[T]):
54
54
  """Streams the container process buffer to the reader."""
55
55
  ...
56
56
 
57
- def _get_logs(
58
- self, skip_empty_messages: bool = True
59
- ) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
57
+ def _get_logs(self, skip_empty_messages: bool = True) -> collections.abc.AsyncGenerator[bytes, None]:
60
58
  """Streams sandbox or process logs from the server to the reader.
61
59
 
62
60
  Logs returned by this method may contain partial or multiple lines at a time.
@@ -66,11 +64,11 @@ class _StreamReaderThroughServer(typing.Generic[T]):
66
64
  """
67
65
  ...
68
66
 
69
- def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
67
+ def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[bytes, None]:
70
68
  """Process logs from the server and yield complete lines only."""
71
69
  ...
72
70
 
73
- def _ensure_stream(self) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
71
+ def _ensure_stream(self) -> collections.abc.AsyncGenerator[T, None]: ...
74
72
  async def __anext__(self) -> T:
75
73
  """mdmd:hidden"""
76
74
  ...
@@ -200,6 +198,14 @@ class _StreamReader(typing.Generic[T]):
200
198
  print(f"Message: {message}")
201
199
  ```
202
200
  """
201
+
202
+ _impl: typing.Union[
203
+ _StreamReaderThroughServer,
204
+ _DevnullStreamReader,
205
+ _TextStreamReaderThroughCommandRouter,
206
+ _BytesStreamReaderThroughCommandRouter,
207
+ ]
208
+
203
209
  def __init__(
204
210
  self,
205
211
  file_descriptor: int,
@@ -392,6 +398,14 @@ class StreamReader(typing.Generic[T]):
392
398
  print(f"Message: {message}")
393
399
  ```
394
400
  """
401
+
402
+ _impl: typing.Union[
403
+ _StreamReaderThroughServer,
404
+ _DevnullStreamReader,
405
+ _TextStreamReaderThroughCommandRouter,
406
+ _BytesStreamReaderThroughCommandRouter,
407
+ ]
408
+
395
409
  def __init__(
396
410
  self,
397
411
  file_descriptor: int,
@@ -516,10 +516,10 @@ class _Sandbox(_Object, type_prefix="sb"):
516
516
  return obj
517
517
 
518
518
  def _hydrate_metadata(self, handle_metadata: Optional[Message]):
519
- self._stdout: _StreamReader[str] = StreamReader[str](
519
+ self._stdout = StreamReader(
520
520
  api_pb2.FILE_DESCRIPTOR_STDOUT, self.object_id, "sandbox", self._client, by_line=True
521
521
  )
522
- self._stderr: _StreamReader[str] = StreamReader[str](
522
+ self._stderr = StreamReader(
523
523
  api_pb2.FILE_DESCRIPTOR_STDERR, self.object_id, "sandbox", self._client, by_line=True
524
524
  )
525
525
  self._stdin = StreamWriter(self.object_id, "sandbox", self._client)
@@ -1265,7 +1265,7 @@ async def _put_missing_blocks(
1265
1265
  file_progress.pending_blocks.add(missing_block.block_index)
1266
1266
  task_progress_cb = functools.partial(progress_cb, task_id=file_progress.task_id)
1267
1267
 
1268
- @retry(n_attempts=5, base_delay=0.5, timeout=None)
1268
+ @retry(n_attempts=11, base_delay=0.5, timeout=None)
1269
1269
  async def put_missing_block_attempt(payload: BytesIOSegmentPayload) -> bytes:
1270
1270
  with payload.reset_on_error(subtract_progress=True):
1271
1271
  async with ClientSessionRegistry.get_session().put(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev22
3
+ Version: 1.2.2.dev2
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.1.dev22"
4
+ __version__ = "1.2.2.dev2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes