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>
This commit is contained in:
114
fastgpt_client/base_client.py
Normal file
114
fastgpt_client/base_client.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user