Ctrl K
ComunidadGuías…

rex/startup.py

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

Archivo original ↗Preguntar a KiBot
"""Startup sequence enforcement for Rex Flask applications.

Enforces the required initialization order and logs each step at INFO so that
the log stream shows exactly where a failure occurred:

  Step 1/4 — Config validation      (environment variables are sane)
  Step 2/4 — Database connectivity  (database file is accessible)
  Step 3/4 — Migration state        (no pending migrations)
  Step 4/4 — Service initialization (caller registers blueprints, starts server)

If any step raises an exception or calls ``sys.exit``, subsequent steps do not
run.  The process exits with code 1 when a step fails.

Usage::

    from rex.startup import run_startup_sequence

    run_startup_sequence()          # must be called before Flask app setup
    app = Flask(__name__)           # step 4: caller completes service init
    # … register blueprints …
    logger.info("Startup complete — accepting traffic")
"""

from __future__ import annotations

import logging
import sys
from pathlib import Path

from rex.audio.speaker_discovery import start_smart_speaker_discovery

logger = logging.getLogger(__name__)

_TOTAL_STEPS = 4


def _log_step(n: int, description: str) -> None:
    logger.info("Startup [step %d/%d]: %s", n, _TOTAL_STEPS, description)


def run_startup_sequence() -> None:
    """Run the startup sequence in order.

    Logs each step at INFO level.  Exits with code 1 if any step fails so
    that subsequent steps never execute.

    Raises:
        SystemExit: with code 1 if any startup step fails.
    """
    _run_config_validation()
    _run_database_connectivity()
    _run_migration_check()
    start_smart_speaker_discovery()


# ---------------------------------------------------------------------------
# Step 1: Config validation
# ---------------------------------------------------------------------------


def _run_config_validation() -> None:
    _log_step(1, "Config validation — verifying environment")
    try:
        _validate_env()
    except SystemExit:
        raise
    except Exception as exc:
        logger.critical("Config validation failed: %s", exc)
        sys.exit(1)


def _validate_env() -> None:
    """Check that the runtime environment is minimally sane.

    Currently a lightweight check.  Add mandatory variable assertions here as
    requirements grow.  Logs warnings for advisory-only issues.
    """
    from rex.credentials import get_persisted_credential  # noqa: PLC0415

    proxy_token = get_persisted_credential("REX_PROXY_TOKEN")
    if not proxy_token:
        logger.warning("REX_PROXY_TOKEN is not stored — bearer-token auth is disabled.")
    logger.debug("Config validation passed")


# ---------------------------------------------------------------------------
# Step 2: Database connectivity
# ---------------------------------------------------------------------------


def _run_database_connectivity() -> None:
    _log_step(2, "Database connectivity — verifying database is accessible")
    try:
        _check_database_connectivity()
    except SystemExit:
        raise
    except Exception as exc:
        logger.critical("Database connectivity check failed: %s", exc)
        sys.exit(1)


def _check_database_connectivity() -> None:
    """Probe the SQLite database for basic read/write access.

    Uses the same default path as the migrations module so that the check is
    consistent with the migration state step.
    """
    import sqlite3  # noqa: PLC0415

    db_path = Path("data/dashboard_notifications.db")

    # Ensure the data directory exists (non-fatal; SQLite will create the file)
    try:
        db_path.parent.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        logger.warning("Could not create data directory: %s", exc)

    try:
        conn = sqlite3.connect(str(db_path), timeout=5.0)
        conn.execute("SELECT 1")
        conn.close()
        logger.debug("Database connectivity OK: %s", db_path)
    except sqlite3.Error as exc:
        raise RuntimeError(f"Cannot connect to database {db_path}: {exc}") from exc


# ---------------------------------------------------------------------------
# Step 3: Migration state
# ---------------------------------------------------------------------------


def _run_migration_check() -> None:
    _log_step(3, "Migration state — checking for pending database migrations")
    try:
        from rex.migrations import validate_migration_state  # noqa: PLC0415

        validate_migration_state()
        logger.debug("Migration state check passed")
    except SystemExit:
        raise
    except Exception as exc:
        logger.critical("Migration state check failed: %s", exc)
        sys.exit(1)


# ---------------------------------------------------------------------------
# Step 4 helper — called by the application entry point
# ---------------------------------------------------------------------------


def log_service_ready() -> None:
    """Log step 4 after Flask setup and blueprint registration are complete."""
    _log_step(4, "Service initialization complete — ready to accept traffic")


__all__ = [
    "run_startup_sequence",
    "log_service_ready",
]