modAL 1.2.7.dev7__tar.gz → 1.2.7.dev9__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 (194) hide show
  1. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/PKG-INFO +1 -1
  2. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/blob_utils.py +5 -4
  3. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/client.pyi +2 -2
  4. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/file_io.py +22 -10
  5. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/io_streams.py +10 -7
  6. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/PKG-INFO +1 -1
  7. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_version/__init__.py +1 -1
  8. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/LICENSE +0 -0
  9. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/README.md +0 -0
  10. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/__init__.py +0 -0
  11. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/__main__.py +0 -0
  12. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_billing.py +0 -0
  13. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_clustered_functions.py +0 -0
  14. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_clustered_functions.pyi +0 -0
  15. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_container_entrypoint.py +0 -0
  16. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_functions.py +0 -0
  17. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_grpc_client.py +0 -0
  18. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_ipython.py +0 -0
  19. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_load_context.py +0 -0
  20. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_location.py +0 -0
  21. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_object.py +0 -0
  22. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_output.py +0 -0
  23. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_partial_function.py +0 -0
  24. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_pty.py +0 -0
  25. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_resolver.py +0 -0
  26. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_resources.py +0 -0
  27. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/__init__.py +0 -0
  28. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/asgi.py +0 -0
  29. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/container_io_manager.py +0 -0
  30. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/container_io_manager.pyi +0 -0
  31. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/execution_context.py +0 -0
  32. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/execution_context.pyi +0 -0
  33. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  34. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/telemetry.py +0 -0
  35. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/user_code_event_loop.py +0 -0
  36. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_runtime/user_code_imports.py +0 -0
  37. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_serialization.py +0 -0
  38. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_traceback.py +0 -0
  39. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_tunnel.py +0 -0
  40. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_tunnel.pyi +0 -0
  41. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_type_manager.py +0 -0
  42. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/__init__.py +0 -0
  43. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/app_utils.py +0 -0
  44. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/async_utils.py +0 -0
  45. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/auth_token_manager.py +0 -0
  46. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/bytes_io_segment_payload.py +0 -0
  47. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/deprecation.py +0 -0
  48. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/docker_utils.py +0 -0
  49. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/function_utils.py +0 -0
  50. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/git_utils.py +0 -0
  51. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/grpc_testing.py +0 -0
  52. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/grpc_utils.py +0 -0
  53. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/hash_utils.py +0 -0
  54. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/http_utils.py +0 -0
  55. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/jwt_utils.py +0 -0
  56. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/logger.py +0 -0
  57. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/mount_utils.py +0 -0
  58. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/name_utils.py +0 -0
  59. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/package_utils.py +0 -0
  60. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/pattern_utils.py +0 -0
  61. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/rand_pb_testing.py +0 -0
  62. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/shell_utils.py +0 -0
  63. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/task_command_router_client.py +0 -0
  64. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_utils/time_utils.py +0 -0
  65. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_vendor/__init__.py +0 -0
  66. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  67. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_vendor/cloudpickle.py +0 -0
  68. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_vendor/tblib.py +0 -0
  69. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/_watcher.py +0 -0
  70. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/app.py +0 -0
  71. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/app.pyi +0 -0
  72. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/billing.py +0 -0
  73. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/2023.12.312.txt +0 -0
  74. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/2023.12.txt +0 -0
  75. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/2024.04.txt +0 -0
  76. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/2024.10.txt +0 -0
  77. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/2025.06.txt +0 -0
  78. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/PREVIEW.txt +0 -0
  79. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/README.md +0 -0
  80. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/builder/base-images.json +0 -0
  81. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/call_graph.py +0 -0
  82. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/__init__.py +0 -0
  83. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/_download.py +0 -0
  84. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/_traceback.py +0 -0
  85. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/app.py +0 -0
  86. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/cluster.py +0 -0
  87. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/config.py +0 -0
  88. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/container.py +0 -0
  89. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/dict.py +0 -0
  90. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/entry_point.py +0 -0
  91. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/environment.py +0 -0
  92. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/import_refs.py +0 -0
  93. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/launch.py +0 -0
  94. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/network_file_system.py +0 -0
  95. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/profile.py +0 -0
  96. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/programs/__init__.py +0 -0
  97. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/programs/run_jupyter.py +0 -0
  98. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/programs/vscode.py +0 -0
  99. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/queues.py +0 -0
  100. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/run.py +0 -0
  101. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/secret.py +0 -0
  102. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/shell.py +0 -0
  103. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/token.py +0 -0
  104. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/utils.py +0 -0
  105. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cli/volume.py +0 -0
  106. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/client.py +0 -0
  107. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cloud_bucket_mount.py +0 -0
  108. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cloud_bucket_mount.pyi +0 -0
  109. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cls.py +0 -0
  110. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/cls.pyi +0 -0
  111. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/config.py +0 -0
  112. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/container_process.py +0 -0
  113. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/container_process.pyi +0 -0
  114. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/dict.py +0 -0
  115. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/dict.pyi +0 -0
  116. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/environments.py +0 -0
  117. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/environments.pyi +0 -0
  118. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/exception.py +0 -0
  119. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/experimental/__init__.py +0 -0
  120. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/experimental/flash.py +0 -0
  121. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/experimental/flash.pyi +0 -0
  122. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/experimental/ipython.py +0 -0
  123. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/file_io.pyi +0 -0
  124. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/file_pattern_matcher.py +0 -0
  125. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/functions.py +0 -0
  126. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/functions.pyi +0 -0
  127. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/gpu.py +0 -0
  128. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/image.py +0 -0
  129. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/image.pyi +0 -0
  130. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/io_streams.pyi +0 -0
  131. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/mount.py +0 -0
  132. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/mount.pyi +0 -0
  133. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/network_file_system.py +0 -0
  134. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/network_file_system.pyi +0 -0
  135. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/object.py +0 -0
  136. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/object.pyi +0 -0
  137. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/output.py +0 -0
  138. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/parallel_map.py +0 -0
  139. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/parallel_map.pyi +0 -0
  140. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/partial_function.py +0 -0
  141. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/partial_function.pyi +0 -0
  142. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/proxy.py +0 -0
  143. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/proxy.pyi +0 -0
  144. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/py.typed +0 -0
  145. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/queue.py +0 -0
  146. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/queue.pyi +0 -0
  147. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/retries.py +0 -0
  148. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/runner.py +0 -0
  149. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/runner.pyi +0 -0
  150. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/running_app.py +0 -0
  151. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/sandbox.py +0 -0
  152. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/sandbox.pyi +0 -0
  153. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/schedule.py +0 -0
  154. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/scheduler_placement.py +0 -0
  155. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/secret.py +0 -0
  156. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/secret.pyi +0 -0
  157. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/serving.py +0 -0
  158. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/serving.pyi +0 -0
  159. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/snapshot.py +0 -0
  160. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/snapshot.pyi +0 -0
  161. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/stream_type.py +0 -0
  162. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/token_flow.py +0 -0
  163. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/token_flow.pyi +0 -0
  164. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/volume.py +0 -0
  165. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal/volume.pyi +0 -0
  166. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/SOURCES.txt +0 -0
  167. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/dependency_links.txt +0 -0
  168. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/entry_points.txt +0 -0
  169. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/requires.txt +0 -0
  170. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal.egg-info/top_level.txt +0 -0
  171. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/__init__.py +0 -0
  172. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/gen_cli_docs.py +0 -0
  173. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/gen_reference_docs.py +0 -0
  174. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/mdmd/__init__.py +0 -0
  175. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/mdmd/mdmd.py +0 -0
  176. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_docs/mdmd/signatures.py +0 -0
  177. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/__init__.py +0 -0
  178. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api.proto +0 -0
  179. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api_grpc.py +0 -0
  180. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api_pb2.py +0 -0
  181. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api_pb2.pyi +0 -0
  182. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api_pb2_grpc.py +0 -0
  183. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/api_pb2_grpc.pyi +0 -0
  184. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/modal_api_grpc.py +0 -0
  185. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/py.typed +0 -0
  186. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router.proto +0 -0
  187. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router_grpc.py +0 -0
  188. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2.py +0 -0
  189. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2.pyi +0 -0
  190. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  191. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/modal_version/__main__.py +0 -0
  193. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/pyproject.toml +0 -0
  194. {modal-1.2.7.dev7 → modal-1.2.7.dev9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev7
3
+ Version: 1.2.7.dev9
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -152,12 +152,13 @@ async def perform_multipart_upload(
152
152
  part_etags = await TaskContext.gather(*upload_coros)
153
153
 
154
154
  # The body of the complete_multipart_upload command needs some data in xml format:
155
- completion_body = "<CompleteMultipartUpload>\n"
155
+ completion_parts = ["<CompleteMultipartUpload>"]
156
156
  for part_number, etag in enumerate(part_etags, 1):
157
- completion_body += f"""<Part>\n<PartNumber>{part_number}</PartNumber>\n<ETag>"{etag}"</ETag>\n</Part>\n"""
158
- completion_body += "</CompleteMultipartUpload>"
157
+ completion_parts.append(f"""<Part>\n<PartNumber>{part_number}</PartNumber>\n<ETag>"{etag}"</ETag>\n</Part>""")
158
+ completion_parts.append("</CompleteMultipartUpload>")
159
+ completion_body = "\n".join(completion_parts)
159
160
 
160
- # etag of combined object should be md5 hex of concatendated md5 *bytes* from parts + `-{num_parts}`
161
+ # etag of combined object should be md5 hex of concatenated md5 *bytes* from parts + `-{num_parts}`
161
162
  bin_hash_parts = [bytes.fromhex(etag) for etag in part_etags]
162
163
 
163
164
  expected_multipart_etag = hashlib.md5(b"".join(bin_hash_parts)).hexdigest() + f"-{len(part_etags)}"
@@ -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.7.dev7",
35
+ version: str = "1.2.7.dev9",
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.7.dev7",
166
+ version: str = "1.2.7.dev9",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -190,7 +190,7 @@ class _FileIO(Generic[T]):
190
190
 
191
191
  async def _wait(self, exec_id: str) -> bytes:
192
192
  # The logic here is similar to how output is read from `exec`
193
- output = b""
193
+ output_buffer = io.BytesIO()
194
194
  completed = False
195
195
  retries_remaining = 10
196
196
  while not completed:
@@ -201,7 +201,7 @@ class _FileIO(Generic[T]):
201
201
  break
202
202
  if isinstance(data, Exception):
203
203
  raise data
204
- output += data
204
+ output_buffer.write(data)
205
205
  except (GRPCError, StreamTerminatedError) as exc:
206
206
  if retries_remaining > 0:
207
207
  retries_remaining -= 1
@@ -212,7 +212,7 @@ class _FileIO(Generic[T]):
212
212
  elif isinstance(exc, StreamTerminatedError):
213
213
  continue
214
214
  raise
215
- return output
215
+ return output_buffer.getvalue()
216
216
 
217
217
  def _validate_type(self, data: Union[bytes, str]) -> None:
218
218
  if self._binary and isinstance(data, str):
@@ -421,10 +421,22 @@ class _FileIO(Generic[T]):
421
421
  task_id=self._task_id,
422
422
  ),
423
423
  )
