modal 1.2.6.dev3__tar.gz → 1.2.7.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.2.6.dev3 → modal-1.2.7.dev0}/PKG-INFO +1 -1
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/task_command_router_client.py +21 -13
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/launch.py +0 -74
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/client.pyi +2 -2
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/SOURCES.txt +0 -2
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_version/__init__.py +1 -1
- modal-1.2.6.dev3/modal/cli/programs/launch_instance_ssh.py +0 -94
- modal-1.2.6.dev3/modal/cli/programs/run_marimo.py +0 -95
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/LICENSE +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/README.md +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/__main__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_billing.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_functions.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_ipython.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_load_context.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_location.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_object.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_output.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_partial_function.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_pty.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_resolver.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_resources.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_serialization.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_traceback.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_tunnel.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_type_manager.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/_watcher.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/app.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/app.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/billing.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/README.md +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/call_graph.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/_download.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/app.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/config.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/container.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/dict.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/environment.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/profile.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/queues.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/run.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/secret.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/shell.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/token.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/utils.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cli/volume.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/client.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cls.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/cls.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/config.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/container_process.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/container_process.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/dict.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/dict.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/environments.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/environments.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/exception.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_io.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_io.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/functions.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/functions.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/gpu.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/image.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/image.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/io_streams.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/mount.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/mount.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/network_file_system.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/object.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/object.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/output.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/parallel_map.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/partial_function.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/proxy.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/proxy.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/py.typed +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/queue.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/queue.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/retries.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/runner.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/runner.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/running_app.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/sandbox.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/sandbox.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/schedule.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/secret.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/secret.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/serving.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/serving.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/snapshot.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/stream_type.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/token_flow.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/volume.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal/volume.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api.proto +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/py.typed +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/modal_version/__main__.py +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/pyproject.toml +0 -0
- {modal-1.2.6.dev3 → modal-1.2.7.dev0}/setup.cfg +0 -0
|
@@ -403,7 +403,10 @@ class TaskCommandRouterClient:
|
|
|
403
403
|
)
|
|
404
404
|
return
|
|
405
405
|
|
|
406
|
+
logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
|
|
406
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
|
+
|
|
407
410
|
# Ensure the server URL remains stable for the lifetime of this client.
|
|
408
411
|
assert resp.url == self._server_url, "Task router URL changed during session"
|
|
409
412
|
self._jwt = resp.jwt
|
|
@@ -434,7 +437,9 @@ class TaskCommandRouterClient:
|
|
|
434
437
|
delay_secs = self.stream_stdio_retry_delay_secs
|
|
435
438
|
delay_factor = self.stream_stdio_retry_delay_factor
|
|
436
439
|
num_retries_remaining = self.stream_stdio_max_retries
|
|
437
|
-
|
|
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
|
|
438
443
|
|
|
439
444
|
async def sleep_and_update_delay_and_num_retries_remaining(e: Exception):
|
|
440
445
|
nonlocal delay_secs, num_retries_remaining
|
|
@@ -458,25 +463,28 @@ class TaskCommandRouterClient:
|
|
|
458
463
|
file_descriptor=file_descriptor,
|
|
459
464
|
)
|
|
460
465
|
|
|
461
|
-
#
|
|
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.
|
|
462
469
|
try:
|
|
463
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
|
|
464
479
|
except GRPCError as exc:
|
|
465
|
-
if exc.status == Status.UNAUTHENTICATED and
|
|
480
|
+
if exc.status == Status.UNAUTHENTICATED and not did_auth_retry:
|
|
466
481
|
await self._refresh_jwt()
|
|
467
|
-
|
|
482
|
+
# Mark that we've retried authentication for this streaming attempt, to
|
|
483
|
+
# prevent subsequent retries.
|
|
484
|
+
did_auth_retry = True
|
|
468
485
|
continue
|
|
469
486
|
raise
|
|
470
487
|
|
|
471
|
-
# We successfully authenticated, reset the auth retry count.
|
|
472
|
-
num_auth_retries = 0
|
|
473
|
-
|
|
474
|
-
async for item in s:
|
|
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
|
|
479
|
-
|
|
480
488
|
# We successfully streamed all output.
|
|
481
489
|
return
|
|
482
490
|
except GRPCError as e:
|
|
@@ -3,8 +3,6 @@ import asyncio
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
import subprocess
|
|
7
|
-
import tempfile
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from typing import Any, Optional
|
|
10
8
|
|
|
@@ -120,75 +118,3 @@ def vscode(
|
|
|
120
118
|
"volume": volume,
|
|
121
119
|
}
|
|
122
120
|
_launch_program("vscode", "vscode.py", detach, args)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@launch_cli.command(name="machine", help="Start an instance on Modal, with direct SSH access.", hidden=True)
|
|
126
|
-
def machine(
|
|
127
|
-
name: str, # Name of the machine App.
|
|
128
|
-
cpu: int = 8, # Reservation of CPU cores (can burst above this value).
|
|
129
|
-
memory: int = 32768, # Reservation of memory in MiB (can burst above this value).
|
|
130
|
-
gpu: Optional[str] = None, # GPU type and count, e.g. "t4" or "h100:2".
|
|
131
|
-
image: Optional[str] = None, # Image tag to use from registry. Defaults to the notebook base image.
|
|
132
|
-
timeout: int = 3600 * 24, # Timeout in seconds for the instance.
|
|
133
|
-
volume: str = "machine-vol", # Attach a persisted `modal.Volume` at /workspace (created if missing).
|
|
134
|
-
):
|
|
135
|
-
tempdir = Path(tempfile.gettempdir())
|
|
136
|
-
key_path = tempdir / "modal-machine-keyfile.pem"
|
|
137
|
-
# Generate a new SSH key pair for this machine instance.
|
|
138
|
-
if not key_path.exists():
|
|
139
|
-
subprocess.run(
|
|
140
|
-
["ssh-keygen", "-t", "ed25519", "-f", str(key_path), "-N", ""],
|
|
141
|
-
check=True,
|
|
142
|
-
stdout=subprocess.DEVNULL,
|
|
143
|
-
)
|
|
144
|
-
# Add the key with expiry 1d to ssh agent.
|
|
145
|
-
subprocess.run(
|
|
146
|
-
["ssh-add", "-t", "1d", str(key_path)],
|
|
147
|
-
check=True,
|
|
148
|
-
stdout=subprocess.DEVNULL,
|
|
149
|
-
stderr=subprocess.DEVNULL,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
os.environ["SSH_PUBLIC_KEY"] = Path(str(key_path) + ".pub").read_text()
|
|
153
|
-
os.environ["MODAL_LOGS_TIMEOUT"] = "0" # hack to work with --detach
|
|
154
|
-
|
|
155
|
-
args = {
|
|
156
|
-
"cpu": cpu,
|
|
157
|
-
"memory": memory,
|
|
158
|
-
"gpu": gpu,
|
|
159
|
-
"image": image,
|
|
160
|
-
"timeout": timeout,
|
|
161
|
-
"volume": volume,
|
|
162
|
-
}
|
|
163
|
-
_launch_program(
|
|
164
|
-
"machine",
|
|
165
|
-
"launch_instance_ssh.py",
|
|
166
|
-
True,
|
|
167
|
-
args,
|
|
168
|
-
description=name,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@launch_cli.command(name="marimo", help="Start a remote Marimo notebook on Modal.", hidden=True)
|
|
173
|
-
def marimo(
|
|
174
|
-
cpu: int = 8,
|
|
175
|
-
memory: int = 32768,
|
|
176
|
-
gpu: Optional[str] = None,
|
|
177
|
-
image: str = "debian:12",
|
|
178
|
-
timeout: int = 3600,
|
|
179
|
-
add_python: Optional[str] = "3.12",
|
|
180
|
-
mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
|
|
181
|
-
volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
|
|
182
|
-
detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
|
|
183
|
-
):
|
|
184
|
-
args = {
|
|
185
|
-
"cpu": cpu,
|
|
186
|
-
"memory": memory,
|
|
187
|
-
"gpu": gpu,
|
|
188
|
-
"timeout": timeout,
|
|
189
|
-
"image": image,
|
|
190
|
-
"add_python": add_python,
|
|
191
|
-
"mount": mount,
|
|
192
|
-
"volume": volume,
|
|
193
|
-
}
|
|
194
|
-
_launch_program("marimo", "run_marimo.py", detach, args)
|
|
@@ -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.
|
|
35
|
+
version: str = "1.2.7.dev0",
|
|
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.
|
|
166
|
+
version: str = "1.2.7.dev0",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -161,9 +161,7 @@ modal/cli/token.py
|
|
|
161
161
|
modal/cli/utils.py
|
|
162
162
|
modal/cli/volume.py
|
|
163
163
|
modal/cli/programs/__init__.py
|
|
164
|
-
modal/cli/programs/launch_instance_ssh.py
|
|
165
164
|
modal/cli/programs/run_jupyter.py
|
|
166
|
-
modal/cli/programs/run_marimo.py
|
|
167
165
|
modal/cli/programs/vscode.py
|
|
168
166
|
modal/experimental/__init__.py
|
|
169
167
|
modal/experimental/flash.py
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Copyright Modal Labs 2023
|
|
2
|
-
# type: ignore
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
import rich
|
|
9
|
-
import rich.panel
|
|
10
|
-
import rich.rule
|
|
11
|
-
|
|
12
|
-
import modal
|
|
13
|
-
import modal.experimental
|
|
14
|
-
|
|
15
|
-
# Passed by `modal launch` locally via CLI.
|
|
16
|
-
args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
|
|
17
|
-
|
|
18
|
-
app = modal.App()
|
|
19
|
-
|
|
20
|
-
image: modal.Image
|
|
21
|
-
if args.get("image"):
|
|
22
|
-
image = modal.Image.from_registry(args.get("image"))
|
|
23
|
-
else:
|
|
24
|
-
# Must be set to the same image builder version as the notebook base image.
|
|
25
|
-
os.environ["MODAL_IMAGE_BUILDER_VERSION"] = "2024.10"
|
|
26
|
-
image = modal.experimental.notebook_base_image(python_version="3.12")
|
|
27
|
-
|
|
28
|
-
volume = (
|
|
29
|
-
modal.Volume.from_name(
|
|
30
|
-
args.get("volume"),
|
|
31
|
-
create_if_missing=True,
|
|
32
|
-
)
|
|
33
|
-
if args.get("volume")
|
|
34
|
-
else None
|
|
35
|
-
)
|
|
36
|
-
volumes = {"/workspace": volume} if volume else {}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
startup_script = """
|
|
40
|
-
set -eu
|
|
41
|
-
mkdir -p /run/sshd
|
|
42
|
-
|
|
43
|
-
# Check if sshd is installed, install if not
|
|
44
|
-
test -x /usr/sbin/sshd || (apt-get update && apt-get install -y openssh-server)
|
|
45
|
-
|
|
46
|
-
# Change default working directory to /workspace
|
|
47
|
-
echo "cd /workspace" >> /root/.profile
|
|
48
|
-
|
|
49
|
-
mkdir -p /root/.ssh
|
|
50
|
-
echo "$SSH_PUBLIC_KEY" >> /root/.ssh/authorized_keys
|
|
51
|
-
/usr/sbin/sshd -D -e
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@app.local_entrypoint()
|
|
56
|
-
def main():
|
|
57
|
-
if not os.environ.get("SSH_PUBLIC_KEY"):
|
|
58
|
-
raise ValueError("SSH_PUBLIC_KEY environment variable is not set")
|
|
59
|
-
|
|
60
|
-
sb = modal.Sandbox.create(
|
|
61
|
-
*("sh", "-c", startup_script),
|
|
62
|
-
app=app,
|
|
63
|
-
image=image,
|
|
64
|
-
cpu=args.get("cpu"),
|
|
65
|
-
memory=args.get("memory"),
|
|
66
|
-
gpu=args.get("gpu"),
|
|
67
|
-
timeout=args.get("timeout"),
|
|
68
|
-
volumes=volumes,
|
|
69
|
-
unencrypted_ports=[22], # Forward SSH port
|
|
70
|
-
secrets=[modal.Secret.from_dict({"SSH_PUBLIC_KEY": os.environ.get("SSH_PUBLIC_KEY")})],
|
|
71
|
-
)
|
|
72
|
-
hostname, port = sb.tunnels()[22].tcp_socket
|
|
73
|
-
connection_cmd = f"ssh -A -p {port} root@{hostname}"
|
|
74
|
-
|
|
75
|
-
rich.print(
|
|
76
|
-
rich.rule.Rule(style="yellow"),
|
|
77
|
-
rich.panel.Panel(
|
|
78
|
-
f"""Your instance is ready! You can SSH into it using the following command:
|
|
79
|
-
|
|
80
|
-
[dim gray]>[/dim gray] [bold cyan]{connection_cmd}[/bold cyan]
|
|
81
|
-
|
|
82
|
-
[italic]Details:[/italic]
|
|
83
|
-
• Name: [magenta]{app.description}[/magenta]
|
|
84
|
-
• CPU: [yellow]{args.get("cpu")} cores[/yellow]
|
|
85
|
-
• Memory: [yellow]{args.get("memory")} MiB[/yellow]
|
|
86
|
-
• Timeout: [yellow]{args.get("timeout")} seconds[/yellow]
|
|
87
|
-
• GPU: [green]{(args.get("gpu") or "N/A").upper()}[/green]""",
|
|
88
|
-
title="SSH Connection",
|
|
89
|
-
expand=False,
|
|
90
|
-
),
|
|
91
|
-
rich.rule.Rule(style="yellow"),
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
sys.exit(0) # Exit immediately to prevent "Timed out waiting for final apps log."
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Copyright Modal Labs 2025
|
|
2
|
-
# type: ignore
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import secrets
|
|
6
|
-
import socket
|
|
7
|
-
import subprocess
|
|
8
|
-
import threading
|
|
9
|
-
import time
|
|
10
|
-
import webbrowser
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
from modal import App, Image, Queue, Secret, Volume, forward
|
|
14
|
-
|
|
15
|
-
# Args injected by `modal launch` CLI.
|
|
16
|
-
args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
|
|
17
|
-
|
|
18
|
-
app = App()
|
|
19
|
-
|
|
20
|
-
image = Image.from_registry(args.get("image"), add_python=args.get("add_python")).uv_pip_install("marimo")
|
|
21
|
-
|
|
22
|
-
# Optional host-filesystem mount (read-only snapshot of your project, useful for editing)
|
|
23
|
-
if args.get("mount"):
|
|
24
|
-
image = image.add_local_dir(args["mount"], remote_path="/root/marimo/mount")
|
|
25
|
-
|
|
26
|
-
# Optional persistent Modal volume
|
|
27
|
-
volume = Volume.from_name(args["volume"], create_if_missing=True) if args.get("volume") else None
|
|
28
|
-
volumes = {"/root/marimo/volume": volume} if volume else {}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _wait_for_port(url: str, q: Queue) -> None:
|
|
32
|
-
start = time.monotonic()
|
|
33
|
-
while True:
|
|
34
|
-
try:
|
|
35
|
-
with socket.create_connection(("localhost", 8888), timeout=30):
|
|
36
|
-
break
|
|
37
|
-
except OSError as exc:
|
|
38
|
-
if time.monotonic() - start > 30:
|
|
39
|
-
raise TimeoutError("marimo server did not start within 30 s") from exc
|
|
40
|
-
time.sleep(0.05)
|
|
41
|
-
q.put(url)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@app.function(
|
|
45
|
-
image=image,
|
|
46
|
-
cpu=args.get("cpu"),
|
|
47
|
-
memory=args.get("memory"),
|
|
48
|
-
gpu=args.get("gpu"),
|
|
49
|
-
timeout=args.get("timeout", 3600),
|
|
50
|
-
secrets=[Secret.from_dict({"MODAL_LAUNCH_ARGS": json.dumps(args)})],
|
|
51
|
-
volumes=volumes,
|
|
52
|
-
max_containers=1 if volume else None,
|
|
53
|
-
)
|
|
54
|
-
def run_marimo(q: Queue):
|
|
55
|
-
os.makedirs("/root/marimo", exist_ok=True)
|
|
56
|
-
|
|
57
|
-
# marimo supports token-based auth; generate one so only you can connect
|
|
58
|
-
token = secrets.token_urlsafe(12)
|
|
59
|
-
|
|
60
|
-
with forward(8888) as tunnel:
|
|
61
|
-
url = f"{tunnel.url}/?access_token={token}"
|
|
62
|
-
threading.Thread(target=_wait_for_port, args=(url, q), daemon=True).start()
|
|
63
|
-
|
|
64
|
-
print("\nmarimo on Modal, opening in browser …")
|
|
65
|
-
print(f" -> {url}\n")
|
|
66
|
-
|
|
67
|
-
# Launch the headless edit server
|
|
68
|
-
subprocess.run(
|
|
69
|
-
[
|
|
70
|
-
"marimo",
|
|
71
|
-
"edit",
|
|
72
|
-
"--headless", # don't open browser in container
|
|
73
|
-
"--host",
|
|
74
|
-
"0.0.0.0", # bind all interfaces
|
|
75
|
-
"--port",
|
|
76
|
-
"8888",
|
|
77
|
-
"--token-password",
|
|
78
|
-
token, # enable session-based auth
|
|
79
|
-
"--skip-update-check",
|
|
80
|
-
"/root/marimo", # workspace directory
|
|
81
|
-
],
|
|
82
|
-
env={**os.environ, "SHELL": "/bin/bash"},
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
q.put("done")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@app.local_entrypoint()
|
|
89
|
-
def main():
|
|
90
|
-
with Queue.ephemeral() as q:
|
|
91
|
-
run_marimo.spawn(q)
|
|
92
|
-
url = q.get() # first message = connect URL
|
|
93
|
-
time.sleep(1) # give server a heartbeat
|
|
94
|
-
webbrowser.open(url)
|
|
95
|
-
assert q.get() == "done"
|
|
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
|