modal 1.5.2.dev18__tar.gz → 1.5.2.dev20__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 (231) hide show
  1. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/PKG-INFO +1 -1
  2. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_environments.py +6 -9
  3. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_image.py +36 -7
  4. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/grpc_utils.py +20 -12
  5. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/name_utils.py +8 -2
  6. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/task_command_router_client.py +2 -2
  7. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_workspace.py +9 -3
  8. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/app.py +0 -3
  9. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/app.pyi +0 -2
  10. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/container.py +5 -5
  11. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/environment.py +2 -2
  12. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/image.py +8 -1
  13. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/client.pyi +2 -2
  14. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/environments.pyi +6 -14
  15. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/types.py +2 -5
  16. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/workspace.pyi +2 -2
  17. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/PKG-INFO +1 -1
  18. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/api_grpc.py +16 -0
  19. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/api_pb2.py +601 -594
  20. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/api_pb2.pyi +38 -22
  21. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/api_pb2_grpc.py +34 -1
  22. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/api_pb2_grpc.pyi +13 -3
  23. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/modal_api_grpc.py +1 -0
  24. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_version/__init__.py +1 -1
  25. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/LICENSE +0 -0
  26. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/README.md +0 -0
  27. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/__init__.py +0 -0
  28. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/__main__.py +0 -0
  29. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_billing.py +0 -0
  30. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_clustered_functions.py +0 -0
  31. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_clustered_functions.pyi +0 -0
  32. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_container_entrypoint.py +0 -0
  33. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_function_variants.py +0 -0
  34. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_functions.py +0 -0
  35. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_grpc_client.py +0 -0
  36. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_ipython.py +0 -0
  37. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_load_context.py +0 -0
  38. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_location.py +0 -0
  39. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_logs.py +0 -0
  40. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_object.py +0 -0
  41. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_output/__init__.py +0 -0
  42. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_output/manager.py +0 -0
  43. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_output/pty.py +0 -0
  44. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_output/rich.py +0 -0
  45. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_output/status.py +0 -0
  46. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_partial_function.py +0 -0
  47. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_resolver.py +0 -0
  48. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_resources.py +0 -0
  49. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/__init__.py +0 -0
  50. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/asgi.py +0 -0
  51. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/container_io_manager.py +0 -0
  52. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/container_io_manager.pyi +0 -0
  53. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/execution_context.py +0 -0
  54. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/execution_context.pyi +0 -0
  55. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  56. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/task_lifecycle_manager.py +0 -0
  57. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  58. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/telemetry.py +0 -0
  59. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/user_code_event_loop.py +0 -0
  60. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_runtime/user_code_imports.py +0 -0
  61. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_serialization.py +0 -0
  62. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_server.py +0 -0
  63. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_traceback.py +0 -0
  64. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_tunnel.py +0 -0
  65. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_tunnel.pyi +0 -0
  66. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_type_manager.py +0 -0
  67. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/__init__.py +0 -0
  68. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/app_utils.py +0 -0
  69. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/async_utils.py +0 -0
  70. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/auth_token_manager.py +0 -0
  71. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/blob_utils.py +0 -0
  72. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/browser_utils.py +0 -0
  73. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/bytes_io_segment_payload.py +0 -0
  74. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/curl_utils.py +0 -0
  75. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/deprecation.py +0 -0
  76. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/docker_utils.py +0 -0
  77. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/function_utils.py +0 -0
  78. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/git_utils.py +0 -0
  79. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/grpc_testing.py +0 -0
  80. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/hash_utils.py +0 -0
  81. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/http_utils.py +0 -0
  82. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/jwt_utils.py +0 -0
  83. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/logger.py +0 -0
  84. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/mount_utils.py +0 -0
  85. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/package_utils.py +0 -0
  86. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/pattern_utils.py +0 -0
  87. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/proxy_support.py +0 -0
  88. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/rand_pb_testing.py +0 -0
  89. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/sandbox_fs_utils.py +0 -0
  90. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/shell_utils.py +0 -0
  91. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_utils/time_utils.py +0 -0
  92. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_vendor/__init__.py +0 -0
  93. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  94. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_vendor/cloudpickle.py +0 -0
  95. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_vendor/tblib.py +0 -0
  96. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_vendor/version.py +0 -0
  97. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/_watcher.py +0 -0
  98. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/billing.py +0 -0
  99. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/billing.pyi +0 -0
  100. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/2023.12.312.txt +0 -0
  101. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/2023.12.txt +0 -0
  102. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/2024.04.txt +0 -0
  103. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/2024.10.txt +0 -0
  104. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/2025.06.txt +0 -0
  105. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/PREVIEW.txt +0 -0
  106. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/README.md +0 -0
  107. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/builder/base-images.json +0 -0
  108. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/call_graph.py +0 -0
  109. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/__init__.py +0 -0
  110. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/_download.py +0 -0
  111. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/_help.py +0 -0
  112. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/_traceback.py +0 -0
  113. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/app.py +0 -0
  114. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/billing.py +0 -0
  115. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/changelog.py +0 -0
  117. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/cluster.py +0 -0
  118. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/config.py +0 -0
  119. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/curl.py +0 -0
  120. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/dashboard.py +0 -0
  121. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/dict.py +0 -0
  122. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/endpoint.py +0 -0
  123. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/entry_point.py +0 -0
  124. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/import_refs.py +0 -0
  125. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/launch.py +0 -0
  126. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/logo.py +0 -0
  127. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/network_file_system.py +0 -0
  128. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/profile.py +0 -0
  129. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/programs/__init__.py +0 -0
  130. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/programs/run_jupyter.py +0 -0
  131. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/programs/vscode.py +0 -0
  132. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/queues.py +0 -0
  133. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/run.py +0 -0
  134. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/secret.py +0 -0
  135. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/selector.py +0 -0
  136. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/shell.py +0 -0
  137. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/skills.py +0 -0
  138. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/token.py +0 -0
  139. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/utils.py +0 -0
  140. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/volume.py +0 -0
  141. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cli/workspace.py +0 -0
  142. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/client.py +0 -0
  143. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cloud_bucket_mount.py +0 -0
  144. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cloud_bucket_mount.pyi +0 -0
  145. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cls.py +0 -0
  146. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/cls.pyi +0 -0
  147. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/config.py +0 -0
  148. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/container_process.py +0 -0
  149. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/container_process.pyi +0 -0
  150. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/dict.py +0 -0
  151. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/dict.pyi +0 -0
  152. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/environments.py +0 -0
  153. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/exception.py +0 -0
  154. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/experimental/__init__.py +0 -0
  155. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/experimental/flash.py +0 -0
  156. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/experimental/flash.pyi +0 -0
  157. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/experimental/ipython.py +0 -0
  158. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/file_io.py +0 -0
  159. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/file_io.pyi +0 -0
  160. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/file_pattern_matcher.py +0 -0
  161. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/functions.py +0 -0
  162. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/functions.pyi +0 -0
  163. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/image.py +0 -0
  164. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/image.pyi +0 -0
  165. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/io_streams.py +0 -0
  166. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/io_streams.pyi +0 -0
  167. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/mount.py +0 -0
  168. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/mount.pyi +0 -0
  169. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/network_file_system.py +0 -0
  170. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/network_file_system.pyi +0 -0
  171. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/object.py +0 -0
  172. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/object.pyi +0 -0
  173. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/output.py +0 -0
  174. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/parallel_map.py +0 -0
  175. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/parallel_map.pyi +0 -0
  176. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/partial_function.py +0 -0
  177. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/partial_function.pyi +0 -0
  178. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/proxy.py +0 -0
  179. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/proxy.pyi +0 -0
  180. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/py.typed +0 -0
  181. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/queue.py +0 -0
  182. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/queue.pyi +0 -0
  183. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/retries.py +0 -0
  184. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/runner.py +0 -0
  185. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/runner.pyi +0 -0
  186. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/running_app.py +0 -0
  187. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/sandbox.py +0 -0
  188. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/sandbox.pyi +0 -0
  189. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/sandbox_fs.py +0 -0
  190. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/sandbox_fs.pyi +0 -0
  191. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/schedule.py +0 -0
  192. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/scheduler_placement.py +0 -0
  193. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/secret.py +0 -0
  194. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/secret.pyi +0 -0
  195. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/server.py +0 -0
  196. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/server.pyi +0 -0
  197. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/serving.py +0 -0
  198. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/serving.pyi +0 -0
  199. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/skills/modal/SKILL.md +0 -0
  200. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/snapshot.py +0 -0
  201. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/snapshot.pyi +0 -0
  202. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/stream_type.py +0 -0
  203. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/token_flow.py +0 -0
  204. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/token_flow.pyi +0 -0
  205. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/volume.py +0 -0
  206. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/volume.pyi +0 -0
  207. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal/workspace.py +0 -0
  208. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/SOURCES.txt +0 -0
  209. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/dependency_links.txt +0 -0
  210. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/entry_points.txt +0 -0
  211. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/requires.txt +0 -0
  212. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal.egg-info/top_level.txt +0 -0
  213. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/__init__.py +0 -0
  214. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/gen_cli_docs.py +0 -0
  215. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/gen_cli_docs_main.py +0 -0
  216. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/gen_reference_docs.py +0 -0
  217. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/gen_reference_docs_main.py +0 -0
  218. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/mdmd/__init__.py +0 -0
  219. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/mdmd/mdmd.py +0 -0
  220. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/mdmd/signatures.py +0 -0
  221. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_docs/mdmd/types.py +0 -0
  222. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/__init__.py +0 -0
  223. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/py.typed +0 -0
  224. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/task_command_router_grpc.py +0 -0
  225. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/task_command_router_pb2.py +0 -0
  226. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/task_command_router_pb2.pyi +0 -0
  227. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  228. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  229. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/modal_version/__main__.py +0 -0
  230. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/pyproject.toml +0 -0
  231. {modal-1.5.2.dev18 → modal-1.5.2.dev20}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev18
