feat(db-list): default to remote DB, add --local override (#3)
By default 'tsr db list' read from the LOCAL SQLite DB even when default_remote was configured, which created a confusing UX where the table never reflected the actual generation host. On a typical setup (chi@fuji with default_remote='runpod') it would happily display ghost entries pointing at /home/madcat/comfyui/models/... paths that don't exist on macOS at all. Flip the default: - 'tsr db list' -> remote (when default_remote set), else local - 'tsr db list --local' -> force local SQLite DB - 'tsr db list -r <name>' -> explicit remote (overrides default_remote) Precedence: --local wins; else -r; else default_remote; else local. Adds remote_db_files() helper in tensors/remote.py that calls the existing GET /api/db/files server endpoint (no server-side changes needed). Mirrors the pattern used by remote_models(). Scope kept minimal: only 'db list' for now. db search / triggers / stats stay local-default; can flip later under the same pattern if the UX wins are similar. Co-authored-by: marauder-actual <marauder@saiden.dev>
This commit is contained in:
+44
-13
@@ -2600,27 +2600,57 @@ def db_cache(
|
|||||||
|
|
||||||
|
|
||||||
@db_app.command("list")
|
@db_app.command("list")
|
||||||
def db_list(
|
def db_list( # noqa: PLR0915
|
||||||
model_type: Annotated[
|
model_type: Annotated[
|
||||||
str | None, typer.Option("-t", "--type", help="Filter by model type (Checkpoint, LORA, VAE, etc.)")
|
str | None, typer.Option("-t", "--type", help="Filter by model type (Checkpoint, LORA, VAE, etc.)")
|
||||||
] = None,
|
] = None,
|
||||||
base: Annotated[
|
base: Annotated[
|
||||||
str | None, typer.Option("-b", "--base", help="Filter by base model (Pony, Illustrious, SDXL 1.0, SD 1.5, etc.)")
|
str | None, typer.Option("-b", "--base", help="Filter by base model (Pony, Illustrious, SDXL 1.0, SD 1.5, etc.)")
|
||||||
] = None,
|
] = None,
|
||||||
|
remote: Annotated[
|
||||||
|
str | None, typer.Option("-r", "--remote", help="Remote server name or URL (overrides default_remote)")
|
||||||
|
] = None,
|
||||||
|
local: Annotated[
|
||||||
|
bool, typer.Option("--local", help="Force read from local DB, ignoring default_remote")
|
||||||
|
] = False,
|
||||||
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False,
|
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""List local files with CivitAI info.
|
"""List files indexed in the tensors DB.
|
||||||
|
|
||||||
|
Defaults to the configured remote (config.toml default_remote) so the
|
||||||
|
table reflects what's actually on the generation host. Pass --local to
|
||||||
|
inspect the local SQLite DB on this machine instead. If no remote is
|
||||||
|
configured and --local is not passed, falls back to local silently.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
tsr db list # All local files
|
tsr db list # Remote (or local if no default_remote)
|
||||||
tsr db list -t Checkpoint # Only checkpoints
|
tsr db list --local # Force local DB
|
||||||
tsr db list -t LORA # Only LoRAs
|
tsr db list -r junkpile # Explicit remote
|
||||||
tsr db list -t Checkpoint -b Pony # Pony checkpoints only
|
tsr db list -t Checkpoint -b Pony # Filter, default source
|
||||||
tsr db list -b "SDXL 1.0" # All SDXL 1.0 models
|
tsr db list -b "SDXL 1.0" # All SDXL 1.0 models
|
||||||
"""
|
"""
|
||||||
with Database() as db:
|
from tensors.config import resolve_remote as do_resolve_remote # noqa: PLC0415
|
||||||
db.init_schema()
|
|
||||||
files = db.list_local_files()
|
# Resolution precedence: --local wins; else explicit -r; else default_remote; else local.
|
||||||
|
remote_url: str | None = None
|
||||||
|
if not local:
|
||||||
|
remote_url = do_resolve_remote(remote) if remote else do_resolve_remote(None)
|
||||||
|
|
||||||
|
files: list[dict[str, Any]] | None
|
||||||
|
if remote_url:
|
||||||
|
from tensors.remote import remote_db_files # noqa: PLC0415
|
||||||
|
|
||||||
|
if not json_output:
|
||||||
|
console.print(f"[dim]Remote: {remote_url}[/dim]")
|
||||||
|
files = remote_db_files(remote or remote_url, console=console)
|
||||||
|
if files is None:
|
||||||
|
raise typer.Exit(1)
|
||||||
|
source_label = "Remote Files"
|
||||||
|
else:
|
||||||
|
with Database() as db:
|
||||||
|
db.init_schema()
|
||||||
|
files = db.list_local_files()
|
||||||
|
source_label = "Local Files"
|
||||||
|
|
||||||
# Apply filters (case-insensitive substring match)
|
# Apply filters (case-insensitive substring match)
|
||||||
if model_type:
|
if model_type:
|
||||||
@@ -2635,17 +2665,18 @@ def db_list(
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not files:
|
if not files:
|
||||||
console.print("[yellow]No files found. Try 'tsr db scan' or adjust filters.[/yellow]")
|
hint = "Remote DB is empty" if remote_url else "Try 'tsr db scan'"
|
||||||
|
console.print(f"[yellow]No files found. {hint} or adjust filters.[/yellow]")
|
||||||
return
|
return
|
||||||
|
|
||||||
title = "Local Files"
|
title = source_label
|
||||||
if model_type or base:
|
if model_type or base:
|
||||||
parts = []
|
parts = []
|
||||||
if model_type:
|
if model_type:
|
||||||
parts.append(model_type)
|
parts.append(model_type)
|
||||||
if base:
|
if base:
|
||||||
parts.append(base)
|
parts.append(base)
|
||||||
title = f"Local Files ({', '.join(parts)})"
|
title = f"{source_label} ({', '.join(parts)})"
|
||||||
|
|
||||||
table = Table(title=title, show_header=True, header_style="bold magenta")
|
table = Table(title=title, show_header=True, header_style="bold magenta")
|
||||||
table.add_column("Path", style="cyan", max_width=50)
|
table.add_column("Path", style="cyan", max_width=50)
|
||||||
|
|||||||
@@ -302,3 +302,48 @@ def remote_download_status(
|
|||||||
return result
|
return result
|
||||||
except (httpx.HTTPStatusError, httpx.RequestError):
|
except (httpx.HTTPStatusError, httpx.RequestError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def remote_db_files(
|
||||||
|
remote: str,
|
||||||
|
*,
|
||||||
|
console: Console | None = None,
|
||||||
|
) -> list[dict[str, Any]] | None:
|
||||||
|
"""Fetch the local-files index from a remote tensors server's DB.
|
||||||
|
|
||||||
|
Returns the same shape as Database.list_local_files() but reflecting the
|
||||||
|
remote host's SQLite DB rather than ours.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote: Remote name or URL (resolved via config)
|
||||||
|
console: Rich console for error output
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of file dicts, or None on connection / API error
|
||||||
|
"""
|
||||||
|
base_url = resolve_remote(remote)
|
||||||
|
if not base_url:
|
||||||
|
if console:
|
||||||
|
console.print("[red]Error: Could not resolve remote server[/red]")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with _build_client(base_url) as client:
|
||||||
|
response = client.get("/api/db/files")
|
||||||
|
response.raise_for_status()
|
||||||
|
result: list[dict[str, Any]] = response.json()
|
||||||
|
return result
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
if console:
|
||||||
|
console.print(f"[red]Remote API error: {e.response.status_code}[/red]")
|
||||||
|
try:
|
||||||
|
detail = e.response.json().get("detail", "")
|
||||||
|
if detail:
|
||||||
|
console.print(f" [yellow]{detail}[/yellow]")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
if console:
|
||||||
|
console.print(f"[red]Remote connection error: {e}[/red]")
|
||||||
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user