Ctrl K
ComunidadGuías…

scripts/record_demo_fixtures.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Capture the demo's canned API responses from a running STT server.

Development tool, never shipped. Hand-writing fixtures for two dozen endpoints
guesses their shapes wrong and drifts silently as the API moves; recording them from
a real server gets the shapes right and can be re-run when something changes.

Everything captured passes through stt.demo_redact first, because a real server's
responses carry the operator's home directory, the machine's hostname, the paired
machine's address and any SMB credentials. Read the residual-flag report it prints
before committing the result.

Usage:
    .venv/bin/python3 scripts/record_demo_fixtures.py --base-url http://127.0.0.1:8080
    .venv/bin/python3 scripts/record_demo_fixtures.py --dry-run     # print, don't write
"""

from __future__ import annotations

import argparse
import json
import os
import socket
import sys
import urllib.error
import urllib.request

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from stt import demo_redact  # noqa: E402

# GET endpoints whose real responses the demo serves back. Only the families the demo
# intercepts — anything the demo lets through to the real server has no fixture.
ENDPOINTS = (
    "/api/models/list",
    "/api/models/manager",
    "/api/models/local",
    "/api/models/nllb-list",
    "/api/models/nllb-status",
    "/api/models/madlad-list",
    "/api/models/gguf-list",
    "/api/models/faster-whisper/list",
    "/api/models/tts-list",
    "/api/models/panns-status",
    "/api/models/silero-vad-status",
    "/api/models/download-status",
    "/api/tts/status",
    "/api/tts/voices",
    "/api/tts/models",
    "/api/translation/settings",
    "/api/translation/status",
    "/api/audio-devices",
    "/api/file-mover/status",
    "/api/tunnel/status",
    "/api/translate/llm-prompt",
)

HEADER = '''"""Canned API responses the demo serves in place of the real backends.

GENERATED by scripts/record_demo_fixtures.py — do not edit by hand; re-record instead.
Captured from a real server so the shapes are right, then passed through
stt.demo_redact to remove the recording machine's identity.
"""

from __future__ import annotations

from typing import Any, Dict

'''


def fetch(base_url: str, path: str, timeout: float = 15.0):
    """The endpoint's JSON, or None if it is not one this server serves."""
    try:
        with urllib.request.urlopen(base_url.rstrip("/") + path, timeout=timeout) as response:
            body = response.read().decode("utf-8")
    except urllib.error.HTTPError as exc:
        print(f"  {path}: HTTP {exc.code} — skipped")
        return None
    except (urllib.error.URLError, OSError, TimeoutError) as exc:
        print(f"  {path}: unreachable ({exc}) — skipped")
        return None
    try:
        return json.loads(body)
    except ValueError:
        print(f"  {path}: not JSON — skipped")
        return None


def render(fixtures) -> str:
    body = json.dumps(fixtures, indent=4, ensure_ascii=False, sort_keys=True)
    return (HEADER
            + "RESPONSES: Dict[str, Any] = "
            + body.replace(": true", ": True").replace(": false", ": False")
                  .replace(": null", ": None")
            + "\n")


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--base-url", default="http://127.0.0.1:8080",
                        help="a running STT server to record from")
    parser.add_argument("--out", default=os.path.join("stt", "demo_fixtures.py"))
    parser.add_argument("--hostname", default=socket.gethostname(),
                        help="the recording machine's hostname, replaced wherever it appears")
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()

    print(f"Recording from {args.base_url} (hostname {args.hostname!r})")
    fixtures = {}
    for path in ENDPOINTS:
        payload = fetch(args.base_url, path)
        if payload is None:
            continue
        fixtures[path] = demo_redact.redact(payload, hostname=args.hostname)
        print(f"  {path}: captured")

    if not fixtures:
        print("Nothing captured — is the server running and reachable?")
        return 1

    flags = []
    for path, payload in fixtures.items():
        flags.extend(f"{path} {flag}" for flag in
                     demo_redact.residual_flags(payload, hostname=args.hostname))

    print(f"\n{len(fixtures)} endpoints captured.")
    if flags:
        print(f"\n!! {len(flags)} residual flags — review before committing:")
        for flag in flags:
            print(f"   {flag}")
    else:
        print("No residual flags.")

    rendered = render(fixtures)
    if args.dry_run:
        print(f"\n--dry-run: would write {len(rendered)} bytes to {args.out}")
        return 0
    with open(args.out, "w", encoding="utf-8") as handle:
        handle.write(rendered)
    print(f"\nWrote {args.out} ({len(rendered)} bytes)")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())