modAL 1.3.6.dev12__tar.gz → 1.3.6.dev14__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 (211) hide show
  1. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/PKG-INFO +1 -1
  2. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_functions.py +16 -88
  3. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_partial_function.py +7 -63
  4. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_resources.py +3 -4
  5. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/deprecation.py +23 -44
  6. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/function_utils.py +17 -0
  7. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/mount_utils.py +20 -0
  8. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/app.py +11 -85
  9. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/app.pyi +6 -36
  10. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/app.py +4 -1
  11. modal-1.3.6.dev14/modal/cli/container.py +264 -0
  12. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/entry_point.py +1 -1
  13. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/import_refs.py +2 -5
  14. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/client.pyi +2 -2
  15. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cls.py +3 -48
  16. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cls.pyi +2 -71
  17. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/dict.py +3 -21
  18. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/dict.pyi +5 -9
  19. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/__init__.py +9 -87
  20. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/functions.py +1 -2
  21. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/functions.pyi +7 -72
  22. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/image.py +72 -46
  23. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/image.pyi +66 -31
  24. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/mount.py +1 -0
  25. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/network_file_system.py +1 -5
  26. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/network_file_system.pyi +0 -5
  27. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/partial_function.pyi +2 -33
  28. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/queue.py +1 -3
  29. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/queue.pyi +0 -2
  30. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/runner.py +26 -18
  31. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/runner.pyi +8 -8
  32. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox.py +7 -24
  33. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox.pyi +8 -47
  34. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/secret.py +3 -12
  35. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/secret.pyi +0 -13
  36. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/serving.py +1 -1
  37. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/volume.py +2 -5
  38. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/volume.pyi +0 -5
  39. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/PKG-INFO +1 -1
  40. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/SOURCES.txt +0 -1
  41. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs.py +0 -1
  42. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_grpc.py +16 -0
  43. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2.py +494 -453
  44. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2.pyi +101 -12
  45. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.py +33 -0
  46. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.pyi +10 -0
  47. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/modal_api_grpc.py +1 -0
  48. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_version/__init__.py +1 -1
  49. modal-1.3.6.dev12/modal/cli/container.py +0 -117
  50. modal-1.3.6.dev12/modal/gpu.py +0 -225
  51. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/LICENSE +0 -0
  52. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/README.md +0 -0
  53. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/__init__.py +0 -0
  54. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/__main__.py +0 -0
  55. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_billing.py +0 -0
  56. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_clustered_functions.py +0 -0
  57. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_clustered_functions.pyi +0 -0
  58. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_container_entrypoint.py +0 -0
  59. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_grpc_client.py +0 -0
  60. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_ipython.py +0 -0
  61. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_load_context.py +0 -0
  62. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_location.py +0 -0
  63. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_logs.py +0 -0
  64. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_object.py +0 -0
  65. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/__init__.py +0 -0
  66. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/manager.py +0 -0
  67. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/pty.py +0 -0
  68. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/rich.py +0 -0
  69. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/status.py +0 -0
  70. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_resolver.py +0 -0
  71. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/__init__.py +0 -0
  72. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/asgi.py +0 -0
  73. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
  74. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  75. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/execution_context.py +0 -0
  76. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
  77. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  78. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/telemetry.py +0 -0
  79. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
  80. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
  81. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_serialization.py +0 -0
  82. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_server.py +0 -0
  83. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_traceback.py +0 -0
  84. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_tunnel.py +0 -0
  85. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_tunnel.pyi +0 -0
  86. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_type_manager.py +0 -0
  87. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/__init__.py +0 -0
  88. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/app_utils.py +0 -0
  89. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/async_utils.py +0 -0
  90. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/auth_token_manager.py +0 -0
  91. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/blob_utils.py +0 -0
  92. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/browser_utils.py +0 -0
  93. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  94. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/docker_utils.py +0 -0
  95. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/git_utils.py +0 -0
  96. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/grpc_testing.py +0 -0
  97. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/grpc_utils.py +0 -0
  98. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/hash_utils.py +0 -0
  99. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/http_utils.py +0 -0
  100. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/jwt_utils.py +0 -0
  101. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/logger.py +0 -0
  102. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/name_utils.py +0 -0
  103. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/package_utils.py +0 -0
  104. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/pattern_utils.py +0 -0
  105. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  106. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
  107. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/shell_utils.py +0 -0
  108. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/task_command_router_client.py +0 -0
  109. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/time_utils.py +0 -0
  110. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/__init__.py +0 -0
  111. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  112. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
  113. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/tblib.py +0 -0
  114. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/version.py +0 -0
  115. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_watcher.py +0 -0
  116. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/billing.py +0 -0
  117. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2023.12.312.txt +0 -0
  118. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2023.12.txt +0 -0
  119. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2024.04.txt +0 -0
  120. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2024.10.txt +0 -0
  121. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2025.06.txt +0 -0
  122. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/PREVIEW.txt +0 -0
  123. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/README.md +0 -0
  124. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/base-images.json +0 -0
  125. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/call_graph.py +0 -0
  126. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/__init__.py +0 -0
  127. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/_download.py +0 -0
  128. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/_traceback.py +0 -0
  129. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/billing.py +0 -0
  130. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/changelog.py +0 -0
  131. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/cluster.py +0 -0
  132. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/config.py +0 -0
  133. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/dashboard.py +0 -0
  134. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/dict.py +0 -0
  135. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/environment.py +0 -0
  136. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/launch.py +0 -0
  137. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/network_file_system.py +0 -0
  138. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/profile.py +0 -0
  139. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/__init__.py +0 -0
  140. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  141. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/vscode.py +0 -0
  142. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/queues.py +0 -0
  143. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/run.py +0 -0
  144. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/secret.py +0 -0
  145. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/selector.py +0 -0
  146. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/shell.py +0 -0
  147. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/token.py +0 -0
  148. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/utils.py +0 -0
  149. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/volume.py +0 -0
  150. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/client.py +0 -0
  151. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.py +0 -0
  152. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  153. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/config.py +0 -0
  154. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/container_process.py +0 -0
  155. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/container_process.pyi +0 -0
  156. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/environments.py +0 -0
  157. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/environments.pyi +0 -0
  158. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/exception.py +0 -0
  159. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/flash.py +0 -0
  160. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/flash.pyi +0 -0
  161. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/ipython.py +0 -0
  162. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_io.py +0 -0
  163. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_io.pyi +0 -0
  164. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_pattern_matcher.py +0 -0
  165. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/io_streams.py +0 -0
  166. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/io_streams.pyi +0 -0
  167. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/mount.pyi +0 -0
  168. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/object.py +0 -0
  169. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/object.pyi +0 -0
  170. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/output.py +0 -0
  171. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/parallel_map.py +0 -0
  172. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/parallel_map.pyi +0 -0
  173. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/partial_function.py +0 -0
  174. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/proxy.py +0 -0
  175. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/proxy.pyi +0 -0
  176. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/py.typed +0 -0
  177. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/retries.py +0 -0
  178. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/running_app.py +0 -0
  179. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox_fs.py +0 -0
  180. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox_fs.pyi +0 -0
  181. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/schedule.py +0 -0
  182. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/scheduler_placement.py +0 -0
  183. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/server.py +0 -0
  184. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/server.pyi +0 -0
  185. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/serving.pyi +0 -0
  186. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/snapshot.py +0 -0
  187. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/snapshot.pyi +0 -0
  188. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/stream_type.py +0 -0
  189. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/token_flow.py +0 -0
  190. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/token_flow.pyi +0 -0
  191. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
  192. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/entry_points.txt +0 -0
  193. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/requires.txt +0 -0
  194. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/top_level.txt +0 -0
  195. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/__init__.py +0 -0
  196. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
  197. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
  198. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
  199. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
  200. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  201. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
  202. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/__init__.py +0 -0
  203. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/py.typed +0 -0
  204. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  205. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  206. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  207. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  208. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  209. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_version/__main__.py +0 -0
  210. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/pyproject.toml +0 -0
  211. {modal-1.3.6.dev12 → modal-1.3.6.dev14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.6.dev12
3
+ Version: 1.3.6.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -2,7 +2,6 @@
2
2
  import asyncio
3
3
  import dataclasses
4
4
  import inspect
5
- import textwrap
6
5
  import time
7
6
  import typing
8
7
  import warnings
@@ -44,7 +43,6 @@ from ._utils.async_utils import (
44
43
  warn_if_generator_is_not_consumed,
45
44
  )
46
45
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
47
- from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
48
46
  from ._utils.function_utils import (
49
47
  ATTEMPT_TIMEOUT_GRACE_PERIOD,
50
48
  OUTPUTS_TIMEOUT,
@@ -54,9 +52,10 @@ from ._utils.function_utils import (
54
52
  _stream_function_call_data,
55
53
  get_function_type,
56
54
  is_async,
55
+ parse_gpu_config,
57
56
  )
58
57
  from ._utils.grpc_utils import Retry, RetryWarningMessage
59
- from ._utils.mount_utils import validate_network_file_systems, validate_volumes
58
+ from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
60
59
  from .call_graph import InputInfo, _reconstruct_call_graph
61
60
  from .client import _Client
62
61
  from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
@@ -67,7 +66,6 @@ from .exception import (
67
66
  NotFoundError,
68
67
  OutputExpiredError,
69
68
  )
70
- from .gpu import GPU_T, parse_gpu_config
71
69
  from .image import _Image
72
70
  from .mount import _get_client_mount, _Mount
73
71
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
@@ -587,7 +585,7 @@ class _FunctionSpec:
587
585
  network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
588
586
  volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
589
587
  # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
590
- gpus: Union[GPU_T, list[GPU_T]]
588
+ gpus: Optional[str | list[str]]
591
589
  cloud: Optional[str]
592
590
  cpu: Optional[Union[float, tuple[float, float]]]
593
591
  memory: Optional[Union[int, tuple[int, int]]]
@@ -666,7 +664,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
666
664
  secrets: Optional[Collection[_Secret]] = None,
667
665
  schedule: Optional[Schedule] = None,
668
666
  is_generator: bool = False,
669
- gpu: Union[GPU_T, list[GPU_T]] = None,
667
+ gpu: Optional[str | list[str]] = None,
670
668
  # TODO: maybe break this out into a separate decorator for notebooks.
671
669
  network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
672
670
  volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
@@ -703,7 +701,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
703
701
  include_source: bool = True,
704
702
  experimental_options: Optional[dict[str, str]] = None,
705
703
  _experimental_proxy_ip: Optional[str] = None,
706
- _experimental_custom_scaling_factor: Optional[float] = None,
707
704
  restrict_output: bool = False,
708
705
  http_config: Optional[api_pb2.HTTPConfig] = None,
709
706
  ) -> "_Function":
@@ -799,11 +796,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
799
796
  f"for clustered Functions"
800
797
  )