3
+ Version: 1.5.2.dev20
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -103,9 +103,6 @@ class _EnvironmentManager:
103
103
  await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
104
104
 
105
105
 
106
- MemberRole = Literal["viewer", "contributor"]
107
-
108
-
109
106
  def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
110
107
  match role:
111
108
  case "viewer":
@@ -116,7 +113,7 @@ def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
116
113
  raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
117
114
 
118
115
 
119
- def _role_from_proto(proto_value: int) -> MemberRole:
116
+ def _role_from_proto(proto_value: int) -> str:
120
117
  match proto_value:
121
118
  case api_pb2.ENVIRONMENT_ROLE_VIEWER:
122
119
  return "viewer"
@@ -137,7 +134,7 @@ class _EnvironmentMembersManager:
137
134
  """mdmd:hidden"""
138
135
  self._environment = environment
139
136
 
140
- async def list(self) -> dict[Literal["users", "service_users"], dict[str, MemberRole]]:
137
+ async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
141
138
  """Return the members of a restricted Environment with their roles.
142
139
 
143
140
  **Examples:**
@@ -155,8 +152,8 @@ class _EnvironmentMembersManager:
155
152
  req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
156
153
  resp = await self._environment.client.stub.EnvironmentGetManaged(req)
157
154
 
158
- users: dict[str, MemberRole] = {}
159
- service_users: dict[str, MemberRole] = {}
155
+ users: dict[str, str] = {}
156
+ service_users: dict[str, str] = {}
160
157
  for principal in resp.principal_roles:
161
158
  role = _role_from_proto(principal.role)
162
159
  if principal.user_id:
@@ -169,8 +166,8 @@ class _EnvironmentMembersManager:
169
166
  async def update(
170
167
  self,
171
168
  *,
172
- users: Mapping[str, MemberRole] | None = None,
173
- service_users: Mapping[str, MemberRole] | None = None,
169
+ users: Mapping[str, str] | None = None,
170
+ service_users: Mapping[str, str] | None = None,
174
171
  ) -> None:
175
172
  """Add or modify roles for members of a restricted Environment.
