modal 1.2.7.dev0__tar.gz → 1.2.7.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/PKG-INFO +3 -3
  2. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/README.md +1 -1
  3. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/__init__.py +2 -2
  4. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/__main__.py +4 -29
  5. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_functions.py +3 -7
  6. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_grpc_client.py +48 -28
  7. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_tunnel.py +5 -9
  8. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/grpc_utils.py +2 -5
  9. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/task_command_router_client.py +52 -46
  10. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/environment.py +2 -16
  11. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/network_file_system.py +3 -15
  12. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/utils.py +1 -13
  13. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/volume.py +3 -15
  14. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/client.pyi +2 -2
  15. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cls.py +0 -6
  16. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/dict.py +3 -5
  17. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/exception.py +142 -16
  18. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/functions.pyi +6 -6
  19. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/image.py +5 -8
  20. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/io_streams.py +3 -6
  21. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/mount.py +0 -1
  22. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/mount.pyi +3 -3
  23. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/queue.py +10 -18
  24. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/runner.py +1 -8
  25. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/sandbox.py +6 -27
  26. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/secret.py +4 -20
  27. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/volume.py +14 -17
  28. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/PKG-INFO +3 -3
  29. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_version/__init__.py +1 -1
  30. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/pyproject.toml +1 -1
  31. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/LICENSE +0 -0
  32. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_billing.py +0 -0
  33. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_clustered_functions.py +0 -0
  34. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_clustered_functions.pyi +0 -0
  35. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_container_entrypoint.py +0 -0
  36. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_ipython.py +0 -0
  37. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_load_context.py +0 -0
  38. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_location.py +0 -0
  39. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_object.py +0 -0
  40. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_output.py +0 -0
  41. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_partial_function.py +0 -0
  42. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_pty.py +0 -0
  43. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_resolver.py +0 -0
  44. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_resources.py +0 -0
  45. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/__init__.py +0 -0
  46. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/asgi.py +0 -0
  47. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/container_io_manager.py +0 -0
  48. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
  49. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/execution_context.py +0 -0
  50. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/execution_context.pyi +0 -0
  51. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  52. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/telemetry.py +0 -0
  53. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
  54. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/user_code_imports.py +0 -0
  55. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_serialization.py +0 -0
  56. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_traceback.py +0 -0
  57. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_tunnel.pyi +0 -0
  58. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_type_manager.py +0 -0
  59. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/__init__.py +0 -0
  60. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/app_utils.py +0 -0
  61. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/async_utils.py +0 -0
  62. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/auth_token_manager.py +0 -0
  63. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/blob_utils.py +0 -0
  64. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
  65. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/deprecation.py +0 -0
  66. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/docker_utils.py +0 -0
  67. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/function_utils.py +0 -0
  68. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/git_utils.py +0 -0
  69. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/grpc_testing.py +0 -0
  70. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/hash_utils.py +0 -0
  71. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/http_utils.py +0 -0
  72. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/jwt_utils.py +0 -0
  73. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/logger.py +0 -0
  74. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/mount_utils.py +0 -0
  75. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/name_utils.py +0 -0
  76. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/package_utils.py +0 -0
  77. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/pattern_utils.py +0 -0
  78. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/rand_pb_testing.py +0 -0
  79. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/shell_utils.py +0 -0
  80. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/time_utils.py +0 -0
  81. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/__init__.py +0 -0
  82. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  83. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/cloudpickle.py +0 -0
  84. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/tblib.py +0 -0
  85. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_watcher.py +0 -0
  86. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/app.py +0 -0
  87. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/app.pyi +0 -0
  88. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/billing.py +0 -0
  89. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2023.12.312.txt +0 -0
  90. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2023.12.txt +0 -0
  91. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2024.04.txt +0 -0
  92. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2024.10.txt +0 -0
  93. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2025.06.txt +0 -0
  94. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/PREVIEW.txt +0 -0
  95. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/README.md +0 -0
  96. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/base-images.json +0 -0
  97. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/call_graph.py +0 -0
  98. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/__init__.py +0 -0
  99. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/_download.py +0 -0
  100. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/_traceback.py +0 -0
  101. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/app.py +0 -0
  102. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/cluster.py +0 -0
  103. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/config.py +0 -0
  104. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/container.py +0 -0
  105. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/dict.py +0 -0
  106. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/entry_point.py +0 -0
  107. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/import_refs.py +0 -0
  108. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/launch.py +0 -0
  109. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/profile.py +0 -0
  110. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/__init__.py +0 -0
  111. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/run_jupyter.py +0 -0
  112. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/vscode.py +0 -0
  113. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/queues.py +0 -0
  114. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/run.py +0 -0
  115. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/secret.py +0 -0
  116. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/shell.py +0 -0
  117. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/token.py +0 -0
  118. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/client.py +0 -0
  119. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cloud_bucket_mount.py +0 -0
  120. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cloud_bucket_mount.pyi +0 -0
  121. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cls.pyi +0 -0
  122. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/config.py +0 -0
  123. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/container_process.py +0 -0
  124. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/container_process.pyi +0 -0
  125. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/dict.pyi +0 -0
  126. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/environments.py +0 -0
  127. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/environments.pyi +0 -0
  128. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/__init__.py +0 -0
  129. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/flash.py +0 -0
  130. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/flash.pyi +0 -0
  131. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/ipython.py +0 -0
  132. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_io.py +0 -0
  133. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_io.pyi +0 -0
  134. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_pattern_matcher.py +0 -0
  135. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/functions.py +0 -0
  136. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/gpu.py +0 -0
  137. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/image.pyi +0 -0
  138. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/io_streams.pyi +0 -0
  139. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/network_file_system.py +0 -0
  140. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/network_file_system.pyi +0 -0
  141. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/object.py +0 -0
  142. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/object.pyi +0 -0
  143. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/output.py +0 -0
  144. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/parallel_map.py +0 -0
  145. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/parallel_map.pyi +0 -0
  146. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/partial_function.py +0 -0
  147. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/partial_function.pyi +0 -0
  148. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/proxy.py +0 -0
  149. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/proxy.pyi +0 -0
  150. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/py.typed +0 -0
  151. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/queue.pyi +0 -0
  152. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/retries.py +0 -0
  153. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/runner.pyi +0 -0
  154. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/running_app.py +0 -0
  155. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/schedule.py +0 -0
  157. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/secret.pyi +0 -0
  159. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/serving.py +0 -0
  160. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/serving.pyi +0 -0
  161. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/snapshot.py +0 -0
  162. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/snapshot.pyi +0 -0
  163. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/stream_type.py +0 -0
  164. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/token_flow.py +0 -0
  165. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/token_flow.pyi +0 -0
  166. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/volume.pyi +0 -0
  167. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/SOURCES.txt +0 -0
  168. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/dependency_links.txt +0 -0
  169. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/entry_points.txt +0 -0
  170. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/requires.txt +0 -0
  171. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/top_level.txt +0 -0
  172. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/__init__.py +0 -0
  173. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/gen_cli_docs.py +0 -0
  174. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/gen_reference_docs.py +0 -0
  175. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/__init__.py +0 -0
  176. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/mdmd.py +0 -0
  177. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/signatures.py +0 -0
  178. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/__init__.py +0 -0
  179. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api.proto +0 -0
  180. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_grpc.py +0 -0
  181. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2.py +0 -0
  182. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2.pyi +0 -0
  183. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2_grpc.py +0 -0
  184. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
  185. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/modal_api_grpc.py +0 -0
  186. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/py.typed +0 -0
  187. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router.proto +0 -0
  188. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_grpc.py +0 -0
  189. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2.py +0 -0
  190. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
  191. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  192. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  193. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_version/__main__.py +0 -0
  194. {modal-1.2.7.dev0 → modal-1.2.7.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev0
3
+ Version: 1.2.7.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -13,7 +13,7 @@ Classifier: Topic :: System :: Distributed Computing
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: <3.14,>=3.9
16
+ Requires-Python: <3.14,>=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
@@ -51,7 +51,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
51
51
 
52
52
  ## Installation
53
53
 
54
- **This library requires Python 3.9 – 3.13.**
54
+ **This library requires Python 3.10 – 3.13.**
55
55
 
56
56
  Install the package with `uv` or `pip`:
57
57
 
@@ -17,7 +17,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
17
17
 
18
18
  ## Installation
19
19
 
20
- **This library requires Python 3.9 – 3.13.**
20
+ **This library requires Python 3.10 – 3.13.**
21
21
 
22
22
  Install the package with `uv` or `pip`:
23
23
 
@@ -1,8 +1,8 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import sys
3
3
 
4
- if sys.version_info[:2] < (3, 9):
5
- raise RuntimeError("This version of Modal requires at least Python 3.9")
4
+ if sys.version_info[:2] < (3, 10):
5
+ raise RuntimeError("This version of Modal requires at least Python 3.10")
6
6
  if sys.version_info[:2] >= (3, 14):
7
7
  raise RuntimeError("This version of Modal does not support Python 3.14+")
8
8
 
@@ -35,37 +35,12 @@ def main():
35
35
  ):