801
798
 
802
- if _experimental_custom_scaling_factor is not None and (
803
- _experimental_custom_scaling_factor < 0 or _experimental_custom_scaling_factor > 1
804
- ):
805
- raise InvalidError("`_experimental_custom_scaling_factor` must be between 0.0 and 1.0 inclusive.")
806
-
807
799
  if not cloud and not is_builder_function:
808
800
  cloud = config.get("default_cloud")
809
801
 
@@ -966,6 +958,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
966
958
  if image._metadata is not None:
967
959
  mount_client_dependencies = image._metadata.image_builder_version > "2024.10"
968
960
 
961
+ # Validate that the same volume (by object_id) isn't mounted at multiple paths
962
+ # This validation happens here (at load time) because volumes need to be hydrated
963
+ # to have their object_id set.
964
+ validate_volumes_by_object_id(validated_volumes_no_cloud_buckets)
965
+
969
966
  # Relies on dicts being ordered (true as of Python 3.6).
970
967
  volume_mounts = [
971
968
  api_pb2.VolumeMount(
@@ -1045,7 +1042,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1045
1042
  _experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
1046
1043
  _experimental_concurrent_cancellations=True,
1047
1044
  _experimental_proxy_ip=_experimental_proxy_ip,
1048
- _experimental_custom_scaling=_experimental_custom_scaling_factor is not None,
1049
1045
  # --- These are deprecated in favor of autoscaler_settings
1050
1046
  warm_pool_size=min_containers or 0,
1051
1047
  concurrency_limit=max_containers or 0,
@@ -1117,8 +1113,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1117
1113
  function_data.ranked_functions.extend(ranked_functions)
1118
1114
  function_definition = None # function_definition is not used in this case
1119
1115
  else:
1120
- # TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
1121
- # assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
1122
1116
  function_definition.resources.CopyFrom(
1123
1117
  convert_fn_config_to_resources_config(
1124
1118
  cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
@@ -1165,7 +1159,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1165
1159
  obj._webhook_config = webhook_config # only set locally
1166
1160
 
1167
1161
  # Used to check whether we should rebuild a modal.Image which uses `run_function`.
1168
- gpus: list[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
1162
+ gpus: list[str] = gpu if isinstance(gpu, list) else [gpu] if gpu else []
1169
1163
  obj._build_args = dict( # See get_build_def
1170
1164
  secrets=repr(secrets),
1171
1165
  gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
@@ -1235,6 +1229,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1235
1229
  assert parent is not None and parent.is_hydrated
1236
1230
 
1237
1231
  if options:
1232
+ # Validate that the same volume (by object_id) isn't mounted at multiple paths
1233
+ validate_volumes_by_object_id(options.validated_volumes)
1234
+
1238
1235
  volume_mounts = [
1239
1236
  api_pb2.VolumeMount(
1240
1237
  mount_path=path,
@@ -1350,43 +1347,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1350
1347
  # One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
1351
1348
  # and then we could return them here (would need some ad hoc dataclass, which I don't love)
1352
1349
 
1353
- @live_method
1354
- async def keep_warm(self, warm_pool_size: int) -> None:
1355
- """mdmd:hidden
1356
- Set the warm pool size for the Function.
1357
-
1358
- DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
1359
-
1360
- ```python notest
1361
- f = modal.Function.from_name("my-app", "function")
1362
-
1363
- # Old pattern (deprecated)
1364
- f.keep_warm(2)
1365
-
1366
- # New pattern
1367
- f.update_autoscaler(min_containers=2)
1368
- ```
1369
- """
1370
- if self._is_method:
1371
- raise InvalidError(
1372
- textwrap.dedent(
1373
- """
1374
- The `.keep_warm()` method can not be used on Modal class *methods*.
1375
-
1376
- Call `.keep_warm()` on the class *instance* instead. All methods of a class are run by the same
1377
- container pool, and this method applies to the size of that container pool.
1378
- """
1379
- )
1380
- )
1381
-
1382
- deprecation_warning(
1383
- (2025, 5, 5),
1384
- "The .keep_warm() method has been deprecated in favor of the more general "
1385
- ".update_autoscaler(min_containers=...) method.",
1386
- show_source=True,
1387
- )
1388
- await self.update_autoscaler(min_containers=warm_pool_size)
1389
-
1390
1350
  @classmethod
1391
1351
  def _from_name(
1392
1352
  cls,
@@ -1436,7 +1396,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1436
1396
  app_name: str,
1437
1397
  name: str,
1438
1398
  *,
1439
- namespace=None, # mdmd:line-hidden
1440
1399
  environment_name: Optional[str] = None,
1441
1400
  client: Optional[_Client] = None,
1442
1401
  ) -> "_Function":
@@ -1451,17 +1410,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1451
1410
  ```
1452
1411
  """
1453
1412
  if "." in name:
1454
- class_name, method_name = name.split(".", 1)
1455
- deprecation_warning(
1456
- (2025, 2, 11),
1457
- "Looking up class methods using Function.from_name will be deprecated"
1458
- " in a future version of Modal.\nUse modal.Cls.from_name instead, e.g.\n\n"
1459
- f'{class_name} = modal.Cls.from_name("{app_name}", "{class_name}")\n'
1460
- f"instance = {class_name}(...)\n"
1461
- f"instance.{method_name}.remote(...)\n",
1413
+ raise InvalidError(
1414
+ f"Invalid Function name: {name!r}. "
1415
+ "If you are trying to look up a class method, use `modal.Cls.from_name` instead."
1462
1416
  )
1463
1417
 
1464
- warn_if_passing_namespace(namespace, "modal.Function.from_name")
1465
1418
  return cls._from_name(
1466
1419
  app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
1467
1420
  )
@@ -1579,21 +1532,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1579
1532
  + f"or call it locally: {self._function_name}.local()"
1580
1533
  )
1581
1534
 
1582
- # TODO (live_method on properties is not great, since it could be blocking the event loop from async contexts)
1583
- @property
1584
- @live_method
1585
- async def web_url(self) -> Optional[str]:
1586
- """mdmd:hidden
1587
- Deprecated. Use the `Function.get_web_url()` method instead.
1588
-
1589
- URL of a Function running as a web endpoint.
1590
- """
1591
- deprecation_msg = """The Function.web_url property will be removed in a future version of Modal.
1592
- Use the `Function.get_web_url()` method instead.
1593
- """
1594
- deprecation_warning((2025, 5, 6), deprecation_msg, pending=True)
1595
- return self._web_url
1596
-
1597
1535
  @live_method
1598
1536
  async def get_web_url(self) -> Optional[str]:
1599
1537
  """URL of a Function running as a web endpoint."""
@@ -2102,13 +2040,3 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2102
2040
  except Exception as exc:
2103
2041
  # TODO: kill all running function calls
2104
2042
  raise exc
2105
-
2106
-
2107
- async def _gather(*function_calls: _FunctionCall[T]) -> typing.Sequence[T]:
2108
- """mdmd:hidden
2109
- Deprecated: Please use `modal.FunctionCall.gather()` instead."""
2110
- deprecation_warning(
2111
- (2025, 2, 24),
2112
- "`modal.functions.gather()` is deprecated; please use `modal.FunctionCall.gather()` instead.",
2113
- )
2114
- return await _FunctionCall.gather(*function_calls)
@@ -17,7 +17,7 @@ from modal_proto import api_pb2
17
17
 
18
18
  from ._functions import _Function
19
19
  from ._utils.async_utils import synchronizer
20
- from ._utils.deprecation import deprecation_warning
20
+ from ._utils.deprecation import deprecation_error
21
21
  from ._utils.function_utils import callable_has_non_self_params
22
22
  from .config import config
23
23
  from .exception import InvalidError
@@ -310,7 +310,7 @@ def _method(
310
310
  def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
311
311
  flags = _PartialFunctionFlags.CALLABLE_INTERFACE
312
312
 
313
- nonlocal is_generator # TODO(michael): we are likely to deprecate the explicit is_generator param
313
+ nonlocal is_generator
314
314
  if is_generator is None:
315
315
  callable = obj.raw_f if isinstance(obj, _PartialFunction) else obj
316
316
  is_generator = inspect.isgeneratorfunction(callable) or inspect.isasyncgenfunction(callable)
@@ -403,72 +403,16 @@ def _fastapi_endpoint(
403
403
 
404
404
  def _web_endpoint(
405
405
  _warn_parentheses_missing=None, # mdmd:line-hidden
406
- *,
407
- method: str = "GET", # REST method for the created endpoint.
408
- label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
409
- docs: bool = False, # Whether to enable interactive documentation for this endpoint at /docs.
410
- custom_domains: Optional[
411
- Iterable[str]
412
- ] = None, # Create an endpoint using a custom domain fully-qualified domain name (FQDN).
413
- requires_proxy_auth: bool = False, # Require Modal-Key and Modal-Secret HTTP Headers on requests.
414
- ) -> Callable[
415
- [Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]]],
416
- _PartialFunction[P, ReturnType, ReturnType],
417
- ]:
418
- """Register a basic web endpoint with this application.
419
-
406
+ *args,
407
+ **kwargs,
408
+ ) -> None:
409
+ """mdmd:hidden
420
410
  DEPRECATED: This decorator has been renamed to `@modal.fastapi_endpoint`.
421
-
422
- This is the simple way to create a web endpoint on Modal. The function
423
- behaves as a [FastAPI](https://fastapi.tiangolo.com/) handler and should
424
- return a response object to the caller.
425
-
426
- Endpoints created with `@modal.web_endpoint` are meant to be simple, single
427
- request handlers and automatically have
428
- [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) enabled.
429
- For more flexibility, use `@modal.asgi_app`.
430
-
431
- To learn how to use Modal with popular web frameworks, see the
432
- [guide on web endpoints](https://modal.com/docs/guide/webhooks).
433
411
  """
434
- if isinstance(_warn_parentheses_missing, str):
435
- # Probably passing the method string as a positional argument.
436
- raise InvalidError('Positional arguments are not allowed. Suggestion: `@modal.web_endpoint(method="GET")`.')
437
- elif _warn_parentheses_missing is not None:
438
- raise InvalidError(
439
- "Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.web_endpoint()`."
440
- )
441
-
442
- deprecation_warning(
412
+ deprecation_error(
443
413
  (2025, 3, 5), "The `@modal.web_endpoint` decorator has been renamed to `@modal.fastapi_endpoint`."
444
414
  )
445
415
 
446
- webhook_config = api_pb2.WebhookConfig(
447
- type=api_pb2.WEBHOOK_TYPE_FUNCTION,
448
- method=method,
449
- web_endpoint_docs=docs,
450
- requested_suffix=label or "",
451
- ephemeral_suffix=config.get("dev_suffix"),
452
- async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
453
- custom_domains=_parse_custom_domains(custom_domains),
454
- requires_proxy_auth=requires_proxy_auth,
455
- )
456
-
457
- flags = _PartialFunctionFlags.WEB_INTERFACE
458
- params = _PartialFunctionParams(webhook_config=webhook_config)
459
-
460
- def wrapper(
461
- obj: Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]],
462
- ) -> _PartialFunction[P, ReturnType, ReturnType]:
463
- if isinstance(obj, _PartialFunction):
464
- pf = obj.stack(flags, params)
465
- else:
466
- pf = _PartialFunction(obj, flags, params)
467
- pf.validate_obj_compatibility("web_endpoint")
468
- return pf
469
-
470
- return wrapper
471
-
472
416
 
473
417
  def _asgi_app(
474
418
  _warn_parentheses_missing=None, # mdmd:line-hidden
@@ -3,19 +3,18 @@ from typing import Optional, Union
3
3
 
4
4
  from modal_proto import api_pb2
5
5
 
6
+ from ._utils.function_utils import parse_gpu_config
6
7
  from .exception import InvalidError
7
- from .gpu import GPU_T, parse_gpu_config
8
8
 
9
9
 
10
10
  def convert_fn_config_to_resources_config(
11
11
  *,
12
12
  cpu: Optional[Union[float, tuple[float, float]]],
13
13
  memory: Optional[Union[int, tuple[int, int]]],
14
- gpu: GPU_T,
14
+ gpu: Optional[str],
15
15
  ephemeral_disk: Optional[int],
16
16
  rdma: Optional[bool] = None,
17
17
  ) -> api_pb2.Resources:
18
- gpu_config = parse_gpu_config(gpu)
19
18
  if cpu and isinstance(cpu, tuple):
20
19
  if not cpu[0]:
21
20
  raise InvalidError("CPU request must be a positive number")
@@ -45,7 +44,7 @@ def convert_fn_config_to_resources_config(
45
44
  return api_pb2.Resources(
46
45
  milli_cpu=milli_cpu,
47
46
  milli_cpu_max=milli_cpu_max,
48
- gpu_config=gpu_config,
47
+ gpu_config=parse_gpu_config(gpu),
49
48
  memory_mb=memory_mb,
50
49
  memory_mb_max=memory_mb_max,
51
50
  ephemeral_disk_mb=ephemeral_disk,
@@ -89,55 +89,34 @@ def renamed_parameter(
89
89
  return decorator
90
90
 
91
91
 
92
- def warn_on_renamed_autoscaler_settings(func: Callable[P, R]) -> Callable[P, R]:
93
- name_map = {
94
- "keep_warm": "min_containers",
95
- "concurrency_limit": "max_containers",
96
- "_experimental_buffer_containers": "buffer_containers",
97
- "container_idle_timeout": "scaledown_window",
98
- }
92
+ def handle_deprecated_parameters(func: Callable[P, R]) -> Callable[P, R]:
93
+ """Meta-decorator for modal.Function decorators that errors when deprecated parameters are passed.
94
+
95
+ We use this for a select parameters that might still be suggested by LLMs.
96
+ """
99
97
 
100
98
  @functools.wraps(func)
101
99
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
102
- mut_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
103
-
104
- substitutions = []
105
- old_params_used = name_map.keys() & mut_kwargs.keys()
106
- for old_param, new_param in name_map.items():
107
- if old_param in old_params_used:
108
- new_param = name_map[old_param]
109
- mut_kwargs[new_param] = mut_kwargs.pop(old_param)
110
- substitutions.append(f"- {old_param} -> {new_param}")
111
-
112
- if substitutions:
113
- substitution_string = "\n".join(substitutions)
114
- message = (
115
- "We have renamed several parameters related to autoscaling."
116
- " Please update your code to use the following new names:"
117
- f"\n\n{substitution_string}"
118
- "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more details."
100
+ _kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
101
+
102
+ # Basic autoscaler parameters
103
+ autoscaler_settings = {
104
+ "keep_warm": "min_containers",
105
+ "concurrency_limit": "max_containers",
106
+ "container_idle_timeout": "scaledown_window",
107
+ }
108
+
109
+ for old_name, new_name in autoscaler_settings.items():
110
+ if old_name in _kwargs:
111
+ deprecation_error((2025, 2, 24), f"The '{old_name}' parameter has been renamed to '{new_name}'.")
112
+
113
+ # Input concurrency
114
+ if "allow_concurrent_inputs" in _kwargs:
115
+ deprecation_error(
116
+ (2025, 4, 9),
117
+ "The 'allow_concurrent_inputs' parameter is deprecated. Use the '@modal.concurrent' decorator instead.",
119
118
  )
120
- deprecation_warning((2025, 2, 24), message, show_source=True)
121
119
 
122
120
  return func(*args, **kwargs)
123
121
 
124
122
  return wrapper
125
-
126
-
127
- def warn_if_passing_namespace(
128
- namespace: Any,
129
- resource_name: str,
130
- ) -> None:
131
- """Issue deprecation warning for namespace parameter if non-None value is passed.
132
-
133
- Args:
134
- namespace: The namespace parameter value (may be None or actual value)
135
- resource_name: Name of the resource type for the warning message
136
- """
137
- if namespace is not None:
138
- deprecation_warning(
139
- (2025, 6, 30),
140
- f"The `namespace` parameter for `{resource_name}` is deprecated and will be"
141
- " removed in a future release. It is no longer needed, so can be removed"
142
- " from your code.",
143
- )
@@ -602,3 +602,20 @@ async def _create_input(
602
602
  ),
603
603
  idx=idx,
604
604
  )
605
+
606
+
607
+ def parse_gpu_config(value: Optional[str]) -> api_pb2.GPUConfig:
608
+ if value is None:
609
+ return api_pb2.GPUConfig()
610
+ elif isinstance(value, str):
611
+ count = 1
612
+ if ":" in value:
613
+ value, count_str = value.split(":", 1)
614
+ try:
615
+ count = int(count_str)
616
+ except ValueError:
617
+ raise InvalidError(f"Invalid GPU count: {count_str}. Value must be an integer.")
618
+ gpu_type = value.upper()
619
+ return api_pb2.GPUConfig(gpu_type=gpu_type, count=count)
620
+ else:
621
+ raise InvalidError(f"Invalid GPU config: {value!r}. Value must be a string or `None`")
@@ -101,3 +101,23 @@ def validate_only_modal_volumes(
101
101
  raise InvalidError(f"{caller_name} only supports volumes that are modal.Volume")
102
102
 
103
103
  return validated_volumes
104
+
105
+
106
+ def validate_volumes_by_object_id(validated_volumes: Sequence[tuple[str, _Volume]]) -> None:
107
+ """Validate that the same volume (by object_id) is not mounted at multiple paths.
108
+
109
+ This validation happens at load time after volumes are hydrated and have object_ids.
110
+ """
111
+ object_id_to_paths: dict[str, list[str]] = {}
112
+ for path, volume in validated_volumes:
113
+ if not volume.is_hydrated:
114
+ # This should never happen since this function is only called at load time
115
+ raise RuntimeError(f"Internal error: Volume at '{path}' is not hydrated when validating mounts")
116
+ object_id_to_paths.setdefault(volume.object_id, []).append(path)
117
+
118
+ for object_id, paths in object_id_to_paths.items():
119
+ if len(paths) > 1:
120
+ conflicting = ", ".join(sorted(paths))
121
+ raise InvalidError(
122
+ f"The same Volume cannot be mounted in multiple locations for the same function: {conflicting}"
123
+ )