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

@@ -8,16 +8,15 @@
- 自定义计费维度
"""
from typing import Any, Dict, List, Optional, Tuple
from __future__ import annotations
from typing import Any
from src.services.billing.models import (
BillingDimension,
BillingUnit,
CostBreakdown,
StandardizedUsage,
)
from src.services.billing.templates import (
BILLING_TEMPLATE_REGISTRY,
BillingTemplates,
get_template,
)
@@ -50,8 +49,8 @@ class BillingCalculator:
def __init__(
self,
dimensions: Optional[List[BillingDimension]] = None,
template: Optional[str] = None,
dimensions: list[BillingDimension] | None = None,
template: str | None = None,
):
"""
初始化计费计算器
@@ -73,10 +72,10 @@ class BillingCalculator:
def calculate(
self,
usage: StandardizedUsage,
prices: Dict[str, float],
tiered_pricing: Optional[Dict[str, Any]] = None,
cache_ttl_minutes: Optional[int] = None,
total_input_context: Optional[int] = None,
prices: dict[str, float],
tiered_pricing: dict[str, Any] | None = None,
cache_ttl_minutes: int | None = None,
total_input_context: int | None = None,
) -> CostBreakdown:
"""
计算费用
@@ -131,9 +130,9 @@ class BillingCalculator:
def _get_tier(
self,
usage: StandardizedUsage,
tiered_pricing: Dict[str, Any],
total_input_context: Optional[int] = None,
) -> Tuple[Optional[Dict[str, Any]], Optional[int]]:
tiered_pricing: dict[str, Any],
total_input_context: int | None = None,
) -> tuple[dict[str, Any] | None, int | None]:
"""
确定价格阶梯
@@ -178,9 +177,9 @@ class BillingCalculator:
def _get_cache_read_price_for_ttl(
self,
tier: Dict[str, Any],
tier: dict[str, Any],
cache_ttl_minutes: int,
) -> Optional[float]:
) -> float | None:
"""
根据缓存 TTL 获取缓存读取价格
@@ -212,7 +211,7 @@ class BillingCalculator:
return None
@classmethod
def from_config(cls, config: Dict[str, Any]) -> "BillingCalculator":
def from_config(cls, config: dict[str, Any]) -> BillingCalculator:
"""
从配置创建计费计算器
@@ -238,15 +237,15 @@ class BillingCalculator:
return cls(template=config.get("template", "claude"))
def get_dimension_names(self) -> List[str]:
def get_dimension_names(self) -> list[str]:
"""获取所有计费维度名称"""
return [dim.name for dim in self.dimensions]
def get_required_price_fields(self) -> List[str]:
def get_required_price_fields(self) -> list[str]:
"""获取所需的价格字段名称"""
return [dim.price_field for dim in self.dimensions]
def get_required_usage_fields(self) -> List[str]:
def get_required_usage_fields(self) -> list[str]:
"""获取所需的 usage 字段名称"""
return [dim.usage_field for dim in self.dimensions]
@@ -258,14 +257,14 @@ def calculate_request_cost(
cache_read_input_tokens: int,
input_price_per_1m: float,
output_price_per_1m: float,
cache_creation_price_per_1m: Optional[float],
cache_read_price_per_1m: Optional[float],
price_per_request: Optional[float],
tiered_pricing: Optional[Dict[str, Any]] = None,
cache_ttl_minutes: Optional[int] = None,
total_input_context: Optional[int] = None,
cache_creation_price_per_1m: float | None,
cache_read_price_per_1m: float | None,
price_per_request: float | None,
tiered_pricing: dict[str, Any] | None = None,
cache_ttl_minutes: int | None = None,
total_input_context: int | None = None,
billing_template: str = "claude",
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
计算请求成本的便捷函数
@@ -309,7 +308,7 @@ def calculate_request_cost(
)
# 构建价格配置
prices: Dict[str, float] = {
prices: dict[str, float] = {
"input_price_per_1m": input_price_per_1m,
"output_price_per_1m": output_price_per_1m,
}

View File

@@ -8,9 +8,10 @@
- CostBreakdown: 计费明细结果
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Optional
from typing import Any
class BillingUnit(str, Enum):
@@ -66,7 +67,7 @@ class BillingDimension:
return 0.0
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> dict[str, Any]:
"""转换为字典(用于序列化)"""
return {
"name": self.name,
@@ -77,7 +78,7 @@ class BillingDimension:
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BillingDimension":
def from_dict(cls, data: dict[str, Any]) -> BillingDimension:
"""从字典创建实例"""
return cls(
name=data["name"],
@@ -114,7 +115,7 @@ class StandardizedUsage:
request_count: int = 1
# 扩展字段(未来可能需要的额外维度)
extra: Dict[str, Any] = field(default_factory=dict)
extra: dict[str, Any] = field(default_factory=dict)
def get(self, field_name: str, default: Any = 0) -> Any:
"""
@@ -149,9 +150,9 @@ class StandardizedUsage:
else:
self.extra[field_name] = value
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> dict[str, Any]:
"""转换为字典"""
result: Dict[str, Any] = {
result: dict[str, Any] = {
"input_tokens": self.input_tokens,
"output_tokens": self.output_tokens,
"cache_creation_tokens": self.cache_creation_tokens,
@@ -165,7 +166,7 @@ class StandardizedUsage:
return result
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "StandardizedUsage":
def from_dict(cls, data: dict[str, Any]) -> StandardizedUsage:
"""从字典创建实例"""
extra = data.pop("extra", {}) if "extra" in data else {}
# 只取已知字段
@@ -191,19 +192,19 @@ class CostBreakdown:
"""
# 各维度费用 {"input": 0.01, "output": 0.02, "cache_read": 0.001, ...}
costs: Dict[str, float] = field(default_factory=dict)
costs: dict[str, float] = field(default_factory=dict)
# 总费用
total_cost: float = 0.0
# 命中的阶梯索引(如果使用阶梯计费)
tier_index: Optional[int] = None
tier_index: int | None = None
# 货币单位
currency: str = "USD"
# 使用的价格(用于记录和审计)
effective_prices: Dict[str, float] = field(default_factory=dict)
effective_prices: dict[str, float] = field(default_factory=dict)
# =========================================================================
# 兼容旧接口的属性(便于渐进式迁移)
@@ -244,7 +245,7 @@ class CostBreakdown:
"""缓存存储费用(豆包等)"""
return self.costs.get("cache_storage", 0.0)
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> dict[str, Any]:
"""转换为字典"""
return {
"costs": self.costs,

View File

@@ -9,7 +9,6 @@
- PER_REQUEST: 按次计费
"""
from typing import Dict, List, Optional
from src.services.billing.models import BillingDimension, BillingUnit
@@ -25,7 +24,7 @@ class BillingTemplates:
# - 缓存读取(约 0.1x 输入价格)
# - 按次计费(可选,配置 price_per_request 时生效)
# =========================================================================
CLAUDE_STANDARD: List[BillingDimension] = [
CLAUDE_STANDARD: list[BillingDimension] = [
BillingDimension(
name="input",
usage_field="input_tokens",
@@ -61,7 +60,7 @@ class BillingTemplates:
# - 缓存读取(部分模型支持,无缓存创建费用)
# - 按次计费(可选,配置 price_per_request 时生效)
# =========================================================================
OPENAI_STANDARD: List[BillingDimension] = [
OPENAI_STANDARD: list[BillingDimension] = [
BillingDimension(
name="input",
usage_field="input_tokens",
@@ -95,7 +94,7 @@ class BillingTemplates:
#
# 注意:豆包的缓存创建是免费的,但存储需要按时付费
# =========================================================================
DOUBAO_STANDARD: List[BillingDimension] = [
DOUBAO_STANDARD: list[BillingDimension] = [
BillingDimension(
name="input",
usage_field="input_tokens",
@@ -132,7 +131,7 @@ class BillingTemplates:
# - 缓存读取
# - 按次计费(用于图片生成等模型,需配置 price_per_request
# =========================================================================
GEMINI_STANDARD: List[BillingDimension] = [
GEMINI_STANDARD: list[BillingDimension] = [
BillingDimension(
name="input",
usage_field="input_tokens",
@@ -161,7 +160,7 @@ class BillingTemplates:
# - 适用于某些图片生成模型、特殊 API 等
# - 仅按请求次数计费,不按 token 计费
# =========================================================================
PER_REQUEST: List[BillingDimension] = [
PER_REQUEST: list[BillingDimension] = [
BillingDimension(
name="request",
usage_field="request_count",
@@ -174,7 +173,7 @@ class BillingTemplates:
# 混合计费(按次 + 按 token
# - 某些模型既有固定费用又有 token 费用
# =========================================================================
HYBRID_STANDARD: List[BillingDimension] = [
HYBRID_STANDARD: list[BillingDimension] = [
BillingDimension(
name="input",
usage_field="input_tokens",
@@ -198,7 +197,7 @@ class BillingTemplates:
# 模板注册表
# =========================================================================
BILLING_TEMPLATE_REGISTRY: Dict[str, List[BillingDimension]] = {
BILLING_TEMPLATE_REGISTRY: dict[str, list[BillingDimension]] = {
# 按厂商名称
"claude": BillingTemplates.CLAUDE_STANDARD,
"anthropic": BillingTemplates.CLAUDE_STANDARD,
@@ -215,7 +214,7 @@ BILLING_TEMPLATE_REGISTRY: Dict[str, List[BillingDimension]] = {
}
def get_template(name: Optional[str]) -> List[BillingDimension]:
def get_template(name: str | None) -> list[BillingDimension]:
"""
获取计费模板
@@ -236,6 +235,6 @@ def get_template(name: Optional[str]) -> List[BillingDimension]:
return template
def list_templates() -> List[str]:
def list_templates() -> list[str]:
"""列出所有可用的模板名称"""
return list(BILLING_TEMPLATE_REGISTRY.keys())

View File

@@ -9,7 +9,7 @@ Usage 字段映射器
- GEMINI / GEMINI_CLI: Google Gemini API
"""
from typing import Any, Dict, Optional
from typing import Any
from src.services.billing.models import StandardizedUsage
@@ -47,7 +47,7 @@ class UsageMapper:
# =========================================================================
# OpenAI 格式字段映射
OPENAI_MAPPING: Dict[str, str] = {
OPENAI_MAPPING: dict[str, str] = {
"prompt_tokens": "input_tokens",
"completion_tokens": "output_tokens",
"prompt_tokens_details.cached_tokens": "cache_read_tokens",
@@ -55,7 +55,7 @@ class UsageMapper:
}
# Claude 格式字段映射
CLAUDE_MAPPING: Dict[str, str] = {
CLAUDE_MAPPING: dict[str, str] = {
"input_tokens": "input_tokens",
"output_tokens": "output_tokens",
"cache_creation_input_tokens": "cache_creation_tokens",
@@ -63,7 +63,7 @@ class UsageMapper:
}
# Gemini 格式字段映射
GEMINI_MAPPING: Dict[str, str] = {
GEMINI_MAPPING: dict[str, str] = {
"promptTokenCount": "input_tokens",
"candidatesTokenCount": "output_tokens",
"cachedContentTokenCount": "cache_read_tokens",
@@ -74,7 +74,7 @@ class UsageMapper:
}
# 格式名称到映射的对应关系
FORMAT_MAPPINGS: Dict[str, Dict[str, str]] = {
FORMAT_MAPPINGS: dict[str, dict[str, str]] = {
"OPENAI": OPENAI_MAPPING,
"OPENAI_CLI": OPENAI_MAPPING,
"CLAUDE": CLAUDE_MAPPING,
@@ -86,9 +86,9 @@ class UsageMapper:
@classmethod
def map(
cls,
raw_usage: Dict[str, Any],
raw_usage: dict[str, Any],
api_format: str,
extra_mapping: Optional[Dict[str, str]] = None,
extra_mapping: dict[str, str] | None = None,
) -> StandardizedUsage:
"""
将原始 usage 映射为标准化格式
@@ -124,7 +124,7 @@ class UsageMapper:
@classmethod
def map_from_response(
cls,
response: Dict[str, Any],
response: dict[str, Any],
api_format: str,
) -> StandardizedUsage:
"""
@@ -145,7 +145,7 @@ class UsageMapper:
format_upper = api_format.upper() if api_format else ""
# 提取 usage 部分
usage_data: Dict[str, Any] = {}
usage_data: dict[str, Any] = {}
if format_upper.startswith("GEMINI"):
# Gemini: usageMetadata
@@ -162,7 +162,7 @@ class UsageMapper:
return cls.map(usage_data, api_format)
@classmethod
def _get_mapping(cls, api_format: str) -> Dict[str, str]:
def _get_mapping(cls, api_format: str) -> dict[str, str]:
"""获取对应格式的字段映射"""
if not api_format:
return cls.CLAUDE_MAPPING
@@ -182,7 +182,7 @@ class UsageMapper:
return cls.CLAUDE_MAPPING
@classmethod
def _get_nested_value(cls, data: Dict[str, Any], path: str) -> Any:
def _get_nested_value(cls, data: dict[str, Any], path: str) -> Any:
"""
获取嵌套字段值
@@ -212,7 +212,7 @@ class UsageMapper:
return value
@classmethod
def register_format(cls, format_name: str, mapping: Dict[str, str]) -> None:
def register_format(cls, format_name: str, mapping: dict[str, str]) -> None:
"""
注册新的格式映射
@@ -234,7 +234,7 @@ class UsageMapper:
def map_usage(
raw_usage: Dict[str, Any],
raw_usage: dict[str, Any],
api_format: str,
) -> StandardizedUsage:
"""
@@ -251,7 +251,7 @@ def map_usage(
def map_usage_from_response(
response: Dict[str, Any],
response: dict[str, Any],
api_format: str,
) -> StandardizedUsage:
"""