together 2.0.0a15__py3-none-any.whl → 2.0.0a17__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.
- together/_base_client.py +134 -11
- together/_client.py +7 -0
- together/_models.py +16 -1
- together/_types.py +9 -0
- together/_version.py +1 -1
- together/constants.py +1 -1
- together/error.py +1 -2
- together/lib/_google_colab.py +39 -0
- together/lib/cli/__init__.py +73 -0
- together/lib/cli/api/beta/clusters/__init__.py +47 -0
- together/lib/cli/api/beta/{clusters.py → clusters/create.py} +5 -179
- together/lib/cli/api/beta/clusters/delete.py +35 -0
- together/lib/cli/api/beta/clusters/get_credentials.py +151 -0
- together/lib/cli/api/beta/clusters/list.py +24 -0
- together/lib/cli/api/beta/clusters/list_regions.py +37 -0
- together/lib/cli/api/beta/clusters/retrieve.py +31 -0
- together/lib/cli/api/beta/clusters/storage/__init__.py +19 -0
- together/lib/cli/api/beta/clusters/storage/create.py +49 -0
- together/lib/cli/api/beta/clusters/storage/delete.py +38 -0
- together/lib/cli/api/beta/clusters/storage/list.py +42 -0
- together/lib/cli/api/beta/clusters/storage/retrieve.py +34 -0
- together/lib/cli/api/beta/clusters/update.py +54 -0
- together/lib/cli/api/endpoints/__init__.py +56 -0
- together/lib/cli/api/endpoints/availability_zones.py +26 -0
- together/lib/cli/api/endpoints/create.py +161 -0
- together/lib/cli/api/endpoints/delete.py +15 -0
- together/lib/cli/api/endpoints/hardware.py +40 -0
- together/lib/cli/api/endpoints/list.py +66 -0
- together/lib/cli/api/endpoints/retrieve.py +23 -0
- together/lib/cli/api/endpoints/start.py +25 -0
- together/lib/cli/api/endpoints/stop.py +25 -0
- together/lib/cli/api/endpoints/update.py +77 -0
- together/lib/cli/api/evals/__init__.py +19 -0
- together/lib/cli/api/{evals.py → evals/create.py} +6 -129
- together/lib/cli/api/evals/list.py +58 -0
- together/lib/cli/api/evals/retrieve.py +21 -0
- together/lib/cli/api/evals/status.py +20 -0
- together/lib/cli/api/files/__init__.py +23 -0
- together/lib/cli/api/files/check.py +21 -0
- together/lib/cli/api/files/delete.py +20 -0
- together/lib/cli/api/files/list.py +34 -0
- together/lib/cli/api/files/retrieve.py +20 -0
- together/lib/cli/api/files/retrieve_content.py +25 -0
- together/lib/cli/api/files/upload.py +38 -0
- together/lib/cli/api/fine_tuning/__init__.py +27 -0
- together/lib/cli/api/fine_tuning/cancel.py +28 -0
- together/lib/cli/api/{fine_tuning.py → fine_tuning/create.py} +5 -257
- together/lib/cli/api/fine_tuning/delete.py +29 -0
- together/lib/cli/api/fine_tuning/download.py +94 -0
- together/lib/cli/api/fine_tuning/list.py +44 -0
- together/lib/cli/api/fine_tuning/list_checkpoints.py +42 -0
- together/lib/cli/api/fine_tuning/list_events.py +35 -0
- together/lib/cli/api/fine_tuning/retrieve.py +27 -0
- together/lib/cli/api/models/__init__.py +15 -0
- together/lib/cli/api/models/list.py +55 -0
- together/lib/cli/api/{models.py → models/upload.py} +4 -51
- together/resources/beta/clusters/clusters.py +36 -28
- together/resources/beta/clusters/storage.py +30 -21
- together/resources/endpoints.py +2 -2
- together/types/__init__.py +4 -3
- together/types/beta/__init__.py +0 -2
- together/types/beta/cluster_create_params.py +3 -3
- together/types/beta/clusters/__init__.py +0 -1
- together/types/beta/clusters/cluster_storage.py +4 -0
- together/types/chat_completions.py +1 -1
- together/types/endpoint_create_params.py +1 -1
- together/types/endpoints.py +1 -1
- {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/METADATA +4 -2
- {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/RECORD +74 -38
- together-2.0.0a17.dist-info/entry_points.txt +2 -0
- together/lib/cli/api/__init__.py +0 -0
- together/lib/cli/api/beta/clusters_storage.py +0 -152
- together/lib/cli/api/endpoints.py +0 -467
- together/lib/cli/api/files.py +0 -133
- together/lib/cli/cli.py +0 -73
- together/types/beta/cluster_create_response.py +0 -9
- together/types/beta/cluster_update_response.py +0 -9
- together/types/beta/clusters/storage_create_response.py +0 -9
- together-2.0.0a15.dist-info/entry_points.txt +0 -2
- /together/lib/cli/api/{utils.py → _utils.py} +0 -0
- /together/lib/cli/api/beta/{beta.py → __init__.py} +0 -0
- {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/WHEEL +0 -0
- {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,64 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json as json_lib
|
|
4
4
|
import getpass
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import List, Literal
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
from rich import print
|
|
9
|
-
from tabulate import tabulate
|
|
10
9
|
|
|
11
|
-
from together import Together
|
|
12
|
-
from together.
|
|
13
|
-
from together.types.beta import
|
|
14
|
-
from together.lib.cli.api.utils import handle_api_errors
|
|
15
|
-
from together.types.beta.cluster_create_params import SharedVolume
|
|
16
|
-
from together.lib.cli.api.beta.clusters_storage import storage
|
|
10
|
+
from together import Together
|
|
11
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
12
|
+
from together.types.beta.cluster_create_params import SharedVolume, ClusterCreateParams
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
data: List[Dict[str, Any]] = []
|
|
21
|
-
for cluster in clusters:
|
|
22
|
-
data.append(
|
|
23
|
-
{
|
|
24
|
-
"ID": cluster.cluster_id,
|
|
25
|
-
"Name": cluster.cluster_name,
|
|
26
|
-
"Status": cluster.status,
|
|
27
|
-
"Region": cluster.region,
|
|
28
|
-
}
|
|
29
|
-
)
|
|
30
|
-
click.echo(tabulate(data, headers="keys", tablefmt="grid"))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@click.group()
|
|
34
|
-
@click.pass_context
|
|
35
|
-
def clusters(ctx: click.Context) -> None:
|
|
36
|
-
"""Clusters API commands"""
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
clusters.add_command(storage)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@clusters.command()
|
|
44
|
-
@click.option(
|
|
45
|
-
"--json",
|
|
46
|
-
is_flag=True,
|
|
47
|
-
help="Output in JSON format",
|
|
48
|
-
)
|
|
49
|
-
@click.pass_context
|
|
50
|
-
def list(ctx: click.Context, json: bool) -> None:
|
|
51
|
-
"""List clusters"""
|
|
52
|
-
client: Together = ctx.obj
|
|
53
|
-
|
|
54
|
-
response = client.beta.clusters.list()
|
|
55
|
-
|
|
56
|
-
if json:
|
|
57
|
-
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
58
|
-
else:
|
|
59
|
-
print_clusters(response.clusters)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@clusters.command()
|
|
15
|
+
@click.command()
|
|
63
16
|
@click.option(
|
|
64
17
|
"--name",
|
|
65
18
|
type=str,
|
|
@@ -228,130 +181,3 @@ def create(
|
|
|
228
181
|
else:
|
|
229
182
|
click.echo(f"Clusters: Cluster created successfully")
|
|
230
183
|
click.echo(f"Clusters: {response.cluster_id}")
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
@clusters.command()
|
|
234
|
-
@click.argument("cluster-id", required=True)
|
|
235
|
-
@click.option(
|
|
236
|
-
"--json",
|
|
237
|
-
is_flag=True,
|
|
238
|
-
help="Output in JSON format",
|
|
239
|
-
)
|
|
240
|
-
@click.pass_context
|
|
241
|
-
@handle_api_errors("Clusters")
|
|
242
|
-
def retrieve(ctx: click.Context, cluster_id: str, json: bool) -> None:
|
|
243
|
-
"""Retrieve a cluster by ID"""
|
|
244
|
-
client: Together = ctx.obj
|
|
245
|
-
|
|
246
|
-
if not json:
|
|
247
|
-
click.echo(f"Clusters: Retrieving cluster...")
|
|
248
|
-
|
|
249
|
-
response = client.beta.clusters.retrieve(cluster_id)
|
|
250
|
-
|
|
251
|
-
if json:
|
|
252
|
-
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
253
|
-
else:
|
|
254
|
-
print(response)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
@clusters.command()
|
|
258
|
-
@click.argument("cluster-id", required=True)
|
|
259
|
-
@click.option(
|
|
260
|
-
"--num-gpus",
|
|
261
|
-
type=int,
|
|
262
|
-
help="Number of GPUs to allocate in the cluster",
|
|
263
|
-
)
|
|
264
|
-
@click.option(
|
|
265
|
-
"--cluster-type",
|
|
266
|
-
type=click.Choice(["KUBERNETES", "SLURM"]),
|
|
267
|
-
help="Cluster type",
|
|
268
|
-
)
|
|
269
|
-
@click.option(
|
|
270
|
-
"--json",
|
|
271
|
-
is_flag=True,
|
|
272
|
-
help="Output in JSON format",
|
|
273
|
-
)
|
|
274
|
-
@click.pass_context
|
|
275
|
-
@handle_api_errors("Clusters")
|
|
276
|
-
def update(
|
|
277
|
-
ctx: click.Context,
|
|
278
|
-
cluster_id: str,
|
|
279
|
-
num_gpus: int | None = None,
|
|
280
|
-
cluster_type: Literal["KUBERNETES", "SLURM"] | None = None,
|
|
281
|
-
json: bool = False,
|
|
282
|
-
) -> None:
|
|
283
|
-
"""Update a cluster"""
|
|
284
|
-
client: Together = ctx.obj
|
|
285
|
-
|
|
286
|
-
if not json:
|
|
287
|
-
click.echo("Clusters: Updating cluster...")
|
|
288
|
-
|
|
289
|
-
client.beta.clusters.update(
|
|
290
|
-
cluster_id,
|
|
291
|
-
num_gpus=num_gpus if num_gpus is not None else omit,
|
|
292
|
-
cluster_type=cluster_type if cluster_type is not None else omit,
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
if json:
|
|
296
|
-
cluster = client.beta.clusters.retrieve(cluster_id)
|
|
297
|
-
click.echo(json_lib.dumps(cluster.model_dump(exclude_none=True), indent=4))
|
|
298
|
-
else:
|
|
299
|
-
click.echo("Clusters: Done")
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
@clusters.command()
|
|
303
|
-
@click.argument("cluster-id", required=True)
|
|
304
|
-
@click.option(
|
|
305
|
-
"--json",
|
|
306
|
-
is_flag=True,
|
|
307
|
-
help="Output in JSON format",
|
|
308
|
-
)
|
|
309
|
-
@click.pass_context
|
|
310
|
-
@handle_api_errors("Clusters")
|
|
311
|
-
def delete(ctx: click.Context, cluster_id: str, json: bool) -> None:
|
|
312
|
-
"""Delete a cluster by ID"""
|
|
313
|
-
client: Together = ctx.obj
|
|
314
|
-
|
|
315
|
-
if json:
|
|
316
|
-
response = client.beta.clusters.delete(cluster_id=cluster_id)
|
|
317
|
-
click.echo(json_lib.dumps(response.model_dump(), indent=2))
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
cluster = client.beta.clusters.retrieve(cluster_id=cluster_id)
|
|
321
|
-
print_clusters([cluster])
|
|
322
|
-
if not click.confirm(f"Clusters: Are you sure you want to delete cluster {cluster.cluster_name}?"):
|
|
323
|
-
return
|
|
324
|
-
|
|
325
|
-
click.echo("Clusters: Deleting cluster...")
|
|
326
|
-
response = client.beta.clusters.delete(cluster_id=cluster_id)
|
|
327
|
-
|
|
328
|
-
click.echo(f"Clusters: Deleted cluster {cluster.cluster_name}")
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
@clusters.command()
|
|
332
|
-
@click.option(
|
|
333
|
-
"--json",
|
|
334
|
-
is_flag=True,
|
|
335
|
-
help="Output in JSON format",
|
|
336
|
-
)
|
|
337
|
-
@click.pass_context
|
|
338
|
-
@handle_api_errors("Clusters")
|
|
339
|
-
def list_regions(ctx: click.Context, json: bool) -> None:
|
|
340
|
-
"""List regions"""
|
|
341
|
-
client: Together = ctx.obj
|
|
342
|
-
|
|
343
|
-
response = client.beta.clusters.list_regions()
|
|
344
|
-
|
|
345
|
-
if json:
|
|
346
|
-
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
347
|
-
else:
|
|
348
|
-
data: List[Dict[str, Any]] = []
|
|
349
|
-
for region in response.regions:
|
|
350
|
-
data.append(
|
|
351
|
-
{
|
|
352
|
-
"Name": region.name,
|
|
353
|
-
"Availability Zones": ", ".join(region.availability_zones) if region.availability_zones else "",
|
|
354
|
-
"Driver Versions": ", ".join(region.driver_versions) if region.driver_versions else "",
|
|
355
|
-
}
|
|
356
|
-
)
|
|
357
|
-
click.echo(tabulate(data, headers="keys", tablefmt="grid"))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together import Together
|
|
6
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.argument("cluster-id", required=True)
|
|
11
|
+
@click.option(
|
|
12
|
+
"--json",
|
|
13
|
+
is_flag=True,
|
|
14
|
+
help="Output in JSON format",
|
|
15
|
+
)
|
|
16
|
+
@click.pass_context
|
|
17
|
+
@handle_api_errors("Clusters")
|
|
18
|
+
def delete(ctx: click.Context, cluster_id: str, json: bool) -> None:
|
|
19
|
+
"""Delete a cluster by ID"""
|
|
20
|
+
client: Together = ctx.obj
|
|
21
|
+
|
|
22
|
+
if json:
|
|
23
|
+
response = client.beta.clusters.delete(cluster_id=cluster_id)
|
|
24
|
+
click.echo(json_lib.dumps(response.model_dump(), indent=2))
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
cluster = client.beta.clusters.retrieve(cluster_id=cluster_id)
|
|
28
|
+
ctx.obj.print_clusters([cluster])
|
|
29
|
+
if not click.confirm(f"Clusters: Are you sure you want to delete cluster {cluster.cluster_name}?"):
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
click.echo("Clusters: Deleting cluster...")
|
|
33
|
+
response = client.beta.clusters.delete(cluster_id=cluster_id)
|
|
34
|
+
|
|
35
|
+
click.echo(f"Clusters: Deleted cluster {cluster.cluster_name}")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import stat
|
|
5
|
+
import errno
|
|
6
|
+
import platform
|
|
7
|
+
from typing import Any
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from together import Together, TogetherError
|
|
13
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.argument("cluster-id", required=True)
|
|
18
|
+
@click.option(
|
|
19
|
+
"--file",
|
|
20
|
+
default=os.path.join(os.path.expanduser("~"), ".kube", "config"),
|
|
21
|
+
show_default=True,
|
|
22
|
+
type=str,
|
|
23
|
+
help="Path to write the kubeconfig to. If you pass `-` it will print the config to stdout instead of writing to a file.",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--context-name",
|
|
27
|
+
type=str,
|
|
28
|
+
help="Name of the context to add to the kubeconfig. By default it will be the cluster name.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--overwrite-existing",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="If there is a conflict with the existing kubeconfig, overwrite the existing kubeconfig instead of raising an error.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--set-default-context",
|
|
37
|
+
is_flag=True,
|
|
38
|
+
help="Change the current context for kubectl to the new context.",
|
|
39
|
+
)
|
|
40
|
+
@click.pass_context
|
|
41
|
+
@handle_api_errors("Clusters")
|
|
42
|
+
def get_credentials(
|
|
43
|
+
ctx: click.Context,
|
|
44
|
+
cluster_id: str,
|
|
45
|
+
file: str,
|
|
46
|
+
context_name: str | None = None,
|
|
47
|
+
overwrite_existing: bool = False,
|
|
48
|
+
set_default_context: bool = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Get cluster credentials"""
|
|
51
|
+
client: Together = ctx.obj
|
|
52
|
+
|
|
53
|
+
cluster = client.beta.clusters.retrieve(cluster_id)
|
|
54
|
+
|
|
55
|
+
import base64
|
|
56
|
+
|
|
57
|
+
kube_config = base64.b64decode(cluster.kube_config).decode("utf-8")
|
|
58
|
+
|
|
59
|
+
if len(kube_config) == 0:
|
|
60
|
+
click.echo("No kubeconfig found for cluster at this time.")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
if file == "-":
|
|
64
|
+
click.echo(kube_config)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
kube_config_path = Path(os.path.expanduser(file if file else "~/.kube/config"))
|
|
68
|
+
|
|
69
|
+
# ensure that at least an empty ~/.kube/config exists
|
|
70
|
+
directory = os.path.dirname(kube_config_path)
|
|
71
|
+
if directory and not os.path.exists(directory):
|
|
72
|
+
try:
|
|
73
|
+
os.makedirs(directory)
|
|
74
|
+
except OSError as ex:
|
|
75
|
+
if ex.errno != errno.EEXIST:
|
|
76
|
+
raise
|
|
77
|
+
if not os.path.exists(kube_config_path):
|
|
78
|
+
with os.fdopen(os.open(kube_config_path, os.O_CREAT | os.O_WRONLY, 0o600), "wt"):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# Write the decoded kubeconfig to the user's default kubeconfig path
|
|
82
|
+
# Ensure the .kube directory exists before writing the config file
|
|
83
|
+
try:
|
|
84
|
+
from yaml import dump, safe_load
|
|
85
|
+
except ImportError:
|
|
86
|
+
click.secho("Together cli dependencies are missing. Please run one of the following commands:\n", fg="red")
|
|
87
|
+
click.secho("uv: uv add together --optional cli", fg="yellow")
|
|
88
|
+
click.secho("pip: pip install together[cli]", fg="yellow")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Load both configs into dictionaries to merge them
|
|
92
|
+
kube_config_dict: dict[str, Any] | None = safe_load(kube_config_path.read_text())
|
|
93
|
+
incoming_config_dict: dict[str, Any] = safe_load(kube_config)
|
|
94
|
+
|
|
95
|
+
# If the user did not pass a custom context name, we will use the cluster name
|
|
96
|
+
if context_name is None:
|
|
97
|
+
context_name = cluster.cluster_name
|
|
98
|
+
|
|
99
|
+
# Update the context name on the incoming data.
|
|
100
|
+
incoming_config_dict["contexts"][0]["name"] = context_name
|
|
101
|
+
incoming_config_dict["contexts"][0]["context"]["cluster"] = context_name
|
|
102
|
+
incoming_config_dict["clusters"][0]["name"] = context_name
|
|
103
|
+
|
|
104
|
+
# If there is not a current kube config on disk, we can safely just take the incoming config
|
|
105
|
+
if kube_config_dict is None:
|
|
106
|
+
kube_config_dict = incoming_config_dict
|
|
107
|
+
else:
|
|
108
|
+
_handle_merge(kube_config_dict, incoming_config_dict, "clusters", overwrite_existing)
|
|
109
|
+
_handle_merge(kube_config_dict, incoming_config_dict, "users", overwrite_existing)
|
|
110
|
+
_handle_merge(kube_config_dict, incoming_config_dict, "contexts", overwrite_existing)
|
|
111
|
+
|
|
112
|
+
# Set the current context to the new context if the user requested it.
|
|
113
|
+
if set_default_context:
|
|
114
|
+
kube_config_dict["current-context"] = context_name
|
|
115
|
+
|
|
116
|
+
# check that ~/.kube/config is only read- and writable by its owner
|
|
117
|
+
if platform.system() != "Windows" and not os.path.islink(kube_config_path):
|
|
118
|
+
existing_file_perms = "{:o}".format(stat.S_IMODE(os.lstat(kube_config_path).st_mode))
|
|
119
|
+
if not existing_file_perms.endswith("600"):
|
|
120
|
+
click.echo(
|
|
121
|
+
f'{kube_config_path} has permissions "{existing_file_perms}".\nIt should be readable and writable only by its owner.',
|
|
122
|
+
)
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
with open(kube_config_path, "w+") as stream:
|
|
126
|
+
stream.write(dump(kube_config_dict))
|
|
127
|
+
|
|
128
|
+
click.secho(f"Kubeconfig written to {kube_config_path}", fg="green")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _handle_merge(existing: dict[str, Any], addition: dict[str, Any], key: str, overwrite_existing: bool) -> None:
|
|
132
|
+
"""Merge the incoming kube config into the existing config for the given key."""
|
|
133
|
+
if not addition.get(key, False):
|
|
134
|
+
return
|
|
135
|
+
if not existing.get(key):
|
|
136
|
+
existing[key] = addition[key]
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
for i in addition[key]:
|
|
140
|
+
for j in existing[key]:
|
|
141
|
+
if not i.get("name", False) or not j.get("name", False):
|
|
142
|
+
continue
|
|
143
|
+
if i["name"] == j["name"]:
|
|
144
|
+
if overwrite_existing or i == j:
|
|
145
|
+
existing[key].remove(j)
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
raise TogetherError(
|
|
149
|
+
f"A different object named {i['name']} already exists in {key} in your kubeconfig file."
|
|
150
|
+
)
|
|
151
|
+
existing[key].append(i)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together import Together
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.option(
|
|
10
|
+
"--json",
|
|
11
|
+
is_flag=True,
|
|
12
|
+
help="Output in JSON format",
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def list(ctx: click.Context, json: bool) -> None:
|
|
16
|
+
"""List clusters"""
|
|
17
|
+
client: Together = ctx.obj
|
|
18
|
+
|
|
19
|
+
response = client.beta.clusters.list()
|
|
20
|
+
|
|
21
|
+
if json:
|
|
22
|
+
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
23
|
+
else:
|
|
24
|
+
ctx.obj.print_clusters(response.clusters)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
|
|
7
|
+
from together import Together
|
|
8
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.option(
|
|
13
|
+
"--json",
|
|
14
|
+
is_flag=True,
|
|
15
|
+
help="Output in JSON format",
|
|
16
|
+
)
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@handle_api_errors("Clusters")
|
|
19
|
+
def list_regions(ctx: click.Context, json: bool) -> None:
|
|
20
|
+
"""List regions"""
|
|
21
|
+
client: Together = ctx.obj
|
|
22
|
+
|
|
23
|
+
response = client.beta.clusters.list_regions()
|
|
24
|
+
|
|
25
|
+
if json:
|
|
26
|
+
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
27
|
+
else:
|
|
28
|
+
data: List[Dict[str, Any]] = []
|
|
29
|
+
for region in response.regions:
|
|
30
|
+
data.append(
|
|
31
|
+
{
|
|
32
|
+
"Name": region.name,
|
|
33
|
+
"Availability Zones": ", ".join(region.availability_zones) if region.availability_zones else "",
|
|
34
|
+
"Driver Versions": ", ".join(region.driver_versions) if region.driver_versions else "",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
click.echo(tabulate(data, headers="keys", tablefmt="grid"))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich import print
|
|
5
|
+
|
|
6
|
+
from together import Together
|
|
7
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.argument("cluster-id", required=True)
|
|
12
|
+
@click.option(
|
|
13
|
+
"--json",
|
|
14
|
+
is_flag=True,
|
|
15
|
+
help="Output in JSON format",
|
|
16
|
+
)
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@handle_api_errors("Clusters")
|
|
19
|
+
def retrieve(ctx: click.Context, cluster_id: str, json: bool) -> None:
|
|
20
|
+
"""Retrieve a cluster by ID"""
|
|
21
|
+
client: Together = ctx.obj
|
|
22
|
+
|
|
23
|
+
if not json:
|
|
24
|
+
click.echo(f"Clusters: Retrieving cluster...")
|
|
25
|
+
|
|
26
|
+
response = client.beta.clusters.retrieve(cluster_id)
|
|
27
|
+
|
|
28
|
+
if json:
|
|
29
|
+
click.echo(json_lib.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
30
|
+
else:
|
|
31
|
+
print(response)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from .list import list
|
|
4
|
+
from .create import create
|
|
5
|
+
from .delete import delete
|
|
6
|
+
from .retrieve import retrieve
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
@click.pass_context
|
|
11
|
+
def storage(ctx: click.Context) -> None:
|
|
12
|
+
"""Clusters Storage API commands"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
storage.add_command(create)
|
|
17
|
+
storage.add_command(retrieve)
|
|
18
|
+
storage.add_command(delete)
|
|
19
|
+
storage.add_command(list)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together import Together
|
|
6
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.option(
|
|
11
|
+
"--region",
|
|
12
|
+
required=True,
|
|
13
|
+
type=str,
|
|
14
|
+
help="Region to create the storage volume in",
|
|
15
|
+
)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--size-tib",
|
|
18
|
+
required=True,
|
|
19
|
+
type=int,
|
|
20
|
+
help="Size of the storage volume in TiB",
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
"--volume-name",
|
|
24
|
+
required=True,
|
|
25
|
+
type=str,
|
|
26
|
+
help="Name of the storage volume",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--json",
|
|
30
|
+
is_flag=True,
|
|
31
|
+
help="Output in JSON format",
|
|
32
|
+
)
|
|
33
|
+
@click.pass_context
|
|
34
|
+
@handle_api_errors("Clusters Storage")
|
|
35
|
+
def create(ctx: click.Context, region: str, size_tib: int, volume_name: str, json: bool) -> None:
|
|
36
|
+
"""Create a storage volume"""
|
|
37
|
+
client: Together = ctx.obj
|
|
38
|
+
|
|
39
|
+
response = client.beta.clusters.storage.create(
|
|
40
|
+
region=region,
|
|
41
|
+
size_tib=size_tib,
|
|
42
|
+
volume_name=volume_name,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if json:
|
|
46
|
+
click.echo(json_lib.dumps(response.model_dump_json(), indent=2))
|
|
47
|
+
else:
|
|
48
|
+
click.echo(f"Storage volume created successfully")
|
|
49
|
+
click.echo(response.volume_id)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together import Together
|
|
6
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.argument(
|
|
11
|
+
"volume-id",
|
|
12
|
+
required=True,
|
|
13
|
+
)
|
|
14
|
+
@click.option(
|
|
15
|
+
"--json",
|
|
16
|
+
is_flag=True,
|
|
17
|
+
help="Output in JSON format",
|
|
18
|
+
)
|
|
19
|
+
@click.pass_context
|
|
20
|
+
@handle_api_errors("Clusters Storage")
|
|
21
|
+
def delete(ctx: click.Context, volume_id: str, json: bool) -> None:
|
|
22
|
+
"""Delete a storage volume"""
|
|
23
|
+
client: Together = ctx.obj
|
|
24
|
+
|
|
25
|
+
if json:
|
|
26
|
+
response = client.beta.clusters.storage.delete(volume_id)
|
|
27
|
+
click.echo(json_lib.dumps(response.model_dump(), indent=2))
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
storage = client.beta.clusters.storage.retrieve(volume_id)
|
|
31
|
+
ctx.obj.print_storage([storage])
|
|
32
|
+
if not click.confirm(f"Clusters Storage: Are you sure you want to delete storage volume {storage.volume_name}?"):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
click.echo("Clusters Storage: Deleting storage volume...")
|
|
36
|
+
response = client.beta.clusters.storage.delete(volume_id)
|
|
37
|
+
|
|
38
|
+
click.echo(f"Clusters Storage: Deleted storage volume {storage.volume_name}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
|
|
7
|
+
from together import Together
|
|
8
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
9
|
+
from together.types.beta.clusters import ClusterStorage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def print_storage(storage: List[ClusterStorage]) -> None:
|
|
13
|
+
data: List[Dict[str, Any]] = []
|
|
14
|
+
for volume in storage:
|
|
15
|
+
data.append(
|
|
16
|
+
{
|
|
17
|
+
"ID": volume.volume_id,
|
|
18
|
+
"Name": volume.volume_name,
|
|
19
|
+
"Size": volume.size_tib,
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
click.echo(tabulate(data, headers="keys", tablefmt="grid"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.option(
|
|
27
|
+
"--json",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="Output in JSON format",
|
|
30
|
+
)
|
|
31
|
+
@click.pass_context
|
|
32
|
+
@handle_api_errors("Clusters Storage")
|
|
33
|
+
def list(ctx: click.Context, json: bool) -> None:
|
|
34
|
+
"""List storage volumes"""
|
|
35
|
+
client: Together = ctx.obj
|
|
36
|
+
|
|
37
|
+
response = client.beta.clusters.storage.list()
|
|
38
|
+
|
|
39
|
+
if json:
|
|
40
|
+
click.echo(json_lib.dumps(response.model_dump(), indent=2))
|
|
41
|
+
else:
|
|
42
|
+
print_storage(response.volumes)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich import print
|
|
5
|
+
|
|
6
|
+
from together import Together
|
|
7
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.argument(
|
|
12
|
+
"volume-id",
|
|
13
|
+
required=True,
|
|
14
|
+
)
|
|
15
|
+
@click.option(
|
|
16
|
+
"--json",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
help="Output in JSON format",
|
|
19
|
+
)
|
|
20
|
+
@click.pass_context
|
|
21
|
+
@handle_api_errors("Clusters Storage")
|
|
22
|
+
def retrieve(ctx: click.Context, volume_id: str, json: bool) -> None:
|
|
23
|
+
"""Retrieve a storage volume"""
|
|
24
|
+
client: Together = ctx.obj
|
|
25
|
+
|
|
26
|
+
if not json:
|
|
27
|
+
click.echo(f"Clusters Storage: Retrieving storage volume...")
|
|
28
|
+
|
|
29
|
+
response = client.beta.clusters.storage.retrieve(volume_id)
|
|
30
|
+
|
|
31
|
+
if json:
|
|
32
|
+
click.echo(json_lib.dumps(response.model_dump(), indent=2))
|
|
33
|
+
else:
|
|
34
|
+
print(response)
|