176
173
 
@@ -117,13 +117,34 @@ def _validate_image_tag(tag: str) -> None:
117
117
  check_object_name(tag, "Image tag")
118
118
 
119
119
 
120
- def _parse_named_image_ref(name: str) -> str:
120
+ def _parse_named_image_ref(name: str) -> tuple[str, str]:
121
+ """Parse an image reference, returning (namespace_prefix, name_tag).
122
+
123
+ If the name contains a '/', the part before the last '/' is extracted as
124
+ a namespace prefix (intended for environment/name or workspace/env/name
125
+ syntax). The actual image name (after the last '/') is validated as a
126
+ standard object name.
127
+
128
+ Returns a tuple of (prefix, "full_name:tag") where prefix is empty string
129
+ if no '/' is present.
130
+ """
121
131
  image_name, sep, tag = name.partition(":")
122
132
  if not sep:
123
133
  tag = "latest"
134
+
135
+ prefix = ""
136
+ if "/" in image_name:
137
+ prefix, image_name = image_name.rsplit("/", 1)
138
+ if not prefix:
139
+ raise InvalidError("Invalid Image name: '/' prefix must be non-empty.")
140
+ if not image_name:
141
+ raise InvalidError("Invalid Image name: name after '/' must be non-empty.")
142
+
124
143
  _validate_image_name(image_name)
