modal 1.3.1.dev35__tar.gz → 1.3.2.dev0__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.3.1.dev35 → modal-1.3.2.dev0}/PKG-INFO +1 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_server.py +1 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/task_command_router_client.py +3 -3
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/token.py +2 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/client.py +6 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/client.pyi +12 -2
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cls.py +57 -44
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cls.pyi +1 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/config.py +0 -2
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/flash.py +84 -36
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/flash.pyi +14 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/queue.py +11 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/queue.pyi +33 -3
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/sandbox.py +3 -6
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api.proto +2 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2.py +158 -158
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2.pyi +4 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_version/__init__.py +1 -1
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/LICENSE +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/README.md +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/__main__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_billing.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_functions.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_ipython.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_load_context.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_location.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_object.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_output.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_partial_function.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_pty.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_resolver.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_resources.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_serialization.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_traceback.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_tunnel.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_type_manager.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_watcher.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/app.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/app.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/billing.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/README.md +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/call_graph.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/_download.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/app.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/config.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/container.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/dict.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/environment.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/launch.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/profile.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/queues.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/run.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/secret.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/shell.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/utils.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/volume.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/container_process.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/container_process.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/dict.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/dict.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/environments.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/environments.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/exception.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_io.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_io.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/functions.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/functions.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/gpu.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/image.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/image.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/io_streams.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/mount.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/mount.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/network_file_system.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/object.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/object.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/output.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/parallel_map.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/partial_function.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/proxy.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/proxy.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/py.typed +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/retries.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/runner.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/runner.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/running_app.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/sandbox.pyi +3 -3
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/schedule.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/secret.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/secret.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/server.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/server.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/serving.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/serving.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/snapshot.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/stream_type.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/token_flow.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/volume.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/volume.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/py.typed +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router.proto +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_version/__main__.py +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/pyproject.toml +0 -0
- {modal-1.3.1.dev35 → modal-1.3.2.dev0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ import grpclib.events
|
|
|
15
15
|
from grpclib import GRPCError, Status
|
|
16
16
|
from grpclib.exceptions import StreamTerminatedError
|
|
17
17
|
|
|
18
|
-
from modal.config import
|
|
18
|
+
from modal.config import logger
|
|
19
19
|
from modal.exception import ConflictError, ExecTimeoutError
|
|
20
20
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
21
21
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
@@ -152,8 +152,8 @@ class TaskCommandRouterClient:
|
|
|
152
152
|
ssl_context = ssl.create_default_context()
|
|
153
153
|
|
|
154
154
|
# Allow insecure TLS when explicitly enabled via config.
|
|
155
|
-
if
|
|
156
|
-
logger.warning("Using insecure TLS for task command router
|
|
155
|
+
if server_client._is_localhost:
|
|
156
|
+
logger.warning("Using insecure TLS for task command router because server client points to localhost")
|
|
157
157
|
ssl_context.check_hostname = False
|
|
158
158
|
ssl_context.verify_mode = ssl.CERT_NONE
|
|
159
159
|
|
|
@@ -87,6 +87,8 @@ async def info():
|
|
|
87
87
|
console.print(f"[dim](Using {env_vars_str} environment variable{plural})[/dim]")
|
|
88
88
|
|
|
89
89
|
console.print(f"[bold]Token:[/bold] {resp.token_id}")
|
|
90
|
+
if resp.token_name:
|
|
91
|
+
console.print(f"[bold]Name:[/bold] {resp.token_name}")
|
|
90
92
|
console.print(f"[bold]Workspace:[/bold] {resp.workspace_name} [dim]({resp.workspace_id})[/dim]")
|
|
91
93
|
|
|
92
94
|
if resp.HasField("user_identity"):
|
|
@@ -100,6 +100,12 @@ class _Client:
|
|
|
100
100
|
def is_closed(self) -> bool:
|
|
101
101
|
return self._closed
|
|
102
102
|
|
|
103
|
+
@property
|
|
104
|
+
def _is_localhost(self) -> bool:
|
|
105
|
+
"""Returns True if the server URL points to localhost."""
|
|
106
|
+
hostname = urllib.parse.urlparse(self.server_url).hostname
|
|
107
|
+
return hostname in {"localhost", "127.0.0.1", "::1", "172.21.0.1"}
|
|
108
|
+
|
|
103
109
|
@property
|
|
104
110
|
def stub(self) -> modal_api_grpc.ModalClientModal:
|
|
105
111
|
"""mdmd:hidden
|
|
@@ -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.3.
|
|
38
|
+
version: str = "1.3.2.dev0",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -43,6 +43,11 @@ class _Client:
|
|
|
43
43
|
...
|
|
44
44
|
|
|
45
45
|
def is_closed(self) -> bool: ...
|
|
46
|
+
@property
|
|
47
|
+
def _is_localhost(self) -> bool:
|
|
48
|
+
"""Returns True if the server URL points to localhost."""
|
|
49
|
+
...
|
|
50
|
+
|
|
46
51
|
@property
|
|
47
52
|
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
|
|
48
53
|
"""mdmd:hidden
|
|
@@ -166,7 +171,7 @@ class Client:
|
|
|
166
171
|
server_url: str,
|
|
167
172
|
client_type: int,
|
|
168
173
|
credentials: typing.Optional[tuple[str, str]],
|
|
169
|
-
version: str = "1.3.
|
|
174
|
+
version: str = "1.3.2.dev0",
|
|
170
175
|
):
|
|
171
176
|
"""mdmd:hidden
|
|
172
177
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -174,6 +179,11 @@ class Client:
|
|
|
174
179
|
...
|
|
175
180
|
|
|
176
181
|
def is_closed(self) -> bool: ...
|
|
182
|
+
@property
|
|
183
|
+
def _is_localhost(self) -> bool:
|
|
184
|
+
"""Returns True if the server URL points to localhost."""
|
|
185
|
+
...
|
|
186
|
+
|
|
177
187
|
@property
|
|
178
188
|
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
|
|
179
189
|
"""mdmd:hidden
|
|
@@ -99,21 +99,27 @@ class _ServiceOptions:
|
|
|
99
99
|
"""Implement protobuf-like MergeFrom semantics for this dataclass.
|
|
100
100
|
|
|
101
101
|
This mostly exists to support "stacking" of `.with_options()` calls.
|
|
102
|
+
Returns a new _ServiceOptions instance without modifying self.
|
|
102
103
|
"""
|
|
104
|
+
# Create a shallow copy of self to start with
|
|
105
|
+
merged = dataclasses.replace(self)
|
|
106
|
+
|
|
103
107
|
# Don't use dataclasses.asdict() because it does a deepcopy(), which chokes on a hydrated object
|
|
104
108
|
new_options_dict = {k.name: getattr(new_options, k.name) for k in dataclasses.fields(new_options)}
|
|
105
109
|
|
|
106
110
|
# Resources needs special merge handling because individual fields are parameters in the public API
|
|
107
111
|
merged_resources = api_pb2.Resources()
|
|
108
|
-
if
|
|
109
|
-
merged_resources.MergeFrom(
|
|
112
|
+
if merged.resources:
|
|
113
|
+
merged_resources.MergeFrom(merged.resources)
|
|
110
114
|
if new_resources := new_options_dict.pop("resources"):
|
|
111
115
|
merged_resources.MergeFrom(new_resources)
|
|
112
|
-
|
|
116
|
+
merged.resources = merged_resources
|
|
113
117
|
|
|
114
118
|
for key, value in new_options_dict.items():
|
|
115
119
|
if value: # Only overwrite data when the value was set in the new options
|
|
116
|
-
setattr(
|
|
120
|
+
setattr(merged, key, value)
|
|
121
|
+
|
|
122
|
+
return merged
|
|
117
123
|
|
|
118
124
|
|
|
119
125
|
def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name: str):
|
|
@@ -743,32 +749,6 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
743
749
|
" please use the `.with_concurrency` method instead.",
|
|
744
750
|
)
|
|
745
751
|
|
|
746
|
-
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
747
|
-
# this is a bit confusing, the cls will always have the same metadata
|
|
748
|
-
# since it has the same *class* service function (i.e. "template")
|
|
749
|
-
# But the (instance) service function for each Obj will be different
|
|
750
|
-
# since it will rebind to whatever `_options` have been assigned on
|
|
751
|
-
# the particular Cls parent
|
|
752
|
-
if not self.is_hydrated:
|
|
753
|
-
# this should only happen for Cls.from_name instances
|
|
754
|
-
# other classes should already be hydrated!
|
|
755
|
-
await resolver.load(self, load_context)
|
|
756
|
-
|
|
757
|
-
new_cls._initialize_from_other(self)
|
|
758
|
-
|
|
759
|
-
def _deps():
|
|
760
|
-
return []
|
|
761
|
-
|
|
762
|
-
cls = _Cls._from_loader(
|
|
763
|
-
_load_from_base,
|
|
764
|
-
rep=f"{self._name}.with_options(...)",
|
|
765
|
-
is_another_app=True,
|
|
766
|
-
deps=_deps,
|
|
767
|
-
load_context_overrides=self._load_context_overrides,
|
|
768
|
-
hydrate_lazily=True,
|
|
769
|
-
)
|
|
770
|
-
cls._initialize_from_other(self)
|
|
771
|
-
|
|
772
752
|
# Validate volumes
|
|
773
753
|
validated_volumes = validate_volumes(volumes)
|
|
774
754
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
@@ -801,8 +781,37 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
801
781
|
target_concurrent_inputs=allow_concurrent_inputs,
|
|
802
782
|
)
|
|
803
783
|
|
|
804
|
-
|
|
805
|
-
|
|
784
|
+
combined_options = self._options.merge_options(new_options)
|
|
785
|
+
|
|
786
|
+
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
787
|
+
# this is a bit confusing, the cls will always have the same metadata
|
|
788
|
+
# since it has the same *class* service function (i.e. "template")
|
|
789
|
+
# But the (instance) service function for each Obj will be different
|
|
790
|
+
# since it will rebind to whatever `_options` have been assigned on
|
|
791
|
+
# the particular Cls parent
|
|
792
|
+
if not self.is_hydrated:
|
|
793
|
+
# this should only happen for Cls.from_name instances
|
|
794
|
+
# other classes should already be hydrated!
|
|
795
|
+
await resolver.load(self, load_context)
|
|
796
|
+
|
|
797
|
+
new_cls._initialize_from_other(self)
|
|
798
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
799
|
+
new_cls._options = combined_options
|
|
800
|
+
|
|
801
|
+
def _deps():
|
|
802
|
+
return []
|
|
803
|
+
|
|
804
|
+
new_cls = _Cls._from_loader(
|
|
805
|
+
_load_from_base,
|
|
806
|
+
rep=f"{self._name}.with_options(...)",
|
|
807
|
+
is_another_app=True,
|
|
808
|
+
deps=_deps,
|
|
809
|
+
load_context_overrides=self._load_context_overrides,
|
|
810
|
+
hydrate_lazily=True,
|
|
811
|
+
)
|
|
812
|
+
new_cls._initialize_from_other(self)
|
|
813
|
+
new_cls._options = combined_options
|
|
814
|
+
return new_cls
|
|
806
815
|
|
|
807
816
|
def with_concurrency(self: "_Cls", *, max_inputs: int, target_inputs: Optional[int] = None) -> "_Cls":
|
|
808
817
|
"""Create an instance of the Cls with input concurrency enabled or overridden with new values.
|
|
@@ -815,16 +824,20 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
815
824
|
ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
|
|
816
825
|
```
|
|
817
826
|
"""
|
|
827
|
+
concurrency_options = _ServiceOptions(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
|
|
828
|
+
combined_options = self._options.merge_options(concurrency_options)
|
|
818
829
|
|
|
819
830
|
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
820
831
|
if not self.is_hydrated:
|
|
821
832
|
await resolver.load(self, load_context)
|
|
822
833
|
new_cls._initialize_from_other(self)
|
|
834
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
835
|
+
new_cls._options = combined_options
|
|
823
836
|
|
|
824
837
|
def _deps():
|
|
825
838
|
return []
|
|
826
839
|
|
|
827
|
-
|
|
840
|
+
new_cls = _Cls._from_loader(
|
|
828
841
|
_load_from_base,
|
|
829
842
|
rep=f"{self._name}.with_concurrency(...)",
|
|
830
843
|
is_another_app=True,
|
|
@@ -832,11 +845,9 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
832
845
|
load_context_overrides=self._load_context_overrides,
|
|
833
846
|
hydrate_lazily=True,
|
|
834
847
|
)
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
cls._options.merge_options(concurrency_options)
|
|
839
|
-
return cls
|
|
848
|
+
new_cls._initialize_from_other(self)
|
|
849
|
+
new_cls._options = combined_options
|
|
850
|
+
return new_cls
|
|
840
851
|
|
|
841
852
|
def with_batching(self: "_Cls", *, max_batch_size: int, wait_ms: int) -> "_Cls":
|
|
842
853
|
"""Create an instance of the Cls with dynamic batching enabled or overridden with new values.
|
|
@@ -849,16 +860,20 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
849
860
|
ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
|
|
850
861
|
```
|
|
851
862
|
"""
|
|
863
|
+
batching_options = _ServiceOptions(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
|
|
864
|
+
combined_options = self._options.merge_options(batching_options)
|
|
852
865
|
|
|
853
866
|
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
854
867
|
if not self.is_hydrated:
|
|
855
868
|
await resolver.load(self, load_context)
|
|
856
869
|
new_cls._initialize_from_other(self)
|
|
870
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
871
|
+
new_cls._options = combined_options
|
|
857
872
|
|
|
858
873
|
def _deps():
|
|
859
874
|
return []
|
|
860
875
|
|
|
861
|
-
|
|
876
|
+
new_cls = _Cls._from_loader(
|
|
862
877
|
_load_from_base,
|
|
863
878
|
rep=f"{self._name}.with_batching(...)",
|
|
864
879
|
is_another_app=True,
|
|
@@ -866,11 +881,9 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
866
881
|
load_context_overrides=self._load_context_overrides,
|
|
867
882
|
hydrate_lazily=True,
|
|
868
883
|
)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
cls._options.merge_options(batching_options)
|
|
873
|
-
return cls
|
|
884
|
+
new_cls._initialize_from_other(self)
|
|
885
|
+
new_cls._options = combined_options
|
|
886
|
+
return new_cls
|
|
874
887
|
|
|
875
888
|
@synchronizer.no_input_translation
|
|
876
889
|
def __call__(self, *args, **kwargs) -> _Obj:
|
|
@@ -251,8 +251,6 @@ _SETTINGS = {
|
|
|
251
251
|
"traceback": _Setting(False, transform=_to_boolean),
|
|
252
252
|
"image_builder_version": _Setting(),
|
|
253
253
|
"strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
|
|
254
|
-
# Allow insecure TLS for the task command router when running locally (testing/dev only)
|
|
255
|
-
"task_command_router_insecure": _Setting(False, transform=_to_boolean),
|
|
256
254
|
"snapshot_debug": _Setting(False, transform=_to_boolean),
|
|
257
255
|
"cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
|
|
258
256
|
"build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
|
|
@@ -15,6 +15,7 @@ from modal.cls import _Cls
|
|
|
15
15
|
from modal.dict import _Dict
|
|
16
16
|
from modal_proto import api_pb2
|
|
17
17
|
|
|
18
|
+
from .._runtime.container_io_manager import UserException
|
|
18
19
|
from .._server import validate_http_server_config
|
|
19
20
|
from .._tunnel import _forward as _forward_tunnel
|
|
20
21
|
from .._utils.async_utils import synchronize_api, synchronizer
|
|
@@ -45,14 +46,12 @@ class _FlashManager:
|
|
|
45
46
|
self.exit_grace_period = exit_grace_period
|
|
46
47
|
self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
|
|
47
48
|
self.stopped = False
|
|
48
|
-
self.
|
|
49
|
+
self.num_heartbeat_failures = 0
|
|
49
50
|
self.task_id = os.environ["MODAL_TASK_ID"]
|
|
50
51
|
|
|
51
52
|
async def is_port_connection_healthy(
|
|
52
53
|
self, process: Optional[subprocess.Popen], timeout: float = 0.5
|
|
53
54
|
) -> tuple[bool, Optional[Exception]]:
|
|
54
|
-
import socket
|
|
55
|
-
|
|
56
55
|
start_time = time.monotonic()
|
|
57
56
|
|
|
58
57
|
def check_process_is_running() -> Optional[Exception]:
|
|
@@ -64,10 +63,16 @@ class _FlashManager:
|
|
|
64
63
|
try:
|
|
65
64
|
if error := check_process_is_running():
|
|
66
65
|
return False, error
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
_, writer = await asyncio.wait_for(asyncio.open_connection("localhost", self.port), timeout=0.5)
|
|
67
|
+
try:
|
|
68
|
+
writer.close()
|
|
69
|
+
await writer.wait_closed()
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
return True, None
|
|
73
|
+
except asyncio.CancelledError:
|
|
74
|
+
raise
|
|
75
|
+
except (OSError, asyncio.TimeoutError):
|
|
71
76
|
await asyncio.sleep(0.1)
|
|
72
77
|
|
|
73
78
|
return False, Exception(f"Waited too long for port {self.port} to start accepting connections")
|
|
@@ -78,9 +83,25 @@ class _FlashManager:
|
|
|
78
83
|
host = parsed_url.hostname
|
|
79
84
|
port = parsed_url.port or 443
|
|
80
85
|
|
|
86
|
+
try:
|
|
87
|
+
await self._wait_for_port_success(host, port)
|
|
88
|
+
except (Exception, KeyboardInterrupt, asyncio.CancelledError):
|
|
89
|
+
await self._deregister()
|
|
90
|
+
await self.tunnel_manager.__aexit__(*sys.exc_info())
|
|
91
|
+
raise
|
|
92
|
+
|
|
81
93
|
self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
|
|
82
94
|
self.drain_task = asyncio.create_task(self._drain_container())
|
|
83
95
|
|
|
96
|
+
async def _deregister(self):
|
|
97
|
+
await asyncio.shield(
|
|
98
|
+
self.client.stub.FlashContainerDeregister(
|
|
99
|
+
api_pb2.FlashContainerDeregisterRequest(),
|
|
100
|
+
timeout=2,
|
|
101
|
+
retry=None,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
84
105
|
async def _drain_container(self):
|
|
85
106
|
"""
|
|
86
107
|
Background task that checks if we've encountered too many failures and drains the container if so.
|
|
@@ -88,7 +109,7 @@ class _FlashManager:
|
|
|
88
109
|
while True:
|
|
89
110
|
try:
|
|
90
111
|
# Check if the container should be drained (e.g., too many failures)
|
|
91
|
-
if self.
|
|
112
|
+
if self.num_heartbeat_failures > _MAX_FAILURES:
|
|
92
113
|
logger.warning(
|
|
93
114
|
f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
|
|
94
115
|
)
|
|
@@ -111,9 +132,37 @@ class _FlashManager:
|
|
|
111
132
|
logger.warning("[Modal Flash] Shutting down...")
|
|
112
133
|
return
|
|
113
134
|
|
|
114
|
-
async def
|
|
115
|
-
first_registration = True
|
|
135
|
+
async def _wait_for_port_success(self, host: str, port: int) -> bool:
|
|
116
136
|
start_time = time.monotonic()
|
|
137
|
+
while time.monotonic() - start_time < self.startup_timeout:
|
|
138
|
+
try:
|
|
139
|
+
port_check_resp, _ = await self.is_port_connection_healthy(process=self.process)
|
|
140
|
+
if port_check_resp:
|
|
141
|
+
resp = await self.client.stub.FlashContainerRegister(
|
|
142
|
+
api_pb2.FlashContainerRegisterRequest(
|
|
143
|
+
priority=10,
|
|
144
|
+
weight=5,
|
|
145
|
+
host=host,
|
|
146
|
+
port=port,
|
|
147
|
+
),
|
|
148
|
+
timeout=10,
|
|
149
|
+
retry=None,
|
|
150
|
+
)
|
|
151
|
+
logger.info(f"Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}")
|
|
152
|
+
return True
|
|
153
|
+
except asyncio.CancelledError:
|
|
154
|
+
logger.warning("Waited too long for port to start accepting connections. Shutting down...")
|
|
155
|
+
raise
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error(f"Error waiting for port to start accepting connections: {e}")
|
|
158
|
+
try:
|
|
159
|
+
await asyncio.sleep(1)
|
|
160
|
+
except asyncio.CancelledError:
|
|
161
|
+
logger.warning("Waited too long for port to start accepting connections. Shutting down...")
|
|
162
|
+
raise
|
|
163
|
+
raise TimeoutError("Waited too long for port to start accepting connections. Shutting down...")
|
|
164
|
+
|
|
165
|
+
async def _run_heartbeat(self, host: str, port: int):
|
|
117
166
|
while True:
|
|
118
167
|
try:
|
|
119
168
|
port_check_resp, port_check_error = await self.is_port_connection_healthy(process=self.process)
|
|
@@ -128,32 +177,24 @@ class _FlashManager:
|
|
|
128
177
|
timeout=10,
|
|
129
178
|
retry=None,
|
|
130
179
|
)
|
|
131
|
-
self.
|
|
132
|
-
if first_registration:
|
|
133
|
-
logger.warning(
|
|
134
|
-
f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
|
|
135
|
-
)
|
|
136
|
-
first_registration = False
|
|
180
|
+
self.num_heartbeat_failures = 0
|
|
137
181
|
else:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
145
|
-
self.num_failures += 1
|
|
146
|
-
await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
|
|
182
|
+
logger.error(
|
|
183
|
+
f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
|
|
184
|
+
f"due to error: {port_check_error}, num_heartbeat_failures: {self.num_heartbeat_failures}"
|
|
185
|
+
)
|
|
186
|
+
self.num_heartbeat_failures += 1
|
|
187
|
+
await self._deregister()
|
|
147
188
|
except asyncio.CancelledError:
|
|
148
189
|
logger.warning("[Modal Flash] Shutting down...")
|
|
190
|
+
await self._deregister()
|
|
149
191
|
break
|
|
150
192
|
except Exception as e:
|
|
151
193
|
logger.error(f"[Modal Flash] Heartbeat failed: {e}")
|
|
152
|
-
|
|
153
194
|
try:
|
|
154
195
|
await asyncio.sleep(1)
|
|
155
196
|
except asyncio.CancelledError:
|
|
156
|
-
|
|
197
|
+
await self._deregister()
|
|
157
198
|
break
|
|
158
199
|
|
|
159
200
|
def get_container_url(self):
|
|
@@ -162,11 +203,14 @@ class _FlashManager:
|
|
|
162
203
|
|
|
163
204
|
async def stop(self):
|
|
164
205
|
try:
|
|
165
|
-
self.heartbeat_task
|
|
206
|
+
if self.heartbeat_task:
|
|
207
|
+
self.heartbeat_task.cancel()
|
|
208
|
+
try:
|
|
209
|
+
await asyncio.wait_for(self.heartbeat_task, timeout=5)
|
|
210
|
+
except (asyncio.TimeoutError, asyncio.CancelledError):
|
|
211
|
+
logger.warning("[Modal Flash] Heartbeat task did not stop within 5s.")
|
|
166
212
|
except Exception as e:
|
|
167
213
|
logger.error(f"[Modal Flash] Error stopping: {e}")
|
|
168
|
-
|
|
169
|
-
await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
|
|
170
214
|
self.stopped = True
|
|
171
215
|
logger.warning(f"[Modal Flash] No longer accepting new requests on {self.tunnel.url}.")
|
|
172
216
|
|
|
@@ -716,12 +760,16 @@ class _FlashContainerEntry:
|
|
|
716
760
|
|
|
717
761
|
def enter(self):
|
|
718
762
|
if self.http_config != api_pb2.HTTPConfig():
|
|
719
|
-
|
|
720
|
-
self.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
763
|
+
try:
|
|
764
|
+
self.flash_manager = flash_forward(
|
|
765
|
+
self.http_config.port,
|
|
766
|
+
startup_timeout=self.http_config.startup_timeout,
|
|
767
|
+
exit_grace_period=self.http_config.exit_grace_period,
|
|
768
|
+
h2_enabled=self.http_config.h2_enabled,
|
|
769
|
+
)
|
|
770
|
+
except Exception as e:
|
|
771
|
+
logger.warning(f"[Modal Flash] Startup failed: {e}")
|
|
772
|
+
raise UserException()
|
|
725
773
|
|
|
726
774
|
def stop(self):
|
|
727
775
|
if self.flash_manager:
|
|
@@ -22,10 +22,12 @@ class _FlashManager:
|
|
|
22
22
|
self, process: typing.Optional[subprocess.Popen], timeout: float = 0.5
|
|
23
23
|
) -> tuple[bool, typing.Optional[Exception]]: ...
|
|
24
24
|
async def _start(self): ...
|
|
25
|
+
async def _deregister(self): ...
|
|
25
26
|
async def _drain_container(self):
|
|
26
27
|
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
|
27
28
|
...
|
|
28
29
|
|
|
30
|
+
async def _wait_for_port_success(self, host: str, port: int) -> bool: ...
|
|
29
31
|
async def _run_heartbeat(self, host: str, port: int): ...
|
|
30
32
|
def get_container_url(self): ...
|
|
31
33
|
async def stop(self): ...
|
|
@@ -59,6 +61,12 @@ class FlashManager:
|
|
|
59
61
|
|
|
60
62
|
_start: ___start_spec
|
|
61
63
|
|
|
64
|
+
class ___deregister_spec(typing_extensions.Protocol):
|
|
65
|
+
def __call__(self, /): ...
|
|
66
|
+
async def aio(self, /): ...
|
|
67
|
+
|
|
68
|
+
_deregister: ___deregister_spec
|
|
69
|
+
|
|
62
70
|
class ___drain_container_spec(typing_extensions.Protocol):
|
|
63
71
|
def __call__(self, /):
|
|
64
72
|
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
|
@@ -70,6 +78,12 @@ class FlashManager:
|
|
|
70
78
|
|
|
71
79
|
_drain_container: ___drain_container_spec
|
|
72
80
|
|
|
81
|
+
class ___wait_for_port_success_spec(typing_extensions.Protocol):
|
|
82
|
+
def __call__(self, /, host: str, port: int) -> bool: ...
|
|
83
|
+
async def aio(self, /, host: str, port: int) -> bool: ...
|
|
84
|
+
|
|
85
|
+
_wait_for_port_success: ___wait_for_port_success_spec
|
|
86
|
+
|
|
73
87
|
class ___run_heartbeat_spec(typing_extensions.Protocol):
|
|
74
88
|
def __call__(self, /, host: str, port: int): ...
|
|
75
89
|
async def aio(self, /, host: str, port: int): ...
|
|
@@ -469,7 +469,17 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
469
469
|
|
|
470
470
|
@live_method
|
|
471
471
|
async def clear(self, *, partition: Optional[str] = None, all: bool = False) -> None:
|
|
472
|
-
"""Clear the contents of a single partition or all partitions.
|
|
472
|
+
"""Clear the contents of a single partition or all partitions.
|
|
473
|
+
|
|
474
|
+
Warning: this is a destructive operation and will irrevocably delete data.
|
|
475
|
+
|
|
476
|
+
**Examples:**
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
q = modal.Queue.from_name("my-queue", create_if_missing=True)
|
|
480
|
+
q.clear()
|
|
481
|
+
```
|
|
482
|
+
"""
|
|
473
483
|
if partition and all:
|
|
474
484
|
raise InvalidError("Partition must be null when requesting to clear all.")
|
|
475
485
|
request = api_pb2.QueueClearRequest(
|
|
@@ -510,7 +510,17 @@ class _Queue(modal._object._Object):
|
|
|
510
510
|
self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
|
|
511
511
|
) -> list[typing.Any]: ...
|
|
512
512
|
async def clear(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
|
|
513
|
-
"""Clear the contents of a single partition or all partitions.
|
|
513
|
+
"""Clear the contents of a single partition or all partitions.
|
|
514
|
+
|
|
515
|
+
Warning: this is a destructive operation and will irrevocably delete data.
|
|
516
|
+
|
|
517
|
+
**Examples:**
|
|
518
|
+
|
|
519
|
+
```python
|
|
520
|
+
q = modal.Queue.from_name("my-queue", create_if_missing=True)
|
|
521
|
+
q.clear()
|
|
522
|
+
```
|
|
523
|
+
"""
|
|
514
524
|
...
|
|
515
525
|
|
|
516
526
|
async def get(
|
|
@@ -840,11 +850,31 @@ class Queue(modal.object.Object):
|
|
|
840
850
|
|
|
841
851
|
class __clear_spec(typing_extensions.Protocol):
|
|
842
852
|
def __call__(self, /, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
|
|
843
|
-
"""Clear the contents of a single partition or all partitions.
|
|
853
|
+
"""Clear the contents of a single partition or all partitions.
|
|
854
|
+
|
|
855
|
+
Warning: this is a destructive operation and will irrevocably delete data.
|
|
856
|
+
|
|
857
|
+
**Examples:**
|
|
858
|
+
|
|
859
|
+
```python
|
|
860
|
+
q = modal.Queue.from_name("my-queue", create_if_missing=True)
|
|
861
|
+
q.clear()
|
|
862
|
+
```
|
|
863
|
+
"""
|
|
844
864
|
...
|
|
845
865
|
|
|
846
866
|
async def aio(self, /, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
|
|
847
|
-
"""Clear the contents of a single partition or all partitions.
|
|
867
|
+
"""Clear the contents of a single partition or all partitions.
|
|
868
|
+
|
|
869
|
+
Warning: this is a destructive operation and will irrevocably delete data.
|
|
870
|
+
|
|
871
|
+
**Examples:**
|
|
872
|
+
|
|
873
|
+
```python
|
|
874
|
+
q = modal.Queue.from_name("my-queue", create_if_missing=True)
|
|
875
|
+
q.clear()
|
|
876
|
+
```
|
|
877
|
+
"""
|
|
848
878
|
...
|
|
849
879
|
|
|
850
880
|
clear: __clear_spec
|
|
@@ -75,8 +75,7 @@ def _validate_exec_args(args: Sequence[str]) -> None:
|
|
|
75
75
|
total_arg_len = sum(len(arg) for arg in args)
|
|
76
76
|
if total_arg_len > ARG_MAX_BYTES:
|
|
77
77
|
raise InvalidError(
|
|
78
|
-
f"Total length of CMD arguments
|
|
79
|
-
f"Got {total_arg_len} bytes."
|
|
78
|
+
f"Total length of CMD arguments cannot exceed {ARG_MAX_BYTES} bytes (ARG_MAX). Got {total_arg_len} bytes."
|
|
80
79
|
)
|
|
81
80
|
|
|
82
81
|
|
|
@@ -307,6 +306,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
307
306
|
h2_ports: Sequence[int] = [],
|
|
308
307
|
# List of ports to tunnel into the sandbox without encryption.
|
|
309
308
|
unencrypted_ports: Sequence[int] = [],
|
|
309
|
+
# Allow connections to the Sandbox via a subdomain of this parent rather than a default Modal domain.
|
|
310
|
+
custom_domain: Optional[str] = None,
|
|
310
311
|
# Reference to a Modal Proxy to use in front of this Sandbox.
|
|
311
312
|
proxy: Optional[_Proxy] = None,
|
|
312
313
|
# Enable verbose logging for sandbox operations.
|
|
@@ -317,10 +318,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
317
318
|
client: Optional[_Client] = None,
|
|
318
319
|
environment_name: Optional[str] = None, # *DEPRECATED* Optionally override the default environment
|
|
319
320
|
pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
|
|
320
|
-
# If set, connections to this sandbox will be subdomains of this domain rather than the default.
|
|
321
|
-
# Custom domains must be configured manually by Modal. They are different from the custom domains
|
|
322
|
-
# in the Modal dashboard.
|
|
323
|
-
custom_domain: Optional[str] = None,
|
|
324
321
|
) -> "_Sandbox":
|
|
325
322
|
"""
|
|
326
323
|
Create a new Sandbox to run untrusted, arbitrary code.
|