mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
550
_deprecated_py_src/services/provider_ops/architectures/base.py
Normal file
550
_deprecated_py_src/services/provider_ops/architectures/base.py
Normal file
@@ -0,0 +1,550 @@
|
||||
"""
|
||||
Provider 架构抽象基类
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from src.services.provider_ops.actions.base import ProviderAction
|
||||
from src.services.provider_ops.types import (
|
||||
ConnectorAuthType,
|
||||
ConnectorState,
|
||||
ConnectorStatus,
|
||||
ProviderActionType,
|
||||
)
|
||||
from src.utils.ssl_utils import get_ssl_context
|
||||
|
||||
# ==================== 连接器基类 ====================
|
||||
|
||||
|
||||
class ProviderConnector(ABC):
|
||||
"""
|
||||
提供商连接器基类
|
||||
|
||||
负责建立与提供商的认证连接,管理凭据状态。
|
||||
每个架构应在自己的文件中实现对应的连接器子类。
|
||||
"""
|
||||
|
||||
# 子类需要定义的类属性
|
||||
auth_type: ConnectorAuthType = ConnectorAuthType.NONE
|
||||
display_name: str = "Base Connector"
|
||||
|
||||
def __init__(self, base_url: str, config: dict[str, Any] | None = None):
|
||||
"""
|
||||
初始化连接器
|
||||
|
||||
Args:
|
||||
base_url: 提供商 API 基础 URL
|
||||
config: 连接器配置
|
||||
"""
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.config = config or {}
|
||||
self._status = ConnectorStatus.DISCONNECTED
|
||||
self._connected_at: datetime | None = None
|
||||
self._expires_at: datetime | None = None
|
||||
self._last_error: str | None = None
|
||||
|
||||
# 代理配置(支持 proxy_node_id、tunnel 和旧的 proxy URL)
|
||||
from src.services.proxy_node.resolver import resolve_ops_proxy_config
|
||||
|
||||
self._proxy: str | httpx.Proxy | None
|
||||
self._tunnel_node_id: str | None
|
||||
self._proxy, self._tunnel_node_id = resolve_ops_proxy_config(self.config)
|
||||
|
||||
# HTTP 客户端配置
|
||||
self._timeout = self.config.get("timeout", 30)
|
||||
self._headers: dict[str, str] = {}
|
||||
|
||||
# 凭据更新回调(Token Rotation 等场景需要持久化新凭据)
|
||||
self._on_credentials_updated: Callable[[dict[str, Any]], None] | None = None
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self, credentials: dict[str, Any]) -> bool:
|
||||
"""
|
||||
建立认证连接
|
||||
|
||||
Args:
|
||||
credentials: 凭据信息(如用户名密码、API Key 等)
|
||||
|
||||
Returns:
|
||||
是否连接成功
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def disconnect(self) -> None:
|
||||
"""断开连接,清理状态"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def is_authenticated(self) -> bool:
|
||||
"""检查当前是否已认证"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _apply_auth(self, request: httpx.Request) -> httpx.Request:
|
||||
"""
|
||||
为请求应用认证信息
|
||||
|
||||
Args:
|
||||
request: 原始请求
|
||||
|
||||
Returns:
|
||||
添加认证信息后的请求
|
||||
"""
|
||||
pass
|
||||
|
||||
async def refresh_auth(self, credentials: dict[str, Any]) -> bool:
|
||||
"""
|
||||
刷新认证(如 Token 过期)
|
||||
|
||||
默认实现:重新连接
|
||||
|
||||
Args:
|
||||
credentials: 凭据信息
|
||||
|
||||
Returns:
|
||||
是否刷新成功
|
||||
"""
|
||||
return await self.connect(credentials)
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_client(self) -> AsyncIterator[httpx.AsyncClient]:
|
||||
"""
|
||||
获取已认证的 HTTP 客户端
|
||||
|
||||
使用 context manager 确保资源正确释放。
|
||||
tunnel 模式下使用 TunnelTransport 替代 proxy transport。
|
||||
|
||||
Yields:
|
||||
已配置认证信息的 AsyncClient
|
||||
"""
|
||||
transport = None
|
||||
if self._tunnel_node_id:
|
||||
from src.services.proxy_node.tunnel_transport import create_tunnel_transport
|
||||
|
||||
transport = create_tunnel_transport(self._tunnel_node_id, timeout=self._timeout)
|
||||
elif self._proxy:
|
||||
transport = httpx.AsyncHTTPTransport(proxy=self._proxy)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
base_url=self.base_url,
|
||||
timeout=self._timeout,
|
||||
transport=transport,
|
||||
event_hooks={"request": [self._auth_hook]},
|
||||
verify=get_ssl_context(),
|
||||
) as client:
|
||||
yield client
|
||||
|
||||
async def _auth_hook(self, request: httpx.Request) -> None:
|
||||
"""请求钩子:应用认证信息"""
|
||||
self._apply_auth(request)
|
||||
|
||||
def get_state(self) -> ConnectorState:
|
||||
"""获取连接器当前状态"""
|
||||
return ConnectorState(
|
||||
status=self._status,
|
||||
auth_type=self.auth_type,
|
||||
connected_at=self._connected_at,
|
||||
expires_at=self._expires_at,
|
||||
last_error=self._last_error,
|
||||
)
|
||||
|
||||
def _set_connected(self, expires_at: datetime | None = None) -> None:
|
||||
"""设置为已连接状态"""
|
||||
self._status = ConnectorStatus.CONNECTED
|
||||
self._connected_at = datetime.now(timezone.utc)
|
||||
self._expires_at = expires_at
|
||||
self._last_error = None
|
||||
|
||||
def _set_error(self, error: str) -> None:
|
||||
"""设置错误状态"""
|
||||
self._status = ConnectorStatus.ERROR
|
||||
self._last_error = error
|
||||
|
||||
def _set_disconnected(self) -> None:
|
||||
"""设置为断开状态"""
|
||||
self._status = ConnectorStatus.DISCONNECTED
|
||||
self._connected_at = None
|
||||
self._expires_at = None
|
||||
|
||||
@classmethod
|
||||
def get_credentials_schema(cls) -> dict[str, Any]:
|
||||
"""
|
||||
获取凭据配置 JSON Schema(用于前端表单生成)
|
||||
|
||||
子类应重写此方法
|
||||
"""
|
||||
return {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
|
||||
# ==================== 验证结果 ====================
|
||||
|
||||
|
||||
@dataclass
|
||||
class VerifyResult:
|
||||
"""认证验证结果"""
|
||||
|
||||
success: bool
|
||||
message: str | None = None
|
||||
username: str | None = None
|
||||
display_name: str | None = None
|
||||
email: str | None = None
|
||||
quota: float | None = None
|
||||
used_quota: float | None = None
|
||||
request_count: int | None = None
|
||||
extra: dict[str, Any] | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
if not self.success:
|
||||
return {"success": False, "message": self.message}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"username": self.username,
|
||||
"display_name": self.display_name or self.username,
|
||||
"email": self.email,
|
||||
"quota": self.quota,
|
||||
"used_quota": self.used_quota,
|
||||
"request_count": self.request_count,
|
||||
"extra": self.extra or {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ==================== 架构基类 ====================
|
||||
|
||||
|
||||
class ProviderArchitecture(ABC):
|
||||
"""
|
||||
提供商架构抽象基类
|
||||
|
||||
架构 = Connector(鉴权方式) + Actions(支持的操作)
|
||||
|
||||
一个架构可以被多个 Provider 复用。
|
||||
例如:generic_api 架构可用于各种中转站。
|
||||
|
||||
## 添加新认证模板的步骤
|
||||
|
||||
1. 在 architectures/ 目录创建新文件
|
||||
2. 继承 ProviderArchitecture 和 ProviderConnector
|
||||
3. 定义类属性:architecture_id, display_name, description
|
||||
4. 实现连接器子类和架构类
|
||||
5. 实现认证相关的抽象方法:
|
||||
- get_credentials_schema(): 返回凭据字段定义
|
||||
- get_verify_endpoint(): 返回验证端点
|
||||
- build_verify_headers(): 构建验证请求 headers
|
||||
- parse_verify_response(): 解析验证响应
|
||||
6. 在 registry.py 的 _register_builtin_architectures() 中注册
|
||||
"""
|
||||
|
||||
# 子类需要定义的类属性
|
||||
architecture_id: str = ""
|
||||
display_name: str = ""
|
||||
description: str = ""
|
||||
|
||||
# 设为 True 时不在架构列表 API 中返回(内部使用的架构)
|
||||
hidden: bool = False
|
||||
|
||||
# 支持的 Connector 类型列表(按优先级排序)
|
||||
supported_connectors: list[type[ProviderConnector]] = []
|
||||
|
||||
# 支持的 Action 类型列表
|
||||
supported_actions: list[type[ProviderAction]] = []
|
||||
|
||||
# 默认操作配置
|
||||
default_action_configs: dict[ProviderActionType, dict[str, Any]] = {}
|
||||
|
||||
def __init__(self, config: dict[str, Any] | None = None):
|
||||
"""
|
||||
初始化架构
|
||||
|
||||
Args:
|
||||
config: 架构配置
|
||||
"""
|
||||
self.config = config or {}
|
||||
|
||||
# ==================== 认证验证相关方法 ====================
|
||||
|
||||
@abstractmethod
|
||||
def get_credentials_schema(self) -> dict[str, Any]:
|
||||
"""
|
||||
获取凭据字段定义(JSON Schema 格式)
|
||||
|
||||
子类必须实现此方法定义需要的凭据字段。
|
||||
|
||||
Returns:
|
||||
JSON Schema 格式的字段定义
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_verify_endpoint(self) -> str:
|
||||
"""
|
||||
获取认证验证端点
|
||||
|
||||
子类必须实现此方法返回验证端点。
|
||||
|
||||
Returns:
|
||||
验证端点路径(如 /api/user/self, /api/v1/auth/profile)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_verify_headers(
|
||||
self,
|
||||
config: dict[str, Any],
|
||||
credentials: dict[str, Any],
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
构建认证验证请求的 Headers
|
||||
|
||||
子类必须实现此方法构建认证 Headers。
|
||||
|
||||
Args:
|
||||
config: 连接器配置(可能包含 prepare_verify_config 返回的额外配置)
|
||||
credentials: 凭据信息
|
||||
|
||||
Returns:
|
||||
Headers 字典
|
||||
"""
|
||||
pass
|
||||
|
||||
def parse_verify_response(
|
||||
self,
|
||||
status_code: int,
|
||||
data: dict[str, Any],
|
||||
) -> VerifyResult:
|
||||
"""
|
||||
解析认证验证响应
|
||||
|
||||
默认实现处理通用的 {"success": bool, "data": {...}} 格式。
|
||||
子类可重写 _auth_fail_message() 和 _build_verify_result() 进行自定义。
|
||||
|
||||
Args:
|
||||
status_code: HTTP 状态码
|
||||
data: 响应 JSON 数据
|
||||
|
||||
Returns:
|
||||
验证结果
|
||||
"""
|
||||
if status_code == 401:
|
||||
return VerifyResult(success=False, message=self._auth_fail_message(401))
|
||||
if status_code == 403:
|
||||
return VerifyResult(success=False, message=self._auth_fail_message(403))
|
||||
if status_code != 200:
|
||||
return VerifyResult(success=False, message=f"验证失败:HTTP {status_code}")
|
||||
|
||||
# 解析通用响应格式
|
||||
if data.get("success") is True and "data" in data:
|
||||
user_data = data["data"]
|
||||
elif data.get("success") is False:
|
||||
message = data.get("message", "验证失败")
|
||||
return VerifyResult(success=False, message=message)
|
||||
else:
|
||||
user_data = data
|
||||
|
||||
return self._build_verify_result(user_data, data)
|
||||
|
||||
def _auth_fail_message(self, status_code: int) -> str:
|
||||
"""
|
||||
获取认证失败消息
|
||||
|
||||
子类可重写以提供自定义消息(如 Cookie 认证场景)。
|
||||
"""
|
||||
if status_code == 401:
|
||||
return "认证失败:无效的凭据"
|
||||
return "认证失败:权限不足"
|
||||
|
||||
def _build_verify_result(
|
||||
self, user_data: dict[str, Any], raw_data: dict[str, Any] | None = None
|
||||
) -> VerifyResult:
|
||||
"""
|
||||
从用户数据构建验证结果
|
||||
|
||||
默认实现提取 username, display_name, email, quota, used_quota, request_count。
|
||||
子类可重写以自定义字段提取。
|
||||
"""
|
||||
known_fields = (
|
||||
"username",
|
||||
"display_name",
|
||||
"email",
|
||||
"quota",
|
||||
"used_quota",
|
||||
"request_count",
|
||||
)
|
||||
return VerifyResult(
|
||||
success=True,
|
||||
username=user_data.get("username"),
|
||||
display_name=user_data.get("display_name") or user_data.get("username"),
|
||||
email=user_data.get("email"),
|
||||
quota=user_data.get("quota"),
|
||||
used_quota=user_data.get("used_quota"),
|
||||
request_count=user_data.get("request_count"),
|
||||
extra={k: v for k, v in user_data.items() if k not in known_fields},
|
||||
)
|
||||
|
||||
# ==================== 可选的钩子方法 ====================
|
||||
|
||||
async def prepare_verify_config(
|
||||
self,
|
||||
base_url: str,
|
||||
config: dict[str, Any],
|
||||
credentials: dict[str, Any],
|
||||
) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]:
|
||||
"""
|
||||
验证前的异步预处理(可选)
|
||||
|
||||
子类可重写以执行异步操作(如获取动态 Cookie、登录获取 Token)。
|
||||
|
||||
Args:
|
||||
base_url: API 基础地址
|
||||
config: 连接器配置
|
||||
credentials: 凭据信息
|
||||
|
||||
Returns:
|
||||
- dict: 额外配置(会与原 config 合并传递给 build_verify_headers)
|
||||
- tuple[dict, dict]: (额外配置, 需持久化的凭据更新)
|
||||
当预处理过程中凭据发生变更时(如 Token Rotation),
|
||||
通过第二个 dict 显式返回需要持久化的字段。
|
||||
"""
|
||||
return {}
|
||||
|
||||
# ==================== 连接器和操作相关方法 ====================
|
||||
|
||||
def get_connector(
|
||||
self,
|
||||
base_url: str,
|
||||
auth_type: ConnectorAuthType | None = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> ProviderConnector:
|
||||
"""
|
||||
获取连接器实例
|
||||
|
||||
Args:
|
||||
base_url: 提供商 API 基础 URL
|
||||
auth_type: 指定的认证类型,None 则使用默认
|
||||
config: 连接器配置
|
||||
|
||||
Returns:
|
||||
连接器实例
|
||||
|
||||
Raises:
|
||||
ValueError: 不支持的认证类型
|
||||
"""
|
||||
if not self.supported_connectors:
|
||||
raise ValueError(f"架构 {self.architecture_id} 未配置支持的连接器")
|
||||
|
||||
# 查找匹配的连接器
|
||||
connector_cls: type[ProviderConnector] | None = None
|
||||
|
||||
if auth_type:
|
||||
for cls in self.supported_connectors:
|
||||
if cls.auth_type == auth_type:
|
||||
connector_cls = cls
|
||||
break
|
||||
|
||||
if not connector_cls:
|
||||
supported = [c.auth_type.value for c in self.supported_connectors]
|
||||
raise ValueError(
|
||||
f"架构 {self.architecture_id} 不支持 {auth_type.value} 认证,"
|
||||
f"支持的类型: {supported}"
|
||||
)
|
||||
else:
|
||||
# 使用第一个(默认)连接器
|
||||
connector_cls = self.supported_connectors[0]
|
||||
|
||||
return connector_cls(base_url, config)
|
||||
|
||||
def get_action(
|
||||
self,
|
||||
action_type: ProviderActionType,
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> ProviderAction:
|
||||
"""
|
||||
获取操作实例
|
||||
|
||||
Args:
|
||||
action_type: 操作类型
|
||||
config: 操作配置(会与默认配置合并)
|
||||
|
||||
Returns:
|
||||
操作实例
|
||||
|
||||
Raises:
|
||||
ValueError: 不支持的操作类型
|
||||
"""
|
||||
action_cls: type[ProviderAction] | None = None
|
||||
|
||||
for cls in self.supported_actions:
|
||||
if cls.action_type == action_type:
|
||||
action_cls = cls
|
||||
break
|
||||
|
||||
if not action_cls:
|
||||
supported = [a.action_type.value for a in self.supported_actions]
|
||||
raise ValueError(
|
||||
f"架构 {self.architecture_id} 不支持 {action_type.value} 操作,"
|
||||
f"支持的操作: {supported}"
|
||||
)
|
||||
|
||||
# 合并默认配置和用户配置
|
||||
merged_config = dict(self.default_action_configs.get(action_type, {}))
|
||||
if config:
|
||||
merged_config.update(config)
|
||||
|
||||
return action_cls(merged_config)
|
||||
|
||||
def supports_action(self, action_type: ProviderActionType) -> bool:
|
||||
"""检查是否支持指定操作"""
|
||||
return any(a.action_type == action_type for a in self.supported_actions)
|
||||
|
||||
def supports_auth_type(self, auth_type: ConnectorAuthType) -> bool:
|
||||
"""检查是否支持指定认证类型"""
|
||||
return any(c.auth_type == auth_type for c in self.supported_connectors)
|
||||
|
||||
def get_supported_auth_types(self) -> list[ConnectorAuthType]:
|
||||
"""获取支持的认证类型列表"""
|
||||
return [c.auth_type for c in self.supported_connectors]
|
||||
|
||||
def get_supported_action_types(self) -> list[ProviderActionType]:
|
||||
"""获取支持的操作类型列表"""
|
||||
return [a.action_type for a in self.supported_actions]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""转换为字典(用于 API 响应)"""
|
||||
return {
|
||||
"architecture_id": self.architecture_id,
|
||||
"display_name": self.display_name,
|
||||
"description": self.description,
|
||||
"credentials_schema": self.get_credentials_schema(),
|
||||
"verify_endpoint": self.get_verify_endpoint(),
|
||||
"supported_auth_types": [
|
||||
{
|
||||
"type": c.auth_type.value,
|
||||
"display_name": c.display_name,
|
||||
"credentials_schema": c.get_credentials_schema(),
|
||||
}
|
||||
for c in self.supported_connectors
|
||||
],
|
||||
"supported_actions": [
|
||||
{
|
||||
"type": a.action_type.value,
|
||||
"display_name": a.display_name,
|
||||
"description": a.description,
|
||||
"config_schema": a.get_config_schema(),
|
||||
}
|
||||
for a in self.supported_actions
|
||||
],
|
||||
"default_connector": (
|
||||
self.supported_connectors[0].auth_type.value if self.supported_connectors else None
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user