Skip to content

Commit e96624e

Browse files
authored
feat(cli): enhance hardware command to display availability status (#422)
* feat(cli): enhance hardware command to display availability status Add formatted table output for hardware options showing: - Hardware ID - GPU type, count, and memory - Availability status with color-coded indicators - Pricing information Changes: - Add format_hardware_table() function for formatted table display - Update fetch_and_print_hardware_options() to use new table format - Show availability column only when model is specified - Color-coded status: green (available), red (unavailable), yellow (insufficient) Resolves: MLE-2935 * style: simplify hardware table formatting using tabulate - Replace manual column-width formatting with tabulate library - Add re module to clean up GPU type names - Improve error handling for invalid hardware selection with multiple error patterns
1 parent f6ff8d4 commit e96624e

File tree

1 file changed

+75
-9
lines changed

1 file changed

+75
-9
lines changed

src/together/cli/api/endpoints.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from __future__ import annotations
22

33
import json
4+
import re
45
import sys
56
from functools import wraps
6-
from typing import Any, Callable, Dict, List, Literal, TypeVar, Union
7+
from typing import Any, Callable, Dict, List, Literal, Sequence, TypeVar, Union
78

89
import click
10+
from tabulate import tabulate
911

1012
from together import Together
1113
from together.error import InvalidRequestError
1214
from together.types import DedicatedEndpoint, ListEndpoint
15+
from together.types.endpoints import HardwareWithStatus
1316

1417

1518
def print_endpoint(
@@ -186,12 +189,18 @@ def create(
186189
availability_zone=availability_zone,
187190
)
188191
except InvalidRequestError as e:
189-
print_api_error(e)
190-
if "check the hardware api" in str(e).lower():
192+
if (
193+
"check the hardware api" in str(e.args[0]).lower()
194+
or "invalid hardware provided" in str(e.args[0]).lower()
195+
or "the selected configuration" in str(e.args[0]).lower()
196+
):
197+
click.secho("Invalid hardware selected.", fg="red", err=True)
198+
click.echo("\nAvailable hardware options:")
191199
fetch_and_print_hardware_options(
192200
client=client, model=model, print_json=False, available=True
193201
)
194-
202+
else:
203+
print_api_error(e)
195204
sys.exit(1)
196205

197206
# Print detailed information to stderr
@@ -258,28 +267,85 @@ def hardware(client: Together, model: str | None, json: bool, available: bool) -
258267
fetch_and_print_hardware_options(client, model, json, available)
259268

260269

270+
def _format_hardware_options(
271+
hardware_options: Sequence[HardwareWithStatus],
272+
show_availability: bool = True,
273+
) -> None:
274+
"""Print hardware options in a formatted table using tabulate."""
275+
if not hardware_options:
276+
click.echo(" No hardware options found.", err=True)
277+
return
278+
279+
display_list: List[Dict[str, Any]] = []
280+
281+
for hw in hardware_options:
282+
data: Dict[str, Any] = {
283+
"Hardware ID": hw.id,
284+
"GPU": (
285+
re.sub(r"\-\d+[a-zA-Z][a-zA-Z]$", "", hw.specs.gpu_type)
286+
if hw.specs and hw.specs.gpu_type
287+
else "N/A"
288+
),
289+
"Memory": f"{int(hw.specs.gpu_memory)}GB" if hw.specs else "N/A",
290+
"Count": hw.specs.gpu_count if hw.specs else "N/A",
291+
"Price (per minute)": (
292+
f"${hw.pricing.cents_per_minute / 100:.2f}" if hw.pricing else "N/A"
293+
),
294+
}
295+
296+
if show_availability:
297+
status_display = "—"
298+
if hw.availability:
299+
status = hw.availability.status
300+
# Add visual indicators for status
301+
if status == "available":
302+
status_display = click.style("✓ available", fg="green")
303+
elif status == "unavailable":
304+
status_display = click.style("✗ unavailable", fg="red")
305+
else: # insufficient
306+
status_display = click.style("⚠ insufficient", fg="yellow")
307+
data["Availability"] = status_display
308+
309+
display_list.append(data)
310+
311+
click.echo(tabulate(display_list, headers="keys", numalign="left"))
312+
313+
261314
def fetch_and_print_hardware_options(
262315
client: Together, model: str | None, print_json: bool, available: bool
263316
) -> None:
264317
"""Print hardware options for a model."""
265-
266-
message = "Available hardware options:" if available else "All hardware options:"
267-
click.echo(message, err=True)
268318
hardware_options = client.endpoints.list_hardware(model)
319+
269320
if available:
270321
hardware_options = [
271322
hardware
272323
for hardware in hardware_options
273324
if hardware.availability is not None
274325
and hardware.availability.status == "available"
275326
]
327+
message = (
328+
f"Available hardware options for model '{model}':"
329+
if model
330+
else "Available hardware options:"
331+
)
332+
else:
333+
message = (
334+
f"Hardware options for model '{model}':"
335+
if model
336+
else "All hardware options:"
337+
)
338+
339+
click.echo(message, err=True)
340+
click.echo("", err=True)
276341

277342
if print_json:
278343
json_output = [hardware.model_dump() for hardware in hardware_options]
279344
click.echo(json.dumps(json_output, indent=2))
280345
else:
281-
for hardware in hardware_options:
282-
click.echo(f" {hardware.id}", err=True)
346+
# Show availability column only when model is specified (availability info is only returned with model filter)
347+
show_availability = model is not None
348+
_format_hardware_options(hardware_options, show_availability=show_availability)
283349

284350

285351
@endpoints.command()

0 commit comments

Comments
 (0)