36
36
  raise
37
37
 
38
- from grpclib import GRPCError, Status
39
38
  from rich.panel import Panel
40
39
 
41
- if isinstance(exc, GRPCError):
42
- status_map = {
43
- Status.ABORTED: "Aborted",
44
- Status.ALREADY_EXISTS: "Already exists",
45
- Status.CANCELLED: "Cancelled",
46
- Status.DATA_LOSS: "Data loss",
47
- Status.DEADLINE_EXCEEDED: "Deadline exceeded",
48
- Status.FAILED_PRECONDITION: "Failed precondition",
49
- Status.INTERNAL: "Internal",
50
- Status.INVALID_ARGUMENT: "Invalid",
51
- Status.NOT_FOUND: "Not found",
52
- Status.OUT_OF_RANGE: "Out of range",
53
- Status.PERMISSION_DENIED: "Permission denied",
54
- Status.RESOURCE_EXHAUSTED: "Resource exhausted",
55
- Status.UNAUTHENTICATED: "Unauthenticaed",
56
- Status.UNAVAILABLE: "Unavailable",
57
- Status.UNIMPLEMENTED: "Unimplemented",
58
- Status.UNKNOWN: "Unknown",
59
- }
60
- title = f"Error: {status_map.get(exc.status, 'Unknown')}"
61
- content = str(exc.message)
62
- if exc.details:
63
- content += f"\n\nDetails: {exc.details}"
64
- else:
65
- title = "Error"
66
- content = str(exc)
67
- if notes := getattr(exc, "__notes__", []):
68
- content = f"{content}\n\nNote: {' '.join(notes)}"
40
+ title = "Error"
41
+ content = str(exc)
42
+ if notes := getattr(exc, "__notes__", []):
43
+ content = f"{content}\n\nNote: {' '.join(notes)}"
69
44
 
