chore: 升级到 Python 3.14 并现代化代码

- 升级 Docker 基础镜像从 Python 3.12 到 3.14
- 更新 pyproject.toml 支持 Python 3.13/3.14
- 移除 Python 3.8/3.9/3.10/3.11 分类器
- 更新 black 和 mypy 配置目标版本
- 将 get_event_loop() 替换为 get_running_loop() 加上 RuntimeError 处理
- 简化 compute_cost_sync 中的 asyncio.run 使用
- Dict/List/Tuple/Set → dict/list/tuple/set (PEP 585)
- Optional[T] → T | None (PEP 604)
- Union[A, B] → A | B (PEP 604)
- 移除废弃的 typing 导入
- 移除不必要的字符串引号注解
This commit is contained in:
AAEE86
2026-01-30 03:10:21 +08:00
parent 3e75bc8964
commit 24d24f6829
255 changed files with 4062 additions and 4173 deletions

View File

@@ -3,7 +3,7 @@ Provider 模型管理 API
"""
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from typing import Any
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session, joinedload
@@ -40,15 +40,15 @@ router = APIRouter(tags=["Model Management"])
pipeline = ApiRequestPipeline()
@router.get("/{provider_id}/models", response_model=List[ModelResponse])
@router.get("/{provider_id}/models", response_model=list[ModelResponse])
async def list_provider_models(
provider_id: str,
request: Request,
is_active: Optional[bool] = None,
is_active: bool | None = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
) -> List[ModelResponse]:
) -> list[ModelResponse]:
"""
获取提供商的所有模型
@@ -222,13 +222,13 @@ async def delete_provider_model(
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/{provider_id}/models/batch", response_model=List[ModelResponse])
@router.post("/{provider_id}/models/batch", response_model=list[ModelResponse])
async def batch_create_provider_models(
provider_id: str,
models_data: List[ModelCreate],
models_data: list[ModelCreate],
request: Request,
db: Session = Depends(get_db),
) -> List[ModelResponse]:
) -> list[ModelResponse]:
"""
批量创建模型
@@ -375,7 +375,7 @@ async def import_models_from_upstream(
@dataclass
class AdminListProviderModelsAdapter(AdminApiAdapter):
provider_id: str
is_active: Optional[bool]
is_active: bool | None
skip: int
limit: int
@@ -482,7 +482,7 @@ class AdminDeleteProviderModelAdapter(AdminApiAdapter):
@dataclass
class AdminBatchCreateModelsAdapter(AdminApiAdapter):
provider_id: str
models_data: List[ModelCreate]
models_data: list[ModelCreate]
async def handle(self, context): # type: ignore[override]
db = context.db
@@ -525,7 +525,7 @@ class AdminGetProviderAvailableSourceModelsAdapter(AdminApiAdapter):
)
# 2. 构建以 GlobalModel 为主键的字典
global_models_dict: Dict[str, Dict[str, Any]] = {}
global_models_dict: dict[str, dict[str, Any]] = {}
for model in models:
global_model = model.global_model

View File

@@ -2,7 +2,6 @@
import asyncio
from datetime import datetime, timezone
from typing import Dict, List, Optional
from fastapi import APIRouter, Depends, Query, Request
from pydantic import BaseModel, ConfigDict, Field, ValidationError
@@ -48,7 +47,7 @@ class MappingMatchingGlobalModel(BaseModel):
global_model_name: str
display_name: str
is_active: bool
matched_models: List[MappingMatchedModel] = Field(
matched_models: list[MappingMatchedModel] = Field(
default_factory=list, description="匹配到的模型列表"
)
@@ -62,8 +61,8 @@ class MappingMatchingKey(BaseModel):
key_name: str
masked_key: str
is_active: bool
allowed_models: List[str] = Field(default_factory=list, description="Key 的模型白名单")
matching_global_models: List[MappingMatchingGlobalModel] = Field(
allowed_models: list[str] = Field(default_factory=list, description="Key 的模型白名单")
matching_global_models: list[MappingMatchingGlobalModel] = Field(
default_factory=list, description="匹配到的 GlobalModel 列表"
)
@@ -75,7 +74,7 @@ class ProviderMappingPreviewResponse(BaseModel):
provider_id: str
provider_name: str
keys: List[MappingMatchingKey] = Field(
keys: list[MappingMatchingKey] = Field(
default_factory=list, description="有白名单配置且匹配到映射的 Key 列表"
)
total_keys: int = Field(0, description="有匹配结果的 Key 数量")
@@ -95,7 +94,7 @@ async def list_providers(
request: Request,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=500),
is_active: Optional[bool] = None,
is_active: bool | None = None,
db: Session = Depends(get_db),
):
"""
@@ -209,7 +208,7 @@ async def delete_provider(provider_id: str, request: Request, db: Session = Depe
class AdminListProvidersAdapter(AdminApiAdapter):
def __init__(self, skip: int, limit: int, is_active: Optional[bool]):
def __init__(self, skip: int, limit: int, is_active: bool | None):
self.skip = skip
self.limit = limit
self.is_active = is_active
@@ -473,7 +472,7 @@ async def get_provider_mapping_preview(
pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode),
timeout=MAPPING_PREVIEW_TIMEOUT_SECONDS,
)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning(f"映射预览超时: provider_id={provider_id}")
raise InvalidRequestException("映射预览超时,请简化配置或稍后重试")
@@ -565,7 +564,7 @@ class AdminGetProviderMappingPreviewAdapter(AdminApiAdapter):
truncated_models = total_models_with_mappings - MAPPING_PREVIEW_MAX_MODELS
# 构建有映射配置的 GlobalModel 映射
models_with_mappings: Dict[str, tuple] = {} # id -> (model_info, mappings)
models_with_mappings: dict[str, tuple] = {} # id -> (model_info, mappings)
for gm in global_models:
config = gm.config or {}
mappings = config.get("model_mappings", [])
@@ -585,7 +584,7 @@ class AdminGetProviderMappingPreviewAdapter(AdminApiAdapter):
truncated_models=0,
)
key_infos: List[MappingMatchingKey] = []
key_infos: list[MappingMatchingKey] = []
total_matches = 0
# 创建 CryptoService 实例
@@ -611,10 +610,10 @@ class AdminGetProviderMappingPreviewAdapter(AdminApiAdapter):
pass
# 查找匹配的 GlobalModel
matching_global_models: List[MappingMatchingGlobalModel] = []
matching_global_models: list[MappingMatchingGlobalModel] = []
for gm_id, (gm, mappings) in models_with_mappings.items():
matched_models: List[MappingMatchedModel] = []
matched_models: list[MappingMatchedModel] = []
for allowed_model in allowed_models_list:
for mapping_pattern in mappings:

View File

@@ -4,7 +4,6 @@ Provider 摘要与健康监控 API
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Dict, List
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy import case, func
@@ -35,11 +34,11 @@ router = APIRouter(tags=["Provider Summary"])
pipeline = ApiRequestPipeline()
@router.get("/summary", response_model=List[ProviderWithEndpointsSummary])
@router.get("/summary", response_model=list[ProviderWithEndpointsSummary])
async def get_providers_summary(
request: Request,
db: Session = Depends(get_db),
) -> List[ProviderWithEndpointsSummary]:
) -> list[ProviderWithEndpointsSummary]:
"""
获取所有提供商摘要信息
@@ -381,8 +380,8 @@ class AdminProviderHealthMonitorAdapter(AdminApiAdapter):
)
attempts = attempts_query.limit(limit_rows).all()
buffered_attempts: Dict[str, List[RequestCandidate]] = {eid: [] for eid in endpoint_ids}
counters: Dict[str, int] = {eid: 0 for eid in endpoint_ids}
buffered_attempts: dict[str, list[RequestCandidate]] = {eid: [] for eid in endpoint_ids}
counters: dict[str, int] = {eid: 0 for eid in endpoint_ids}
for attempt in attempts:
if not attempt.endpoint_id or attempt.endpoint_id not in buffered_attempts:
@@ -392,10 +391,10 @@ class AdminProviderHealthMonitorAdapter(AdminApiAdapter):
buffered_attempts[attempt.endpoint_id].append(attempt)
counters[attempt.endpoint_id] += 1
endpoint_monitors: List[EndpointHealthMonitor] = []
endpoint_monitors: list[EndpointHealthMonitor] = []
for endpoint in endpoints:
attempt_list = list(reversed(buffered_attempts.get(endpoint.id, [])))
events: List[EndpointHealthEvent] = []
events: list[EndpointHealthEvent] = []
for attempt in attempt_list:
event_timestamp = attempt.finished_at or attempt.started_at or attempt.created_at
events.append(