modAL 1.2.5.dev27__tar.gz → 1.2.6__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.5.dev27 → modal-1.2.6}/PKG-INFO +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_output.py +2 -2
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/container_io_manager.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/user_code_imports.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_traceback.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/task_command_router_client.py +59 -80
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/network_file_system.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/run.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/volume.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/client.pyi +2 -10
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/container_process.py +2 -2
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/io_streams.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/mount.py +8 -8
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/task_command_router.proto +20 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/task_command_router_grpc.py +33 -0
- modal-1.2.6/modal_proto/task_command_router_pb2.py +180 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/task_command_router_pb2.pyi +51 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/task_command_router_pb2_grpc.py +69 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/task_command_router_pb2_grpc.pyi +25 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_version/__init__.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_version/__main__.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.6}/pyproject.toml +11 -1
- modal-1.2.5.dev27/modal_proto/task_command_router_pb2.py +0 -149
- {modal-1.2.5.dev27 → modal-1.2.6}/LICENSE +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/README.md +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/__main__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_billing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_clustered_functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_grpc_client.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_ipython.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_load_context.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_location.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_object.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_partial_function.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_pty.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_resolver.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_resources.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_serialization.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_tunnel.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_tunnel.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_type_manager.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/logger.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/_watcher.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/app.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/billing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/README.md +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/builder/base-images.json +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/call_graph.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/_download.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/_traceback.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/cluster.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/config.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/container.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/dict.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/entry_point.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/environment.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/import_refs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/launch.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/profile.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/queues.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/secret.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/shell.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/token.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cli/utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/client.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cls.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/cls.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/config.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/container_process.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/dict.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/dict.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/environments.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/environments.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/exception.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/experimental/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/experimental/flash.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/experimental/ipython.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/file_io.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/file_io.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/functions.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/gpu.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/image.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/image.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/io_streams.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/mount.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/network_file_system.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/network_file_system.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/object.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/object.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/output.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/parallel_map.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/parallel_map.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/partial_function.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/partial_function.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/proxy.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/proxy.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/py.typed +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/queue.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/queue.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/retries.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/runner.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/runner.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/running_app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/sandbox.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/sandbox.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/schedule.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/scheduler_placement.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/secret.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/secret.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/serving.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/serving.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/snapshot.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/snapshot.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/stream_type.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/token_flow.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/token_flow.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/volume.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal/volume.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api.proto +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/modal_proto/py.typed +0 -0
- {modal-1.2.5.dev27 → modal-1.2.6}/setup.cfg +0 -0
|
@@ -556,7 +556,7 @@ async def get_app_logs_loop(
|
|
|
556
556
|
async def stop_pty_shell():
|
|
557
557
|
nonlocal pty_shell_finish_event, pty_shell_input_task
|
|
558
558
|
if pty_shell_finish_event:
|
|
559
|
-
print("\r", end="") # move cursor to beginning of line
|
|
559
|
+
print("\r", end="") # move cursor to beginning of line # noqa: T201
|
|
560
560
|
pty_shell_finish_event.set()
|
|
561
561
|
pty_shell_finish_event = None
|
|
562
562
|
|
|
@@ -623,7 +623,7 @@ async def get_app_logs_loop(
|
|
|
623
623
|
# This corresponds to the `modal run -i` use case where a breakpoint
|
|
624
624
|
# triggers and the task drops into an interactive PTY mode
|
|
625
625
|
if pty_shell_finish_event:
|
|
626
|
-
print("ERROR: concurrent PTY shells are not supported.")
|
|
626
|
+
print("ERROR: concurrent PTY shells are not supported.") # noqa: T201
|
|
627
627
|
else:
|
|
628
628
|
pty_shell_stdout = output_mgr._stdout
|
|
629
629
|
pty_shell_finish_event = asyncio.Event()
|
|
@@ -1007,7 +1007,7 @@ class _ContainerIOManager:
|
|
|
1007
1007
|
# Start a debugger if the worker tells us to
|
|
1008
1008
|
if int(restored_state.get("snapshot_debug", 0)):
|
|
1009
1009
|
logger.debug("Entering snapshot debugger")
|
|
1010
|
-
breakpoint()
|
|
1010
|
+
breakpoint() # noqa: T100
|
|
1011
1011
|
|
|
1012
1012
|
# Local ContainerIOManager state.
|
|
1013
1013
|
for key in ["task_id", "function_id"]:
|
|
@@ -243,7 +243,7 @@ def create_breakpoint_wrapper(container_io_manager: "modal._runtime.container_io
|
|
|
243
243
|
def breakpoint_wrapper():
|
|
244
244
|
# note: it would be nice to not have breakpoint_wrapper() included in the backtrace
|
|
245
245
|
container_io_manager.interact(from_breakpoint=True)
|
|
246
|
-
import pdb
|
|
246
|
+
import pdb # noqa: T100
|
|
247
247
|
|
|
248
248
|
current_frame = inspect.currentframe()
|
|
249
249
|
if current_frame is not None:
|
|
@@ -119,7 +119,7 @@ def print_exception(exc: Optional[type[BaseException]], value: Optional[BaseExce
|
|
|
119
119
|
traceback.print_exception(exc, value, tb)
|
|
120
120
|
if sys.version_info < (3, 11) and value is not None: # type: ignore
|
|
121
121
|
notes = getattr(value, "__notes__", [])
|
|
122
|
-
print(*notes, sep="\n", file=sys.stderr)
|
|
122
|
+
print(*notes, sep="\n", file=sys.stderr) # noqa: T201
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
|
|
@@ -158,8 +158,10 @@ class TaskCommandRouterClient:
|
|
|
158
158
|
)
|
|
159
159
|
|
|
160
160
|
await connect_channel(channel)
|
|
161
|
+
loop = asyncio.get_running_loop()
|
|
162
|
+
jwt_refresh_lock = asyncio.Lock()
|
|
161
163
|
|
|
162
|
-
return cls(server_client, task_id, resp.url, resp.jwt, channel)
|
|
164
|
+
return cls(server_client, task_id, resp.url, resp.jwt, channel, loop, jwt_refresh_lock)
|
|
163
165
|
|
|
164
166
|
def __init__(
|
|
165
167
|
self,
|
|
@@ -168,12 +170,18 @@ class TaskCommandRouterClient:
|
|
|
168
170
|
server_url: str,
|
|
169
171
|
jwt: str,
|
|
170
172
|
channel: grpclib.client.Channel,
|
|
173
|
+
loop: asyncio.AbstractEventLoop,
|
|
174
|
+
jwt_refresh_lock: asyncio.Lock,
|
|
171
175
|
*,
|
|
172
176
|
stream_stdio_retry_delay_secs: float = 0.01,
|
|
173
177
|
stream_stdio_retry_delay_factor: float = 2,
|
|
174
178
|
stream_stdio_max_retries: int = 10,
|
|
175
179
|
) -> None:
|
|
176
180
|
"""Callers should not use this directly. Use TaskCommandRouterClient.try_init() instead."""
|
|
181
|
+
# Record the loop this instance is bound to so __del__ can safely schedule cleanup
|
|
182
|
+
# even if finalization happens from a different thread (e.g. via synchronicity).
|
|
183
|
+
self._loop = loop
|
|
184
|
+
|
|
177
185
|
# Attach bearer token on all requests to the worker-side router service.
|
|
178
186
|
self._server_client = server_client
|
|
179
187
|
self._task_id = task_id
|
|
@@ -187,12 +195,10 @@ class TaskCommandRouterClient:
|
|
|
187
195
|
|
|
188
196
|
# JWT refresh coordination
|
|
189
197
|
self._jwt_exp: Optional[float] = _parse_jwt_expiration(jwt)
|
|
190
|
-
|
|
191
|
-
self.
|
|
192
|
-
self._closed = False
|
|
198
|
+
# This is passed in as an argument to ensure it's created from within the correct event loop.
|
|
199
|
+
self._jwt_refresh_lock = jwt_refresh_lock
|
|
193
200
|
|
|
194
|
-
|
|
195
|
-
self._jwt_refresh_task = asyncio.create_task(self._jwt_refresh_loop())
|
|
201
|
+
self._closed = False
|
|
196
202
|
|
|
197
203
|
async def send_request(event: grpclib.events.SendRequest) -> None:
|
|
198
204
|
# This will get the most recent JWT for every request. No need to
|
|
@@ -205,29 +211,40 @@ class TaskCommandRouterClient:
|
|
|
205
211
|
self._stub = TaskCommandRouterStub(self._channel)
|
|
206
212
|
|
|
207
213
|
def __del__(self) -> None:
|
|
208
|
-
"""
|
|
209
|
-
|
|
214
|
+
"""Best-effort cleanup if the caller forgot to close().
|
|
215
|
+
|
|
216
|
+
This object is typically used through synchronicity wrappers, which means this finalizer
|
|
217
|
+
may run on a different thread than the event loop that owns the channel. Closing the
|
|
218
|
+
channel is therefore scheduled onto the owning loop using call_soon_threadsafe.
|
|
219
|
+
|
|
220
|
+
Use getattr in the event that attributes are not yet initialized or the
|
|
221
|
+
object is in a half-torn-down state.
|
|
222
|
+
"""
|
|
223
|
+
if getattr(self, "_closed", False):
|
|
210
224
|
return
|
|
225
|
+
self._closed = True
|
|
211
226
|
|
|
212
|
-
self
|
|
227
|
+
channel = getattr(self, "_channel", None)
|
|
228
|
+
if channel is None:
|
|
229
|
+
return
|
|
213
230
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
loop = getattr(self, "_loop", None)
|
|
232
|
+
|
|
233
|
+
if loop is not None and not loop.is_closed():
|
|
234
|
+
try:
|
|
235
|
+
loop.call_soon_threadsafe(channel.close)
|
|
236
|
+
except Exception:
|
|
237
|
+
# call_soon_threadsafe could throw if the loop is torn down
|
|
238
|
+
# after calling is_closed. This is safe to ignore, and we don't
|
|
239
|
+
# want to raise an exception from a destructor.
|
|
240
|
+
pass
|
|
218
241
|
|
|
219
242
|
async def close(self) -> None:
|
|
220
|
-
"""Close the client
|
|
243
|
+
"""Close the client."""
|
|
221
244
|
if self._closed:
|
|
222
245
|
return
|
|
223
246
|
|
|
224
247
|
self._closed = True
|
|
225
|
-
self._jwt_refresh_task.cancel()
|
|
226
|
-
try:
|
|
227
|
-
logger.debug(f"Waiting for JWT refresh task to complete for exec with task ID {self._task_id}")
|
|
228
|
-
await self._jwt_refresh_task
|
|
229
|
-
except asyncio.CancelledError:
|
|
230
|
-
pass
|
|
231
248
|
self._channel.close()
|
|
232
249
|
|
|
233
250
|
async def exec_start(self, request: sr_pb2.TaskExecStartRequest) -> sr_pb2.TaskExecStartResponse:
|
|
@@ -370,10 +387,7 @@ class TaskCommandRouterClient:
|
|
|
370
387
|
raise ExecTimeoutError(f"Deadline exceeded while waiting for exec {exec_id}")
|
|
371
388
|
|
|
372
389
|
async def _refresh_jwt(self) -> None:
|
|
373
|
-
"""Refresh JWT from the server and update internal state.
|
|
374
|
-
|
|
375
|
-
Concurrency-safe: only one refresh runs at a time.
|
|
376
|
-
"""
|
|
390
|
+
"""Refresh JWT from the server and update internal state."""
|
|
377
391
|
async with self._jwt_refresh_lock:
|
|
378
392
|
if self._closed:
|
|
379
393
|
return
|
|
@@ -389,13 +403,14 @@ class TaskCommandRouterClient:
|
|
|
389
403
|
)
|
|
390
404
|
return
|
|
391
405
|
|
|
406
|
+
logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
|
|
392
407
|
resp = await fetch_command_router_access(self._server_client, self._task_id)
|
|
408
|
+
logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
|
|
409
|
+
|
|
393
410
|
# Ensure the server URL remains stable for the lifetime of this client.
|
|
394
411
|
assert resp.url == self._server_url, "Task router URL changed during session"
|
|
395
412
|
self._jwt = resp.jwt
|
|
396
413
|
self._jwt_exp = _parse_jwt_expiration(resp.jwt)
|
|
397
|
-
# Wake up the background loop to recompute its next sleep.
|
|
398
|
-
self._jwt_refresh_event.set()
|
|
399
414
|
|
|
400
415
|
async def _call_with_auth_retry(self, func, *args, **kwargs):
|
|
401
416
|
try:
|
|
@@ -407,47 +422,6 @@ class TaskCommandRouterClient:
|
|
|
407
422
|
return await func(*args, **kwargs)
|
|
408
423
|
raise
|
|
409
424
|
|
|
410
|
-
async def _jwt_refresh_loop(self) -> None:
|
|
411
|
-
"""Background task that refreshes JWT 30 seconds before expiration.
|
|
412
|
-
|
|
413
|
-
Uses an event to wake early when a manual refresh happens or token changes.
|
|
414
|
-
"""
|
|
415
|
-
while not self._closed:
|
|
416
|
-
try:
|
|
417
|
-
exp = self._jwt_exp
|
|
418
|
-
now = time.time()
|
|
419
|
-
if exp is None:
|
|
420
|
-
# Unknown expiration: re-check periodically or until event wakes us.
|
|
421
|
-
sleep_s = 60.0
|
|
422
|
-
else:
|
|
423
|
-
refresh_at = exp - 30.0
|
|
424
|
-
sleep_s = max(refresh_at - now, 0.0)
|
|
425
|
-
|
|
426
|
-
self._jwt_refresh_event.clear()
|
|
427
|
-
if sleep_s > 0:
|
|
428
|
-
try:
|
|
429
|
-
logger.debug(f"Waiting for JWT refresh for {sleep_s}s for exec with task ID {self._task_id}")
|
|
430
|
-
# Wait until it's time to refresh, unless woken early.
|
|
431
|
-
await asyncio.wait_for(self._jwt_refresh_event.wait(), timeout=sleep_s)
|
|
432
|
-
logger.debug(f"Stopped waiting for JWT refresh for exec with task ID {self._task_id}")
|
|
433
|
-
# Event fired (e.g., token changed) -> recompute timings.
|
|
434
|
-
continue
|
|
435
|
-
except asyncio.TimeoutError:
|
|
436
|
-
logger.debug(f"Done waiting for JWT refresh for exec with task ID {self._task_id}")
|
|
437
|
-
pass
|
|
438
|
-
|
|
439
|
-
# Time to refresh.
|
|
440
|
-
logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
|
|
441
|
-
await self._refresh_jwt()
|
|
442
|
-
except asyncio.CancelledError:
|
|
443
|
-
logger.debug(f"Cancelled JWT refresh loop for exec with task ID {self._task_id}")
|
|
444
|
-
break
|
|
445
|
-
except Exception as e:
|
|
446
|
-
# Exceptions here can stem from non-transient errors against the server sending
|
|
447
|
-
# the TaskGetCommandRouterAccess RPC, for instance, if the task has finished.
|
|
448
|
-
logger.debug(f"Background JWT refresh failed for exec with task ID {self._task_id}: {e}")
|
|
449
|
-
break
|
|
450
|
-
|
|
451
425
|
async def _stream_stdio(
|
|
452
426
|
self,
|
|
453
427
|
task_id: str,
|
|
@@ -463,7 +437,9 @@ class TaskCommandRouterClient:
|
|
|
463
437
|
delay_secs = self.stream_stdio_retry_delay_secs
|
|
464
438
|
delay_factor = self.stream_stdio_retry_delay_factor
|
|
465
439
|
num_retries_remaining = self.stream_stdio_max_retries
|
|
466
|
-
|
|
440
|
+
# Flag to prevent infinite auth retries in the event that the JWT
|
|
441
|
+
# refresh yields an invalid JWT somehow or that the JWT is otherwise invalid.
|
|
442
|
+
did_auth_retry = False
|
|
467
443
|
|
|
468
444
|
async def sleep_and_update_delay_and_num_retries_remaining(e: Exception):
|
|
469
445
|
nonlocal delay_secs, num_retries_remaining
|
|
@@ -487,25 +463,28 @@ class TaskCommandRouterClient:
|
|
|
487
463
|
file_descriptor=file_descriptor,
|
|
488
464
|
)
|
|
489
465
|
|
|
490
|
-
#
|
|
466
|
+
# Auth retry is scoped to a single refresh per streaming attempt. While auth metadata is
|
|
467
|
+
# sent on request start, UNAUTHENTICATED may sometimes surface during iteration,
|
|
468
|
+
# so we handle it at both send and receive boundaries.
|
|
491
469
|
try:
|
|
492
470
|
await s.send_message(req, end=True)
|
|
471
|
+
async for item in s:
|
|
472
|
+
# We successfully authenticated after a JWT refresh, reset the auth retry flag.
|
|
473
|
+
if did_auth_retry:
|
|
474
|
+
did_auth_retry = False
|
|
475
|
+
# Reset retry backoff after any successful chunk.
|
|
476
|
+
delay_secs = self.stream_stdio_retry_delay_secs
|
|
477
|
+
offset += len(item.data)
|
|
478
|
+
yield item
|
|
493
479
|
except GRPCError as exc:
|
|
494
|
-
if exc.status == Status.UNAUTHENTICATED and
|
|
480
|
+
if exc.status == Status.UNAUTHENTICATED and not did_auth_retry:
|
|
495
481
|
await self._refresh_jwt()
|
|
496
|
-
|
|
482
|
+
# Mark that we've retried authentication for this streaming attempt, to
|
|
483
|
+
# prevent subsequent retries.
|
|
484
|
+
did_auth_retry = True
|
|
497
485
|
continue
|
|
498
486
|
raise
|
|
499
487
|
|
|
500
|
-
# We successfully authenticated, reset the auth retry count.
|
|
501
|
-
num_auth_retries = 0
|
|
502
|
-
|
|
503
|
-
async for item in s:
|
|
504
|
-
# Reset retry backoff after any successful chunk.
|
|
505
|
-
delay_secs = self.stream_stdio_retry_delay_secs
|
|
506
|
-
offset += len(item.data)
|
|
507
|
-
yield item
|
|
508
|
-
|
|
509
488
|
# We successfully streamed all output.
|
|
510
489
|
return
|
|
511
490
|
except GRPCError as e:
|
|
@@ -292,7 +292,7 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
|
292
292
|
@click.pass_context
|
|
293
293
|
def f(ctx, *args, **kwargs):
|
|
294
294
|
if ctx.obj["detach"]:
|
|
295
|
-
print(
|
|
295
|
+
print( # noqa: T201
|
|
296
296
|
"Note that running a local entrypoint in detached mode only keeps the last "
|
|
297
297
|
"triggered Modal function alive after the parent process has been killed or disconnected."
|
|
298
298
|
)
|
|
@@ -148,7 +148,7 @@ async def ls(
|
|
|
148
148
|
if not json and not sys.stdout.isatty():
|
|
149
149
|
# Legacy behavior -- I am not sure why exactly we did this originally but I don't want to break it
|
|
150
150
|
for entry in entries:
|
|
151
|
-
print(entry.path)
|
|
151
|
+
print(entry.path) # noqa: T201
|
|
152
152
|
else:
|
|
153
153
|
rows = []
|
|
154
154
|
for entry in entries:
|
|
@@ -28,11 +28,7 @@ class _Client:
|
|
|
28
28
|
_snapshotted: bool
|
|
29
29
|
|
|
30
30
|
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
server_url: str,
|
|
33
|
-
client_type: int,
|
|
34
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.5.dev27",
|
|
31
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.2.6"
|
|
36
32
|
):
|
|
37
33
|
"""mdmd:hidden
|
|
38
34
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -159,11 +155,7 @@ class Client:
|
|
|
159
155
|
_snapshotted: bool
|
|
160
156
|
|
|
161
157
|
def __init__(
|
|
162
|
-
self,
|
|
163
|
-
server_url: str,
|
|
164
|
-
client_type: int,
|
|
165
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.5.dev27",
|
|
158
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.2.6"
|
|
167
159
|
):
|
|
168
160
|
"""mdmd:hidden
|
|
169
161
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -146,7 +146,7 @@ class _ContainerProcessThroughServer(Generic[T]):
|
|
|
146
146
|
async def attach(self):
|
|
147
147
|
"""mdmd:hidden"""
|
|
148
148
|
if platform.system() == "Windows":
|
|
149
|
-
print("interactive exec is not currently supported on Windows.")
|
|
149
|
+
print("interactive exec is not currently supported on Windows.") # noqa: T201
|
|
150
150
|
return
|
|
151
151
|
|
|
152
152
|
from ._output import make_console
|
|
@@ -347,7 +347,7 @@ class _ContainerProcessThroughCommandRouter(Generic[T]):
|
|
|
347
347
|
|
|
348
348
|
async def attach(self):
|
|
349
349
|
if platform.system() == "Windows":
|
|
350
|
-
print("interactive exec is not currently supported on Windows.")
|
|
350
|
+
print("interactive exec is not currently supported on Windows.") # noqa: T201
|
|
351
351
|
return
|
|
352
352
|
|
|
353
353
|
from ._output import make_console
|
|
@@ -183,7 +183,7 @@ class _StreamReaderThroughServer(Generic[T]):
|
|
|
183
183
|
async for message, batch_index in iterator:
|
|
184
184
|
if self._stream_type == StreamType.STDOUT and message:
|
|
185
185
|
# TODO: rearchitect this, since these bytes aren't necessarily decodable
|
|
186
|
-
print(message.decode("utf-8"), end="")
|
|
186
|
+
print(message.decode("utf-8"), end="") # noqa: T201
|
|
187
187
|
elif self._stream_type == StreamType.PIPE:
|
|
188
188
|
self._container_process_buffer.append(message)
|
|
189
189
|
|
|
@@ -775,13 +775,13 @@ async def _create_single_client_dependency_mount(
|
|
|
775
775
|
if check_if_exists:
|
|
776
776
|
try:
|
|
777
777
|
await Mount.from_name(mount_name, namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL).hydrate.aio(client)
|
|
778
|
-
print(f"➖ Found existing mount {mount_name} in global namespace.")
|
|
778
|
+
print(f"➖ Found existing mount {mount_name} in global namespace.") # noqa: T201
|
|
779
779
|
return
|
|
780
780
|
except modal.exception.NotFoundError:
|
|
781
781
|
pass
|
|
782
782
|
|
|
783
783
|
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpd:
|
|
784
|
-
print(f"📦 Building {mount_name}.")
|
|
784
|
+
print(f"📦 Building {mount_name}.") # noqa: T201
|
|
785
785
|
requirements = os.path.join(os.path.dirname(__file__), f"builder/{builder_version}.txt")
|
|
786
786
|
cmd = " ".join(
|
|
787
787
|
[
|
|
@@ -810,11 +810,11 @@ async def _create_single_client_dependency_mount(
|
|
|
810
810
|
await proc.wait()
|
|
811
811
|
if proc.returncode:
|
|
812
812
|
stdout, stderr = await proc.communicate()
|
|
813
|
-
print(stdout.decode("utf-8"))
|
|
814
|
-
print(stderr.decode("utf-8"))
|
|
813
|
+
print(stdout.decode("utf-8")) # noqa: T201
|
|
814
|
+
print(stderr.decode("utf-8")) # noqa: T201
|
|
815
815
|
raise RuntimeError(f"Subprocess failed with {proc.returncode}")
|
|
816
816
|
|
|
817
|
-
print(f"🌐 Downloaded and unpacked {mount_name} packages to {tmpd}.")
|
|
817
|
+
print(f"🌐 Downloaded and unpacked {mount_name} packages to {tmpd}.") # noqa: T201
|
|
818
818
|
|
|
819
819
|
python_mount = Mount._from_local_dir(tmpd, remote_path=REMOTE_PACKAGES_PATH)
|
|
820
820
|
|
|
@@ -838,11 +838,11 @@ async def _create_single_client_dependency_mount(
|
|
|
838
838
|
allow_overwrite=allow_overwrite,
|
|
839
839
|
client=client,
|
|
840
840
|
)
|
|
841
|
-
print(f"✅ Deployed mount {mount_name} to global namespace.")
|
|
841
|
+
print(f"✅ Deployed mount {mount_name} to global namespace.") # noqa: T201
|
|
842
842
|
except GRPCError as e:
|
|
843
|
-
print(f"⚠️ Mount creation failed with {e.status}: {e.message}")
|
|
843
|
+
print(f"⚠️ Mount creation failed with {e.status}: {e.message}") # noqa: T201
|
|
844
844
|
else:
|
|
845
|
-
print(f"Dry run - skipping deployment of mount {mount_name}")
|
|
845
|
+
print(f"Dry run - skipping deployment of mount {mount_name}") # noqa: T201
|
|
846
846
|
|
|
847
847
|
|
|
848
848
|
async def _create_client_dependency_mounts(
|
|
@@ -2,6 +2,7 @@ syntax = "proto3";
|
|
|
2
2
|
|
|
3
3
|
option go_package = "github.com/modal-labs/modal/go/proto";
|
|
4
4
|
|
|
5
|
+
import "google/protobuf/empty.proto";
|
|
5
6
|
import "modal_proto/api.proto";
|
|
6
7
|
|
|
7
8
|
package modal.task_command_router;
|
|
@@ -130,6 +131,21 @@ message TaskExecWaitResponse {
|
|
|
130
131
|
// and termination by Modal (due to task timeout, exec exceeded deadline, etc.)
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
message TaskMountDirectoryRequest {
|
|
135
|
+
string task_id = 1;
|
|
136
|
+
bytes path = 2;
|
|
137
|
+
string image_id = 3;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
message TaskSnapshotDirectoryRequest {
|
|
141
|
+
string task_id = 1;
|
|
142
|
+
bytes path = 2;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
message TaskSnapshotDirectoryResponse {
|
|
146
|
+
string image_id = 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
133
149
|
service TaskCommandRouter {
|
|
134
150
|
// Poll for the exit status of an exec'd command.
|
|
135
151
|
rpc TaskExecPoll(TaskExecPollRequest) returns (TaskExecPollResponse);
|
|
@@ -141,4 +157,8 @@ service TaskCommandRouter {
|
|
|
141
157
|
rpc TaskExecStdioRead(TaskExecStdioReadRequest) returns (stream TaskExecStdioReadResponse);
|
|
142
158
|
// Wait for an exec'd command to exit and return the exit code.
|
|
143
159
|
rpc TaskExecWait(TaskExecWaitRequest) returns (TaskExecWaitResponse);
|
|
160
|
+
// Mount an image at a directory in the container.
|
|
161
|
+
rpc TaskMountDirectory(TaskMountDirectoryRequest) returns (google.protobuf.Empty);
|
|
162
|
+
// Snapshot a directory with a mounted image, including any local changes, into a new image.
|
|
163
|
+
rpc TaskSnapshotDirectory(TaskSnapshotDirectoryRequest) returns (TaskSnapshotDirectoryResponse);
|
|
144
164
|
}
|
|
@@ -9,6 +9,7 @@ import grpclib.client
|
|
|
9
9
|
if typing.TYPE_CHECKING:
|
|
10
10
|
import grpclib.server
|
|
11
11
|
|
|
12
|
+
import google.protobuf.empty_pb2
|
|
12
13
|
import modal_proto.api_pb2
|
|
13
14
|
import modal_proto.task_command_router_pb2
|
|
14
15
|
|
|
@@ -35,6 +36,14 @@ class TaskCommandRouterBase(abc.ABC):
|
|
|
35
36
|
async def TaskExecWait(self, stream: 'grpclib.server.Stream[modal_proto.task_command_router_pb2.TaskExecWaitRequest, modal_proto.task_command_router_pb2.TaskExecWaitResponse]') -> None:
|
|
36
37
|
pass
|
|
37
38
|
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
async def TaskMountDirectory(self, stream: 'grpclib.server.Stream[modal_proto.task_command_router_pb2.TaskMountDirectoryRequest, google.protobuf.empty_pb2.Empty]') -> None:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
async def TaskSnapshotDirectory(self, stream: 'grpclib.server.Stream[modal_proto.task_command_router_pb2.TaskSnapshotDirectoryRequest, modal_proto.task_command_router_pb2.TaskSnapshotDirectoryResponse]') -> None:
|
|
45
|
+
pass
|
|
46
|
+
|
|
38
47
|
def __mapping__(self) -> typing.Dict[str, grpclib.const.Handler]:
|
|
39
48
|
return {
|
|
40
49
|
'/modal.task_command_router.TaskCommandRouter/TaskExecPoll': grpclib.const.Handler(
|
|
@@ -67,6 +76,18 @@ class TaskCommandRouterBase(abc.ABC):
|
|
|
67
76
|
modal_proto.task_command_router_pb2.TaskExecWaitRequest,
|
|
68
77
|
modal_proto.task_command_router_pb2.TaskExecWaitResponse,
|
|
69
78
|
),
|
|
79
|
+
'/modal.task_command_router.TaskCommandRouter/TaskMountDirectory': grpclib.const.Handler(
|
|
80
|
+
self.TaskMountDirectory,
|
|
81
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
82
|
+
modal_proto.task_command_router_pb2.TaskMountDirectoryRequest,
|
|
83
|
+
google.protobuf.empty_pb2.Empty,
|
|
84
|
+
),
|
|
85
|
+
'/modal.task_command_router.TaskCommandRouter/TaskSnapshotDirectory': grpclib.const.Handler(
|
|
86
|
+
self.TaskSnapshotDirectory,
|
|
87
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
88
|
+
modal_proto.task_command_router_pb2.TaskSnapshotDirectoryRequest,
|
|
89
|
+
modal_proto.task_command_router_pb2.TaskSnapshotDirectoryResponse,
|
|
90
|
+
),
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
|
|
@@ -103,3 +124,15 @@ class TaskCommandRouterStub:
|
|
|
103
124
|
modal_proto.task_command_router_pb2.TaskExecWaitRequest,
|
|
104
125
|
modal_proto.task_command_router_pb2.TaskExecWaitResponse,
|
|
105
126
|
)
|
|
127
|
+
self.TaskMountDirectory = grpclib.client.UnaryUnaryMethod(
|
|
128
|
+
channel,
|
|
129
|
+
'/modal.task_command_router.TaskCommandRouter/TaskMountDirectory',
|
|
130
|
+
modal_proto.task_command_router_pb2.TaskMountDirectoryRequest,
|
|
131
|
+
google.protobuf.empty_pb2.Empty,
|
|
132
|
+
)
|
|
133
|
+
self.TaskSnapshotDirectory = grpclib.client.UnaryUnaryMethod(
|
|
134
|
+
channel,
|
|
135
|
+
'/modal.task_command_router.TaskCommandRouter/TaskSnapshotDirectory',
|
|
136
|
+
modal_proto.task_command_router_pb2.TaskSnapshotDirectoryRequest,
|
|
137
|
+
modal_proto.task_command_router_pb2.TaskSnapshotDirectoryResponse,
|
|
138
|
+
)
|