70
45
  console = make_console(stderr=True)
71
46
  panel = Panel(content, title=title, title_align="left", border_style="red")
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
13
13
 
14
14
  import typing_extensions
15
15
  from google.protobuf.message import Message
16
- from grpclib import GRPCError, Status
16
+ from grpclib import Status
17
17
  from synchronicity.combined_types import MethodWithAio
18
18
 
19
19
  from modal_proto import api_pb2
@@ -1126,12 +1126,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1126
1126
  )
1127
1127
  try:
1128
1128
  response: api_pb2.FunctionCreateResponse = await load_context.client.stub.FunctionCreate(request)
1129
- except GRPCError as exc:
1130
- if exc.status == Status.INVALID_ARGUMENT:
1131
- raise InvalidError(exc.message)
1132
- if exc.status == Status.FAILED_PRECONDITION:
1133
- raise InvalidError(exc.message)
1134
- if exc.message and "Received :status = '413'" in exc.message:
1129
+ except Exception as exc:
1130
+ if "Received :status = '413'" in str(exc):
1135
1131
  raise InvalidError(f"Function {info.function_name} is too large to deploy.")
1136
1132
  raise
1137
1133
  function_creation_status.set_response(response)
@@ -5,10 +5,10 @@ import grpclib.client
5
5
  from google.protobuf.message import Message
6
6
  from grpclib import GRPCError, Status
7
7
 
8
+ from . import exception
8
9
  from ._traceback import suppress_tb_frames
9
10
  from ._utils.grpc_utils import Retry, _retry_transient_errors
10
11
  from .config import config, logger
11
- from .exception import InvalidError, NotFoundError
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from .client import _Client
@@ -20,6 +20,29 @@ RequestType = TypeVar("RequestType", bound=Message)
20
20
  ResponseType = TypeVar("ResponseType", bound=Message)
21
21
 
22
22
 