125
144
  _validate_image_tag(tag)
126
- return f"{image_name}:{tag}"
145
+
146
+ full_name = f"{prefix}/{image_name}" if prefix else image_name
147
+ return prefix, f"{full_name}:{tag}"
127
148
 
128
149
 
129
150
  def _validate_python_version(
@@ -2923,12 +2944,15 @@ class _Image(_Object, type_prefix="im"):
2923
2944
  ...
2924
2945
  ```
2925
2946
  """
2926
- tag = _parse_named_image_ref(name)
2947
+ namespace_prefix, tag = _parse_named_image_ref(name)
2948
+
2949
+ if namespace_prefix and environment_name:
2950
+ raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
2927
2951
 
2928
2952
  async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
2929
2953
  req = api_pb2.ImageGetByTagRequest(
2930
2954
  tag=tag,
2931
- environment_name=load_context.environment_name,
2955
+ environment_name="" if namespace_prefix else load_context.environment_name,
2932
2956
  )
2933
2957
  response = await load_context.client.stub.ImageGetByTag(req)
2934
2958
  self._hydrate(response.image_id, load_context.client, None)
@@ -2963,15 +2987,20 @@ class _Image(_Object, type_prefix="im"):
2963
2987
  image.publish("my-image-with-numpy:v1")
2964
2988
  ```
2965
2989
  """
2966
- tag = _parse_named_image_ref(name)
2990
+ namespace_prefix, tag = _parse_named_image_ref(name)
2991
+
2992
+ if namespace_prefix:
2993
+ if environment_name:
2994
+ raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
2995
+ resolved_env = ""
2996
+ else:
2997
+ resolved_env = environment_name or config.get("environment") or ""
2967
2998
 
2968
2999
  if self._object_id is None:
2969
3000
  raise InvalidError("Cannot publish an image that has not been created yet. Call `.build()` first.")
2970
3001
 
2971
3002
  _client = client or await _Client.from_env()
2972
3003
 
2973
- resolved_env = environment_name or config.get("environment") or ""
2974
-
2975
3004
  await _client.stub.ImagePublish(
2976
3005
  api_pb2.ImagePublishRequest(
2977
3006
  image_id=self._object_id,
@@ -163,7 +163,6 @@ RETRYABLE_GRPC_STATUS_CODES = [
163
163
  Status.UNKNOWN,
164
164
  ]
165
165
  SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
166
- # Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
167
166
  DEFAULT_MAX_RETRIES = 3
168
167
 
169
168
 
@@ -308,11 +307,6 @@ def create_channel(
308
307
 
309
308
  # With 18 attempts, the max delays between calls is: 0.1 + 0.2 + 0.4 + 0.8 + 1.6 + 3.2 + 5*11 ~ 62
310
309
  @retry(n_attempts=18, base_delay=0.1, attempt_timeout=10.0, max_delay=5.0, total_timeout=63.0)
311
- async def connect_channel(channel: grpclib.client.Channel):
312
- """Connect to socket and raise exceptions when there is a connection issue."""
313
- await channel.__connect__()
314
-
315
-
316
310
  async def create_channel_with_fallbacks(
317
311
  server_url: str,
318
312
  metadata: dict[str, str] = {},
@@ -323,21 +317,35 @@ async def create_channel_with_fallbacks(
323
317
  `server_url` may be a single URL or a comma-separated list of URLs. The connection attempts
324
318
  are raced against each other and the first one to connect wins, which lets the client fail
325
319
  over to a secondary endpoint when the primary is unreachable (e.g. a DNS outage on the
326
- primary's domain). Attempts are staggered: the candidate at index `i` waits `i * stagger_delay`
327
- seconds before starting, so earlier URLs (e.g. the primary) get a head start and are preferred
328
- when reachable. Losing attempts are cancelled and their channels closed.
320
+ primary's domain).
321
+
322
+ Attempts are staggered so earlier URLs (e.g. the primary) get a head start and are preferred
323
+ when reachable: the candidate at index `i` waits up to `i * stagger_delay` seconds before
324
+ starting. That wait is cut short as soon as the previous candidate *fails*, so a fast failure
325
+ falls over to the next URL immediately instead of waiting out the full stagger. Losing attempts
326
+ are cancelled and their channels closed.
329
327
  """
330
328
  urls = [url.strip() for url in server_url.split(",")]
331
329
 
330
+ # Set when a candidate's connection attempt fails, letting the next candidate start immediately
331
+ # instead of waiting out its stagger delay.
332
+ failed = [asyncio.Event() for _ in urls]
333
+
332
334
  async def connect(index: int, url: str) -> grpclib.client.Channel:
333
- # Stagger the start so earlier URLs are preferred; once started, all attempts race.
334
- await asyncio.sleep(index * stagger_delay)
335
+ if index > 0:
336
+ # Stagger the start so earlier URLs are preferred, but start early if the previous
337
+ # candidate has already failed.
338
+ try:
339
+ await asyncio.wait_for(failed[index - 1].wait(), timeout=index * stagger_delay)
340
+ except asyncio.TimeoutError:
341
+ pass
335
342
  channel = create_channel(url, metadata)
336
343
  try:
337
- await connect_channel(channel)
344
+ await channel.__connect__()
338
345
  except BaseException:
339
346
  # Close the channel of a losing/cancelled attempt so its socket doesn't leak.
340
347
  channel.close()
348
+ failed[index].set()
341
349
  raise
342
350
  return channel
343
351
 
@@ -29,7 +29,13 @@ def is_valid_object_name(name: str) -> bool:
29
29
 
30
30
  def is_valid_environment_name(name: str) -> bool:
31
31
  # first char is alnum, the rest allows other chars
32
- return len(name) <= 64 and re.match(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]+$", name) is not None
32
+ # The "en-" prefix is reserved for environment IDs, so that references
33
+ # to environments can be resolved by either name or ID unambiguously.
34
+ return (
35
+ len(name) <= 64
36
+ and re.match(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]+$", name) is not None
37
+ and not name.lower().startswith("en-")
38
+ )
33
39
 
34
40
 
35
41
  def is_valid_tag(tag: str, max_length: int = 50) -> bool:
@@ -66,7 +72,7 @@ def check_environment_name(name: str) -> None:
66
72
  f"Invalid Environment name: '{name}'."
67
73
  "\n\nEnvironment names can only start with alphanumeric characters,"
68
74
  " may contain only alphanumeric characters, dashes, periods, and underscores,"
69
- " and must be shorter than 64 characters."
75
+ " must be shorter than 64 characters, and cannot start with 'en-'."
70
76
  )
