Ctrl K
ComunidadGuías…

rex/dep_errors.py

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

Archivo original ↗Preguntar a KiBot
"""Utilities for user-friendly dependency and connection error messages.

Provides helpers to:
- Format ImportError with human-readable messages and install hints (AC1).
- Detect network connection errors for friendly LM Studio messages (AC2).
- Query debug mode so callers can decide whether to show tracebacks (AC3).
"""

from __future__ import annotations

import os

# ---------------------------------------------------------------------------
# Debug mode detection
# ---------------------------------------------------------------------------


def is_debug_mode() -> bool:
    """Return True when REX_DEBUG env var is set to a truthy value.

    Callers use this to decide whether to show full tracebacks.
    """
    return os.environ.get("REX_DEBUG", "").lower() in ("1", "true", "yes")


# ---------------------------------------------------------------------------
# Friendly ImportError formatting
# ---------------------------------------------------------------------------


def friendly_import_error(
    package_name: str,
    install_cmd: str,
    exc: ImportError,
) -> ImportError:
    """Return a new ImportError with a user-friendly message and install hint.

    Args:
        package_name: Display name of the missing package (e.g. ``"openai"``).
        install_cmd:  Full install command (e.g. ``"pip install openai"``).
        exc:          The original ImportError to chain.

    Returns:
        A new :class:`ImportError` with a message like::

            Rex requires 'openai' for this feature but it is not installed.
            Install it with: pip install openai
    """
    msg = (
        f"Rex requires '{package_name}' for this feature but it is not installed.\n"
        f"Install it with: {install_cmd}"
    )
    new_exc = ImportError(msg)
    new_exc.__cause__ = exc
    return new_exc


# ---------------------------------------------------------------------------
# Connection error detection
# ---------------------------------------------------------------------------

# Names of exception types that represent a failed TCP/HTTP connection.
_CONNECTION_ERROR_TYPE_NAMES = frozenset(
    {
        "ConnectError",
        "ConnectionError",
        "ConnectTimeout",
        "RemoteDisconnected",
        "NewConnectionError",
        "MaxRetryError",
    }
)


def is_connection_error(exc: BaseException) -> bool:
    """Return True if *exc* looks like a network connection failure.

    Handles both stdlib errors (``ConnectionRefusedError``, ``OSError``) and
    the error types used by third-party HTTP libraries (``httpx``,
    ``requests``) without importing those libraries.
    """
    if isinstance(exc, (ConnectionRefusedError, ConnectionError, BrokenPipeError)):
        return True
    # OSError errno 111 is ECONNREFUSED on Linux; check the family broadly.
    if isinstance(exc, OSError) and exc.errno is not None:
        return True
    type_name = type(exc).__name__
    if type_name in _CONNECTION_ERROR_TYPE_NAMES:
        return True
    # Check cause chain — httpx sometimes wraps errors
    if exc.__cause__ is not None and is_connection_error(exc.__cause__):
        return True
    return False


# ---------------------------------------------------------------------------
# Typed exception for LM Studio connectivity
# ---------------------------------------------------------------------------


class LMStudioConnectionError(RuntimeError):
    """Raised when Rex cannot reach the LM Studio API server.

    The message always follows the pattern::

        Rex can't reach LM Studio at <url>. Is LM Studio running?
    """


__all__ = [
    "LMStudioConnectionError",
    "friendly_import_error",
    "is_connection_error",
    "is_debug_mode",
]