modal 1.2.5.dev27__tar.gz → 1.2.5.dev28__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.5.dev28}/PKG-INFO +1 -1
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/task_command_router_client.py +38 -67
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/client.pyi +2 -2
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_version/__init__.py +1 -1
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/LICENSE +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/README.md +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/__main__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_billing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_clustered_functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_grpc_client.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_ipython.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_load_context.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_location.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_object.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_output.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_partial_function.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_pty.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_resolver.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_resources.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_serialization.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_traceback.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_tunnel.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_tunnel.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_type_manager.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/logger.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/_watcher.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/app.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/billing.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/README.md +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/builder/base-images.json +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/call_graph.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/_download.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/_traceback.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/cluster.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/config.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/container.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/dict.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/entry_point.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/environment.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/import_refs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/launch.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/profile.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/queues.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/run.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/secret.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/shell.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/token.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/utils.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cli/volume.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/client.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cls.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/cls.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/config.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/container_process.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/container_process.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/dict.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/dict.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/environments.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/environments.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/exception.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/experimental/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/experimental/flash.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/experimental/ipython.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/file_io.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/file_io.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/functions.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/functions.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/gpu.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/image.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/image.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/io_streams.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/io_streams.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/mount.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/mount.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/network_file_system.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/network_file_system.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/object.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/object.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/output.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/parallel_map.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/parallel_map.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/partial_function.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/partial_function.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/proxy.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/proxy.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/py.typed +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/queue.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/queue.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/retries.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/runner.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/runner.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/running_app.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/sandbox.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/sandbox.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/schedule.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/scheduler_placement.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/secret.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/secret.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/serving.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/serving.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/snapshot.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/snapshot.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/stream_type.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/token_flow.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/token_flow.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/volume.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal/volume.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/__init__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api.proto +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/py.typed +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/modal_version/__main__.py +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/pyproject.toml +0 -0
- {modal-1.2.5.dev27 → modal-1.2.5.dev28}/setup.cfg +0 -0
|
@@ -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):
|
|
224
|
+
return
|
|
225
|
+
self._closed = True
|
|
226
|
+
|
|
227
|
+
channel = getattr(self, "_channel", None)
|
|
228
|
+
if channel is None:
|
|
210
229
|
return
|
|
211
230
|
|
|
212
|
-
self
|
|
231
|
+
loop = getattr(self, "_loop", None)
|
|
213
232
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
@@ -394,8 +408,6 @@ class TaskCommandRouterClient:
|
|
|
394
408
|
assert resp.url == self._server_url, "Task router URL changed during session"
|
|
395
409
|
self._jwt = resp.jwt
|
|
396
410
|
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
411
|
|
|
400
412
|
async def _call_with_auth_retry(self, func, *args, **kwargs):
|
|
401
413
|
try:
|
|
@@ -407,47 +419,6 @@ class TaskCommandRouterClient:
|
|
|
407
419
|
return await func(*args, **kwargs)
|
|
408
420
|
raise
|
|
409
421
|
|
|
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
422
|
async def _stream_stdio(
|
|
452
423
|
self,
|
|
453
424
|
task_id: str,
|
|
@@ -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.5.
|
|
35
|
+
version: str = "1.2.5.dev28",
|
|
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.5.
|
|
166
|
+
version: str = "1.2.5.dev28",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|