modal 1.1.2.dev12__tar.gz → 1.1.2.dev13__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.1.2.dev12 → modal-1.1.2.dev13}/PKG-INFO +1 -1
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/container_io_manager.py +0 -1
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/launch.py +78 -2
- modal-1.1.2.dev13/modal/cli/programs/launch_instance_ssh.py +94 -0
- modal-1.1.2.dev13/modal/cli/programs/run_marimo.py +95 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/client.pyi +2 -2
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/experimental/__init__.py +104 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/functions.pyi +6 -6
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/SOURCES.txt +2 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_version/__init__.py +1 -1
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/LICENSE +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/README.md +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/__main__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_clustered_functions.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_functions.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_ipython.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_location.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_object.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_output.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_partial_function.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_pty.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_resolver.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_resources.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_serialization.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_traceback.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_tunnel.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_tunnel.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_type_manager.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/logger.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/_watcher.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/app.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/app.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/README.md +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/builder/base-images.json +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/call_graph.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/_download.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/_traceback.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/app.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/cluster.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/config.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/container.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/dict.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/entry_point.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/environment.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/import_refs.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/profile.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/queues.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/run.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/secret.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/token.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/utils.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cli/volume.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/client.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cls.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/cls.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/config.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/container_process.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/container_process.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/dict.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/dict.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/environments.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/environments.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/exception.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/experimental/flash.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/experimental/flash.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/experimental/ipython.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/file_io.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/file_io.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/functions.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/gpu.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/image.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/image.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/io_streams.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/io_streams.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/mount.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/mount.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/network_file_system.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/network_file_system.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/object.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/object.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/output.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/parallel_map.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/parallel_map.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/partial_function.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/partial_function.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/proxy.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/proxy.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/py.typed +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/queue.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/queue.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/retries.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/runner.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/runner.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/running_app.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/sandbox.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/sandbox.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/schedule.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/scheduler_placement.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/secret.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/secret.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/serving.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/serving.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/snapshot.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/snapshot.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/stream_type.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/token_flow.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/token_flow.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/volume.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal/volume.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/__init__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api.proto +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api_pb2.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options.proto +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_proto/py.typed +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/modal_version/__main__.py +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/pyproject.toml +0 -0
- {modal-1.1.2.dev12 → modal-1.1.2.dev13}/setup.cfg +0 -0
@@ -3,6 +3,8 @@ import asyncio
|
|
3
3
|
import inspect
|
4
4
|
import json
|
5
5
|
import os
|
6
|
+
import subprocess
|
7
|
+
import tempfile
|
6
8
|
from pathlib import Path
|
7
9
|
from typing import Any, Optional
|
8
10
|
|
@@ -26,7 +28,9 @@ launch_cli = Typer(
|
|
26
28
|
)
|
27
29
|
|
28
30
|
|
29
|
-
def _launch_program(
|
31
|
+
def _launch_program(
|
32
|
+
name: str, filename: str, detach: bool, args: dict[str, Any], *, description: Optional[str] = None
|
33
|
+
) -> None:
|
30
34
|
os.environ["MODAL_LAUNCH_ARGS"] = json.dumps(args)
|
31
35
|
|
32
36
|
program_path = str(Path(__file__).parent / "programs" / filename)
|
@@ -35,7 +39,7 @@ def _launch_program(name: str, filename: str, detach: bool, args: dict[str, Any]
|
|
35
39
|
entrypoint = module.main
|
36
40
|
|
37
41
|
app = _get_runnable_app(entrypoint)
|
38
|
-
app.set_description(base_cmd)
|
42
|
+
app.set_description(description if description else base_cmd)
|
39
43
|
|
40
44
|
# `launch/` scripts must have a `local_entrypoint()` with no args, for simplicity here.
|
41
45
|
func = entrypoint.info.raw_f
|
@@ -108,3 +112,75 @@ def vscode(
|
|
108
112
|
"volume": volume,
|
109
113
|
}
|
110
114
|
_launch_program("vscode", "vscode.py", detach, args)
|
115
|
+
|
116
|
+
|
117
|
+
@launch_cli.command(name="machine", help="Start an instance on Modal, with direct SSH access.")
|
118
|
+
def machine(
|
119
|
+
name: str, # Name of the machine App.
|
120
|
+
cpu: int = 8, # Reservation of CPU cores (can burst above this value).
|
121
|
+
memory: int = 32768, # Reservation of memory in MiB (can burst above this value).
|
122
|
+
gpu: Optional[str] = None, # GPU type and count, e.g. "t4" or "h100:2".
|
123
|
+
image: Optional[str] = None, # Image tag to use from registry. Defaults to the notebook base image.
|
124
|
+
timeout: int = 3600 * 24, # Timeout in seconds for the instance.
|
125
|
+
volume: str = "machine-vol", # Attach a persisted `modal.Volume` at /workspace (created if missing).
|
126
|
+
):
|
127
|
+
tempdir = Path(tempfile.gettempdir())
|
128
|
+
key_path = tempdir / "modal-machine-keyfile.pem"
|
129
|
+
# Generate a new SSH key pair for this machine instance.
|
130
|
+
if not key_path.exists():
|
131
|
+
subprocess.run(
|
132
|
+
["ssh-keygen", "-t", "ed25519", "-f", str(key_path), "-N", ""],
|
133
|
+
check=True,
|
134
|
+
stdout=subprocess.DEVNULL,
|
135
|
+
)
|
136
|
+
# Add the key with expiry 1d to ssh agent.
|
137
|
+
subprocess.run(
|
138
|
+
["ssh-add", "-t", "1d", str(key_path)],
|
139
|
+
check=True,
|
140
|
+
stdout=subprocess.DEVNULL,
|
141
|
+
stderr=subprocess.DEVNULL,
|
142
|
+
)
|
143
|
+
|
144
|
+
os.environ["SSH_PUBLIC_KEY"] = Path(str(key_path) + ".pub").read_text()
|
145
|
+
os.environ["MODAL_LOGS_TIMEOUT"] = "0" # hack to work with --detach
|
146
|
+
|
147
|
+
args = {
|
148
|
+
"cpu": cpu,
|
149
|
+
"memory": memory,
|
150
|
+
"gpu": gpu,
|
151
|
+
"image": image,
|
152
|
+
"timeout": timeout,
|
153
|
+
"volume": volume,
|
154
|
+
}
|
155
|
+
_launch_program(
|
156
|
+
"machine",
|
157
|
+
"launch_instance_ssh.py",
|
158
|
+
True,
|
159
|
+
args,
|
160
|
+
description=name,
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
@launch_cli.command(name="marimo", help="Start a remote Marimo notebook on Modal.")
|
165
|
+
def marimo(
|
166
|
+
cpu: int = 8,
|
167
|
+
memory: int = 32768,
|
168
|
+
gpu: Optional[str] = None,
|
169
|
+
image: str = "debian:12",
|
170
|
+
timeout: int = 3600,
|
171
|
+
add_python: Optional[str] = "3.12",
|
172
|
+
mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
|
173
|
+
volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
|
174
|
+
detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
|
175
|
+
):
|
176
|
+
args = {
|
177
|
+
"cpu": cpu,
|
178
|
+
"memory": memory,
|
179
|
+
"gpu": gpu,
|
180
|
+
"timeout": timeout,
|
181
|
+
"image": image,
|
182
|
+
"add_python": add_python,
|
183
|
+
"mount": mount,
|
184
|
+
"volume": volume,
|
185
|
+
}
|
186
|
+
_launch_program("marimo", "run_marimo.py", detach, args)
|
@@ -0,0 +1,94 @@
|
|
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."
|
@@ -0,0 +1,95 @@
|
|
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"),
|
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"
|
@@ -33,7 +33,7 @@ class _Client:
|
|
33
33
|
server_url: str,
|
34
34
|
client_type: int,
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
36
|
-
version: str = "1.1.2.
|
36
|
+
version: str = "1.1.2.dev13",
|
37
37
|
):
|
38
38
|
"""mdmd:hidden
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -164,7 +164,7 @@ class Client:
|
|
164
164
|
server_url: str,
|
165
165
|
client_type: int,
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
167
|
-
version: str = "1.1.2.
|
167
|
+
version: str = "1.1.2.dev13",
|
168
168
|
):
|
169
169
|
"""mdmd:hidden
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2025
|
2
2
|
import os
|
3
|
+
import shlex
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import Literal, Optional, Union
|
@@ -212,6 +213,109 @@ async def raw_registry_image(
|
|
212
213
|
)
|
213
214
|
|
214
215
|
|
216
|
+
@synchronizer.create_blocking
|
217
|
+
async def notebook_base_image(*, python_version: Optional[str] = None, force_build: bool = False) -> _Image:
|
218
|
+
"""Default image used for Modal notebook kernels, with common libraries.
|
219
|
+
|
220
|
+
This can be used to bootstrap development workflows quickly. We don't
|
221
|
+
recommend using this image for production Modal Functions though, as it may
|
222
|
+
change at any time in the future.
|
223
|
+
"""
|
224
|
+
# Include several common packages, as well as kernelshim dependencies (except 'modal').
|
225
|
+
# These packages aren't pinned, so they may change over time with builds.
|
226
|
+
#
|
227
|
+
# We plan to use `--exclude-newer` in the future, with date-specific image builds.
|
228
|
+
base_image = _Image.debian_slim(python_version=python_version)
|
229
|
+
|
230
|
+
environment_packages: list[str] = [
|
231
|
+
"accelerate",
|
232
|
+
"aiohttp",
|
233
|
+
"altair",
|
234
|
+
"anthropic",
|
235
|
+
"asyncpg",
|
236
|
+
"beautifulsoup4",
|
237
|
+
"bokeh",
|
238
|
+
"boto3[crt]",
|
239
|
+
"click",
|
240
|
+
"diffusers[torch,flax]",
|
241
|
+
"dm-sonnet",
|
242
|
+
"flax",
|
243
|
+
"ftfy",
|
244
|
+
"h5py",
|
245
|
+
"urllib3",
|
246
|
+
"httpx",
|
247
|
+
"huggingface-hub",
|
248
|
+
"ipywidgets",
|
249
|
+
"jax[cuda12]",
|
250
|
+
"keras",
|
251
|
+
"matplotlib",
|
252
|
+
"nbformat",
|
253
|
+
"numba",
|
254
|
+
"numpy",
|
255
|
+
"openai",
|
256
|
+
"optax",
|
257
|
+
"pandas",
|
258
|
+
"plotly[express]",
|
259
|
+
"polars",
|
260
|
+
"psycopg2",
|
261
|
+
"requests",
|
262
|
+
"safetensors",
|
263
|
+
"scikit-image",
|
264
|
+
"scikit-learn",
|
265
|
+
"scipy",
|
266
|
+
"seaborn",
|
267
|
+
"sentencepiece",
|
268
|
+
"sqlalchemy",
|
269
|
+
"statsmodels",
|
270
|
+
"sympy",
|
271
|
+
"tabulate",
|
272
|
+
"tensorboard",
|
273
|
+
"toml",
|
274
|
+
"transformers",
|
275
|
+
"triton",
|
276
|
+
"typer",
|
277
|
+
"vega-datasets",
|
278
|
+
"watchfiles",
|
279
|
+
"websockets",
|
280
|
+
]
|
281
|
+
|
282
|
+
# Kernelshim dependencies. (see NOTEBOOK_KERNELSHIM_DEPENDENCIES)
|
283
|
+
kernelshim_packages: list[str] = [
|
284
|
+
"authlib>=1.3",
|
285
|
+
"basedpyright>=1.28",
|
286
|
+
"fastapi>=0.100",
|
287
|
+
"ipykernel>=6",
|
288
|
+
"pydantic>=2",
|
289
|
+
"pyzmq>=26",
|
290
|
+
"ruff>=0.11",
|
291
|
+
"uvicorn>=0.32",
|
292
|
+
]
|
293
|
+
|
294
|
+
commands: list[str] = [
|
295
|
+
"apt-get update",
|
296
|
+
"apt-get install -y libpq-dev pkg-config cmake git curl wget unzip zip libsqlite3-dev openssh-server",
|
297
|
+
# Install uv since it's faster than pip for installing packages.
|
298
|
+
"pip install uv",
|
299
|
+
# https://github.com/astral-sh/uv/issues/11480
|
300
|
+
"pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126",
|
301
|
+
f"uv pip install --system {shlex.join(sorted(environment_packages))}",
|
302
|
+
f"uv pip install --system {shlex.join(sorted(kernelshim_packages))}",
|
303
|
+
]
|
304
|
+
|
305
|
+
# TODO: Also install the CUDA Toolkit, so `nvcc` is available.
|
306
|
+
# https://github.com/charlesfrye/cuda-modal/blob/7fef8db12402986cf42d9c8cca8c63d1da6d7700/cuda/use_cuda.py#L158-L188
|
307
|
+
|
308
|
+
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
309
|
+
return DockerfileSpec(commands=["FROM base", *(f"RUN {cmd}" for cmd in commands)], context_files={})
|
310
|
+
|
311
|
+
return _Image._from_args(
|
312
|
+
base_images={"base": base_image},
|
313
|
+
dockerfile_function=build_dockerfile,
|
314
|
+
force_build=force_build,
|
315
|
+
_namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
316
|
+
)
|
317
|
+
|
318
|
+
|
215
319
|
@synchronizer.create_blocking
|
216
320
|
async def update_autoscaler(
|
217
321
|
obj: Union[_Function, _Obj],
|
@@ -433,7 +433,7 @@ class Function(
|
|
433
433
|
|
434
434
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
435
435
|
|
436
|
-
class __remote_spec(typing_extensions.Protocol[
|
436
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
437
437
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
438
438
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
439
439
|
...
|
@@ -442,7 +442,7 @@ class Function(
|
|
442
442
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
443
443
|
...
|
444
444
|
|
445
|
-
remote: __remote_spec[modal._functions.
|
445
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
446
446
|
|
447
447
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
448
448
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
@@ -469,7 +469,7 @@ class Function(
|
|
469
469
|
"""
|
470
470
|
...
|
471
471
|
|
472
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
472
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
473
473
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
474
474
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
475
475
|
|
@@ -493,7 +493,7 @@ class Function(
|
|
493
493
|
...
|
494
494
|
|
495
495
|
_experimental_spawn: ___experimental_spawn_spec[
|
496
|
-
modal._functions.
|
496
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
497
497
|
]
|
498
498
|
|
499
499
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
@@ -502,7 +502,7 @@ class Function(
|
|
502
502
|
|
503
503
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
504
504
|
|
505
|
-
class __spawn_spec(typing_extensions.Protocol[
|
505
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
506
506
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
507
507
|
"""Calls the function with the given arguments, without waiting for the results.
|
508
508
|
|
@@ -523,7 +523,7 @@ class Function(
|
|
523
523
|
"""
|
524
524
|
...
|
525
525
|
|
526
|
-
spawn: __spawn_spec[modal._functions.
|
526
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
527
527
|
|
528
528
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
529
529
|
"""Return the inner Python object wrapped by this Modal Function."""
|
@@ -154,7 +154,9 @@ modal/cli/token.py
|
|
154
154
|
modal/cli/utils.py
|
155
155
|
modal/cli/volume.py
|
156
156
|
modal/cli/programs/__init__.py
|
157
|
+
modal/cli/programs/launch_instance_ssh.py
|
157
158
|
modal/cli/programs/run_jupyter.py
|
159
|
+
modal/cli/programs/run_marimo.py
|
158
160
|
modal/cli/programs/vscode.py
|
159
161
|
modal/experimental/__init__.py
|
160
162
|
modal/experimental/flash.py
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|