"""Base client mixin with retry logic and validation.""" import logging import time from typing import Any class BaseClientMixin: """Mixin class providing retry logic, validation, and logging for FastGPT clients.""" def __init__( self, api_key: str, base_url: str, timeout: float = 60.0, max_retries: int = 3, retry_delay: float = 1.0, enable_logging: bool = False, ): """Initialize base client. Args: api_key: FastGPT API key base_url: Base URL for FastGPT API timeout: Request timeout in seconds max_retries: Maximum number of retry attempts retry_delay: Delay between retries in seconds enable_logging: Whether to enable request logging """ self.api_key = api_key self.base_url = base_url self.timeout = timeout self.max_retries = max_retries self.retry_delay = retry_delay self.enable_logging = enable_logging self.logger = logging.getLogger(__name__) def _validate_params(self, **params) -> None: """Validate request parameters. Args: **params: Parameters to validate Raises: ValidationError: If any parameter is invalid """ for key, value in params.items(): if value is None: continue # Check for empty strings that should be non-empty if isinstance(value, str) and key in ("query", "chatId", "appId", "dataId", "content"): if not value.strip(): from .exceptions import ValidationError raise ValidationError(f"{key} must be a non-empty string") # Check for valid lists/dicts elif isinstance(value, (list, dict)) and not value: # Empty lists/dicts are usually valid, but log for debugging if self.enable_logging and self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"Parameter {key} is empty") def _retry_request(self, request_func, request_context: str): """Execute a request with retry logic. Args: request_func: Function that executes the HTTP request request_context: Description of the request for logging Returns: Response from the request Raises: APIError: If all retries are exhausted """ last_exception = None for attempt in range(self.max_retries): try: response = request_func() # Success on non-5xx responses if response.status_code < 500: return response # Server error - will retry if self.enable_logging: self.logger.warning( f"{request_context} failed with status {response.status_code} " f"(attempt {attempt + 1}/{self.max_retries})" ) if attempt < self.max_retries - 1: # Exponential backoff sleep_time = self.retry_delay * (2 ** attempt) time.sleep(sleep_time) except Exception as e: last_exception = e if self.enable_logging: self.logger.warning( f"{request_context} raised exception: {e} " f"(attempt {attempt + 1}/{self.max_retries})" ) if attempt < self.max_retries - 1: sleep_time = self.retry_delay * (2 ** attempt) time.sleep(sleep_time) # All retries exhausted if last_exception: from .exceptions import APIError raise APIError(f"Request failed after {self.max_retries} attempts: {last_exception}") from .exceptions import APIError raise APIError(f"Request failed after {self.max_retries} attempts")