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 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.55"
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.55"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.55
3
+ Version: 0.74.56
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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=9fUzrcuA2uqCuRxHuIZxhsTR-lopTQuKxs_VvusFdLY,8343
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=dOosuCwhfznwTCB4oMljUFhihq5aLUVoAz7RhcBEDnc,4189
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.55.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
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=OZ_XmoTkO3MCoksyO8lR_vFkMH1zV93AgoruWFvLPis,149
174
- modal-0.74.55.dist-info/METADATA,sha256=oCE7_7EOAb5XgRznncfj2CpEJmlHutoa2JPOv9cDg_I,2451
175
- modal-0.74.55.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
- modal-0.74.55.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
- modal-0.74.55.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
- modal-0.74.55.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 55 # git: aa1b399
4
+ build_number = 56 # git: 9749b47