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

@@ -4,11 +4,13 @@
提供在异步上下文中安全执行同步函数的工具,避免阻塞事件循环。
"""
from __future__ import annotations
import asyncio
from functools import partial, wraps
from typing import Any, Callable, Coroutine, TypeVar
from typing import Any, TypeVar
from collections.abc import Callable
from collections.abc import Coroutine
T = TypeVar("T")

View File

@@ -4,7 +4,6 @@
"""
import hashlib
from typing import Optional
from fastapi import Depends, Header, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
@@ -117,7 +116,7 @@ async def get_current_user(
async def get_current_user_from_header(
authorization: Optional[str] = Header(None), db: Session = Depends(get_db)
authorization: str | None = Header(None), db: Session = Depends(get_db)
) -> User:
"""
从Header中获取当前用户兼容性函数

View File

@@ -2,7 +2,9 @@
import functools
import json
from typing import Any, Callable
from typing import Any
from collections.abc import Callable
from src.core.logger import logger

View File

@@ -6,10 +6,10 @@
import gzip
import json
from typing import Any, Optional
from typing import Any
def compress_json(data: Any) -> Optional[bytes]:
def compress_json(data: Any) -> bytes | None:
"""
将JSON数据压缩为gzip格式的字节
@@ -33,7 +33,7 @@ def compress_json(data: Any) -> Optional[bytes]:
return None
def decompress_json(compressed_data: Optional[bytes]) -> Optional[Any]:
def decompress_json(compressed_data: bytes | None) -> Any | None:
"""
解压gzip格式的字节为JSON数据

View File

@@ -3,7 +3,6 @@
提供统一的HTTP请求信息提取功能
"""
from typing import Optional
from fastapi import Request
@@ -61,7 +60,7 @@ def get_user_agent(request: Request) -> str:
return request.headers.get("User-Agent", "unknown")
def get_request_id(request: Request) -> Optional[str]:
def get_request_id(request: Request) -> str | None:
"""
获取请求ID如果存在

View File

@@ -1,6 +1,3 @@
from typing import Dict, List, Optional, Union
class SSEEventParser:
"""轻量SSE解析器按行接收输入并输出完整事件。"""
@@ -8,14 +5,14 @@ class SSEEventParser:
self._reset_buffer()
def _reset_buffer(self) -> None:
self._buffer: Dict[str, Union[Optional[str], List[str]]] = {
self._buffer: dict[str, str | None | list[str]] = {
"event": None,
"data": [],
"id": None,
"retry": None,
}
def _finalize_event(self) -> Optional[Dict[str, Optional[str]]]:
def _finalize_event(self) -> dict[str, str | None] | None:
data_lines = self._buffer.get("data", [])
if not isinstance(data_lines, list) or not data_lines:
self._reset_buffer()
@@ -25,7 +22,7 @@ class SSEEventParser:
event_val = self._buffer.get("event")
id_val = self._buffer.get("id")
retry_val = self._buffer.get("retry")
event: Dict[str, Optional[str]] = {
event: dict[str, str | None] = {
"event": event_val if isinstance(event_val, str) else None,
"data": data_str,
"id": id_val if isinstance(id_val, str) else None,
@@ -35,11 +32,11 @@ class SSEEventParser:
self._reset_buffer()
return event
def feed_line(self, line: Optional[str]) -> List[Dict[str, Optional[str]]]:
def feed_line(self, line: str | None) -> list[dict[str, str | None]]:
"""处理单行SSE文本返回所有完成的事件。"""
normalized_line = (line or "").rstrip("\r")
events: List[Dict[str, Optional[str]]] = []
events: list[dict[str, str | None]] = []
# 空行表示事件结束
if normalized_line == "":
@@ -98,7 +95,7 @@ class SSEEventParser:
self._append_data_line(normalized_line)
return events
def flush(self) -> List[Dict[str, Optional[str]]]:
def flush(self) -> list[dict[str, str | None]]:
"""在流结束时调用,输出尚未完成的事件。"""
event = self._finalize_event()

View File

@@ -9,12 +9,10 @@
- 多实例部署:设置 SINGLE_INSTANCE_MODE=false 禁用启动清理
"""
from __future__ import annotations
import os
import pathlib
import uuid
from typing import Dict, Optional
from src.core.logger import logger
@@ -31,10 +29,10 @@ class StartupTaskCoordinator:
# 注意:这在 fork 模式下每个 worker 都是独立的
_startup_cleanup_attempted = False
def __init__(self, redis_client=None, lock_dir: Optional[str] = None):
def __init__(self, redis_client=None, lock_dir: str | None = None):
self.redis = redis_client
self._tokens: Dict[str, str] = {}
self._file_handles: Dict[str, object] = {}
self._tokens: dict[str, str] = {}
self._file_handles: dict[str, object] = {}
self._lock_dir = pathlib.Path(lock_dir or os.getenv("TASK_LOCK_DIR", "./.locks"))
if not self._lock_dir.exists():
self._lock_dir.mkdir(parents=True, exist_ok=True)
@@ -44,7 +42,7 @@ class StartupTaskCoordinator:
def _redis_key(self, name: str) -> str:
return f"task_lock:{name}"
async def acquire(self, name: str, ttl: Optional[int] = None) -> bool:
async def acquire(self, name: str, ttl: int | None = None) -> bool:
ttl = ttl or int(os.getenv("TASK_COORDINATOR_LOCK_TTL", "86400"))
if self.redis:
@@ -142,7 +140,7 @@ class StartupTaskCoordinator:
return False
async def ensure_singleton_task(name: str, redis_client=None, ttl: Optional[int] = None):
async def ensure_singleton_task(name: str, redis_client=None, ttl: int | None = None):
"""便捷协程,返回 (coordinator, acquired)"""
coordinator = StartupTaskCoordinator(redis_client)

View File

@@ -6,7 +6,9 @@
import asyncio
from functools import wraps
from typing import Any, Callable, Optional, TypeVar
from typing import Any, TypeVar
from collections.abc import Callable
from src.core.logger import logger
@@ -23,7 +25,7 @@ class AsyncTimeoutError(TimeoutError):
self.timeout = timeout
def with_timeout(seconds: float, operation_name: Optional[str] = None):
def with_timeout(seconds: float, operation_name: str | None = None):
"""
装饰器:为异步函数添加超时保护
@@ -47,7 +49,7 @@ def with_timeout(seconds: float, operation_name: Optional[str] = None):
op_name = operation_name or func.__name__
try:
return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning(f"操作超时: {op_name} (timeout={seconds}s)")
raise AsyncTimeoutError(
f"{op_name} 操作超时({seconds}秒)",
@@ -89,7 +91,7 @@ async def run_with_timeout(
"""
try:
return await asyncio.wait_for(coro, timeout=timeout)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning(f"操作超时: {operation_name} (timeout={timeout}s)")
if raise_on_timeout:
raise AsyncTimeoutError(
@@ -113,7 +115,7 @@ class TimeoutContext:
def __init__(self, timeout: float, operation_name: str = "operation"):
self.timeout = timeout
self.operation_name = operation_name
self._task: Optional[asyncio.Task] = None
self._task: asyncio.Task | None = None
async def __aenter__(self):
return self
@@ -172,7 +174,7 @@ async def read_first_chunk_with_ttfb_timeout(
try:
first_chunk = await asyncio.wait_for(aiter.__anext__(), timeout=timeout)
return first_chunk, aiter
except asyncio.TimeoutError:
except TimeoutError:
# 完整的资源清理:先关闭迭代器,再关闭底层响应
await _cleanup_iterator_resources(aiter, request_id)
logger.warning(

View File

@@ -7,7 +7,10 @@
import functools
import inspect
from contextlib import contextmanager
from typing import Any, Callable, Generator, Optional
from typing import Any
from collections.abc import Callable
from collections.abc import Generator
from sqlalchemy.exc import DatabaseError, IntegrityError
from sqlalchemy.orm import Session
@@ -22,7 +25,7 @@ class TransactionError(Exception):
pass
def _find_db_session(args, kwargs) -> Optional[Session]:
def _find_db_session(args, kwargs) -> Session | None:
"""从参数中查找数据库会话"""
# 从位置参数中查找Session
for arg in args:
@@ -152,8 +155,8 @@ def transaction_scope(
db: Session,
commit_on_success: bool = True,
rollback_on_error: bool = True,
operation_name: Optional[str] = None,
) -> Generator[Session, None, None]:
operation_name: str | None = None,
) -> Generator[Session]:
"""
事务上下文管理器