|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import json |
| 4 | +import re |
4 | 5 | import sys |
5 | 6 | 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 |
7 | 8 |
|
8 | 9 | import click |
| 10 | +from tabulate import tabulate |
9 | 11 |
|
10 | 12 | from together import Together |
11 | 13 | from together.error import InvalidRequestError |
12 | 14 | from together.types import DedicatedEndpoint, ListEndpoint |
| 15 | +from together.types.endpoints import HardwareWithStatus |
13 | 16 |
|
14 | 17 |
|
15 | 18 | def print_endpoint( |
@@ -186,12 +189,18 @@ def create( |
186 | 189 | availability_zone=availability_zone, |
187 | 190 | ) |
188 | 191 | 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:") |
191 | 199 | fetch_and_print_hardware_options( |
192 | 200 | client=client, model=model, print_json=False, available=True |
193 | 201 | ) |
194 | | - |
| 202 | + else: |
| 203 | + print_api_error(e) |
195 | 204 | sys.exit(1) |
196 | 205 |
|
197 | 206 | # Print detailed information to stderr |
@@ -258,28 +267,85 @@ def hardware(client: Together, model: str | None, json: bool, available: bool) - |
258 | 267 | fetch_and_print_hardware_options(client, model, json, available) |
259 | 268 |
|
260 | 269 |
|
| 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 | + |
261 | 314 | def fetch_and_print_hardware_options( |
262 | 315 | client: Together, model: str | None, print_json: bool, available: bool |
263 | 316 | ) -> None: |
264 | 317 | """Print hardware options for a model.""" |
265 | | - |
266 | | - message = "Available hardware options:" if available else "All hardware options:" |
267 | | - click.echo(message, err=True) |
268 | 318 | hardware_options = client.endpoints.list_hardware(model) |
| 319 | + |
269 | 320 | if available: |
270 | 321 | hardware_options = [ |
271 | 322 | hardware |
272 | 323 | for hardware in hardware_options |
273 | 324 | if hardware.availability is not None |
274 | 325 | and hardware.availability.status == "available" |
275 | 326 | ] |
| 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) |
276 | 341 |
|
277 | 342 | if print_json: |
278 | 343 | json_output = [hardware.model_dump() for hardware in hardware_options] |
279 | 344 | click.echo(json.dumps(json_output, indent=2)) |
280 | 345 | 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) |
283 | 349 |
|
284 | 350 |
|
285 | 351 | @endpoints.command() |
|
0 commit comments