424
+
425
+ def end_of_event(item: bytes, buffer: io.BytesIO, boundary_token: bytes) -> bool:
426
+ if not item.endswith(b"\n"):
427
+ return False
428
+ boundary_token_size = len(boundary_token)
429
+ if buffer.tell() < boundary_token_size:
430
+ return False
431
+ buffer.seek(-boundary_token_size, io.SEEK_END)
432
+ if buffer.read(boundary_token_size) == boundary_token:
433
+ return True
434
+ return False
435
+
424
436
  async with TaskContext() as tc:
425
437
  tc.create_task(self._consume_watch_output(resp.exec_id))
426
438
 
427
- buffer = b""
439
+ item_buffer = io.BytesIO()
428
440
  while True:
429
441
  if len(self._watch_output_buffer) > 0:
430
442
  item = self._watch_output_buffer.pop(0)
@@ -432,12 +444,12 @@ class _FileIO(Generic[T]):
432
444
  break
433
445
  if isinstance(item, Exception):
434
446
  raise item
435
- buffer += item
436
- # a single event may be split across multiple messages
437
- # the end of an event is marked by two newlines
438
- if buffer.endswith(b"\n\n"):
447
+ item_buffer.write(item)
448
+ assert isinstance(item, bytes)
449
+ # Single events may span multiple messages so we need to check for a special event boundary token
450
+ if end_of_event(item, item_buffer, boundary_token=b"\n\n"):
439
451
  try:
440
- event_json = json.loads(buffer.strip().decode())
452
+ event_json = json.loads(item_buffer.getvalue().strip().decode())
441
453
  event = FileWatchEvent(
442
454
  type=FileWatchEventType(event_json["event_type"]),
443
455
  paths=event_json["paths"],
@@ -447,7 +459,7 @@ class _FileIO(Generic[T]):
447
459
  except (json.JSONDecodeError, KeyError, ValueError):
448
460
  # skip invalid events
449
461
  pass
450
- buffer = b""
462
+ item_buffer = io.BytesIO()
451
463
  else:
452
464
  await asyncio.sleep(0.1)
453
465
 
@@ -23,7 +23,7 @@ from grpclib.exceptions import GRPCError, StreamTerminatedError
23
23
  from modal.exception import ClientClosed, ExecTimeoutError, InvalidError
24
24
  from modal_proto import api_pb2
25
25
 
26
- from ._utils.async_utils import aclosing, synchronize_api
26
+ from ._utils.async_utils import aclosing, synchronize_api, synchronizer
27
27
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
28
28
  from ._utils.task_command_router_client import TaskCommandRouterClient
29
29
  from .client import _Client
@@ -153,17 +153,17 @@ class _StreamReaderThroughServer(Generic[T]):
153
153
  """Fetch the entire contents of the stream until EOF."""
154
154
  logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read starting")
155
155
  if self._text:
156
- data_str = ""
156
+ buffer = io.StringIO()
157
157
  async for message in _decode_bytes_stream_to_str(self._get_logs()):
158
- data_str += message
158
+ buffer.write(message)
159
159
  logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
160
- return cast(T, data_str)
160
+ return cast(T, buffer.getvalue())
161
161
  else:
162
- data_bytes = b""
162
+ buffer = io.BytesIO()
163
163
  async for message in self._get_logs():
164
- data_bytes += message
164
+ buffer.write(message)
165
165
  logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
166
- return cast(T, data_bytes)
166
+ return cast(T, buffer.getvalue())
167
167
 
168
168
  async def _consume_container_process_stream(self):
169
169
  """Consume the container process stream and store messages in the buffer."""
@@ -566,6 +566,9 @@ class _StreamReader(Generic[T]):
566
566
  task_id: Optional[str] = None,
567
567
  ) -> None:
568
568
  """mdmd:hidden"""
569
+ # we can remove this once we ensure no constructors use async code
570
+ assert asyncio.get_running_loop() == synchronizer._get_loop(start=False)
571
+
569
572
  if by_line and not text:
570
573
  raise ValueError("line-buffering is only supported when text=True")
571
574
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev7
3
+ Version: 1.2.7.dev9
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.7.dev7"
4
+ __version__ = "1.2.7.dev9"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes