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

@@ -2,7 +2,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import JSONResponse
@@ -55,13 +54,13 @@ class CreateManagementTokenRequest(BaseModel):
"""创建 Management Token 请求"""
name: str = Field(..., min_length=1, max_length=100, description="Token 名称")
description: Optional[str] = Field(None, max_length=500, description="描述")
allowed_ips: Optional[list[str]] = Field(None, description="IP 白名单")
expires_at: Optional[datetime] = Field(None, description="过期时间")
description: str | None = Field(None, max_length=500, description="描述")
allowed_ips: list[str] | None = Field(None, description="IP 白名单")
expires_at: datetime | None = Field(None, description="过期时间")
@field_validator("allowed_ips")
@classmethod
def validate_allowed_ips(cls, v: Optional[list[str]]) -> Optional[list[str]]:
def validate_allowed_ips(cls, v: list[str] | None) -> list[str] | None:
return validate_ip_list(v)
@field_validator("expires_at", mode="before")
@@ -81,10 +80,10 @@ class UpdateManagementTokenRequest(BaseModel):
model_config = {"extra": "allow"} # 允许额外字段以便检测哪些字段被显式提供
name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
allowed_ips: Optional[list[str]] = None
expires_at: Optional[datetime] = None
name: str | None = Field(None, min_length=1, max_length=100)
description: str | None = Field(None, max_length=500)
allowed_ips: list[str] | None = None
expires_at: datetime | None = None
# 用于追踪哪些字段被显式提供(包括显式设为 null 的情况)
_provided_fields: set[str] = set()
@@ -101,7 +100,7 @@ class UpdateManagementTokenRequest(BaseModel):
@field_validator("allowed_ips")
@classmethod
def validate_allowed_ips(cls, v: Optional[list[str]]) -> Optional[list[str]]:
def validate_allowed_ips(cls, v: list[str] | None) -> list[str] | None:
# 如果是 None表示要清空直接返回
if v is None:
return None
@@ -122,7 +121,7 @@ class UpdateManagementTokenRequest(BaseModel):
@router.get("")
async def list_my_management_tokens(
request: Request,
is_active: Optional[bool] = Query(None, description="筛选激活状态"),
is_active: bool | None = Query(None, description="筛选激活状态"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db),
@@ -347,7 +346,7 @@ class ListMyManagementTokensAdapter(ManagementTokenApiAdapter):
"""列出用户的 Management Tokens"""
name: str = "list_my_management_tokens"
is_active: Optional[bool] = None
is_active: bool | None = None
skip: int = 0
limit: int = 50

View File

@@ -2,14 +2,12 @@
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import ValidationError
from sqlalchemy import and_, func
from sqlalchemy.orm import Session
from src.api.base.adapter import ApiAdapter, ApiMode
from src.api.base.authenticated_adapter import AuthenticatedApiAdapter
from src.api.base.pipeline import ApiRequestPipeline
from src.core.crypto import crypto_service
@@ -170,9 +168,9 @@ async def toggle_my_api_key(key_id: str, request: Request, db: Session = Depends
@router.get("/usage")
async def get_my_usage(
request: Request,
start_date: Optional[datetime] = Query(None, description="开始时间ISO 格式)"),
end_date: Optional[datetime] = Query(None, description="结束时间ISO 格式)"),
search: Optional[str] = Query(None, description="搜索关键词(密钥名、模型名)"),
start_date: datetime | None = Query(None, description="开始时间ISO 格式)"),
end_date: datetime | None = Query(None, description="结束时间ISO 格式)"),
search: str | None = Query(None, description="搜索关键词(密钥名、模型名)"),
limit: int = Query(100, ge=1, le=200, description="每页记录数默认100最大200"),
offset: int = Query(0, ge=0, le=2000, description="偏移量用于分页最大2000"),
db: Session = Depends(get_db),
@@ -200,7 +198,7 @@ async def get_my_usage(
@router.get("/usage/active")
async def get_my_active_requests(
request: Request,
ids: Optional[str] = Query(None, description="请求 ID 列表,逗号分隔"),
ids: str | None = Query(None, description="请求 ID 列表,逗号分隔"),
db: Session = Depends(get_db),
):
"""
@@ -268,7 +266,7 @@ async def list_available_models(
request: Request,
skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制"),
search: Optional[str] = Query(None, description="搜索关键词"),
search: str | None = Query(None, description="搜索关键词"),
db: Session = Depends(get_db),
):
"""
@@ -721,9 +719,9 @@ class ToggleMyApiKeyAdapter(AuthenticatedApiAdapter):
class GetUsageAdapter(AuthenticatedApiAdapter):
"""获取用户使用统计的适配器"""
start_date: Optional[datetime]
end_date: Optional[datetime]
search: Optional[str] = None
start_date: datetime | None
end_date: datetime | None
search: str | None = None
limit: int = 100
offset: int = 0
@@ -983,7 +981,7 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
class GetActiveRequestsAdapter(AuthenticatedApiAdapter):
"""轻量级活跃请求状态查询适配器(用于用户端轮询)"""
ids: Optional[str] = None
ids: str | None = None
async def handle(self, context): # type: ignore[override]
from src.services.usage import UsageService
@@ -1045,7 +1043,7 @@ class ListAvailableModelsAdapter(AuthenticatedApiAdapter):
skip: int
limit: int
search: Optional[str]
search: str | None
async def handle(self, context): # type: ignore[override]
from sqlalchemy import or_
@@ -1220,7 +1218,6 @@ class ListAvailableProvidersAdapter(AuthenticatedApiAdapter):
async def handle(self, context): # type: ignore[override]
from sqlalchemy.orm import selectinload
from src.models.database import ProviderEndpoint
db = context.db