23
+ class WrappedGRPCError(exception.Error, exception._GRPCErrorWrapper): ...
24
+
25
+
26
+ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
27
+ Status.CANCELLED: exception.ServiceError,
28
+ Status.UNKNOWN: exception.ServiceError,
29
+ Status.INVALID_ARGUMENT: exception.InvalidError,
30
+ Status.DEADLINE_EXCEEDED: exception.ServiceError,
31
+ Status.NOT_FOUND: exception.NotFoundError,
32
+ Status.ALREADY_EXISTS: exception.AlreadyExistsError,
33
+ Status.PERMISSION_DENIED: exception.PermissionDeniedError,
34
+ Status.RESOURCE_EXHAUSTED: exception.ResourceExhaustedError,
35
+ Status.FAILED_PRECONDITION: exception.ConflictError,
36
+ Status.ABORTED: exception.ConflictError,
37
+ Status.OUT_OF_RANGE: exception.InvalidError,
38
+ Status.UNIMPLEMENTED: exception.UnimplementedError,
39
+ Status.INTERNAL: exception.InternalError,
40
+ Status.UNAVAILABLE: exception.ServiceError,
41
+ Status.DATA_LOSS: exception.ServiceError,
42
+ Status.UNAUTHENTICATED: exception.AuthError,
43
+ }
44
+
45
+
23
46
  class grpc_error_converter:
24
47
  def __enter__(self):
25
48
  pass
@@ -29,20 +52,14 @@ class grpc_error_converter:
29
52
  use_full_traceback = config.get("traceback")
30
53
  with suppress_tb_frames(1):
31
54
  if isinstance(exc, GRPCError):
32
- if exc.status == Status.NOT_FOUND:
33
- if use_full_traceback:
34
- raise NotFoundError(exc.message)
35
- else:
36
- raise NotFoundError(exc.message) from None # from None to skip the grpc-internal cause
37
-
38
- if not use_full_traceback:
39
- # just include the frame in grpclib that actually raises the GRPCError
40
- tb = exc.__traceback__
41
- while tb.tb_next:
42
- tb = tb.tb_next
43
- exc.with_traceback(tb)
44
- raise exc from None # from None to skip the grpc-internal cause
45
- raise exc
55
+ modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
56
+ modal_exc._grpc_message = exc.message
57
+ modal_exc._grpc_status = exc.status
58
+ modal_exc._grpc_details = exc.details
59
+ if use_full_traceback:
60
+ raise modal_exc
61
+ else:
62
+ raise modal_exc from None # from None to skip the grpc-internal cause
46
63
 
47
64
  return False
48
65
 
@@ -100,17 +117,20 @@ class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
100
117
  ) -> ResponseType:
101
118
  with suppress_tb_frames(1):
102
119
  if timeout is not None and retry is not None:
103
- raise InvalidError("Retry must be None when timeout is set")
120
+ raise exception.InvalidError("Retry must be None when timeout is set")
104
121
 
105
122
  if retry is None:
106
- return await self.direct(req, timeout=timeout, metadata=metadata)
107
-
108
- return await _retry_transient_errors(
109
- self, # type: ignore
110
- req,
111
- retry=retry,
112
- metadata=metadata,
113
- )
123
+ with grpc_error_converter():
124
+ return await self.direct(req, timeout=timeout, metadata=metadata)
125
+
126
+ # TODO do we need suppress_error_frames(1) here too?
127
+ with grpc_error_converter():
128
+ return await _retry_transient_errors(
129
+ self, # type: ignore
130
+ req,
131
+ retry=retry,
132
+ metadata=metadata,
133
+ )
114
134
 
