modal 0.74.55__py3-none-any.whl → 0.74.56__py3-none-any.whl
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/cli/cluster.py +82 -0
- modal/cli/entry_point.py +3 -0
- modal/client.pyi +2 -2
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/METADATA +1 -1
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/RECORD +10 -9
- modal_version/_version_generated.py +1 -1
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/WHEEL +0 -0
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/entry_points.txt +0 -0
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/licenses/LICENSE +0 -0
- {modal-0.74.55.dist-info → modal-0.74.56.dist-info}/top_level.txt +0 -0
modal/cli/cluster.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Copyright Modal Labs 2022
|
2
|
+
from typing import Optional, Union
|
3
|
+
|
4
|
+
import typer
|
5
|
+
from rich.console import Console
|
6
|
+
from rich.text import Text
|
7
|
+
|
8
|
+
from modal._object import _get_environment_name
|
9
|
+
from modal._pty import get_pty_info
|
10
|
+
from modal._utils.async_utils import synchronizer
|
11
|
+
from modal.cli.utils import ENV_OPTION, display_table, is_tty, timestamp_to_local
|
12
|
+
from modal.client import _Client
|
13
|
+
from modal.config import config
|
14
|
+
from modal.container_process import _ContainerProcess
|
15
|
+
from modal.environments import ensure_env
|
16
|
+
from modal.stream_type import StreamType
|
17
|
+
from modal_proto import api_pb2
|
18
|
+
|
19
|
+
cluster_cli = typer.Typer(
|
20
|
+
name="cluster", help="Manage and connect to running multi-node clusters.", no_args_is_help=True
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
@cluster_cli.command("list")
|
25
|
+
@synchronizer.create_blocking
|
26
|
+
async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
|
27
|
+
"""List all clusters that are currently running."""
|
28
|
+
env = ensure_env(env)
|
29
|
+
client = await _Client.from_env()
|
30
|
+
environment_name = _get_environment_name(env)
|
31
|
+
res: api_pb2.ClusterListResponse = await client.stub.ClusterList(
|
32
|
+
api_pb2.ClusterListRequest(environment_name=environment_name)
|
33
|
+
)
|
34
|
+
|
35
|
+
column_names = ["Cluster ID", "App ID", "Start Time", "Nodes"]
|
36
|
+
rows: list[list[Union[Text, str]]] = []
|
37
|
+
res.clusters.sort(key=lambda c: c.started_at, reverse=True)
|
38
|
+
|
39
|
+
for c in res.clusters:
|
40
|
+
rows.append(
|
41
|
+
[
|
42
|
+
c.cluster_id,
|
43
|
+
c.app_id,
|
44
|
+
timestamp_to_local(c.started_at, json) if c.started_at else "Pending",
|
45
|
+
str(len(c.task_ids)),
|
46
|
+
]
|
47
|
+
)
|
48
|
+
|
49
|
+
display_table(column_names, rows, json=json, title=f"Active Multi-node Clusters in environment: {environment_name}")
|
50
|
+
|
51
|
+
|
52
|
+
@cluster_cli.command("shell")
|
53
|
+
@synchronizer.create_blocking
|
54
|
+
async def shell(
|
55
|
+
cluster_id: str = typer.Argument(help="Cluster ID"),
|
56
|
+
rank: int = typer.Option(default=0, help="Rank of the node to shell into"),
|
57
|
+
):
|
58
|
+
"""Open a shell to a multi-node cluster node."""
|
59
|
+
client = await _Client.from_env()
|
60
|
+
res: api_pb2.ClusterGetResponse = await client.stub.ClusterGet(api_pb2.ClusterGetRequest(cluster_id=cluster_id))
|
61
|
+
if len(res.cluster.task_ids) <= rank:
|
62
|
+
raise typer.Abort(f"No node with rank {rank} in cluster {cluster_id}")
|
63
|
+
task_id = res.cluster.task_ids[rank]
|
64
|
+
console = Console()
|
65
|
+
is_main = "(main)" if rank == 0 else ""
|
66
|
+
console.print(
|
67
|
+
f"Opening shell to node {rank} {is_main} of cluster {cluster_id} (container {task_id})", style="green"
|
68
|
+
)
|
69
|
+
|
70
|
+
pty = is_tty()
|
71
|
+
req = api_pb2.ContainerExecRequest(
|
72
|
+
task_id=task_id,
|
73
|
+
command=["/bin/bash"],
|
74
|
+
pty_info=get_pty_info(shell=True) if pty else None,
|
75
|
+
runtime_debug=config.get("function_runtime_debug"),
|
76
|
+
)
|
77
|
+
exec_res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(req)
|
78
|
+
if pty:
|
79
|
+
await _ContainerProcess(exec_res.exec_id, client).attach()
|
80
|
+
else:
|
81
|
+
# TODO: redirect stderr to its own stream?
|
82
|
+
await _ContainerProcess(exec_res.exec_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT).wait()
|
modal/cli/entry_point.py
CHANGED
@@ -10,6 +10,7 @@ from modal._utils.async_utils import synchronizer
|
|
10
10
|
|
11
11
|
from . import run
|
12
12
|
from .app import app_cli
|
13
|
+
from .cluster import cluster_cli
|
13
14
|
from .config import config_cli
|
14
15
|
from .container import container_cli
|
15
16
|
from .dict import dict_cli
|
@@ -92,6 +93,8 @@ entrypoint_cli_typer.add_typer(launch_cli)
|
|
92
93
|
# Deployments
|
93
94
|
entrypoint_cli_typer.add_typer(app_cli, rich_help_panel="Deployments")
|
94
95
|
entrypoint_cli_typer.add_typer(container_cli, rich_help_panel="Deployments")
|
96
|
+
# TODO: cluster is hidden while multi-node is in beta/experimental
|
97
|
+
entrypoint_cli_typer.add_typer(cluster_cli, rich_help_panel="Deployments", hidden=True)
|
95
98
|
|
96
99
|
# Storage
|
97
100
|
entrypoint_cli_typer.add_typer(dict_cli, rich_help_panel="Storage")
|
modal/client.pyi
CHANGED
@@ -27,7 +27,7 @@ class _Client:
|
|
27
27
|
_snapshotted: bool
|
28
28
|
|
29
29
|
def __init__(
|
30
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.
|
30
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.56"
|
31
31
|
): ...
|
32
32
|
def is_closed(self) -> bool: ...
|
33
33
|
@property
|
@@ -86,7 +86,7 @@ class Client:
|
|
86
86
|
_snapshotted: bool
|
87
87
|
|
88
88
|
def __init__(
|
89
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.
|
89
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.56"
|
90
90
|
): ...
|
91
91
|
def is_closed(self) -> bool: ...
|
92
92
|
@property
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=r-9vVU1lrR1CWtJEo60fuaianvxY_oOXZyv1Qx1DEkI,51231
|
|
22
22
|
modal/app.pyi,sha256=0QNtnUpAFbOPcbwCt119ge7OmoBqMFw5SajLgdE5eOw,28600
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=o-aQThHpvDHUzg_kUafyhWzACViUBhY2WLZ2EitnSHA,16787
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=QrmnL4WEe1xp9xBSlNgy90e8f2BqH5331EUj6sI8ITo,8343
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
28
28
|
modal/cls.py,sha256=aHoMEWMZUN7bOezs3tRPxzS1FP3gTxZBORVjbPmtxyg,35338
|
@@ -118,10 +118,11 @@ modal/cli/__init__.py,sha256=6FRleWQxBDT19y7OayO4lBOzuL6Bs9r0rLINYYYbHwQ,769
|
|
118
118
|
modal/cli/_download.py,sha256=t6BXZwjTd9MgznDvbsV8rp0FZWggdzC-lUAGZU4xx1g,3984
|
119
119
|
modal/cli/_traceback.py,sha256=4ywtmFcmPnY3tqb4-3fA061N2tRiM01xs8fSagtkwhE,7293
|
120
120
|
modal/cli/app.py,sha256=87LWg3bTQQIHFOqs8iiJYD_X03omXBZ6lFYR0rMJV-I,8433
|
121
|
+
modal/cli/cluster.py,sha256=EBDhkzfOtPSbwknYdYPBGYvRAwl4Gm7OJkD6_zxrcus,3106
|
121
122
|
modal/cli/config.py,sha256=QvFsqO4eUOtI7d_pQAOAyfq_ZitjhPtav3C6GIDQcZM,1680
|
122
123
|
modal/cli/container.py,sha256=FYwEgjf93j4NMorAjGbSV98i1wpebqdAeNU1wfrFp1k,3668
|
123
124
|
modal/cli/dict.py,sha256=8Wq3w-UDaywk8EVNdj-ECCNV9TYHqh4kzhUqhhulatM,4593
|
124
|
-
modal/cli/entry_point.py,sha256=
|
125
|
+
modal/cli/entry_point.py,sha256=Ytpsy0MTLQC1RSClI0wNhCbiy6ecPO8555PMmsrxoSc,4377
|
125
126
|
modal/cli/environment.py,sha256=Ayddkiq9jdj3XYDJ8ZmUqFpPPH8xajYlbexRkzGtUcg,4334
|
126
127
|
modal/cli/import_refs.py,sha256=pmzY0hpexx6DtvobNmCOvRqEdS9IriEP4BpMw1TIy2w,13911
|
127
128
|
modal/cli/launch.py,sha256=0_sBu6bv2xJEPWi-rbGS6Ri9ggnkWQvrGlgpYSUBMyY,3097
|
@@ -145,7 +146,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
|
|
145
146
|
modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
|
146
147
|
modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
|
147
148
|
modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
|
148
|
-
modal-0.74.
|
149
|
+
modal-0.74.56.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
149
150
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
150
151
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
151
152
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
@@ -170,9 +171,9 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
170
171
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
171
172
|
modal_version/__init__.py,sha256=m94xZNWIjH8oUtJk4l9xfovzDJede2o7X-q0MHVECtM,470
|
172
173
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
173
|
-
modal_version/_version_generated.py,sha256=
|
174
|
-
modal-0.74.
|
175
|
-
modal-0.74.
|
176
|
-
modal-0.74.
|
177
|
-
modal-0.74.
|
178
|
-
modal-0.74.
|
174
|
+
modal_version/_version_generated.py,sha256=mWCznyF_KU6y7pcK7_8wqzPaR8ffAIUYXN0M9edQdgI,149
|
175
|
+
modal-0.74.56.dist-info/METADATA,sha256=han4gW42B3IgaYxb_xsACvwdhj32_bptrSf9hGDaOQw,2451
|
176
|
+
modal-0.74.56.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
177
|
+
modal-0.74.56.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-0.74.56.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-0.74.56.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|