Files
fastgpt-python-sdk/fastgpt_client/base_client.py
Xin Wang 0495dd4676 Initial commit: FastGPT Python SDK Phase 1
Implement core infrastructure:

- BaseClientMixin with retry logic and validation
- FastGPTClient base class with httpx
- ChatClient with 11 chat operation methods
- AppClient for analytics and logs
- Custom exceptions (APIError, AuthenticationError, etc.)
- Package configuration (pyproject.toml, setup.py)
- Documentation (README.md, CLAUDE.md)
- Basic usage examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 14:39:33 +08:00

115 lines
4.0 KiB
Python

"""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")