115
135
  async def direct(
116
136
  self,
@@ -135,8 +155,7 @@ class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
135
155
  #
136
156
  # [1]: https://github.com/vmagamedov/grpclib/blob/62f968a4c84e3f64e6966097574ff0a59969ea9b/grpclib/client.py#L844
137
157
  self.wrapped_method.channel = await self.client._get_channel(self.server_url)
138
- with suppress_tb_frames(1), grpc_error_converter():
139
- return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
158
+ return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
140
159
 
141
160
 
142
161
  class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
@@ -167,5 +186,6 @@ class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
167
186
  logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
168
187
  self.client = await _Client.from_env()
169
188
  self.wrapped_method.channel = await self.client._get_channel(self.server_url)
170
- async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
171
- yield response
189
+ with grpc_error_converter():
190
+ async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
191
+ yield response
@@ -5,14 +5,13 @@ from collections.abc import AsyncIterator
5
5
  from dataclasses import dataclass
6
6
  from typing import Optional
7
7
 
8
- from grpclib import GRPCError, Status
9
8
  from synchronicity.async_wrap import asynccontextmanager
10
9
 
11
10
  from modal_proto import api_pb2
12
11
 
13
12
  from ._utils.async_utils import synchronize_api
14
13
  from .client import _Client
15
- from .exception import InvalidError, RemoteError
14
+ from .exception import AlreadyExistsError, InvalidError, RemoteError, ServiceError
16
15
 
17
16
 
18
17
  @dataclass(frozen=True)
@@ -186,13 +185,10 @@ async def _forward(
186
185
  response = await client.stub.TunnelStart(
187
186
  api_pb2.TunnelStartRequest(port=port, unencrypted=unencrypted, tunnel_type=tunnel_type)
188
187
  )
189
- except GRPCError as exc:
190
- if exc.status == Status.ALREADY_EXISTS:
191
- raise InvalidError(f"Port {port} is already forwarded")
192
- elif exc.status == Status.UNAVAILABLE:
193
- raise RemoteError("Relay server is unavailable") from exc
194
- else:
195
- raise
188
+ except AlreadyExistsError as exc:
189
+ raise InvalidError(f"Port {port} is already forwarded")
190
+ except ServiceError as exc:
191
+ raise RemoteError("Relay server is unavailable") from exc
196
192
 
197
193
  try:
198
194
  yield Tunnel(response.host, response.port, response.unencrypted_host, response.unencrypted_port)
@@ -25,7 +25,7 @@ from grpclib.encoding.base import StatusDetailsCodecBase
25
25
  from grpclib.exceptions import StreamTerminatedError
26
26
  from grpclib.protocol import H2Protocol
27
27
 
28
- from modal.exception import AuthError, ConnectionError
28
+ from modal.exception import ConnectionError
29
29
  from modal_proto import api_pb2
30
30
  from modal_version import __version__
31
31
 
@@ -408,10 +408,7 @@ async def _retry_transient_errors(
408
408
 
409
409
  # Client handles retry
410
410
  if isinstance(exc, GRPCError) and exc.status not in status_codes:
411
- if exc.status == Status.UNAUTHENTICATED:
412
- raise AuthError(exc.message)
413
- else:
414
- raise exc
411
+ raise exc
415
412
  if retry.max_retries is not None and n_retries >= retry.max_retries:
416
413
  final_attempt = True
417
414
  elif total_deadline is not None and time.time() + delay + retry.attempt_timeout_floor >= total_deadline:
@@ -14,10 +14,11 @@ from grpclib import GRPCError, Status
14
14
  from grpclib.exceptions import StreamTerminatedError
15
15
 
16
16
  from modal.config import config, logger
17
- from modal.exception import ExecTimeoutError
17
+ from modal.exception import ConflictError, ExecTimeoutError
18
18
  from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
19
19
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
20
20
 
21
+ from .._grpc_client import grpc_error_converter
21
22
  from .async_utils import aclosing
22
23
  from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES, connect_channel
23
24
 
@@ -124,11 +125,9 @@ class TaskCommandRouterClient:
124
125
  """
125
126
  try:
126
127
  resp = await fetch_command_router_access(server_client, task_id)
127
- except GRPCError as exc:
128
- if exc.status == Status.FAILED_PRECONDITION:
129
- logger.debug(f"Command router access is not enabled for task {task_id}")
130
- return None
131
- raise
128
+ except ConflictError:
129
+ logger.debug(f"Command router access is not enabled for task {task_id}")
130
+ return None
132
131
 
133
132
  logger.debug(f"Using command router access for task {task_id}")
134
133
 
@@ -249,9 +248,10 @@ class TaskCommandRouterClient:
249
248
 
250
249
  async def exec_start(self, request: sr_pb2.TaskExecStartRequest) -> sr_pb2.TaskExecStartResponse:
251
250
  """Start an exec'd command, properly retrying on transient errors."""
252
- return await call_with_retries_on_transient_errors(
253
- lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
254
- )
251
+ with grpc_error_converter():
252
+ return await call_with_retries_on_transient_errors(
253
+ lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
254
+ )
255
255
 
256
256
  async def exec_stdio_read(
257
257
  self,
@@ -286,9 +286,10 @@ class TaskCommandRouterClient:
286
286
  else:
287
287
  raise ValueError(f"Invalid file descriptor: {file_descriptor}")
288
288
 
289
- async with aclosing(self._stream_stdio(task_id, exec_id, sr_fd, deadline)) as stream:
290
- async for item in stream:
291
- yield item
289
+ with grpc_error_converter():
290
+ async with aclosing(self._stream_stdio(task_id, exec_id, sr_fd, deadline)) as stream:
291
+ async for item in stream:
292
+ yield item
292
293
 
293
294
  async def exec_stdin_write(
294
295
  self, task_id: str, exec_id: str, offset: int, data: bytes, eof: bool
@@ -306,9 +307,10 @@ class TaskCommandRouterClient:
306
307
  from the RPC itself.
307
308
  """
308
309
  request = sr_pb2.TaskExecStdinWriteRequest(task_id=task_id, exec_id=exec_id, offset=offset, data=data, eof=eof)
309
- return await call_with_retries_on_transient_errors(
310
- lambda: self._call_with_auth_retry(self._stub.TaskExecStdinWrite, request)
311
- )
310
+ with grpc_error_converter():
311
+ return await call_with_retries_on_transient_errors(
312
+ lambda: self._call_with_auth_retry(self._stub.TaskExecStdinWrite, request)
313
+ )
312
314
 
313
315
  async def exec_poll(
314
316
  self, task_id: str, exec_id: str, deadline: Optional[float] = None
@@ -332,15 +334,17 @@ class TaskCommandRouterClient:
332
334
  timeout = deadline - time.monotonic() if deadline is not None else None
333
335
  if timeout is not None and timeout <= 0:
334
336
  raise ExecTimeoutError(f"Deadline exceeded while polling for exec {exec_id}")
335
- try:
336
- return await asyncio.wait_for(
337
- call_with_retries_on_transient_errors(
338
- lambda: self._call_with_auth_retry(self._stub.TaskExecPoll, request)
339
- ),
340
- timeout=timeout,
341
- )
342
- except asyncio.TimeoutError:
343
- raise ExecTimeoutError(f"Deadline exceeded while polling for exec {exec_id}")
337
+
338
+ with grpc_error_converter():
339
+ try:
340
+ return await asyncio.wait_for(
341
+ call_with_retries_on_transient_errors(
342
+ lambda: self._call_with_auth_retry(self._stub.TaskExecPoll, request)
343
+ ),
344
+ timeout=timeout,
345
+ )
346
+ except asyncio.TimeoutError:
347
+ raise ExecTimeoutError(f"Deadline exceeded while polling for exec {exec_id}")
344
348
 
345
349
  async def exec_wait(
346
350
  self,
@@ -363,28 +367,30 @@ class TaskCommandRouterClient:
363
367
  timeout = deadline - time.monotonic() if deadline is not None else None
364
368
  if timeout is not None and timeout <= 0:
365
369
  raise ExecTimeoutError(f"Deadline exceeded while waiting for exec {exec_id}")
366
- try:
367
- return await asyncio.wait_for(
368
- call_with_retries_on_transient_errors(
369
- # We set a 60s timeout here to avoid waiting forever if there's an unanticipated hang
370
- # due to a networking issue. call_with_retries_on_transient_errors will retry if the
371
- # timeout is exceeded, so we'll retry every 60s until the command exits.
372
- #
373
- # Safety:
374
- # * If just the task shuts down, the task command router will return a NOT_FOUND error,
375
- # and we'll stop retrying.
376
- # * If the task shut down AND the worker shut down, this could
377
- # infinitely retry. For callers without an exec deadline, this
378
- # could hang indefinitely.
379
- lambda: self._call_with_auth_retry(self._stub.TaskExecWait, request, timeout=60),
380
- base_delay_secs=1, # Retry after 1s since total time is expected to be long.
381
- delay_factor=1, # Fixed delay.
382
- max_retries=None, # Retry forever.
383
- ),
384
- timeout=timeout,
385
- )
386
- except asyncio.TimeoutError:
387
- raise ExecTimeoutError(f"Deadline exceeded while waiting for exec {exec_id}")
370
+
371
+ with grpc_error_converter():
372
+ try:
373
+ return await asyncio.wait_for(
374
+ call_with_retries_on_transient_errors(
375
+ # We set a 60s timeout here to avoid waiting forever if there's an unanticipated hang
376
+ # due to a networking issue. call_with_retries_on_transient_errors will retry if the
377
+ # timeout is exceeded, so we'll retry every 60s until the command exits.
378
+ #
379
+ # Safety:
380
+ # * If just the task shuts down, the task command router will return a NOT_FOUND error,
381
+ # and we'll stop retrying.
382
+ # * If the task shut down AND the worker shut down, this could
383
+ # infinitely retry. For callers without an exec deadline, this
384
+ # could hang indefinitely.
385
+ lambda: self._call_with_auth_retry(self._stub.TaskExecWait, request, timeout=60),
386
+ base_delay_secs=1, # Retry after 1s since total time is expected to be long.
387
+ delay_factor=1, # Fixed delay.
388
+ max_retries=None, # Retry forever.
389
+ ),
390
+ timeout=timeout,
391
+ )
392
+ except asyncio.TimeoutError:
393
+ raise ExecTimeoutError(f"Deadline exceeded while waiting for exec {exec_id}")
388
394
 
389
395
  async def _refresh_jwt(self) -> None:
390
396
  """Refresh JWT from the server and update internal state."""
@@ -3,14 +3,12 @@ from typing import Annotated, Optional, Union
3
3
 
4
4
  import typer
5
5
  from click import UsageError
6
- from grpclib import GRPCError, Status
7
6
  from rich.text import Text
8
7
 
9
8
  from modal import environments
10
9
  from modal._utils.name_utils import check_environment_name
11
10
  from modal.cli.utils import YES_OPTION, display_table
12
11
  from modal.config import config
13
- from modal.exception import InvalidError
14
12
 
15
13
  ENVIRONMENT_HELP_TEXT = """Create and interact with Environments
16
14
 
@@ -61,13 +59,7 @@ ENVIRONMENT_CREATE_HELP = """Create a new environment in the current workspace""
61
59
  @environment_cli.command(name="create", help=ENVIRONMENT_CREATE_HELP)
62
60
  def create(name: Annotated[str, typer.Argument(help="Name of the new environment. Must be unique. Case sensitive")]):
63
61
  check_environment_name(name)
64
-
65
- try:
66
- environments.create_environment(name)
67
- except GRPCError as exc:
68
- if exc.status == Status.INVALID_ARGUMENT:
69
- raise InvalidError(exc.message)
70
- raise
62
+ environments.create_environment(name)
71
63
  typer.echo(f"Environment created: {name}")
72
64
 
73
65
 
@@ -114,11 +106,5 @@ def update(
114
106
  if set_name:
115
107
  check_environment_name(set_name)
116
108
 
117
- try:
118
- environments.update_environment(current_name, new_name=set_name, new_web_suffix=set_web_suffix)
119
- except GRPCError as exc:
120
- if exc.status == Status.INVALID_ARGUMENT:
121
- raise InvalidError(exc.message)
122
- raise
123
-
109
+ environments.update_environment(current_name, new_name=set_name, new_web_suffix=set_web_suffix)
124
110
  typer.echo("Environment updated")
@@ -6,7 +6,6 @@ from typing import Optional
6
6
 
7
7
  import typer
8
8
  from click import UsageError
9
- from grpclib import GRPCError, Status
10
9
  from rich.syntax import Syntax
11
10
  from rich.table import Table
12
11
  from typer import Argument, Typer
@@ -81,12 +80,7 @@ async def ls(
81
80
  ):
82
81
  ensure_env(env)
83
82
  volume = _NetworkFileSystem.from_name(volume_name)
84
- try:
85
- entries = await volume.listdir(path)
86
- except GRPCError as exc:
87
- if exc.status in (Status.INVALID_ARGUMENT, Status.NOT_FOUND):
88
- raise UsageError(exc.message)
89
- raise
83
+ entries = await volume.listdir(path)
90
84
 
91
85
  if sys.stdout.isatty():
92
86
  console = make_console()
@@ -200,14 +194,8 @@ async def rm(
200
194
  ensure_env(env)
201
195
  volume = _NetworkFileSystem.from_name(volume_name)
202
196
  console = make_console()
203
- try:
204
- await volume.remove_file(remote_path, recursive=recursive)
205
- console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
206
-
207
- except GRPCError as exc:
208
- if exc.status in (Status.NOT_FOUND, Status.INVALID_ARGUMENT):
209
- raise UsageError(exc.message)
210
- raise
197
+ await volume.remove_file(remote_path, recursive=recursive)
198
+ console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
211
199
 
212
200
 
213
201
  @nfs_cli.command(
@@ -5,8 +5,6 @@ from json import dumps
5
5
  from typing import Optional, Union
6
6
 
7
7
  import typer
8
- from click import UsageError
9
- from grpclib import GRPCError, Status
10
8
  from rich.table import Column, Table
11
9
  from rich.text import Text
12
10
 
@@ -33,11 +31,6 @@ async def stream_app_logs(
33
31
  await get_app_logs_loop(client, output_mgr, app_id=app_id, task_id=task_id, app_logs_url=app_logs_url)
34
32
  except asyncio.CancelledError:
35
33
  pass
36
- except GRPCError as exc:
37
- if exc.status in (Status.INVALID_ARGUMENT, Status.NOT_FOUND):
38
- raise UsageError(exc.message)
39
- else:
40
- raise
41
34
  except KeyboardInterrupt:
42
35
  pass
43
36
 
@@ -48,12 +41,7 @@ async def get_app_id_from_name(name: str, env: Optional[str], client: Optional[_
48
41
  client = await _Client.from_env()
49
42
  env_name = ensure_env(env)
50
43
  request = api_pb2.AppGetByDeploymentNameRequest(name=name, environment_name=env_name)
51
- try:
52
- resp = await client.stub.AppGetByDeploymentName(request)
53
- except GRPCError as exc:
54
- if exc.status in (Status.INVALID_ARGUMENT, Status.NOT_FOUND):
55
- raise UsageError(exc.message or "")
56
- raise
44
+ resp = await client.stub.AppGetByDeploymentName(request)
57
45
  if not resp.app_id:
58
46
  env_comment = f" in the '{env_name}' environment" if env_name else ""
59
47
  raise NotFoundError(f"Could not find a deployed app named '{name}'{env_comment}.")
@@ -6,7 +6,6 @@ from typing import Optional
6
6
 
7
7
  import typer
8
8
  from click import UsageError
9
- from grpclib import GRPCError, Status
10
9
  from rich.syntax import Syntax
11
10
  from typer import Argument, Option, Typer
12
11
 
@@ -137,13 +136,7 @@ async def ls(
137
136
  ):
138
137
  ensure_env(env)
139
138
  vol = _Volume.from_name(volume_name, environment_name=env)
140
-
141
- try:
142
- entries = await vol.listdir(path)
143
- except GRPCError as exc:
144
- if exc.status in (Status.INVALID_ARGUMENT, Status.NOT_FOUND):
145
- raise UsageError(exc.message)
146
- raise
139
+ entries = await vol.listdir(path)
147
140
 
148
141
  if not json and not sys.stdout.isatty():
149
142
  # Legacy behavior -- I am not sure why exactly we did this originally but I don't want to break it
@@ -247,14 +240,9 @@ async def rm(
247
240
  ):
248
241
  ensure_env(env)
249
242
  volume = _Volume.from_name(volume_name, environment_name=env)
243
+ await volume.remove_file(remote_path, recursive=recursive)
250
244
  console = make_console()
251
- try:
252
- await volume.remove_file(remote_path, recursive=recursive)
253
- console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
254
- except GRPCError as exc:
255
- if exc.status in (Status.NOT_FOUND, Status.INVALID_ARGUMENT):
256
- raise UsageError(exc.message)
257
- raise
245
+ console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
258
246
 
259
247
 
260
248
  @volume_cli.command(
@@ -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.dev0",
35
+ version: str = "1.2.7.dev2",
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.dev0",
166
+ version: str = "1.2.7.dev2",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -7,7 +7,6 @@ from pathlib import PurePosixPath
7
7
  from typing import Any, Callable, Optional, Sequence, TypeVar, Union
8
8
 
9
9
  from google.protobuf.message import Message
10
- from grpclib import GRPCError, Status
11
10
 
12
11
  from modal_proto import api_pb2
13
12
 
@@ -659,11 +658,6 @@ More information on class parameterization can be found here: https://modal.com/
659
658
  raise NotFoundError(
660
659
  f"Lookup failed for Cls '{name}' from the '{app_name}' app{env_context}: {exc}."
661
660
  ) from None
662
- except GRPCError as exc:
663
- if exc.status == Status.FAILED_PRECONDITION:
664
- raise InvalidError(exc.message) from None
665
- else:
666
- raise
667
661
 
668
662
  print_server_warnings(response.server_warnings)
669
663
  await resolver.load(self._class_service_function, load_context)
@@ -5,7 +5,7 @@ from datetime import datetime
5
5
  from typing import Any, Optional, Union
6
6
 
7
7
  from google.protobuf.message import Message
8
- from grpclib import GRPCError, Status
8
+ from grpclib import GRPCError
9
9
  from synchronicity import classproperty
10
10
  from synchronicity.async_wrap import asynccontextmanager
11
11
 
@@ -126,10 +126,8 @@ class _DictManager:
126
126
  )
127
127
  try:
128
128
  await client.stub.DictGetOrCreate(req)
129
- except GRPCError as exc:
130
- if exc.status == Status.ALREADY_EXISTS and not allow_existing:
131
- raise AlreadyExistsError(exc.message)
132
- else:
129
+ except AlreadyExistsError:
130
+ if not allow_existing:
133
131
  raise
134
132
 
135
133
  @staticmethod