Bump version to 0.2.0 and enhance documentation

- Updated version number in pyproject.toml and __init__.py to 0.2.0.
- Revised README.md to reflect the current state of the project, including usage instructions and setup steps.
- Improved CLI documentation in cli.md, adding details about new commands and their functionalities.
- Enhanced the quick start section in README.md for better clarity on initial setup.
- Updated local folder documentation to clarify file handling and commands.
- Added a new command for listing GPU flavors and improved error handling in the CLI.
- Implemented a watchdog feature in the tunnel to manage server states effectively.
This commit is contained in:
Leonid Pershin
2026-08-21 03:38:01 +03:00
parent 343f741baa
commit a563ae06c4
30 changed files with 1750 additions and 132 deletions
+78
View File
@@ -0,0 +1,78 @@
"""User-facing prints: flavor list, cost warnings (no invented ₽)."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from gpu_rent.config import Config
from gpu_rent.inventory import FlavorInfo, looks_like_gpu, rank_flavors, resolve_flavor
Log = Callable[[str], None]
def list_ranked_flavors(flavors: list[Any], cfg: Config) -> list[FlavorInfo]:
gpu = [f for f in flavors if looks_like_gpu(f)]
return rank_flavors(gpu or flavors, cfg.flavor_preference)
def format_flavor_lines(ranked: list[FlavorInfo], picked: FlavorInfo | None = None) -> list[str]:
if not ranked:
return ["flavors: пусто (проверь регион / FLAVOR_PREFERENCE)"]
lines = ["flavors (по FLAVOR_PREFERENCE):"]
for i, info in enumerate(ranked, 1):
mark = " ← выберем" if picked and info.id == picked.id else ""
ram = f"{info.ram_mb // 1024}GB" if info.ram_mb else "?"
vcpu = str(info.vcpus) if info.vcpus is not None else "?"
label = info.label or ""
lines.append(f" {i}. [{label}] {info.name} vCPU={vcpu} RAM={ram} id={info.id}{mark}")
return lines
def cost_and_risk_lines(cfg: Config, *, spot: bool, flavor_name: str) -> list[str]:
"""Honest billing notes — OpenStack has no ₽ prices."""
return [
f"план: {'preemptible ' if spot else ''}{flavor_name}, data {cfg.data_volume_size_gb} GB",
"₽: цены в OpenStack API нет — смотри панель Selectel (GPU ₽/час + диск ₽/мес).",
"диск data тарифицируется 24/7, даже когда GPU выключен (stop).",
f"idle-killer: {cfg.idle_grace_minutes} мин льготы после boot, потом "
f"{cfg.idle_minutes} мин пустой очереди → delete compute. Отложить: gpu-rent hold",
"preemptible: хостер может усыпить (~24 ч окно) → EXPIRED; tunnel сам unshelve, "
"или gpu-rent up",
"Ctrl+C на tunnel GPU не гасит — только gpu-rent stop или idle-killer",
]
def print_up_preview(
cfg: Config,
flavors: list[Any],
*,
picked: FlavorInfo,
spot: bool,
log: Log,
) -> None:
ranked = list_ranked_flavors(flavors, cfg)
for line in format_flavor_lines(ranked, picked):
log(line)
log("")
for line in cost_and_risk_lines(cfg, spot=spot, flavor_name=picked.name):
log(f"! {line}")
def resolve_and_preview(
cfg: Config,
flavors: list[Any],
*,
explicit: str | None,
spot: bool,
log: Log,
) -> FlavorInfo:
picked = resolve_flavor(
flavors,
cfg.flavor_preference,
explicit=explicit,
default_id=cfg.default_flavor_id or None,
fallback=cfg.flavor_fallback,
)
print_up_preview(cfg, flavors, picked=picked, spot=spot, log=log)
return picked