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.
Files changed (83) hide show
  1. together/_base_client.py +134 -11
  2. together/_client.py +7 -0
  3. together/_models.py +16 -1
  4. together/_types.py +9 -0
  5. together/_version.py +1 -1
  6. together/constants.py +1 -1
  7. together/error.py +1 -2
  8. together/lib/_google_colab.py +39 -0
  9. together/lib/cli/__init__.py +73 -0
  10. together/lib/cli/api/beta/clusters/__init__.py +47 -0
  11. together/lib/cli/api/beta/{clusters.py → clusters/create.py} +5 -179
  12. together/lib/cli/api/beta/clusters/delete.py +35 -0
  13. together/lib/cli/api/beta/clusters/get_credentials.py +151 -0
  14. together/lib/cli/api/beta/clusters/list.py +24 -0
  15. together/lib/cli/api/beta/clusters/list_regions.py +37 -0
  16. together/lib/cli/api/beta/clusters/retrieve.py +31 -0
  17. together/lib/cli/api/beta/clusters/storage/__init__.py +19 -0
  18. together/lib/cli/api/beta/clusters/storage/create.py +49 -0
  19. together/lib/cli/api/beta/clusters/storage/delete.py +38 -0
  20. together/lib/cli/api/beta/clusters/storage/list.py +42 -0
  21. together/lib/cli/api/beta/clusters/storage/retrieve.py +34 -0
  22. together/lib/cli/api/beta/clusters/update.py +54 -0
  23. together/lib/cli/api/endpoints/__init__.py +56 -0
  24. together/lib/cli/api/endpoints/availability_zones.py +26 -0
  25. together/lib/cli/api/endpoints/create.py +161 -0
  26. together/lib/cli/api/endpoints/delete.py +15 -0
  27. together/lib/cli/api/endpoints/hardware.py +40 -0
  28. together/lib/cli/api/endpoints/list.py +66 -0
  29. together/lib/cli/api/endpoints/retrieve.py +23 -0
  30. together/lib/cli/api/endpoints/start.py +25 -0
  31. together/lib/cli/api/endpoints/stop.py +25 -0
  32. together/lib/cli/api/endpoints/update.py +77 -0
  33. together/lib/cli/api/evals/__init__.py +19 -0
  34. together/lib/cli/api/{evals.py → evals/create.py} +6 -129
  35. together/lib/cli/api/evals/list.py +58 -0
  36. together/lib/cli/api/evals/retrieve.py +21 -0
  37. together/lib/cli/api/evals/status.py +20 -0
  38. together/lib/cli/api/files/__init__.py +23 -0
  39. together/lib/cli/api/files/check.py +21 -0
  40. together/lib/cli/api/files/delete.py +20 -0
  41. together/lib/cli/api/files/list.py +34 -0
  42. together/lib/cli/api/files/retrieve.py +20 -0
  43. together/lib/cli/api/files/retrieve_content.py +25 -0
  44. together/lib/cli/api/files/upload.py +38 -0
  45. together/lib/cli/api/fine_tuning/__init__.py +27 -0
  46. together/lib/cli/api/fine_tuning/cancel.py +28 -0
  47. together/lib/cli/api/{fine_tuning.py → fine_tuning/create.py} +5 -257
  48. together/lib/cli/api/fine_tuning/delete.py +29 -0
  49. together/lib/cli/api/fine_tuning/download.py +94 -0
  50. together/lib/cli/api/fine_tuning/list.py +44 -0
  51. together/lib/cli/api/fine_tuning/list_checkpoints.py +42 -0
  52. together/lib/cli/api/fine_tuning/list_events.py +35 -0
  53. together/lib/cli/api/fine_tuning/retrieve.py +27 -0
  54. together/lib/cli/api/models/__init__.py +15 -0
  55. together/lib/cli/api/models/list.py +55 -0
  56. together/lib/cli/api/{models.py → models/upload.py} +4 -51
  57. together/resources/beta/clusters/clusters.py +36 -28
  58. together/resources/beta/clusters/storage.py +30 -21
  59. together/resources/endpoints.py +2 -2
  60. together/types/__init__.py +4 -3
  61. together/types/beta/__init__.py +0 -2
  62. together/types/beta/cluster_create_params.py +3 -3
  63. together/types/beta/clusters/__init__.py +0 -1
  64. together/types/beta/clusters/cluster_storage.py +4 -0
  65. together/types/chat_completions.py +1 -1
  66. together/types/endpoint_create_params.py +1 -1
  67. together/types/endpoints.py +1 -1
  68. {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/METADATA +4 -2
  69. {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/RECORD +74 -38
  70. together-2.0.0a17.dist-info/entry_points.txt +2 -0
  71. together/lib/cli/api/__init__.py +0 -0
  72. together/lib/cli/api/beta/clusters_storage.py +0 -152
  73. together/lib/cli/api/endpoints.py +0 -467
  74. together/lib/cli/api/files.py +0 -133
  75. together/lib/cli/cli.py +0 -73
  76. together/types/beta/cluster_create_response.py +0 -9
  77. together/types/beta/cluster_update_response.py +0 -9
  78. together/types/beta/clusters/storage_create_response.py +0 -9
  79. together-2.0.0a15.dist-info/entry_points.txt +0 -2
  80. /together/lib/cli/api/{utils.py → _utils.py} +0 -0
  81. /together/lib/cli/api/beta/{beta.py → __init__.py} +0 -0
  82. {together-2.0.0a15.dist-info → together-2.0.0a17.dist-info}/WHEEL +0 -0
  83. {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 Any, Dict, List, Literal
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, omit
12
- from together._response import APIResponse as APIResponse
13
- from together.types.beta import Cluster, ClusterCreateParams
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
- def print_clusters(clusters: List[Cluster]) -> None:
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)