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.
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/PKG-INFO +3 -3
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/README.md +1 -1
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/__init__.py +2 -2
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/__main__.py +4 -29
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_functions.py +3 -7
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_grpc_client.py +48 -28
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_tunnel.py +5 -9
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/grpc_utils.py +2 -5
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/task_command_router_client.py +52 -46
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/environment.py +2 -16
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/network_file_system.py +3 -15
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/utils.py +1 -13
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/volume.py +3 -15
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/client.pyi +2 -2
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cls.py +0 -6
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/dict.py +3 -5
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/exception.py +142 -16
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/functions.pyi +6 -6
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/image.py +5 -8
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/io_streams.py +3 -6
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/mount.py +0 -1
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/mount.pyi +3 -3
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/queue.py +10 -18
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/runner.py +1 -8
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/sandbox.py +6 -27
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/secret.py +4 -20
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/volume.py +14 -17
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/PKG-INFO +3 -3
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_version/__init__.py +1 -1
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/pyproject.toml +1 -1
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/LICENSE +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_billing.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_clustered_functions.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_ipython.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_load_context.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_location.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_object.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_output.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_partial_function.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_pty.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_resolver.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_resources.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_serialization.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_traceback.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_tunnel.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_type_manager.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/logger.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/_watcher.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/app.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/app.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/billing.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/README.md +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/builder/base-images.json +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/call_graph.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/_download.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/_traceback.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/app.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/cluster.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/config.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/container.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/dict.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/entry_point.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/import_refs.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/launch.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/profile.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/queues.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/run.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/secret.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/shell.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cli/token.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/client.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/cls.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/config.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/container_process.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/container_process.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/dict.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/environments.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/environments.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/flash.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/experimental/ipython.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_io.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_io.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/functions.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/gpu.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/image.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/io_streams.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/network_file_system.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/network_file_system.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/object.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/object.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/output.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/parallel_map.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/parallel_map.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/partial_function.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/partial_function.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/proxy.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/proxy.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/py.typed +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/queue.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/retries.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/runner.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/running_app.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/sandbox.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/schedule.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/scheduler_placement.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/secret.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/serving.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/serving.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/snapshot.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/snapshot.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/stream_type.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/token_flow.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/token_flow.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal/volume.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/__init__.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api.proto +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/py.typed +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev0 → modal-1.2.7.dev2}/modal_version/__main__.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
54
|
+
**This library requires Python 3.10 – 3.13.**
|
|
55
55
|
|
|
56
56
|
Install the package with `uv` or `pip`:
|
|
57
57
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
-
if sys.version_info[:2] < (3,
|
|
5
|
-
raise RuntimeError("This version of Modal requires at least Python 3.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
1130
|
-
if
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
290
|
-
async
|
|
291
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
130
|
-
if
|
|
131
|
-
raise AlreadyExistsError(exc.message)
|
|
132
|
-
else:
|
|
129
|
+
except AlreadyExistsError:
|
|
130
|
+
if not allow_existing:
|
|
133
131
|
raise
|
|
134
132
|
|
|
135
133
|
@staticmethod
|