71
77
  if not is_valid_environment_name(name):
72
78
  raise InvalidError(message)
@@ -32,8 +32,8 @@ from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
32
32
  async def _connect_channel(channel: grpclib.client.Channel):
33
33
  """Connect to the command router channel.
34
34
 
35
- Uses a longer retry budget than grpc_utils.connect_channel. In rare cases the sandbox
36
- may take a long time to start on the worker after scheduling.
35
+ Uses a longer retry budget than grpc_utils.create_channel_with_fallbacks. In rare cases the
36
+ sandbox may take a long time to start on the worker after scheduling.
37
37
 
38
38
  Retries with exponential backoff (1, 2, 4, 8, 10, 10, ...) capped at 10s per delay.
39
39
  Total sleep between attempts: 1 + 2 + 4 + 8 + 10*29 = 305s (~5 min).
@@ -13,13 +13,19 @@ from ._object import _Object
13
13
  from ._resolver import Resolver
14
14
  from ._utils.time_utils import timestamp_to_localized_dt
15
15
  from .client import _Client
16
- from .types import BillingReportItem, MemberRole, ProxyTokenInfo, TokenData, WorkspaceMemberInfo, WorkspaceSettings
16
+ from .types import (
17
+ BillingReportItem,
18
+ ProxyTokenInfo,
19
+ TokenData,
20
+ WorkspaceMemberInfo,
21
+ WorkspaceSettings,
22
+ )
17
23
 
18
24
 
19
- def _member_role_from_proto(proto_value: int) -> MemberRole:
25
+ def _member_role_from_proto(proto_value: int) -> str:
20
26
  match proto_value:
21
27
  case api_pb2.MEMBER_ROLE_USER:
22
- return "user"
28
+ return "member"
23
29
  case api_pb2.MEMBER_ROLE_MANAGER:
24
30
  return "manager"
25
31
  case api_pb2.MEMBER_ROLE_OWNER:
@@ -1266,9 +1266,6 @@ class _App:
1266
1266
 
1267
1267
  return wrapper
1268
1268
 
1269
- def _experimental_server(self, **kwargs):
1270
- return self.server(**kwargs)
1271
-
1272
1269
  def server(
1273
1270
  self,
1274
1271
  _warn_parentheses_missing=None, # mdmd:line-hidden
@@ -728,7 +728,6 @@ class _App:
728
728
  """
729
729
  ...
730
730
 
731
- def _experimental_server(self, **kwargs): ...
732
731
  def server(
733
732
  self,
734
733
  _warn_parentheses_missing=None,
@@ -1733,7 +1732,6 @@ class App:
1733
1732
  """
1734
1733
  ...
1735
1734
 
1736
- def _experimental_server(self, **kwargs): ...
1737
1735
  def server(
1738
1736
  self,
1739
1737
  _warn_parentheses_missing=None,
@@ -202,20 +202,20 @@ async def logs(
202
202
  task_info_resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=task_id))
203
203
  app_id = task_info_resp.app_id
204
204
 
205
- if not task_info_resp.info.started_at:
206
- # Unlikely race or Modal backend issue, don't treat as a usage exception
205
+ if not task_info_resp.info.enqueued_at:
207
206
  return
208
- container_started_dt = datetime.fromtimestamp(task_info_resp.info.started_at, timezone.utc)
207
+
208
+ container_enqueued_dt = datetime.fromtimestamp(task_info_resp.info.enqueued_at, timezone.utc)
209
209
 
210
210
  now = datetime.now(timezone.utc)
211
211
  if all_logs:
212
- since_dt = container_started_dt
212
+ since_dt = container_enqueued_dt
213
213
  if task_info_resp.info.finished_at:
214
214
  until_dt = datetime.fromtimestamp(task_info_resp.info.finished_at, timezone.utc)
215
215
  else:
216
216
  until_dt = now
217
217
  else:
218
- since_dt = _parse_time_arg(since, default=container_started_dt)
218
+ since_dt = _parse_time_arg(since, default=container_enqueued_dt)
219
219
  if task_info_resp.info.finished_at:
220
220
  default_until_dt = datetime.fromtimestamp(task_info_resp.info.finished_at, timezone.utc)
221
221
  else:
@@ -7,7 +7,7 @@ from click import UsageError
7
7
  from rich.text import Text
8
8
 
9
9
  from modal import environments
10
- from modal._environments import MemberRole, _Environment
10
+ from modal._environments import _Environment
11
11
  from modal._utils.async_utils import synchronizer
12
12
  from modal._utils.name_utils import check_environment_name
13
13
  from modal._utils.time_utils import format_interval
@@ -158,7 +158,7 @@ def members_list(environment: str, json: bool = False):
158
158
  "--role", type=click.Choice(["contributor", "viewer"]), required=True, help="Role to assign to the member"
159
159
  )
160
160
  @service_user_option
161
- def members_update(environment: str, member: str, role: MemberRole, service_user: bool = False):
161
+ def members_update(environment: str, member: str, role: str, service_user: bool = False):
162
162
  env = Environment.from_name(environment)
163
163
  if service_user:
164
164
  env.members.update(service_users={member: role})
@@ -168,11 +168,18 @@ async def history(
168
168
  env: Optional[str] = None,
169
169
  json: bool = False,
170
170
  ):
171
+ explicit_env = env # None when --env is not passed by the user
171
172
  env = ensure_env(env)
172
173
  try:
173
- name_tag = _parse_named_image_ref(name)
174
+ namespace_prefix, name_tag = _parse_named_image_ref(name)
174
175
  except InvalidError as exc:
175
176
  raise click.UsageError(str(exc)) from exc
177
+
178
+ if namespace_prefix:
179
+ if explicit_env is not None:
180
+ raise click.UsageError("Cannot specify '--env' when the image name contains a '/'.")
181
+ env = ""
182
+
176
183
  client = await _Client.from_env()
177
184
 
178
185
  first_page = await _fetch_history_page(client, env, name_tag, "")
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.5.2.dev18",
38
+ version: str = "1.5.2.dev20",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -205,7 +205,7 @@ class Client:
205
205
  server_url: str,
206
206
  client_type: int,
207
207
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.5.2.dev18",
208
+ version: str = "1.5.2.dev20",
209
209
  ):
210
210
  """mdmd:hidden
211
211
  The Modal client object is not intended to be instantiated directly by users.
@@ -112,9 +112,7 @@ class EnvironmentMembersManager:
112
112
  ...
113
113
 
114
114
  class __list_spec(typing_extensions.Protocol):
115
- def __call__(
116
- self, /
117
- ) -> dict[typing.Literal["users", "service_users"], dict[str, typing.Literal["viewer", "contributor"]]]:
115
+ def __call__(self, /) -> dict[typing.Literal["users", "service_users"], dict[str, str]]:
118
116
  """Return the members of a restricted Environment with their roles.
119
117
 
120
118
  **Examples:**
@@ -130,9 +128,7 @@ class EnvironmentMembersManager:
130
128
  """
131
129
  ...
132
130
 
133
- async def aio(
134
- self, /
135
- ) -> dict[typing.Literal["users", "service_users"], dict[str, typing.Literal["viewer", "contributor"]]]:
131
+ async def aio(self, /) -> dict[typing.Literal["users", "service_users"], dict[str, str]]:
136
132
  """Return the members of a restricted Environment with their roles.
137
133
 
138
134
  **Examples:**
@@ -155,10 +151,8 @@ class EnvironmentMembersManager:
155
151
  self,
156
152
  /,
157
153
  *,
158
- users: typing.Optional[collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]] = None,
159
- service_users: typing.Optional[
160
- collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]
161
- ] = None,
154
+ users: typing.Optional[collections.abc.Mapping[str, str]] = None,
155
+ service_users: typing.Optional[collections.abc.Mapping[str, str]] = None,
162
156
  ) -> None:
163
157
  """Add or modify roles for members of a restricted Environment.
164
158
 
@@ -181,10 +175,8 @@ class EnvironmentMembersManager:
181
175
  self,
182
176
  /,
183
177
  *,
184
- users: typing.Optional[collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]] = None,
185
- service_users: typing.Optional[
186
- collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]
187
- ] = None,
178
+ users: typing.Optional[collections.abc.Mapping[str, str]] = None,
179
+ service_users: typing.Optional[collections.abc.Mapping[str, str]] = None,
188
180
  ) -> None:
189
181
  """Add or modify roles for members of a restricted Environment.
190
182
 
@@ -5,7 +5,7 @@ import enum
5
5
  from dataclasses import FrozenInstanceError, dataclass
6
6
  from datetime import datetime, timezone
7
7
  from decimal import Decimal
8
- from typing import Any, Iterable, Literal, Optional
8
+ from typing import Any, Iterable, Optional
9
9
 
10
10
  from modal_proto import api_pb2
11
11
 
@@ -54,9 +54,6 @@ class FileWatchEventType(enum.Enum):
54
54
  Remove = "Remove"
55
55
 
56
56
 
57
- MemberRole = Literal["user", "manager", "owner"]
58
-
59
-
60
57
  @dataclass
61
58
  class DictInfo:
62
59
  """Information about a Dict object."""
@@ -206,7 +203,7 @@ class WorkspaceMemberInfo:
206
203
  name: str
207
204
  email: str
208
205
  user_id: str
209
- role: MemberRole
206
+ role: str
210
207
  joined_at: datetime
211
208
  last_active_at: Optional[datetime] # None if the member has never been active
212
209
 
@@ -72,7 +72,7 @@ class WorkspaceMemberInfo:
72
72
  name: str
73
73
  email: str
74
74
  user_id: str
75
- role: typing.Literal["user", "manager", "owner"]
75
+ role: str
76
76
  joined_at: datetime.datetime
77
77
  last_active_at: typing.Optional[datetime.datetime]
78
78
 
@@ -81,7 +81,7 @@ class WorkspaceMemberInfo:
81
81
  name: str,
82
82
  email: str,
83
83
  user_id: str,
84
- role: typing.Literal["user", "manager", "owner"],
84
+ role: str,
85
85
  joined_at: datetime.datetime,
86
86
  last_active_at: typing.Optional[datetime.datetime],
87
87
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev18
3
+ Version: 1.5.2.dev20
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -483,6 +483,10 @@ class ModalClientBase(abc.ABC):
483
483
  async def MapStartOrContinue(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.MapStartOrContinueRequest, modal_proto.api_pb2.MapStartOrContinueResponse]') -> None:
484
484
  pass
485
485
 
486
+ @abc.abstractmethod
487
+ async def MountBatchedCheckExistence(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.MountBatchedCheckExistenceRequest, modal_proto.api_pb2.MountBatchedCheckExistenceResponse]') -> None:
488
+ pass
489
+
486
490
  @abc.abstractmethod
487
491
  async def MountGetOrCreate(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.MountGetOrCreateRequest, modal_proto.api_pb2.MountGetOrCreateResponse]') -> None:
488
492
  pass
@@ -1609,6 +1613,12 @@ class ModalClientBase(abc.ABC):
1609
1613
  modal_proto.api_pb2.MapStartOrContinueRequest,
1610
1614
  modal_proto.api_pb2.MapStartOrContinueResponse,
1611
1615
  ),
1616
+ '/modal.client.ModalClient/MountBatchedCheckExistence': grpclib.const.Handler(
1617
+ self.MountBatchedCheckExistence,
1618
+ grpclib.const.Cardinality.UNARY_UNARY,
1619
+ modal_proto.api_pb2.MountBatchedCheckExistenceRequest,
1620
+ modal_proto.api_pb2.MountBatchedCheckExistenceResponse,
1621
+ ),
1612
1622
  '/modal.client.ModalClient/MountGetOrCreate': grpclib.const.Handler(
1613
1623
  self.MountGetOrCreate,
1614
1624
  grpclib.const.Cardinality.UNARY_UNARY,
@@ -2953,6 +2963,12 @@ class ModalClientStub:
2953
2963
  modal_proto.api_pb2.MapStartOrContinueRequest,
2954
2964
  modal_proto.api_pb2.MapStartOrContinueResponse,
2955
2965
  )
2966
+ self.MountBatchedCheckExistence = grpclib.client.UnaryUnaryMethod(
2967
+ channel,
2968
+ '/modal.client.ModalClient/MountBatchedCheckExistence',
2969
+ modal_proto.api_pb2.MountBatchedCheckExistenceRequest,
2970
+ modal_proto.api_pb2.MountBatchedCheckExistenceResponse,
2971
+ )
2956
2972
  self.MountGetOrCreate = grpclib.client.UnaryUnaryMethod(
2957
2973
  channel,
2958
2974
  '/modal.client.ModalClient/MountGetOrCreate',