From 17bda3dbce321ff7888b236daee783bfeb850364 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Fri, 27 Feb 2026 18:09:24 +0800 Subject: [PATCH] fix: auto-fix code issues (cron) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复重复导入/字段 - 修复异常处理 - 修复PEP8格式问题 - 添加类型注解 --- backend/ai_manager.py | 140 +-- backend/api_key_manager.py | 73 +- backend/collaboration_manager.py | 90 +- backend/db_manager.py | 95 +- backend/developer_ecosystem_manager.py | 237 +++-- backend/document_processor.py | 5 +- backend/enterprise_manager.py | 238 ++--- backend/entity_aligner.py | 30 +- backend/export_manager.py | 49 +- backend/growth_manager.py | 183 ++-- backend/image_processor.py | 27 +- backend/init_db.py | 4 +- backend/knowledge_reasoner.py | 36 +- backend/llm_client.py | 19 +- backend/localization_manager.py | 134 +-- backend/main.py | 1176 ++++++++++++------------ backend/multimodal_entity_linker.py | 35 +- backend/multimodal_processor.py | 21 +- backend/neo4j_manager.py | 45 +- backend/ops_manager.py | 227 ++--- backend/oss_uploader.py | 1 + backend/performance_manager.py | 101 +- backend/plugin_manager.py | 113 +-- backend/rate_limiter.py | 20 +- backend/search_manager.py | 135 ++- backend/security_manager.py | 156 ++-- backend/subscription_manager.py | 178 ++-- backend/tenant_manager.py | 140 +-- backend/test_multimodal.py | 14 +- backend/test_phase7_task6_8.py | 18 +- backend/test_phase8_task1.py | 8 +- backend/test_phase8_task2.py | 7 +- backend/test_phase8_task4.py | 7 +- backend/test_phase8_task5.py | 14 +- backend/test_phase8_task6.py | 17 +- backend/test_phase8_task8.py | 15 +- backend/tingwu_client.py | 16 +- backend/workflow_manager.py | 141 +-- 38 files changed, 1993 insertions(+), 1972 deletions(-) diff --git a/backend/ai_manager.py b/backend/ai_manager.py index 648a654..8aab05e 100644 --- a/backend/ai_manager.py +++ b/backend/ai_manager.py @@ -8,25 +8,25 @@ AI 能力增强模块 - 预测性分析(趋势预测、异常检测) """ -import os -import json -import sqlite3 -import httpx import asyncio +import json +import os import random +import sqlite3 import statistics -from typing import List, Dict, Optional +import uuid +from collections import defaultdict from dataclasses import dataclass from datetime import datetime -from enum import Enum -from collections import defaultdict -import uuid +from enum import StrEnum + +import httpx # Database path DB_PATH = os.path.join(os.path.dirname(__file__), "insightflow.db") -class ModelType(str, Enum): +class ModelType(StrEnum): """模型类型""" CUSTOM_NER = "custom_ner" # 自定义实体识别 @@ -35,7 +35,7 @@ class ModelType(str, Enum): PREDICTION = "prediction" # 预测 -class ModelStatus(str, Enum): +class ModelStatus(StrEnum): """模型状态""" PENDING = "pending" @@ -45,7 +45,7 @@ class ModelStatus(str, Enum): ARCHIVED = "archived" -class MultimodalProvider(str, Enum): +class MultimodalProvider(StrEnum): """多模态模型提供商""" GPT4V = "gpt-4-vision" @@ -54,7 +54,7 @@ class MultimodalProvider(str, Enum): KIMI_VL = "kimi-vl" -class PredictionType(str, Enum): +class PredictionType(StrEnum): """预测类型""" TREND = "trend" # 趋势预测 @@ -73,13 +73,13 @@ class CustomModel: description: str model_type: ModelType status: ModelStatus - training_data: Dict # 训练数据配置 - hyperparameters: Dict # 超参数 - metrics: Dict # 训练指标 - model_path: Optional[str] # 模型文件路径 + training_data: dict # 训练数据配置 + hyperparameters: dict # 超参数 + metrics: dict # 训练指标 + model_path: str | None # 模型文件路径 created_at: str updated_at: str - trained_at: Optional[str] + trained_at: str | None created_by: str @@ -90,8 +90,8 @@ class TrainingSample: id: str model_id: str text: str - entities: List[Dict] # [{"start": 0, "end": 5, "label": "PERSON", "text": "张三"}] - metadata: Dict + entities: list[dict] # [{"start": 0, "end": 5, "label": "PERSON", "text": "张三"}] + metadata: dict created_at: str @@ -104,9 +104,9 @@ class MultimodalAnalysis: project_id: str provider: MultimodalProvider input_type: str # image, video, audio, mixed - input_urls: List[str] + input_urls: list[str] prompt: str - result: Dict # 分析结果 + result: dict # 分析结果 tokens_used: int cost: float created_at: str @@ -121,9 +121,9 @@ class KnowledgeGraphRAG: project_id: str name: str description: str - kg_config: Dict # 知识图谱配置 - retrieval_config: Dict # 检索配置 - generation_config: Dict # 生成配置 + kg_config: dict # 知识图谱配置 + retrieval_config: dict # 检索配置 + generation_config: dict # 生成配置 is_active: bool created_at: str updated_at: str @@ -136,9 +136,9 @@ class RAGQuery: id: str rag_id: str query: str - context: Dict # 检索到的上下文 + context: dict # 检索到的上下文 answer: str - sources: List[Dict] # 来源信息 + sources: list[dict] # 来源信息 confidence: float tokens_used: int latency_ms: int @@ -154,11 +154,11 @@ class PredictionModel: project_id: str name: str prediction_type: PredictionType - target_entity_type: Optional[str] # 目标实体类型 - features: List[str] # 特征列表 - model_config: Dict # 模型配置 - accuracy: Optional[float] - last_trained_at: Optional[str] + target_entity_type: str | None # 目标实体类型 + features: list[str] # 特征列表 + model_config: dict # 模型配置 + accuracy: float | None + last_trained_at: str | None prediction_count: int is_active: bool created_at: str @@ -172,12 +172,12 @@ class PredictionResult: id: str model_id: str prediction_type: PredictionType - target_id: Optional[str] # 预测目标ID - prediction_data: Dict # 预测数据 + target_id: str | None # 预测目标ID + prediction_data: dict # 预测数据 confidence: float explanation: str # 预测解释 - actual_value: Optional[str] # 实际值(用于验证) - is_correct: Optional[bool] + actual_value: str | None # 实际值(用于验证) + is_correct: bool | None created_at: str @@ -192,8 +192,8 @@ class SmartSummary: source_id: str summary_type: str # extractive, abstractive, key_points, timeline content: str - key_points: List[str] - entities_mentioned: List[str] + key_points: list[str] + entities_mentioned: list[str] confidence: float tokens_used: int created_at: str @@ -223,8 +223,8 @@ class AIManager: name: str, description: str, model_type: ModelType, - training_data: Dict, - hyperparameters: Dict, + training_data: dict, + hyperparameters: dict, created_by: str, ) -> CustomModel: """创建自定义模型""" @@ -277,7 +277,7 @@ class AIManager: return model - def get_custom_model(self, model_id: str) -> Optional[CustomModel]: + def get_custom_model(self, model_id: str) -> CustomModel | None: """获取自定义模型""" with self._get_db() as conn: row = conn.execute("SELECT * FROM custom_models WHERE id = ?", (model_id,)).fetchone() @@ -288,8 +288,8 @@ class AIManager: return self._row_to_custom_model(row) def list_custom_models( - self, tenant_id: str, model_type: Optional[ModelType] = None, status: Optional[ModelStatus] = None - ) -> List[CustomModel]: + self, tenant_id: str, model_type: ModelType | None = None, status: ModelStatus | None = None + ) -> list[CustomModel]: """列出自定义模型""" query = "SELECT * FROM custom_models WHERE tenant_id = ?" params = [tenant_id] @@ -308,7 +308,7 @@ class AIManager: return [self._row_to_custom_model(row) for row in rows] def add_training_sample( - self, model_id: str, text: str, entities: List[Dict], metadata: Dict = None + self, model_id: str, text: str, entities: list[dict], metadata: dict = None ) -> TrainingSample: """添加训练样本""" sample_id = f"ts_{uuid.uuid4().hex[:16]}" @@ -338,7 +338,7 @@ class AIManager: return sample - def get_training_samples(self, model_id: str) -> List[TrainingSample]: + def get_training_samples(self, model_id: str) -> list[TrainingSample]: """获取训练样本""" with self._get_db() as conn: rows = conn.execute( @@ -410,7 +410,7 @@ class AIManager: conn.commit() raise e - async def predict_with_custom_model(self, model_id: str, text: str) -> List[Dict]: + async def predict_with_custom_model(self, model_id: str, text: str) -> list[dict]: """使用自定义模型进行预测""" model = self.get_custom_model(model_id) if not model or model.status != ModelStatus.READY: @@ -461,7 +461,7 @@ class AIManager: project_id: str, provider: MultimodalProvider, input_type: str, - input_urls: List[str], + input_urls: list[str], prompt: str, ) -> MultimodalAnalysis: """多模态分析""" @@ -517,7 +517,7 @@ class AIManager: return analysis - async def _call_gpt4v(self, image_urls: List[str], prompt: str) -> Dict: + async def _call_gpt4v(self, image_urls: list[str], prompt: str) -> dict: """调用 GPT-4V""" headers = {"Authorization": f"Bearer {self.openai_api_key}", "Content-Type": "application/json"} @@ -544,7 +544,7 @@ class AIManager: "cost": result["usage"]["total_tokens"] * 0.00001, # 估算成本 } - async def _call_claude3(self, image_urls: List[str], prompt: str) -> Dict: + async def _call_claude3(self, image_urls: list[str], prompt: str) -> dict: """调用 Claude 3""" headers = { "x-api-key": self.anthropic_api_key, @@ -576,7 +576,7 @@ class AIManager: "cost": (result["usage"]["input_tokens"] + result["usage"]["output_tokens"]) * 0.000015, } - async def _call_kimi_multimodal(self, image_urls: List[str], prompt: str) -> Dict: + async def _call_kimi_multimodal(self, image_urls: list[str], prompt: str) -> dict: """调用 Kimi 多模态模型""" headers = {"Authorization": f"Bearer {self.kimi_api_key}", "Content-Type": "application/json"} @@ -600,7 +600,7 @@ class AIManager: "cost": result["usage"]["total_tokens"] * 0.000005, } - def get_multimodal_analyses(self, tenant_id: str, project_id: Optional[str] = None) -> List[MultimodalAnalysis]: + def get_multimodal_analyses(self, tenant_id: str, project_id: str | None = None) -> list[MultimodalAnalysis]: """获取多模态分析历史""" query = "SELECT * FROM multimodal_analyses WHERE tenant_id = ?" params = [tenant_id] @@ -623,9 +623,9 @@ class AIManager: project_id: str, name: str, description: str, - kg_config: Dict, - retrieval_config: Dict, - generation_config: Dict, + kg_config: dict, + retrieval_config: dict, + generation_config: dict, ) -> KnowledgeGraphRAG: """创建知识图谱 RAG 配置""" rag_id = f"kgr_{uuid.uuid4().hex[:16]}" @@ -671,7 +671,7 @@ class AIManager: return rag - def get_kg_rag(self, rag_id: str) -> Optional[KnowledgeGraphRAG]: + def get_kg_rag(self, rag_id: str) -> KnowledgeGraphRAG | None: """获取知识图谱 RAG 配置""" with self._get_db() as conn: row = conn.execute("SELECT * FROM kg_rag_configs WHERE id = ?", (rag_id,)).fetchone() @@ -681,7 +681,7 @@ class AIManager: return self._row_to_kg_rag(row) - def list_kg_rags(self, tenant_id: str, project_id: Optional[str] = None) -> List[KnowledgeGraphRAG]: + def list_kg_rags(self, tenant_id: str, project_id: str | None = None) -> list[KnowledgeGraphRAG]: """列出知识图谱 RAG 配置""" query = "SELECT * FROM kg_rag_configs WHERE tenant_id = ?" params = [tenant_id] @@ -697,7 +697,7 @@ class AIManager: return [self._row_to_kg_rag(row) for row in rows] async def query_kg_rag( - self, rag_id: str, query: str, project_entities: List[Dict], project_relations: List[Dict] + self, rag_id: str, query: str, project_entities: list[dict], project_relations: list[dict] ) -> RAGQuery: """基于知识图谱的 RAG 查询""" import time @@ -832,7 +832,7 @@ class AIManager: return rag_query - def _build_kg_context(self, entities: List[Dict], relations: List[Dict]) -> str: + def _build_kg_context(self, entities: list[dict], relations: list[dict]) -> str: """构建知识图谱上下文文本""" context = [] @@ -858,7 +858,7 @@ class AIManager: return "\n".join(context) async def generate_smart_summary( - self, tenant_id: str, project_id: str, source_type: str, source_id: str, summary_type: str, content_data: Dict + self, tenant_id: str, project_id: str, source_type: str, source_id: str, summary_type: str, content_data: dict ) -> SmartSummary: """生成智能摘要""" summary_id = f"ss_{uuid.uuid4().hex[:16]}" @@ -999,9 +999,9 @@ class AIManager: project_id: str, name: str, prediction_type: PredictionType, - target_entity_type: Optional[str], - features: List[str], - model_config: Dict, + target_entity_type: str | None, + features: list[str], + model_config: dict, ) -> PredictionModel: """创建预测模型""" model_id = f"pm_{uuid.uuid4().hex[:16]}" @@ -1053,7 +1053,7 @@ class AIManager: return model - def get_prediction_model(self, model_id: str) -> Optional[PredictionModel]: + def get_prediction_model(self, model_id: str) -> PredictionModel | None: """获取预测模型""" with self._get_db() as conn: row = conn.execute("SELECT * FROM prediction_models WHERE id = ?", (model_id,)).fetchone() @@ -1063,7 +1063,7 @@ class AIManager: return self._row_to_prediction_model(row) - def list_prediction_models(self, tenant_id: str, project_id: Optional[str] = None) -> List[PredictionModel]: + def list_prediction_models(self, tenant_id: str, project_id: str | None = None) -> list[PredictionModel]: """列出预测模型""" query = "SELECT * FROM prediction_models WHERE tenant_id = ?" params = [tenant_id] @@ -1078,7 +1078,7 @@ class AIManager: rows = conn.execute(query, params).fetchall() return [self._row_to_prediction_model(row) for row in rows] - async def train_prediction_model(self, model_id: str, historical_data: List[Dict]) -> PredictionModel: + async def train_prediction_model(self, model_id: str, historical_data: list[dict]) -> PredictionModel: """训练预测模型""" model = self.get_prediction_model(model_id) if not model: @@ -1105,7 +1105,7 @@ class AIManager: return self.get_prediction_model(model_id) - async def predict(self, model_id: str, input_data: Dict) -> PredictionResult: + async def predict(self, model_id: str, input_data: dict) -> PredictionResult: """进行预测""" model = self.get_prediction_model(model_id) if not model or not model.is_active: @@ -1172,7 +1172,7 @@ class AIManager: return result - def _predict_trend(self, input_data: Dict, model: PredictionModel) -> Dict: + def _predict_trend(self, input_data: dict, model: PredictionModel) -> dict: """趋势预测""" historical_values = input_data.get("historical_values", []) @@ -1211,7 +1211,7 @@ class AIManager: "explanation": f"基于{len(historical_values)}个历史数据点,预测趋势为{trend}", } - def _detect_anomaly(self, input_data: Dict, model: PredictionModel) -> Dict: + def _detect_anomaly(self, input_data: dict, model: PredictionModel) -> dict: """异常检测""" value = input_data.get("value") historical_values = input_data.get("historical_values", []) @@ -1245,7 +1245,7 @@ class AIManager: "explanation": f"当前值偏离均值{z_score:.2f}个标准差,{'检测到异常' if is_anomaly else '处于正常范围'}", } - def _predict_entity_growth(self, input_data: Dict, model: PredictionModel) -> Dict: + def _predict_entity_growth(self, input_data: dict, model: PredictionModel) -> dict: """实体增长预测""" entity_history = input_data.get("entity_history", []) @@ -1273,7 +1273,7 @@ class AIManager: "explanation": f"基于过去{len(entity_history)}个周期的数据,预测增长率{avg_growth_rate * 100:.1f}%", } - def _predict_relation_evolution(self, input_data: Dict, model: PredictionModel) -> Dict: + def _predict_relation_evolution(self, input_data: dict, model: PredictionModel) -> dict: """关系演变预测""" relation_history = input_data.get("relation_history", []) @@ -1299,7 +1299,7 @@ class AIManager: "explanation": f"基于{len(relation_history)}个历史快照分析关系演变趋势", } - def get_prediction_results(self, model_id: str, limit: int = 100) -> List[PredictionResult]: + def get_prediction_results(self, model_id: str, limit: int = 100) -> list[PredictionResult]: """获取预测结果历史""" with self._get_db() as conn: rows = conn.execute( diff --git a/backend/api_key_manager.py b/backend/api_key_manager.py index 23e1d5f..65cae67 100644 --- a/backend/api_key_manager.py +++ b/backend/api_key_manager.py @@ -4,14 +4,13 @@ InsightFlow API Key Manager - Phase 6 API Key 管理模块:生成、验证、撤销 """ -import os -import json import hashlib +import json +import os import secrets import sqlite3 -from datetime import datetime, timedelta -from typing import Optional, List, Dict from dataclasses import dataclass +from datetime import datetime, timedelta from enum import Enum DB_PATH = os.getenv("DB_PATH", "/app/data/insightflow.db") @@ -29,15 +28,15 @@ class ApiKey: key_hash: str # 存储哈希值,不存储原始 key key_preview: str # 前8位预览,如 "ak_live_abc..." name: str # 密钥名称/描述 - owner_id: Optional[str] # 所有者ID(预留多用户支持) - permissions: List[str] # 权限列表,如 ["read", "write"] + owner_id: str | None # 所有者ID(预留多用户支持) + permissions: list[str] # 权限列表,如 ["read", "write"] rate_limit: int # 每分钟请求限制 status: str # active, revoked, expired created_at: str - expires_at: Optional[str] - last_used_at: Optional[str] - revoked_at: Optional[str] - revoked_reason: Optional[str] + expires_at: str | None + last_used_at: str | None + revoked_at: str | None + revoked_reason: str | None total_calls: int = 0 @@ -131,10 +130,10 @@ class ApiKeyManager: def create_key( self, name: str, - owner_id: Optional[str] = None, - permissions: List[str] = None, + owner_id: str | None = None, + permissions: list[str] = None, rate_limit: int = 60, - expires_days: Optional[int] = None, + expires_days: int | None = None, ) -> tuple[str, ApiKey]: """ 创建新的 API Key @@ -196,7 +195,7 @@ class ApiKeyManager: return raw_key, api_key - def validate_key(self, key: str) -> Optional[ApiKey]: + def validate_key(self, key: str) -> ApiKey | None: """ 验证 API Key @@ -231,7 +230,7 @@ class ApiKeyManager: return api_key - def revoke_key(self, key_id: str, reason: str = "", owner_id: Optional[str] = None) -> bool: + def revoke_key(self, key_id: str, reason: str = "", owner_id: str | None = None) -> bool: """撤销 API Key""" with sqlite3.connect(self.db_path) as conn: # 验证所有权(如果提供了 owner_id) @@ -251,7 +250,7 @@ class ApiKeyManager: conn.commit() return cursor.rowcount > 0 - def get_key_by_id(self, key_id: str, owner_id: Optional[str] = None) -> Optional[ApiKey]: + def get_key_by_id(self, key_id: str, owner_id: str | None = None) -> ApiKey | None: """通过 ID 获取 API Key(不包含敏感信息)""" with sqlite3.connect(self.db_path) as conn: conn.row_factory = sqlite3.Row @@ -268,8 +267,8 @@ class ApiKeyManager: return None def list_keys( - self, owner_id: Optional[str] = None, status: Optional[str] = None, limit: int = 100, offset: int = 0 - ) -> List[ApiKey]: + self, owner_id: str | None = None, status: str | None = None, limit: int = 100, offset: int = 0 + ) -> list[ApiKey]: """列出 API Keys""" with sqlite3.connect(self.db_path) as conn: conn.row_factory = sqlite3.Row @@ -294,10 +293,10 @@ class ApiKeyManager: def update_key( self, key_id: str, - name: Optional[str] = None, - permissions: Optional[List[str]] = None, - rate_limit: Optional[int] = None, - owner_id: Optional[str] = None, + name: str | None = None, + permissions: list[str] | None = None, + rate_limit: int | None = None, + owner_id: str | None = None, ) -> bool: """更新 API Key 信息""" updates = [] @@ -371,12 +370,12 @@ class ApiKeyManager: def get_call_logs( self, - api_key_id: Optional[str] = None, - start_date: Optional[str] = None, - end_date: Optional[str] = None, + api_key_id: str | None = None, + start_date: str | None = None, + end_date: str | None = None, limit: int = 100, offset: int = 0, - ) -> List[Dict]: + ) -> list[dict]: """获取 API 调用日志""" with sqlite3.connect(self.db_path) as conn: conn.row_factory = sqlite3.Row @@ -402,13 +401,13 @@ class ApiKeyManager: rows = conn.execute(query, params).fetchall() return [dict(row) for row in rows] - def get_call_stats(self, api_key_id: Optional[str] = None, days: int = 30) -> Dict: + def get_call_stats(self, api_key_id: str | None = None, days: int = 30) -> dict: """获取 API 调用统计""" with sqlite3.connect(self.db_path) as conn: conn.row_factory = sqlite3.Row # 总体统计 - query = """ + query = f""" SELECT COUNT(*) as total_calls, COUNT(CASE WHEN status_code < 400 THEN 1 END) as success_calls, @@ -417,8 +416,8 @@ class ApiKeyManager: MAX(response_time_ms) as max_response_time, MIN(response_time_ms) as min_response_time FROM api_call_logs - WHERE created_at >= date('now', '-{} days') - """.format(days) + WHERE created_at >= date('now', '-{days} days') + """ params = [] if api_key_id: @@ -428,15 +427,15 @@ class ApiKeyManager: row = conn.execute(query, params).fetchone() # 按端点统计 - endpoint_query = """ + endpoint_query = f""" SELECT endpoint, method, COUNT(*) as calls, AVG(response_time_ms) as avg_time FROM api_call_logs - WHERE created_at >= date('now', '-{} days') - """.format(days) + WHERE created_at >= date('now', '-{days} days') + """ endpoint_params = [] if api_key_id: @@ -448,14 +447,14 @@ class ApiKeyManager: endpoint_rows = conn.execute(endpoint_query, endpoint_params).fetchall() # 按天统计 - daily_query = """ + daily_query = f""" SELECT date(created_at) as date, COUNT(*) as calls, COUNT(CASE WHEN status_code < 400 THEN 1 END) as success FROM api_call_logs - WHERE created_at >= date('now', '-{} days') - """.format(days) + WHERE created_at >= date('now', '-{days} days') + """ daily_params = [] if api_key_id: @@ -500,7 +499,7 @@ class ApiKeyManager: # 全局实例 -_api_key_manager: Optional[ApiKeyManager] = None +_api_key_manager: ApiKeyManager | None = None def get_api_key_manager() -> ApiKeyManager: diff --git a/backend/collaboration_manager.py b/backend/collaboration_manager.py index 1cdb254..d7e0f8a 100644 --- a/backend/collaboration_manager.py +++ b/backend/collaboration_manager.py @@ -3,13 +3,13 @@ InsightFlow - 协作与共享模块 (Phase 7 Task 4) 支持项目分享、评论批注、变更历史、团队空间 """ +import hashlib import json import uuid -import hashlib -from datetime import datetime, timedelta -from typing import List, Optional, Dict, Any from dataclasses import dataclass +from datetime import datetime, timedelta from enum import Enum +from typing import Any class SharePermission(Enum): @@ -50,10 +50,10 @@ class ProjectShare: permission: str # 权限级别 created_by: str # 创建者 created_at: str - expires_at: Optional[str] # 过期时间 - max_uses: Optional[int] # 最大使用次数 + expires_at: str | None # 过期时间 + max_uses: int | None # 最大使用次数 use_count: int # 已使用次数 - password_hash: Optional[str] # 密码保护 + password_hash: str | None # 密码保护 is_active: bool # 是否激活 allow_download: bool # 允许下载 allow_export: bool # 允许导出 @@ -67,17 +67,17 @@ class Comment: project_id: str target_type: str # 评论目标类型 target_id: str # 目标ID - parent_id: Optional[str] # 父评论ID(支持回复) + parent_id: str | None # 父评论ID(支持回复) author: str # 作者 author_name: str # 作者显示名 content: str # 评论内容 created_at: str updated_at: str resolved: bool # 是否已解决 - resolved_by: Optional[str] # 解决者 - resolved_at: Optional[str] # 解决时间 - mentions: List[str] # 提及的用户 - attachments: List[Dict] # 附件 + resolved_by: str | None # 解决者 + resolved_at: str | None # 解决时间 + mentions: list[str] # 提及的用户 + attachments: list[dict] # 附件 @dataclass @@ -93,13 +93,13 @@ class ChangeRecord: changed_by: str # 变更者 changed_by_name: str # 变更者显示名 changed_at: str - old_value: Optional[Dict] # 旧值 - new_value: Optional[Dict] # 新值 + old_value: dict | None # 旧值 + new_value: dict | None # 新值 description: str # 变更描述 - session_id: Optional[str] # 会话ID(批量变更关联) + session_id: str | None # 会话ID(批量变更关联) reverted: bool # 是否已回滚 - reverted_at: Optional[str] # 回滚时间 - reverted_by: Optional[str] # 回滚者 + reverted_at: str | None # 回滚时间 + reverted_by: str | None # 回滚者 @dataclass @@ -114,8 +114,8 @@ class TeamMember: role: str # 角色 (owner/admin/editor/viewer) joined_at: str invited_by: str # 邀请者 - last_active_at: Optional[str] # 最后活跃时间 - permissions: List[str] # 具体权限列表 + last_active_at: str | None # 最后活跃时间 + permissions: list[str] # 具体权限列表 @dataclass @@ -130,7 +130,7 @@ class TeamSpace: updated_at: str member_count: int project_count: int - settings: Dict[str, Any] # 团队设置 + settings: dict[str, Any] # 团队设置 class CollaborationManager: @@ -138,8 +138,8 @@ class CollaborationManager: def __init__(self, db_manager=None): self.db = db_manager - self._shares_cache: Dict[str, ProjectShare] = {} - self._comments_cache: Dict[str, List[Comment]] = {} + self._shares_cache: dict[str, ProjectShare] = {} + self._comments_cache: dict[str, list[Comment]] = {} # ============ 项目分享 ============ @@ -148,9 +148,9 @@ class CollaborationManager: project_id: str, created_by: str, permission: str = "read_only", - expires_in_days: Optional[int] = None, - max_uses: Optional[int] = None, - password: Optional[str] = None, + expires_in_days: int | None = None, + max_uses: int | None = None, + password: str | None = None, allow_download: bool = False, allow_export: bool = False, ) -> ProjectShare: @@ -224,7 +224,7 @@ class CollaborationManager: ) self.db.conn.commit() - def validate_share_token(self, token: str, password: Optional[str] = None) -> Optional[ProjectShare]: + def validate_share_token(self, token: str, password: str | None = None) -> ProjectShare | None: """验证分享令牌""" # 从缓存或数据库获取 share = self._shares_cache.get(token) @@ -256,7 +256,7 @@ class CollaborationManager: return share - def _get_share_from_db(self, token: str) -> Optional[ProjectShare]: + def _get_share_from_db(self, token: str) -> ProjectShare | None: """从数据库获取分享记录""" cursor = self.db.conn.cursor() cursor.execute( @@ -320,7 +320,7 @@ class CollaborationManager: return cursor.rowcount > 0 return False - def list_project_shares(self, project_id: str) -> List[ProjectShare]: + def list_project_shares(self, project_id: str) -> list[ProjectShare]: """列出项目的所有分享链接""" if not self.db: return [] @@ -366,9 +366,9 @@ class CollaborationManager: author: str, author_name: str, content: str, - parent_id: Optional[str] = None, - mentions: Optional[List[str]] = None, - attachments: Optional[List[Dict]] = None, + parent_id: str | None = None, + mentions: list[str] | None = None, + attachments: list[dict] | None = None, ) -> Comment: """添加评论""" comment_id = str(uuid.uuid4()) @@ -434,7 +434,7 @@ class CollaborationManager: ) self.db.conn.commit() - def get_comments(self, target_type: str, target_id: str, include_resolved: bool = True) -> List[Comment]: + def get_comments(self, target_type: str, target_id: str, include_resolved: bool = True) -> list[Comment]: """获取评论列表""" if not self.db: return [] @@ -484,7 +484,7 @@ class CollaborationManager: attachments=json.loads(row[14]) if row[14] else [], ) - def update_comment(self, comment_id: str, content: str, updated_by: str) -> Optional[Comment]: + def update_comment(self, comment_id: str, content: str, updated_by: str) -> Comment | None: """更新评论""" if not self.db: return None @@ -505,7 +505,7 @@ class CollaborationManager: return self._get_comment_by_id(comment_id) return None - def _get_comment_by_id(self, comment_id: str) -> Optional[Comment]: + def _get_comment_by_id(self, comment_id: str) -> Comment | None: """根据ID获取评论""" cursor = self.db.conn.cursor() cursor.execute("SELECT * FROM comments WHERE id = ?", (comment_id,)) @@ -551,7 +551,7 @@ class CollaborationManager: self.db.conn.commit() return cursor.rowcount > 0 - def get_project_comments(self, project_id: str, limit: int = 50, offset: int = 0) -> List[Comment]: + def get_project_comments(self, project_id: str, limit: int = 50, offset: int = 0) -> list[Comment]: """获取项目下的所有评论""" if not self.db: return [] @@ -583,10 +583,10 @@ class CollaborationManager: entity_name: str, changed_by: str, changed_by_name: str, - old_value: Optional[Dict] = None, - new_value: Optional[Dict] = None, + old_value: dict | None = None, + new_value: dict | None = None, description: str = "", - session_id: Optional[str] = None, + session_id: str | None = None, ) -> ChangeRecord: """记录变更""" record_id = str(uuid.uuid4()) @@ -651,11 +651,11 @@ class CollaborationManager: def get_change_history( self, project_id: str, - entity_type: Optional[str] = None, - entity_id: Optional[str] = None, + entity_type: str | None = None, + entity_id: str | None = None, limit: int = 50, offset: int = 0, - ) -> List[ChangeRecord]: + ) -> list[ChangeRecord]: """获取变更历史""" if not self.db: return [] @@ -719,7 +719,7 @@ class CollaborationManager: reverted_by=row[15], ) - def get_entity_version_history(self, entity_type: str, entity_id: str) -> List[ChangeRecord]: + def get_entity_version_history(self, entity_type: str, entity_id: str) -> list[ChangeRecord]: """获取实体的版本历史(用于版本对比)""" if not self.db: return [] @@ -757,7 +757,7 @@ class CollaborationManager: self.db.conn.commit() return cursor.rowcount > 0 - def get_change_stats(self, project_id: str) -> Dict[str, Any]: + def get_change_stats(self, project_id: str) -> dict[str, Any]: """获取变更统计""" if not self.db: return {} @@ -823,7 +823,7 @@ class CollaborationManager: user_email: str, role: str, invited_by: str, - permissions: Optional[List[str]] = None, + permissions: list[str] | None = None, ) -> TeamMember: """添加团队成员""" member_id = str(uuid.uuid4()) @@ -851,7 +851,7 @@ class CollaborationManager: return member - def _get_default_permissions(self, role: str) -> List[str]: + def _get_default_permissions(self, role: str) -> list[str]: """获取角色的默认权限""" permissions_map = { "owner": ["read", "write", "delete", "share", "admin", "export"], @@ -887,7 +887,7 @@ class CollaborationManager: ) self.db.conn.commit() - def get_team_members(self, project_id: str) -> List[TeamMember]: + def get_team_members(self, project_id: str) -> list[TeamMember]: """获取团队成员列表""" if not self.db: return [] diff --git a/backend/db_manager.py b/backend/db_manager.py index 0b61569..667f5cc 100644 --- a/backend/db_manager.py +++ b/backend/db_manager.py @@ -5,13 +5,12 @@ InsightFlow Database Manager - Phase 5 支持实体属性扩展 """ -import os import json +import os import sqlite3 import uuid -from datetime import datetime -from typing import List, Dict, Optional from dataclasses import dataclass +from datetime import datetime DB_PATH = os.getenv("DB_PATH", "/app/data/insightflow.db") @@ -33,9 +32,9 @@ class Entity: type: str definition: str = "" canonical_name: str = "" - aliases: List[str] = None + aliases: list[str] = None embedding: str = "" # Phase 3: 实体嵌入向量 - attributes: Dict = None # Phase 5: 实体属性 + attributes: dict = None # Phase 5: 实体属性 created_at: str = "" updated_at: str = "" @@ -54,7 +53,7 @@ class AttributeTemplate: project_id: str name: str type: str # text, number, date, select, multiselect, boolean - options: List[str] = None # 用于 select/multiselect + options: list[str] = None # 用于 select/multiselect default_value: str = "" description: str = "" is_required: bool = False @@ -73,11 +72,11 @@ class EntityAttribute: id: str entity_id: str - template_id: Optional[str] = None + template_id: str | None = None name: str = "" # 属性名称 type: str = "text" # 属性类型 value: str = "" - options: List[str] = None # 选项列表 + options: list[str] = None # 选项列表 template_name: str = "" # 关联查询时填充 template_type: str = "" # 关联查询时填充 created_at: str = "" @@ -126,7 +125,7 @@ class DatabaseManager: def init_db(self): """初始化数据库表""" - with open(os.path.join(os.path.dirname(__file__), "schema.sql"), "r") as f: + with open(os.path.join(os.path.dirname(__file__), "schema.sql")) as f: schema = f.read() conn = self.get_conn() @@ -147,7 +146,7 @@ class DatabaseManager: conn.close() return Project(id=project_id, name=name, description=description, created_at=now, updated_at=now) - def get_project(self, project_id: str) -> Optional[Project]: + def get_project(self, project_id: str) -> Project | None: conn = self.get_conn() row = conn.execute("SELECT * FROM projects WHERE id = ?", (project_id,)).fetchone() conn.close() @@ -155,7 +154,7 @@ class DatabaseManager: return Project(**dict(row)) return None - def list_projects(self) -> List[Project]: + def list_projects(self) -> list[Project]: conn = self.get_conn() rows = conn.execute("SELECT * FROM projects ORDER BY updated_at DESC").fetchall() conn.close() @@ -184,7 +183,7 @@ class DatabaseManager: conn.close() return entity - def get_entity_by_name(self, project_id: str, name: str) -> Optional[Entity]: + def get_entity_by_name(self, project_id: str, name: str) -> Entity | None: """通过名称查找实体(用于对齐)""" conn = self.get_conn() row = conn.execute( @@ -198,7 +197,7 @@ class DatabaseManager: return Entity(**data) return None - def find_similar_entities(self, project_id: str, name: str, threshold: float = 0.8) -> List[Entity]: + def find_similar_entities(self, project_id: str, name: str, threshold: float = 0.8) -> list[Entity]: """查找相似实体""" conn = self.get_conn() rows = conn.execute( @@ -245,7 +244,7 @@ class DatabaseManager: conn.close() return self.get_entity(target_id) - def get_entity(self, entity_id: str) -> Optional[Entity]: + def get_entity(self, entity_id: str) -> Entity | None: conn = self.get_conn() row = conn.execute("SELECT * FROM entities WHERE id = ?", (entity_id,)).fetchone() conn.close() @@ -255,7 +254,7 @@ class DatabaseManager: return Entity(**data) return None - def list_project_entities(self, project_id: str) -> List[Entity]: + def list_project_entities(self, project_id: str) -> list[Entity]: conn = self.get_conn() rows = conn.execute( "SELECT * FROM entities WHERE project_id = ? ORDER BY updated_at DESC", (project_id,) @@ -333,7 +332,7 @@ class DatabaseManager: conn.close() return mention - def get_entity_mentions(self, entity_id: str) -> List[EntityMention]: + def get_entity_mentions(self, entity_id: str) -> list[EntityMention]: conn = self.get_conn() rows = conn.execute( "SELECT * FROM entity_mentions WHERE entity_id = ? ORDER BY transcript_id, start_pos", (entity_id,) @@ -355,13 +354,13 @@ class DatabaseManager: conn.commit() conn.close() - def get_transcript(self, transcript_id: str) -> Optional[dict]: + def get_transcript(self, transcript_id: str) -> dict | None: conn = self.get_conn() row = conn.execute("SELECT * FROM transcripts WHERE id = ?", (transcript_id,)).fetchone() conn.close() return dict(row) if row else None - def list_project_transcripts(self, project_id: str) -> List[dict]: + def list_project_transcripts(self, project_id: str) -> list[dict]: conn = self.get_conn() rows = conn.execute( "SELECT * FROM transcripts WHERE project_id = ? ORDER BY created_at DESC", (project_id,) @@ -404,7 +403,7 @@ class DatabaseManager: conn.close() return relation_id - def get_entity_relations(self, entity_id: str) -> List[dict]: + def get_entity_relations(self, entity_id: str) -> list[dict]: conn = self.get_conn() rows = conn.execute( """SELECT * FROM entity_relations @@ -415,7 +414,7 @@ class DatabaseManager: conn.close() return [dict(r) for r in rows] - def list_project_relations(self, project_id: str) -> List[dict]: + def list_project_relations(self, project_id: str) -> list[dict]: conn = self.get_conn() rows = conn.execute( "SELECT * FROM entity_relations WHERE project_id = ? ORDER BY created_at DESC", (project_id,) @@ -473,7 +472,7 @@ class DatabaseManager: conn.close() return term_id - def list_glossary(self, project_id: str) -> List[dict]: + def list_glossary(self, project_id: str) -> list[dict]: conn = self.get_conn() rows = conn.execute( "SELECT * FROM glossary WHERE project_id = ? ORDER BY frequency DESC", (project_id,) @@ -489,7 +488,7 @@ class DatabaseManager: # ==================== Phase 4: Agent & Provenance ==================== - def get_relation_with_details(self, relation_id: str) -> Optional[dict]: + def get_relation_with_details(self, relation_id: str) -> dict | None: conn = self.get_conn() row = conn.execute( """SELECT r.*, @@ -505,7 +504,7 @@ class DatabaseManager: conn.close() return dict(row) if row else None - def get_entity_with_mentions(self, entity_id: str) -> Optional[dict]: + def get_entity_with_mentions(self, entity_id: str) -> dict | None: conn = self.get_conn() entity_row = conn.execute("SELECT * FROM entities WHERE id = ?", (entity_id,)).fetchone() if not entity_row: @@ -539,7 +538,7 @@ class DatabaseManager: conn.close() return entity - def search_entities(self, project_id: str, query: str) -> List[Entity]: + def search_entities(self, project_id: str, query: str) -> list[Entity]: conn = self.get_conn() rows = conn.execute( """SELECT * FROM entities @@ -616,7 +615,7 @@ class DatabaseManager: def get_project_timeline( self, project_id: str, entity_id: str = None, start_date: str = None, end_date: str = None - ) -> List[dict]: + ) -> list[dict]: conn = self.get_conn() conditions = ["t.project_id = ?"] @@ -722,7 +721,7 @@ class DatabaseManager: conn.close() return template - def get_attribute_template(self, template_id: str) -> Optional[AttributeTemplate]: + def get_attribute_template(self, template_id: str) -> AttributeTemplate | None: conn = self.get_conn() row = conn.execute("SELECT * FROM attribute_templates WHERE id = ?", (template_id,)).fetchone() conn.close() @@ -732,7 +731,7 @@ class DatabaseManager: return AttributeTemplate(**data) return None - def list_attribute_templates(self, project_id: str) -> List[AttributeTemplate]: + def list_attribute_templates(self, project_id: str) -> list[AttributeTemplate]: conn = self.get_conn() rows = conn.execute( """SELECT * FROM attribute_templates WHERE project_id = ? @@ -748,7 +747,7 @@ class DatabaseManager: templates.append(AttributeTemplate(**data)) return templates - def update_attribute_template(self, template_id: str, **kwargs) -> Optional[AttributeTemplate]: + def update_attribute_template(self, template_id: str, **kwargs) -> AttributeTemplate | None: conn = self.get_conn() allowed_fields = ["name", "type", "options", "default_value", "description", "is_required", "sort_order"] updates = [] @@ -834,7 +833,7 @@ class DatabaseManager: conn.close() return attr - def get_entity_attributes(self, entity_id: str) -> List[EntityAttribute]: + def get_entity_attributes(self, entity_id: str) -> list[EntityAttribute]: conn = self.get_conn() rows = conn.execute( """SELECT ea.*, at.name as template_name, at.type as template_type @@ -846,7 +845,7 @@ class DatabaseManager: conn.close() return [EntityAttribute(**dict(r)) for r in rows] - def get_entity_with_attributes(self, entity_id: str) -> Optional[Entity]: + def get_entity_with_attributes(self, entity_id: str) -> Entity | None: entity = self.get_entity(entity_id) if not entity: return None @@ -889,7 +888,7 @@ class DatabaseManager: def get_attribute_history( self, entity_id: str = None, template_id: str = None, limit: int = 50 - ) -> List[AttributeHistory]: + ) -> list[AttributeHistory]: conn = self.get_conn() conditions = [] params = [] @@ -913,7 +912,7 @@ class DatabaseManager: conn.close() return [AttributeHistory(**dict(r)) for r in rows] - def search_entities_by_attributes(self, project_id: str, attribute_filters: Dict[str, str]) -> List[Entity]: + def search_entities_by_attributes(self, project_id: str, attribute_filters: dict[str, str]) -> list[Entity]: entities = self.list_project_entities(project_id) if not attribute_filters: return entities @@ -962,11 +961,11 @@ class DatabaseManager: filename: str, duration: float = 0, fps: float = 0, - resolution: Dict = None, + resolution: dict = None, audio_transcript_id: str = None, full_ocr_text: str = "", - extracted_entities: List[Dict] = None, - extracted_relations: List[Dict] = None, + extracted_entities: list[dict] = None, + extracted_relations: list[dict] = None, ) -> str: """创建视频记录""" conn = self.get_conn() @@ -998,7 +997,7 @@ class DatabaseManager: conn.close() return video_id - def get_video(self, video_id: str) -> Optional[Dict]: + def get_video(self, video_id: str) -> dict | None: """获取视频信息""" conn = self.get_conn() row = conn.execute("SELECT * FROM videos WHERE id = ?", (video_id,)).fetchone() @@ -1012,7 +1011,7 @@ class DatabaseManager: return data return None - def list_project_videos(self, project_id: str) -> List[Dict]: + def list_project_videos(self, project_id: str) -> list[dict]: """获取项目的所有视频""" conn = self.get_conn() rows = conn.execute( @@ -1037,7 +1036,7 @@ class DatabaseManager: timestamp: float, image_url: str = None, ocr_text: str = None, - extracted_entities: List[Dict] = None, + extracted_entities: list[dict] = None, ) -> str: """创建视频帧记录""" conn = self.get_conn() @@ -1062,7 +1061,7 @@ class DatabaseManager: conn.close() return frame_id - def get_video_frames(self, video_id: str) -> List[Dict]: + def get_video_frames(self, video_id: str) -> list[dict]: """获取视频的所有帧""" conn = self.get_conn() rows = conn.execute( @@ -1084,8 +1083,8 @@ class DatabaseManager: filename: str, ocr_text: str = "", description: str = "", - extracted_entities: List[Dict] = None, - extracted_relations: List[Dict] = None, + extracted_entities: list[dict] = None, + extracted_relations: list[dict] = None, ) -> str: """创建图片记录""" conn = self.get_conn() @@ -1113,7 +1112,7 @@ class DatabaseManager: conn.close() return image_id - def get_image(self, image_id: str) -> Optional[Dict]: + def get_image(self, image_id: str) -> dict | None: """获取图片信息""" conn = self.get_conn() row = conn.execute("SELECT * FROM images WHERE id = ?", (image_id,)).fetchone() @@ -1126,7 +1125,7 @@ class DatabaseManager: return data return None - def list_project_images(self, project_id: str) -> List[Dict]: + def list_project_images(self, project_id: str) -> list[dict]: """获取项目的所有图片""" conn = self.get_conn() rows = conn.execute( @@ -1168,7 +1167,7 @@ class DatabaseManager: conn.close() return mention_id - def get_entity_multimodal_mentions(self, entity_id: str) -> List[Dict]: + def get_entity_multimodal_mentions(self, entity_id: str) -> list[dict]: """获取实体的多模态提及""" conn = self.get_conn() rows = conn.execute( @@ -1181,7 +1180,7 @@ class DatabaseManager: conn.close() return [dict(r) for r in rows] - def get_project_multimodal_mentions(self, project_id: str, modality: str = None) -> List[Dict]: + def get_project_multimodal_mentions(self, project_id: str, modality: str = None) -> list[dict]: """获取项目的多模态提及""" conn = self.get_conn() @@ -1214,7 +1213,7 @@ class DatabaseManager: link_type: str, confidence: float = 1.0, evidence: str = "", - modalities: List[str] = None, + modalities: list[str] = None, ) -> str: """创建多模态实体关联""" conn = self.get_conn() @@ -1231,7 +1230,7 @@ class DatabaseManager: conn.close() return link_id - def get_entity_multimodal_links(self, entity_id: str) -> List[Dict]: + def get_entity_multimodal_links(self, entity_id: str) -> list[dict]: """获取实体的多模态关联""" conn = self.get_conn() rows = conn.execute( @@ -1251,7 +1250,7 @@ class DatabaseManager: links.append(data) return links - def get_project_multimodal_stats(self, project_id: str) -> Dict: + def get_project_multimodal_stats(self, project_id: str) -> dict: """获取项目多模态统计信息""" conn = self.get_conn() diff --git a/backend/developer_ecosystem_manager.py b/backend/developer_ecosystem_manager.py index 60782c2..c0bbc89 100644 --- a/backend/developer_ecosystem_manager.py +++ b/backend/developer_ecosystem_manager.py @@ -10,20 +10,19 @@ InsightFlow Developer Ecosystem Manager - Phase 8 Task 6 作者: InsightFlow Team """ -import os import json +import os import sqlite3 import uuid -from typing import List, Dict, Optional from dataclasses import dataclass from datetime import datetime -from enum import Enum +from enum import StrEnum # Database path DB_PATH = os.path.join(os.path.dirname(__file__), "insightflow.db") -class SDKLanguage(str, Enum): +class SDKLanguage(StrEnum): """SDK 语言类型""" PYTHON = "python" @@ -34,7 +33,7 @@ class SDKLanguage(str, Enum): RUST = "rust" -class SDKStatus(str, Enum): +class SDKStatus(StrEnum): """SDK 状态""" DRAFT = "draft" # 草稿 @@ -44,7 +43,7 @@ class SDKStatus(str, Enum): ARCHIVED = "archived" # 已归档 -class TemplateCategory(str, Enum): +class TemplateCategory(StrEnum): """模板分类""" MEDICAL = "medical" # 医疗 @@ -55,7 +54,7 @@ class TemplateCategory(str, Enum): GENERAL = "general" # 通用 -class TemplateStatus(str, Enum): +class TemplateStatus(StrEnum): """模板状态""" PENDING = "pending" # 待审核 @@ -65,7 +64,7 @@ class TemplateStatus(str, Enum): UNLISTED = "unlisted" # 未列出 -class PluginStatus(str, Enum): +class PluginStatus(StrEnum): """插件状态""" PENDING = "pending" # 待审核 @@ -76,7 +75,7 @@ class PluginStatus(str, Enum): SUSPENDED = "suspended" # 已暂停 -class PluginCategory(str, Enum): +class PluginCategory(StrEnum): """插件分类""" INTEGRATION = "integration" # 集成 @@ -87,7 +86,7 @@ class PluginCategory(str, Enum): CUSTOM = "custom" # 自定义 -class DeveloperStatus(str, Enum): +class DeveloperStatus(StrEnum): """开发者认证状态""" UNVERIFIED = "unverified" # 未认证 @@ -113,13 +112,13 @@ class SDKRelease: package_name: str # pip/npm/go module name status: SDKStatus min_platform_version: str - dependencies: List[Dict] # [{"name": "requests", "version": ">=2.0"}] + dependencies: list[dict] # [{"name": "requests", "version": ">=2.0"}] file_size: int checksum: str download_count: int created_at: str updated_at: str - published_at: Optional[str] + published_at: str | None created_by: str @@ -148,17 +147,17 @@ class TemplateMarketItem: name: str description: str category: TemplateCategory - subcategory: Optional[str] - tags: List[str] + subcategory: str | None + tags: list[str] author_id: str author_name: str status: TemplateStatus price: float # 0 = 免费 currency: str - preview_image_url: Optional[str] - demo_url: Optional[str] - documentation_url: Optional[str] - download_url: Optional[str] + preview_image_url: str | None + demo_url: str | None + documentation_url: str | None + download_url: str | None install_count: int rating: float rating_count: int @@ -169,7 +168,7 @@ class TemplateMarketItem: checksum: str created_at: str updated_at: str - published_at: Optional[str] + published_at: str | None @dataclass @@ -196,20 +195,20 @@ class PluginMarketItem: name: str description: str category: PluginCategory - tags: List[str] + tags: list[str] author_id: str author_name: str status: PluginStatus price: float currency: str pricing_model: str # free, paid, freemium, subscription - preview_image_url: Optional[str] - demo_url: Optional[str] - documentation_url: Optional[str] - repository_url: Optional[str] - download_url: Optional[str] - webhook_url: Optional[str] # 用于插件回调 - permissions: List[str] # 需要的权限列表 + preview_image_url: str | None + demo_url: str | None + documentation_url: str | None + repository_url: str | None + download_url: str | None + webhook_url: str | None # 用于插件回调 + permissions: list[str] # 需要的权限列表 install_count: int active_install_count: int rating: float @@ -221,10 +220,10 @@ class PluginMarketItem: checksum: str created_at: str updated_at: str - published_at: Optional[str] - reviewed_by: Optional[str] - reviewed_at: Optional[str] - review_notes: Optional[str] + published_at: str | None + reviewed_by: str | None + reviewed_at: str | None + review_notes: str | None @dataclass @@ -251,12 +250,12 @@ class DeveloperProfile: user_id: str display_name: str email: str - bio: Optional[str] - website: Optional[str] - github_url: Optional[str] - avatar_url: Optional[str] + bio: str | None + website: str | None + github_url: str | None + avatar_url: str | None status: DeveloperStatus - verification_documents: Dict # 认证文档 + verification_documents: dict # 认证文档 total_sales: float total_downloads: int plugin_count: int @@ -264,7 +263,7 @@ class DeveloperProfile: rating_average: float created_at: str updated_at: str - verified_at: Optional[str] + verified_at: str | None @dataclass @@ -296,11 +295,11 @@ class CodeExample: category: str code: str explanation: str - tags: List[str] + tags: list[str] author_id: str author_name: str - sdk_id: Optional[str] # 关联的 SDK - api_endpoints: List[str] # 涉及的 API 端点 + sdk_id: str | None # 关联的 SDK + api_endpoints: list[str] # 涉及的 API 端点 view_count: int copy_count: int rating: float @@ -330,16 +329,16 @@ class DeveloperPortalConfig: name: str description: str theme: str - custom_css: Optional[str] - custom_js: Optional[str] - logo_url: Optional[str] - favicon_url: Optional[str] + custom_css: str | None + custom_js: str | None + logo_url: str | None + favicon_url: str | None primary_color: str secondary_color: str support_email: str - support_url: Optional[str] - github_url: Optional[str] - discord_url: Optional[str] + support_url: str | None + github_url: str | None + discord_url: str | None api_base_url: str is_active: bool created_at: str @@ -373,7 +372,7 @@ class DeveloperEcosystemManager: repository_url: str, package_name: str, min_platform_version: str, - dependencies: List[Dict], + dependencies: list[dict], file_size: int, checksum: str, created_by: str, @@ -442,7 +441,7 @@ class DeveloperEcosystemManager: return sdk - def get_sdk_release(self, sdk_id: str) -> Optional[SDKRelease]: + def get_sdk_release(self, sdk_id: str) -> SDKRelease | None: """获取 SDK 发布详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM sdk_releases WHERE id = ?", (sdk_id,)).fetchone() @@ -452,8 +451,8 @@ class DeveloperEcosystemManager: return None def list_sdk_releases( - self, language: Optional[SDKLanguage] = None, status: Optional[SDKStatus] = None, search: Optional[str] = None - ) -> List[SDKRelease]: + self, language: SDKLanguage | None = None, status: SDKStatus | None = None, search: str | None = None + ) -> list[SDKRelease]: """列出 SDK 发布""" query = "SELECT * FROM sdk_releases WHERE 1=1" params = [] @@ -474,7 +473,7 @@ class DeveloperEcosystemManager: rows = conn.execute(query, params).fetchall() return [self._row_to_sdk_release(row) for row in rows] - def update_sdk_release(self, sdk_id: str, **kwargs) -> Optional[SDKRelease]: + def update_sdk_release(self, sdk_id: str, **kwargs) -> SDKRelease | None: """更新 SDK 发布""" allowed_fields = [ "name", @@ -499,7 +498,7 @@ class DeveloperEcosystemManager: return self.get_sdk_release(sdk_id) - def publish_sdk_release(self, sdk_id: str) -> Optional[SDKRelease]: + def publish_sdk_release(self, sdk_id: str) -> SDKRelease | None: """发布 SDK""" now = datetime.now().isoformat() @@ -529,7 +528,7 @@ class DeveloperEcosystemManager: ) conn.commit() - def get_sdk_versions(self, sdk_id: str) -> List[SDKVersion]: + def get_sdk_versions(self, sdk_id: str) -> list[SDKVersion]: """获取 SDK 版本历史""" with self._get_db() as conn: rows = conn.execute( @@ -588,16 +587,16 @@ class DeveloperEcosystemManager: name: str, description: str, category: TemplateCategory, - subcategory: Optional[str], - tags: List[str], + subcategory: str | None, + tags: list[str], author_id: str, author_name: str, price: float = 0.0, currency: str = "CNY", - preview_image_url: Optional[str] = None, - demo_url: Optional[str] = None, - documentation_url: Optional[str] = None, - download_url: Optional[str] = None, + preview_image_url: str | None = None, + demo_url: str | None = None, + documentation_url: str | None = None, + download_url: str | None = None, version: str = "1.0.0", min_platform_version: str = "1.0.0", file_size: int = 0, @@ -679,7 +678,7 @@ class DeveloperEcosystemManager: return template - def get_template(self, template_id: str) -> Optional[TemplateMarketItem]: + def get_template(self, template_id: str) -> TemplateMarketItem | None: """获取模板详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM template_market WHERE id = ?", (template_id,)).fetchone() @@ -690,14 +689,14 @@ class DeveloperEcosystemManager: def list_templates( self, - category: Optional[TemplateCategory] = None, - status: Optional[TemplateStatus] = None, - search: Optional[str] = None, - author_id: Optional[str] = None, - min_price: Optional[float] = None, - max_price: Optional[float] = None, + category: TemplateCategory | None = None, + status: TemplateStatus | None = None, + search: str | None = None, + author_id: str | None = None, + min_price: float | None = None, + max_price: float | None = None, sort_by: str = "created_at", - ) -> List[TemplateMarketItem]: + ) -> list[TemplateMarketItem]: """列出模板""" query = "SELECT * FROM template_market WHERE 1=1" params = [] @@ -735,7 +734,7 @@ class DeveloperEcosystemManager: rows = conn.execute(query, params).fetchall() return [self._row_to_template(row) for row in rows] - def approve_template(self, template_id: str, reviewed_by: str) -> Optional[TemplateMarketItem]: + def approve_template(self, template_id: str, reviewed_by: str) -> TemplateMarketItem | None: """审核通过模板""" now = datetime.now().isoformat() @@ -752,7 +751,7 @@ class DeveloperEcosystemManager: return self.get_template(template_id) - def publish_template(self, template_id: str) -> Optional[TemplateMarketItem]: + def publish_template(self, template_id: str) -> TemplateMarketItem | None: """发布模板""" now = datetime.now().isoformat() @@ -769,7 +768,7 @@ class DeveloperEcosystemManager: return self.get_template(template_id) - def reject_template(self, template_id: str, reason: str) -> Optional[TemplateMarketItem]: + def reject_template(self, template_id: str, reason: str) -> TemplateMarketItem | None: """拒绝模板""" now = datetime.now().isoformat() @@ -874,7 +873,7 @@ class DeveloperEcosystemManager: (round(row["avg_rating"], 2) if row["avg_rating"] else 0, row["count"], row["count"], template_id), ) - def get_template_reviews(self, template_id: str, limit: int = 50) -> List[TemplateReview]: + def get_template_reviews(self, template_id: str, limit: int = 50) -> list[TemplateReview]: """获取模板评价""" with self._get_db() as conn: rows = conn.execute( @@ -893,19 +892,19 @@ class DeveloperEcosystemManager: name: str, description: str, category: PluginCategory, - tags: List[str], + tags: list[str], author_id: str, author_name: str, price: float = 0.0, currency: str = "CNY", pricing_model: str = "free", - preview_image_url: Optional[str] = None, - demo_url: Optional[str] = None, - documentation_url: Optional[str] = None, - repository_url: Optional[str] = None, - download_url: Optional[str] = None, - webhook_url: Optional[str] = None, - permissions: List[str] = None, + preview_image_url: str | None = None, + demo_url: str | None = None, + documentation_url: str | None = None, + repository_url: str | None = None, + download_url: str | None = None, + webhook_url: str | None = None, + permissions: list[str] = None, version: str = "1.0.0", min_platform_version: str = "1.0.0", file_size: int = 0, @@ -1003,7 +1002,7 @@ class DeveloperEcosystemManager: return plugin - def get_plugin(self, plugin_id: str) -> Optional[PluginMarketItem]: + def get_plugin(self, plugin_id: str) -> PluginMarketItem | None: """获取插件详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM plugin_market WHERE id = ?", (plugin_id,)).fetchone() @@ -1014,12 +1013,12 @@ class DeveloperEcosystemManager: def list_plugins( self, - category: Optional[PluginCategory] = None, - status: Optional[PluginStatus] = None, - search: Optional[str] = None, - author_id: Optional[str] = None, + category: PluginCategory | None = None, + status: PluginStatus | None = None, + search: str | None = None, + author_id: str | None = None, sort_by: str = "created_at", - ) -> List[PluginMarketItem]: + ) -> list[PluginMarketItem]: """列出插件""" query = "SELECT * FROM plugin_market WHERE 1=1" params = [] @@ -1051,7 +1050,7 @@ class DeveloperEcosystemManager: def review_plugin( self, plugin_id: str, reviewed_by: str, status: PluginStatus, notes: str = "" - ) -> Optional[PluginMarketItem]: + ) -> PluginMarketItem | None: """审核插件""" now = datetime.now().isoformat() @@ -1068,7 +1067,7 @@ class DeveloperEcosystemManager: return self.get_plugin(plugin_id) - def publish_plugin(self, plugin_id: str) -> Optional[PluginMarketItem]: + def publish_plugin(self, plugin_id: str) -> PluginMarketItem | None: """发布插件""" now = datetime.now().isoformat() @@ -1182,7 +1181,7 @@ class DeveloperEcosystemManager: (round(row["avg_rating"], 2) if row["avg_rating"] else 0, row["count"], row["count"], plugin_id), ) - def get_plugin_reviews(self, plugin_id: str, limit: int = 50) -> List[PluginReview]: + def get_plugin_reviews(self, plugin_id: str, limit: int = 50) -> list[PluginReview]: """获取插件评价""" with self._get_db() as conn: rows = conn.execute( @@ -1268,8 +1267,8 @@ class DeveloperEcosystemManager: return revenue def get_developer_revenues( - self, developer_id: str, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None - ) -> List[DeveloperRevenue]: + self, developer_id: str, start_date: datetime | None = None, end_date: datetime | None = None + ) -> list[DeveloperRevenue]: """获取开发者收益记录""" query = "SELECT * FROM developer_revenues WHERE developer_id = ?" params = [developer_id] @@ -1287,7 +1286,7 @@ class DeveloperEcosystemManager: rows = conn.execute(query, params).fetchall() return [self._row_to_developer_revenue(row) for row in rows] - def get_developer_revenue_summary(self, developer_id: str) -> Dict: + def get_developer_revenue_summary(self, developer_id: str) -> dict: """获取开发者收益汇总""" with self._get_db() as conn: row = conn.execute( @@ -1318,10 +1317,10 @@ class DeveloperEcosystemManager: user_id: str, display_name: str, email: str, - bio: Optional[str] = None, - website: Optional[str] = None, - github_url: Optional[str] = None, - avatar_url: Optional[str] = None, + bio: str | None = None, + website: str | None = None, + github_url: str | None = None, + avatar_url: str | None = None, ) -> DeveloperProfile: """创建开发者档案""" profile_id = f"dev_{uuid.uuid4().hex[:16]}" @@ -1382,7 +1381,7 @@ class DeveloperEcosystemManager: return profile - def get_developer_profile(self, developer_id: str) -> Optional[DeveloperProfile]: + def get_developer_profile(self, developer_id: str) -> DeveloperProfile | None: """获取开发者档案""" with self._get_db() as conn: row = conn.execute("SELECT * FROM developer_profiles WHERE id = ?", (developer_id,)).fetchone() @@ -1391,7 +1390,7 @@ class DeveloperEcosystemManager: return self._row_to_developer_profile(row) return None - def get_developer_profile_by_user(self, user_id: str) -> Optional[DeveloperProfile]: + def get_developer_profile_by_user(self, user_id: str) -> DeveloperProfile | None: """通过用户 ID 获取开发者档案""" with self._get_db() as conn: row = conn.execute("SELECT * FROM developer_profiles WHERE user_id = ?", (user_id,)).fetchone() @@ -1400,7 +1399,7 @@ class DeveloperEcosystemManager: return self._row_to_developer_profile(row) return None - def verify_developer(self, developer_id: str, status: DeveloperStatus) -> Optional[DeveloperProfile]: + def verify_developer(self, developer_id: str, status: DeveloperStatus) -> DeveloperProfile | None: """验证开发者""" now = datetime.now().isoformat() @@ -1473,11 +1472,11 @@ class DeveloperEcosystemManager: category: str, code: str, explanation: str, - tags: List[str], + tags: list[str], author_id: str, author_name: str, - sdk_id: Optional[str] = None, - api_endpoints: List[str] = None, + sdk_id: str | None = None, + api_endpoints: list[str] = None, ) -> CodeExample: """创建代码示例""" example_id = f"ex_{uuid.uuid4().hex[:16]}" @@ -1536,7 +1535,7 @@ class DeveloperEcosystemManager: return example - def get_code_example(self, example_id: str) -> Optional[CodeExample]: + def get_code_example(self, example_id: str) -> CodeExample | None: """获取代码示例""" with self._get_db() as conn: row = conn.execute("SELECT * FROM code_examples WHERE id = ?", (example_id,)).fetchone() @@ -1547,11 +1546,11 @@ class DeveloperEcosystemManager: def list_code_examples( self, - language: Optional[str] = None, - category: Optional[str] = None, - sdk_id: Optional[str] = None, - search: Optional[str] = None, - ) -> List[CodeExample]: + language: str | None = None, + category: str | None = None, + sdk_id: str | None = None, + search: str | None = None, + ) -> list[CodeExample]: """列出代码示例""" query = "SELECT * FROM code_examples WHERE 1=1" params = [] @@ -1650,7 +1649,7 @@ class DeveloperEcosystemManager: return doc - def get_api_documentation(self, doc_id: str) -> Optional[APIDocumentation]: + def get_api_documentation(self, doc_id: str) -> APIDocumentation | None: """获取 API 文档""" with self._get_db() as conn: row = conn.execute("SELECT * FROM api_documentation WHERE id = ?", (doc_id,)).fetchone() @@ -1659,7 +1658,7 @@ class DeveloperEcosystemManager: return self._row_to_api_documentation(row) return None - def get_latest_api_documentation(self) -> Optional[APIDocumentation]: + def get_latest_api_documentation(self) -> APIDocumentation | None: """获取最新 API 文档""" with self._get_db() as conn: row = conn.execute("SELECT * FROM api_documentation ORDER BY generated_at DESC LIMIT 1").fetchone() @@ -1675,16 +1674,16 @@ class DeveloperEcosystemManager: name: str, description: str, theme: str = "default", - custom_css: Optional[str] = None, - custom_js: Optional[str] = None, - logo_url: Optional[str] = None, - favicon_url: Optional[str] = None, + custom_css: str | None = None, + custom_js: str | None = None, + logo_url: str | None = None, + favicon_url: str | None = None, primary_color: str = "#1890ff", secondary_color: str = "#52c41a", support_email: str = "support@insightflow.io", - support_url: Optional[str] = None, - github_url: Optional[str] = None, - discord_url: Optional[str] = None, + support_url: str | None = None, + github_url: str | None = None, + discord_url: str | None = None, api_base_url: str = "https://api.insightflow.io", ) -> DeveloperPortalConfig: """创建开发者门户配置""" @@ -1746,7 +1745,7 @@ class DeveloperEcosystemManager: return config - def get_portal_config(self, config_id: str) -> Optional[DeveloperPortalConfig]: + def get_portal_config(self, config_id: str) -> DeveloperPortalConfig | None: """获取开发者门户配置""" with self._get_db() as conn: row = conn.execute("SELECT * FROM developer_portal_configs WHERE id = ?", (config_id,)).fetchone() @@ -1755,7 +1754,7 @@ class DeveloperEcosystemManager: return self._row_to_portal_config(row) return None - def get_active_portal_config(self) -> Optional[DeveloperPortalConfig]: + def get_active_portal_config(self) -> DeveloperPortalConfig | None: """获取活跃的开发者门户配置""" with self._get_db() as conn: row = conn.execute("SELECT * FROM developer_portal_configs WHERE is_active = 1 LIMIT 1").fetchone() diff --git a/backend/document_processor.py b/backend/document_processor.py index ecf923d..a705961 100644 --- a/backend/document_processor.py +++ b/backend/document_processor.py @@ -4,9 +4,8 @@ Document Processor - Phase 3 支持 PDF 和 DOCX 文档导入 """ -import os import io -from typing import Dict +import os class DocumentProcessor: @@ -21,7 +20,7 @@ class DocumentProcessor: ".md": self._extract_txt, } - def process(self, content: bytes, filename: str) -> Dict[str, str]: + def process(self, content: bytes, filename: str) -> dict[str, str]: """ 处理文档并提取文本 diff --git a/backend/enterprise_manager.py b/backend/enterprise_manager.py index 3745125..6d86c1b 100644 --- a/backend/enterprise_manager.py +++ b/backend/enterprise_manager.py @@ -10,19 +10,19 @@ InsightFlow Phase 8 - 企业级功能管理模块 作者: InsightFlow Team """ -import sqlite3 import json -import uuid -from datetime import datetime, timedelta -from typing import Optional, List, Dict, Any, Tuple -from dataclasses import dataclass -from enum import Enum import logging +import sqlite3 +import uuid +from dataclasses import dataclass +from datetime import datetime, timedelta +from enum import StrEnum +from typing import Any logger = logging.getLogger(__name__) -class SSOProvider(str, Enum): +class SSOProvider(StrEnum): """SSO 提供商类型""" WECHAT_WORK = "wechat_work" # 企业微信 @@ -34,7 +34,7 @@ class SSOProvider(str, Enum): CUSTOM_SAML = "custom_saml" # 自定义 SAML -class SSOStatus(str, Enum): +class SSOStatus(StrEnum): """SSO 配置状态""" DISABLED = "disabled" # 未启用 @@ -43,7 +43,7 @@ class SSOStatus(str, Enum): ERROR = "error" # 配置错误 -class SCIMSyncStatus(str, Enum): +class SCIMSyncStatus(StrEnum): """SCIM 同步状态""" IDLE = "idle" # 空闲 @@ -52,7 +52,7 @@ class SCIMSyncStatus(str, Enum): FAILED = "failed" # 同步失败 -class AuditLogExportFormat(str, Enum): +class AuditLogExportFormat(StrEnum): """审计日志导出格式""" JSON = "json" @@ -61,7 +61,7 @@ class AuditLogExportFormat(str, Enum): XLSX = "xlsx" -class DataRetentionAction(str, Enum): +class DataRetentionAction(StrEnum): """数据保留策略动作""" ARCHIVE = "archive" # 归档 @@ -69,7 +69,7 @@ class DataRetentionAction(str, Enum): ANONYMIZE = "anonymize" # 匿名化 -class ComplianceStandard(str, Enum): +class ComplianceStandard(StrEnum): """合规标准""" SOC2 = "soc2" @@ -87,29 +87,29 @@ class SSOConfig: tenant_id: str provider: str # SSO 提供商 status: str # 状态 - entity_id: Optional[str] # SAML Entity ID - sso_url: Optional[str] # SAML SSO URL - slo_url: Optional[str] # SAML SLO URL - certificate: Optional[str] # SAML 证书 (X.509) - metadata_url: Optional[str] # SAML 元数据 URL - metadata_xml: Optional[str] # SAML 元数据 XML + entity_id: str | None # SAML Entity ID + sso_url: str | None # SAML SSO URL + slo_url: str | None # SAML SLO URL + certificate: str | None # SAML 证书 (X.509) + metadata_url: str | None # SAML 元数据 URL + metadata_xml: str | None # SAML 元数据 XML # OAuth/OIDC 配置 - client_id: Optional[str] - client_secret: Optional[str] - authorization_url: Optional[str] - token_url: Optional[str] - userinfo_url: Optional[str] - scopes: List[str] + client_id: str | None + client_secret: str | None + authorization_url: str | None + token_url: str | None + userinfo_url: str | None + scopes: list[str] # 属性映射 - attribute_mapping: Dict[str, str] # 如 {"email": "user.mail", "name": "user.name"} + attribute_mapping: dict[str, str] # 如 {"email": "user.mail", "name": "user.name"} # 其他配置 auto_provision: bool # 自动创建用户 default_role: str # 默认角色 - domain_restriction: List[str] # 允许的邮箱域名 + domain_restriction: list[str] # 允许的邮箱域名 created_at: datetime updated_at: datetime - last_tested_at: Optional[datetime] - last_error: Optional[str] + last_tested_at: datetime | None + last_error: str | None @dataclass @@ -125,14 +125,14 @@ class SCIMConfig: scim_token: str # SCIM 访问令牌 # 同步配置 sync_interval_minutes: int # 同步间隔(分钟) - last_sync_at: Optional[datetime] - last_sync_status: Optional[str] - last_sync_error: Optional[str] + last_sync_at: datetime | None + last_sync_status: str | None + last_sync_error: str | None last_sync_users_count: int # 属性映射 - attribute_mapping: Dict[str, str] + attribute_mapping: dict[str, str] # 同步规则 - sync_rules: Dict[str, Any] # 过滤规则、转换规则等 + sync_rules: dict[str, Any] # 过滤规则、转换规则等 created_at: datetime updated_at: datetime @@ -146,12 +146,12 @@ class SCIMUser: external_id: str # 外部系统 ID user_name: str email: str - display_name: Optional[str] - given_name: Optional[str] - family_name: Optional[str] + display_name: str | None + given_name: str | None + family_name: str | None active: bool - groups: List[str] - raw_data: Dict[str, Any] # 原始 SCIM 数据 + groups: list[str] + raw_data: dict[str, Any] # 原始 SCIM 数据 synced_at: datetime created_at: datetime updated_at: datetime @@ -166,20 +166,20 @@ class AuditLogExport: export_format: str start_date: datetime end_date: datetime - filters: Dict[str, Any] # 过滤条件 - compliance_standard: Optional[str] + filters: dict[str, Any] # 过滤条件 + compliance_standard: str | None status: str # pending/processing/completed/failed - file_path: Optional[str] - file_size: Optional[int] - record_count: Optional[int] - checksum: Optional[str] # 文件校验和 - downloaded_by: Optional[str] - downloaded_at: Optional[datetime] - expires_at: Optional[datetime] # 文件过期时间 + file_path: str | None + file_size: int | None + record_count: int | None + checksum: str | None # 文件校验和 + downloaded_by: str | None + downloaded_at: datetime | None + expires_at: datetime | None # 文件过期时间 created_by: str created_at: datetime - completed_at: Optional[datetime] - error_message: Optional[str] + completed_at: datetime | None + error_message: str | None @dataclass @@ -189,23 +189,23 @@ class DataRetentionPolicy: id: str tenant_id: str name: str - description: Optional[str] + description: str | None resource_type: str # project/transcript/entity/audit_log/user_data retention_days: int # 保留天数 action: str # archive/delete/anonymize # 条件 - conditions: Dict[str, Any] # 触发条件 + conditions: dict[str, Any] # 触发条件 # 执行配置 auto_execute: bool # 自动执行 - execute_at: Optional[str] # 执行时间 (cron 表达式) + execute_at: str | None # 执行时间 (cron 表达式) notify_before_days: int # 提前通知天数 # 归档配置 - archive_location: Optional[str] # 归档位置 + archive_location: str | None # 归档位置 archive_encryption: bool # 归档加密 # 状态 is_active: bool - last_executed_at: Optional[datetime] - last_execution_result: Optional[str] + last_executed_at: datetime | None + last_execution_result: str | None created_at: datetime updated_at: datetime @@ -218,13 +218,13 @@ class DataRetentionJob: policy_id: str tenant_id: str status: str # pending/running/completed/failed - started_at: Optional[datetime] - completed_at: Optional[datetime] + started_at: datetime | None + completed_at: datetime | None affected_records: int archived_records: int deleted_records: int error_count: int - details: Dict[str, Any] + details: dict[str, Any] created_at: datetime @@ -236,11 +236,11 @@ class SAMLAuthRequest: tenant_id: str sso_config_id: str request_id: str # SAML Request ID - relay_state: Optional[str] + relay_state: str | None created_at: datetime expires_at: datetime used: bool - used_at: Optional[datetime] + used_at: datetime | None @dataclass @@ -250,13 +250,13 @@ class SAMLAuthResponse: id: str request_id: str tenant_id: str - user_id: Optional[str] - email: Optional[str] - name: Optional[str] - attributes: Dict[str, Any] - session_index: Optional[str] + user_id: str | None + email: str | None + name: str | None + attributes: dict[str, Any] + session_index: str | None processed: bool - processed_at: Optional[datetime] + processed_at: datetime | None created_at: datetime @@ -548,22 +548,22 @@ class EnterpriseManager: self, tenant_id: str, provider: str, - entity_id: Optional[str] = None, - sso_url: Optional[str] = None, - slo_url: Optional[str] = None, - certificate: Optional[str] = None, - metadata_url: Optional[str] = None, - metadata_xml: Optional[str] = None, - client_id: Optional[str] = None, - client_secret: Optional[str] = None, - authorization_url: Optional[str] = None, - token_url: Optional[str] = None, - userinfo_url: Optional[str] = None, - scopes: Optional[List[str]] = None, - attribute_mapping: Optional[Dict[str, str]] = None, + entity_id: str | None = None, + sso_url: str | None = None, + slo_url: str | None = None, + certificate: str | None = None, + metadata_url: str | None = None, + metadata_xml: str | None = None, + client_id: str | None = None, + client_secret: str | None = None, + authorization_url: str | None = None, + token_url: str | None = None, + userinfo_url: str | None = None, + scopes: list[str] | None = None, + attribute_mapping: dict[str, str] | None = None, auto_provision: bool = True, default_role: str = "member", - domain_restriction: Optional[List[str]] = None, + domain_restriction: list[str] | None = None, ) -> SSOConfig: """创建 SSO 配置""" conn = self._get_connection() @@ -649,7 +649,7 @@ class EnterpriseManager: finally: conn.close() - def get_sso_config(self, config_id: str) -> Optional[SSOConfig]: + def get_sso_config(self, config_id: str) -> SSOConfig | None: """获取 SSO 配置""" conn = self._get_connection() try: @@ -664,7 +664,7 @@ class EnterpriseManager: finally: conn.close() - def get_tenant_sso_config(self, tenant_id: str, provider: Optional[str] = None) -> Optional[SSOConfig]: + def get_tenant_sso_config(self, tenant_id: str, provider: str | None = None) -> SSOConfig | None: """获取租户的 SSO 配置""" conn = self._get_connection() try: @@ -698,7 +698,7 @@ class EnterpriseManager: finally: conn.close() - def update_sso_config(self, config_id: str, **kwargs) -> Optional[SSOConfig]: + def update_sso_config(self, config_id: str, **kwargs) -> SSOConfig | None: """更新 SSO 配置""" conn = self._get_connection() try: @@ -772,7 +772,7 @@ class EnterpriseManager: finally: conn.close() - def list_sso_configs(self, tenant_id: str) -> List[SSOConfig]: + def list_sso_configs(self, tenant_id: str) -> list[SSOConfig]: """列出租户的所有 SSO 配置""" conn = self._get_connection() try: @@ -835,7 +835,7 @@ class EnterpriseManager: return metadata def create_saml_auth_request( - self, tenant_id: str, config_id: str, relay_state: Optional[str] = None + self, tenant_id: str, config_id: str, relay_state: str | None = None ) -> SAMLAuthRequest: """创建 SAML 认证请求""" conn = self._get_connection() @@ -881,7 +881,7 @@ class EnterpriseManager: finally: conn.close() - def get_saml_auth_request(self, request_id: str) -> Optional[SAMLAuthRequest]: + def get_saml_auth_request(self, request_id: str) -> SAMLAuthRequest | None: """获取 SAML 认证请求""" conn = self._get_connection() try: @@ -901,7 +901,7 @@ class EnterpriseManager: finally: conn.close() - def process_saml_response(self, request_id: str, saml_response: str) -> Optional[SAMLAuthResponse]: + def process_saml_response(self, request_id: str, saml_response: str) -> SAMLAuthResponse | None: """处理 SAML 响应""" # 这里应该实现实际的 SAML 响应解析 # 简化实现:假设响应已经验证并解析 @@ -954,7 +954,7 @@ class EnterpriseManager: finally: conn.close() - def _parse_saml_response(self, saml_response: str) -> Dict[str, Any]: + def _parse_saml_response(self, saml_response: str) -> dict[str, Any]: """解析 SAML 响应(简化实现)""" # 实际应该使用 python-saml 库解析 # 这里返回模拟数据 @@ -974,8 +974,8 @@ class EnterpriseManager: scim_base_url: str, scim_token: str, sync_interval_minutes: int = 60, - attribute_mapping: Optional[Dict[str, str]] = None, - sync_rules: Optional[Dict[str, Any]] = None, + attribute_mapping: dict[str, str] | None = None, + sync_rules: dict[str, Any] | None = None, ) -> SCIMConfig: """创建 SCIM 配置""" conn = self._get_connection() @@ -1035,7 +1035,7 @@ class EnterpriseManager: finally: conn.close() - def get_scim_config(self, config_id: str) -> Optional[SCIMConfig]: + def get_scim_config(self, config_id: str) -> SCIMConfig | None: """获取 SCIM 配置""" conn = self._get_connection() try: @@ -1050,7 +1050,7 @@ class EnterpriseManager: finally: conn.close() - def get_tenant_scim_config(self, tenant_id: str) -> Optional[SCIMConfig]: + def get_tenant_scim_config(self, tenant_id: str) -> SCIMConfig | None: """获取租户的 SCIM 配置""" conn = self._get_connection() try: @@ -1071,7 +1071,7 @@ class EnterpriseManager: finally: conn.close() - def update_scim_config(self, config_id: str, **kwargs) -> Optional[SCIMConfig]: + def update_scim_config(self, config_id: str, **kwargs) -> SCIMConfig | None: """更新 SCIM 配置""" conn = self._get_connection() try: @@ -1121,7 +1121,7 @@ class EnterpriseManager: finally: conn.close() - def sync_scim_users(self, config_id: str) -> Dict[str, Any]: + def sync_scim_users(self, config_id: str) -> dict[str, Any]: """执行 SCIM 用户同步""" config = self.get_scim_config(config_id) if not config: @@ -1184,13 +1184,13 @@ class EnterpriseManager: finally: conn.close() - def _fetch_scim_users(self, config: SCIMConfig) -> List[Dict[str, Any]]: + def _fetch_scim_users(self, config: SCIMConfig) -> list[dict[str, Any]]: """从 SCIM 服务端获取用户(模拟实现)""" # 实际应该使用 HTTP 请求获取 # GET {scim_base_url}/Users return [] - def _upsert_scim_user(self, conn: sqlite3.Connection, tenant_id: str, user_data: Dict[str, Any]): + def _upsert_scim_user(self, conn: sqlite3.Connection, tenant_id: str, user_data: dict[str, Any]): """插入或更新 SCIM 用户""" cursor = conn.cursor() @@ -1238,7 +1238,7 @@ class EnterpriseManager: ), ) - def list_scim_users(self, tenant_id: str, active_only: bool = True) -> List[SCIMUser]: + def list_scim_users(self, tenant_id: str, active_only: bool = True) -> list[SCIMUser]: """列出 SCIM 用户""" conn = self._get_connection() try: @@ -1269,8 +1269,8 @@ class EnterpriseManager: start_date: datetime, end_date: datetime, created_by: str, - filters: Optional[Dict[str, Any]] = None, - compliance_standard: Optional[str] = None, + filters: dict[str, Any] | None = None, + compliance_standard: str | None = None, ) -> AuditLogExport: """创建审计日志导出任务""" conn = self._get_connection() @@ -1337,7 +1337,7 @@ class EnterpriseManager: finally: conn.close() - def process_audit_export(self, export_id: str, db_manager=None) -> Optional[AuditLogExport]: + def process_audit_export(self, export_id: str, db_manager=None) -> AuditLogExport | None: """处理审计日志导出任务""" export = self.get_audit_export(export_id) if not export: @@ -1401,8 +1401,8 @@ class EnterpriseManager: conn.close() def _fetch_audit_logs( - self, tenant_id: str, start_date: datetime, end_date: datetime, filters: Dict[str, Any], db_manager=None - ) -> List[Dict[str, Any]]: + self, tenant_id: str, start_date: datetime, end_date: datetime, filters: dict[str, Any], db_manager=None + ) -> list[dict[str, Any]]: """获取审计日志数据""" if db_manager is None: return [] @@ -1411,7 +1411,7 @@ class EnterpriseManager: # 这里简化实现 return [] - def _apply_compliance_filter(self, logs: List[Dict[str, Any]], standard: str) -> List[Dict[str, Any]]: + def _apply_compliance_filter(self, logs: list[dict[str, Any]], standard: str) -> list[dict[str, Any]]: """应用合规标准字段过滤""" fields = self.COMPLIANCE_FIELDS.get(ComplianceStandard(standard), []) @@ -1425,10 +1425,10 @@ class EnterpriseManager: return filtered_logs - def _generate_export_file(self, export_id: str, logs: List[Dict[str, Any]], format: str) -> Tuple[str, int, str]: + def _generate_export_file(self, export_id: str, logs: list[dict[str, Any]], format: str) -> tuple[str, int, str]: """生成导出文件""" - import os import hashlib + import os export_dir = "/tmp/insightflow/exports" os.makedirs(export_dir, exist_ok=True) @@ -1461,7 +1461,7 @@ class EnterpriseManager: return file_path, file_size, checksum - def get_audit_export(self, export_id: str) -> Optional[AuditLogExport]: + def get_audit_export(self, export_id: str) -> AuditLogExport | None: """获取审计日志导出记录""" conn = self._get_connection() try: @@ -1476,7 +1476,7 @@ class EnterpriseManager: finally: conn.close() - def list_audit_exports(self, tenant_id: str, limit: int = 100) -> List[AuditLogExport]: + def list_audit_exports(self, tenant_id: str, limit: int = 100) -> list[AuditLogExport]: """列出审计日志导出记录""" conn = self._get_connection() try: @@ -1524,12 +1524,12 @@ class EnterpriseManager: resource_type: str, retention_days: int, action: str, - description: Optional[str] = None, - conditions: Optional[Dict[str, Any]] = None, + description: str | None = None, + conditions: dict[str, Any] | None = None, auto_execute: bool = False, - execute_at: Optional[str] = None, + execute_at: str | None = None, notify_before_days: int = 7, - archive_location: Optional[str] = None, + archive_location: str | None = None, archive_encryption: bool = True, ) -> DataRetentionPolicy: """创建数据保留策略""" @@ -1599,7 +1599,7 @@ class EnterpriseManager: finally: conn.close() - def get_retention_policy(self, policy_id: str) -> Optional[DataRetentionPolicy]: + def get_retention_policy(self, policy_id: str) -> DataRetentionPolicy | None: """获取数据保留策略""" conn = self._get_connection() try: @@ -1614,7 +1614,7 @@ class EnterpriseManager: finally: conn.close() - def list_retention_policies(self, tenant_id: str, resource_type: Optional[str] = None) -> List[DataRetentionPolicy]: + def list_retention_policies(self, tenant_id: str, resource_type: str | None = None) -> list[DataRetentionPolicy]: """列出数据保留策略""" conn = self._get_connection() try: @@ -1637,7 +1637,7 @@ class EnterpriseManager: finally: conn.close() - def update_retention_policy(self, policy_id: str, **kwargs) -> Optional[DataRetentionPolicy]: + def update_retention_policy(self, policy_id: str, **kwargs) -> DataRetentionPolicy | None: """更新数据保留策略""" conn = self._get_connection() try: @@ -1818,7 +1818,7 @@ class EnterpriseManager: def _retain_audit_logs( self, conn: sqlite3.Connection, policy: DataRetentionPolicy, cutoff_date: datetime - ) -> Dict[str, int]: + ) -> dict[str, int]: """保留审计日志""" cursor = conn.cursor() @@ -1851,19 +1851,19 @@ class EnterpriseManager: def _retain_projects( self, conn: sqlite3.Connection, policy: DataRetentionPolicy, cutoff_date: datetime - ) -> Dict[str, int]: + ) -> dict[str, int]: """保留项目数据""" # 简化实现 return {"affected": 0, "archived": 0, "deleted": 0, "errors": 0} def _retain_transcripts( self, conn: sqlite3.Connection, policy: DataRetentionPolicy, cutoff_date: datetime - ) -> Dict[str, int]: + ) -> dict[str, int]: """保留转录数据""" # 简化实现 return {"affected": 0, "archived": 0, "deleted": 0, "errors": 0} - def get_retention_job(self, job_id: str) -> Optional[DataRetentionJob]: + def get_retention_job(self, job_id: str) -> DataRetentionJob | None: """获取数据保留任务""" conn = self._get_connection() try: @@ -1878,7 +1878,7 @@ class EnterpriseManager: finally: conn.close() - def list_retention_jobs(self, policy_id: str, limit: int = 100) -> List[DataRetentionJob]: + def list_retention_jobs(self, policy_id: str, limit: int = 100) -> list[DataRetentionJob]: """列出数据保留任务""" conn = self._get_connection() try: diff --git a/backend/entity_aligner.py b/backend/entity_aligner.py index 8691d38..80639a8 100644 --- a/backend/entity_aligner.py +++ b/backend/entity_aligner.py @@ -4,12 +4,12 @@ Entity Aligner - Phase 3 使用 embedding 进行实体对齐 """ -import os import json +import os +from dataclasses import dataclass + import httpx import numpy as np -from typing import List, Optional, Dict -from dataclasses import dataclass # API Keys KIMI_API_KEY = os.getenv("KIMI_API_KEY", "") @@ -21,7 +21,7 @@ class EntityEmbedding: entity_id: str name: str definition: str - embedding: List[float] + embedding: list[float] class EntityAligner: @@ -29,9 +29,9 @@ class EntityAligner: def __init__(self, similarity_threshold: float = 0.85): self.similarity_threshold = similarity_threshold - self.embedding_cache: Dict[str, List[float]] = {} + self.embedding_cache: dict[str, list[float]] = {} - def get_embedding(self, text: str) -> Optional[List[float]]: + def get_embedding(self, text: str) -> list[float] | None: """ 使用 Kimi API 获取文本的 embedding @@ -67,7 +67,7 @@ class EntityAligner: print(f"Embedding API failed: {e}") return None - def compute_similarity(self, embedding1: List[float], embedding2: List[float]) -> float: + def compute_similarity(self, embedding1: list[float], embedding2: list[float]) -> float: """ 计算两个 embedding 的余弦相似度 @@ -111,9 +111,9 @@ class EntityAligner: project_id: str, name: str, definition: str = "", - exclude_id: Optional[str] = None, - threshold: Optional[float] = None, - ) -> Optional[object]: + exclude_id: str | None = None, + threshold: float | None = None, + ) -> object | None: """ 查找相似的实体 @@ -175,8 +175,8 @@ class EntityAligner: return best_match def _fallback_similarity_match( - self, entities: List[object], name: str, exclude_id: Optional[str] = None - ) -> Optional[object]: + self, entities: list[object], name: str, exclude_id: str | None = None + ) -> object | None: """ 回退到简单的相似度匹配(不使用 embedding) @@ -209,8 +209,8 @@ class EntityAligner: return None def batch_align_entities( - self, project_id: str, new_entities: List[Dict], threshold: Optional[float] = None - ) -> List[Dict]: + self, project_id: str, new_entities: list[dict], threshold: float | None = None + ) -> list[dict]: """ 批量对齐实体 @@ -257,7 +257,7 @@ class EntityAligner: return results - def suggest_entity_aliases(self, entity_name: str, entity_definition: str = "") -> List[str]: + def suggest_entity_aliases(self, entity_name: str, entity_definition: str = "") -> list[str]: """ 使用 LLM 建议实体的别名 diff --git a/backend/export_manager.py b/backend/export_manager.py index 1eac5b6..da174cf 100644 --- a/backend/export_manager.py +++ b/backend/export_manager.py @@ -3,12 +3,12 @@ InsightFlow Export Module - Phase 5 支持导出知识图谱、项目报告、实体数据和转录文本 """ +import base64 import io import json -import base64 -from datetime import datetime -from typing import List, Dict, Any from dataclasses import dataclass +from datetime import datetime +from typing import Any try: import pandas as pd @@ -20,9 +20,16 @@ except ImportError: try: from reportlab.lib import colors from reportlab.lib.pagesizes import A4 - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet from reportlab.lib.units import inch - from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak + from reportlab.platypus import ( + PageBreak, + Paragraph, + SimpleDocTemplate, + Spacer, + Table, + TableStyle, + ) REPORTLAB_AVAILABLE = True except ImportError: @@ -35,9 +42,9 @@ class ExportEntity: name: str type: str definition: str - aliases: List[str] + aliases: list[str] mention_count: int - attributes: Dict[str, Any] + attributes: dict[str, Any] @dataclass @@ -56,8 +63,8 @@ class ExportTranscript: name: str type: str # audio/document content: str - segments: List[Dict] - entity_mentions: List[Dict] + segments: list[dict] + entity_mentions: list[dict] class ExportManager: @@ -67,7 +74,7 @@ class ExportManager: self.db = db_manager def export_knowledge_graph_svg( - self, project_id: str, entities: List[ExportEntity], relations: List[ExportRelation] + self, project_id: str, entities: list[ExportEntity], relations: list[ExportRelation] ) -> str: """ 导出知识图谱为 SVG 格式 @@ -207,7 +214,7 @@ class ExportManager: return "\n".join(svg_parts) def export_knowledge_graph_png( - self, project_id: str, entities: List[ExportEntity], relations: List[ExportRelation] + self, project_id: str, entities: list[ExportEntity], relations: list[ExportRelation] ) -> bytes: """ 导出知识图谱为 PNG 格式 @@ -226,7 +233,7 @@ class ExportManager: svg_content = self.export_knowledge_graph_svg(project_id, entities, relations) return base64.b64encode(svg_content.encode("utf-8")) - def export_entities_excel(self, entities: List[ExportEntity]) -> bytes: + def export_entities_excel(self, entities: list[ExportEntity]) -> bytes: """ 导出实体数据为 Excel 格式 @@ -275,7 +282,7 @@ class ExportManager: return output.getvalue() - def export_entities_csv(self, entities: List[ExportEntity]) -> str: + def export_entities_csv(self, entities: list[ExportEntity]) -> str: """ 导出实体数据为 CSV 格式 @@ -306,7 +313,7 @@ class ExportManager: return output.getvalue() - def export_relations_csv(self, relations: List[ExportRelation]) -> str: + def export_relations_csv(self, relations: list[ExportRelation]) -> str: """ 导出关系数据为 CSV 格式 @@ -324,7 +331,7 @@ class ExportManager: return output.getvalue() - def export_transcript_markdown(self, transcript: ExportTranscript, entities_map: Dict[str, ExportEntity]) -> str: + def export_transcript_markdown(self, transcript: ExportTranscript, entities_map: dict[str, ExportEntity]) -> str: """ 导出转录文本为 Markdown 格式 @@ -387,9 +394,9 @@ class ExportManager: self, project_id: str, project_name: str, - entities: List[ExportEntity], - relations: List[ExportRelation], - transcripts: List[ExportTranscript], + entities: list[ExportEntity], + relations: list[ExportRelation], + transcripts: list[ExportTranscript], summary: str = "", ) -> bytes: """ @@ -529,9 +536,9 @@ class ExportManager: self, project_id: str, project_name: str, - entities: List[ExportEntity], - relations: List[ExportRelation], - transcripts: List[ExportTranscript], + entities: list[ExportEntity], + relations: list[ExportRelation], + transcripts: list[ExportTranscript], ) -> str: """ 导出完整项目数据为 JSON 格式 diff --git a/backend/growth_manager.py b/backend/growth_manager.py index eed7fdc..a630e34 100644 --- a/backend/growth_manager.py +++ b/backend/growth_manager.py @@ -10,25 +10,26 @@ InsightFlow Growth Manager - Phase 8 Task 5 作者: InsightFlow Team """ -import os -import json -import sqlite3 -import httpx import asyncio +import hashlib +import json +import os import random -from typing import List, Dict, Optional, Any, Tuple +import re +import sqlite3 +import uuid from dataclasses import dataclass from datetime import datetime, timedelta -from enum import Enum -import hashlib -import uuid -import re +from enum import StrEnum +from typing import Any + +import httpx # Database path DB_PATH = os.path.join(os.path.dirname(__file__), "insightflow.db") -class EventType(str, Enum): +class EventType(StrEnum): """事件类型""" PAGE_VIEW = "page_view" # 页面浏览 @@ -44,7 +45,7 @@ class EventType(str, Enum): REFERRAL_REWARD = "referral_reward" # 推荐奖励 -class ExperimentStatus(str, Enum): +class ExperimentStatus(StrEnum): """实验状态""" DRAFT = "draft" # 草稿 @@ -54,7 +55,7 @@ class ExperimentStatus(str, Enum): ARCHIVED = "archived" # 已归档 -class TrafficAllocationType(str, Enum): +class TrafficAllocationType(StrEnum): """流量分配类型""" RANDOM = "random" # 随机分配 @@ -62,7 +63,7 @@ class TrafficAllocationType(str, Enum): TARGETED = "targeted" # 定向分配 -class EmailTemplateType(str, Enum): +class EmailTemplateType(StrEnum): """邮件模板类型""" WELCOME = "welcome" # 欢迎邮件 @@ -74,7 +75,7 @@ class EmailTemplateType(str, Enum): NEWSLETTER = "newsletter" # 新闻通讯 -class EmailStatus(str, Enum): +class EmailStatus(StrEnum): """邮件状态""" DRAFT = "draft" # 草稿 @@ -88,7 +89,7 @@ class EmailStatus(str, Enum): FAILED = "failed" # 失败 -class WorkflowTriggerType(str, Enum): +class WorkflowTriggerType(StrEnum): """工作流触发类型""" USER_SIGNUP = "user_signup" # 用户注册 @@ -100,7 +101,7 @@ class WorkflowTriggerType(str, Enum): CUSTOM_EVENT = "custom_event" # 自定义事件 -class ReferralStatus(str, Enum): +class ReferralStatus(StrEnum): """推荐状态""" PENDING = "pending" # 待处理 @@ -118,14 +119,14 @@ class AnalyticsEvent: user_id: str event_type: EventType event_name: str - properties: Dict[str, Any] # 事件属性 + properties: dict[str, Any] # 事件属性 timestamp: datetime - session_id: Optional[str] - device_info: Dict[str, str] # 设备信息 - referrer: Optional[str] - utm_source: Optional[str] - utm_medium: Optional[str] - utm_campaign: Optional[str] + session_id: str | None + device_info: dict[str, str] # 设备信息 + referrer: str | None + utm_source: str | None + utm_medium: str | None + utm_campaign: str | None @dataclass @@ -139,8 +140,8 @@ class UserProfile: last_seen: datetime total_sessions: int total_events: int - feature_usage: Dict[str, int] # 功能使用次数 - subscription_history: List[Dict] + feature_usage: dict[str, int] # 功能使用次数 + subscription_history: list[dict] ltv: float # 生命周期价值 churn_risk_score: float # 流失风险分数 engagement_score: float # 参与度分数 @@ -156,7 +157,7 @@ class Funnel: tenant_id: str name: str description: str - steps: List[Dict] # 漏斗步骤 + steps: list[dict] # 漏斗步骤 created_at: datetime updated_at: datetime @@ -169,9 +170,9 @@ class FunnelAnalysis: period_start: datetime period_end: datetime total_users: int - step_conversions: List[Dict] # 每步转化数据 + step_conversions: list[dict] # 每步转化数据 overall_conversion: float # 总体转化率 - drop_off_points: List[Dict] # 流失点 + drop_off_points: list[dict] # 流失点 @dataclass @@ -184,14 +185,14 @@ class Experiment: description: str hypothesis: str status: ExperimentStatus - variants: List[Dict] # 实验变体 + variants: list[dict] # 实验变体 traffic_allocation: TrafficAllocationType - traffic_split: Dict[str, float] # 流量分配比例 - target_audience: Dict # 目标受众 + traffic_split: dict[str, float] # 流量分配比例 + target_audience: dict # 目标受众 primary_metric: str # 主要指标 - secondary_metrics: List[str] # 次要指标 - start_date: Optional[datetime] - end_date: Optional[datetime] + secondary_metrics: list[str] # 次要指标 + start_date: datetime | None + end_date: datetime | None min_sample_size: int # 最小样本量 confidence_level: float # 置信水平 created_at: datetime @@ -210,7 +211,7 @@ class ExperimentResult: sample_size: int mean_value: float std_dev: float - confidence_interval: Tuple[float, float] + confidence_interval: tuple[float, float] p_value: float is_significant: bool uplift: float # 提升幅度 @@ -228,11 +229,11 @@ class EmailTemplate: subject: str html_content: str text_content: str - variables: List[str] # 模板变量 - preview_text: Optional[str] + variables: list[str] # 模板变量 + preview_text: str | None from_name: str from_email: str - reply_to: Optional[str] + reply_to: str | None is_active: bool created_at: datetime updated_at: datetime @@ -254,9 +255,9 @@ class EmailCampaign: clicked_count: int bounced_count: int failed_count: int - scheduled_at: Optional[datetime] - started_at: Optional[datetime] - completed_at: Optional[datetime] + scheduled_at: datetime | None + started_at: datetime | None + completed_at: datetime | None created_at: datetime @@ -272,13 +273,13 @@ class EmailLog: template_id: str status: EmailStatus subject: str - sent_at: Optional[datetime] - delivered_at: Optional[datetime] - opened_at: Optional[datetime] - clicked_at: Optional[datetime] - ip_address: Optional[str] - user_agent: Optional[str] - error_message: Optional[str] + sent_at: datetime | None + delivered_at: datetime | None + opened_at: datetime | None + clicked_at: datetime | None + ip_address: str | None + user_agent: str | None + error_message: str | None created_at: datetime @@ -291,8 +292,8 @@ class AutomationWorkflow: name: str description: str trigger_type: WorkflowTriggerType - trigger_conditions: Dict # 触发条件 - actions: List[Dict] # 执行动作 + trigger_conditions: dict # 触发条件 + actions: list[dict] # 执行动作 is_active: bool execution_count: int created_at: datetime @@ -327,15 +328,15 @@ class Referral: program_id: str tenant_id: str referrer_id: str # 推荐人 - referee_id: Optional[str] # 被推荐人 + referee_id: str | None # 被推荐人 referral_code: str status: ReferralStatus referrer_rewarded: bool referee_rewarded: bool referrer_reward_value: float referee_reward_value: float - converted_at: Optional[datetime] - rewarded_at: Optional[datetime] + converted_at: datetime | None + rewarded_at: datetime | None expires_at: datetime created_at: datetime @@ -382,11 +383,11 @@ class GrowthManager: user_id: str, event_type: EventType, event_name: str, - properties: Dict = None, + properties: dict = None, session_id: str = None, - device_info: Dict = None, + device_info: dict = None, referrer: str = None, - utm_params: Dict = None, + utm_params: dict = None, ) -> AnalyticsEvent: """追踪事件""" event_id = f"evt_{uuid.uuid4().hex[:16]}" @@ -554,7 +555,7 @@ class GrowthManager: conn.commit() - def get_user_profile(self, tenant_id: str, user_id: str) -> Optional[UserProfile]: + def get_user_profile(self, tenant_id: str, user_id: str) -> UserProfile | None: """获取用户画像""" with self._get_db() as conn: row = conn.execute( @@ -567,7 +568,7 @@ class GrowthManager: def get_user_analytics_summary( self, tenant_id: str, start_date: datetime = None, end_date: datetime = None - ) -> Dict: + ) -> dict: """获取用户分析汇总""" with self._get_db() as conn: query = """ @@ -619,7 +620,7 @@ class GrowthManager: "event_type_distribution": {r["event_type"]: r["count"] for r in type_rows}, } - def create_funnel(self, tenant_id: str, name: str, description: str, steps: List[Dict], created_by: str) -> Funnel: + def create_funnel(self, tenant_id: str, name: str, description: str, steps: list[dict], created_by: str) -> Funnel: """创建转化漏斗""" funnel_id = f"fnl_{uuid.uuid4().hex[:16]}" now = datetime.now().isoformat() @@ -657,7 +658,7 @@ class GrowthManager: def analyze_funnel( self, funnel_id: str, period_start: datetime = None, period_end: datetime = None - ) -> Optional[FunnelAnalysis]: + ) -> FunnelAnalysis | None: """分析漏斗转化率""" with self._get_db() as conn: funnel_row = conn.execute("SELECT * FROM funnels WHERE id = ?", (funnel_id,)).fetchone() @@ -728,7 +729,7 @@ class GrowthManager: drop_off_points=drop_off_points, ) - def calculate_retention(self, tenant_id: str, cohort_date: datetime, periods: List[int] = None) -> Dict: + def calculate_retention(self, tenant_id: str, cohort_date: datetime, periods: list[int] = None) -> dict: """计算留存率""" if periods is None: periods = [1, 3, 7, 14, 30] @@ -787,12 +788,12 @@ class GrowthManager: name: str, description: str, hypothesis: str, - variants: List[Dict], + variants: list[dict], traffic_allocation: TrafficAllocationType, - traffic_split: Dict[str, float], - target_audience: Dict, + traffic_split: dict[str, float], + target_audience: dict, primary_metric: str, - secondary_metrics: List[str], + secondary_metrics: list[str], min_sample_size: int = 100, confidence_level: float = 0.95, created_by: str = None, @@ -859,7 +860,7 @@ class GrowthManager: return experiment - def get_experiment(self, experiment_id: str) -> Optional[Experiment]: + def get_experiment(self, experiment_id: str) -> Experiment | None: """获取实验详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM experiments WHERE id = ?", (experiment_id,)).fetchone() @@ -868,7 +869,7 @@ class GrowthManager: return self._row_to_experiment(row) return None - def list_experiments(self, tenant_id: str, status: ExperimentStatus = None) -> List[Experiment]: + def list_experiments(self, tenant_id: str, status: ExperimentStatus = None) -> list[Experiment]: """列出实验""" query = "SELECT * FROM experiments WHERE tenant_id = ?" params = [tenant_id] @@ -883,7 +884,7 @@ class GrowthManager: rows = conn.execute(query, params).fetchall() return [self._row_to_experiment(row) for row in rows] - def assign_variant(self, experiment_id: str, user_id: str, user_attributes: Dict = None) -> Optional[str]: + def assign_variant(self, experiment_id: str, user_id: str, user_attributes: dict = None) -> str | None: """为用户分配实验变体""" experiment = self.get_experiment(experiment_id) if not experiment or experiment.status != ExperimentStatus.RUNNING: @@ -929,7 +930,7 @@ class GrowthManager: return variant_id - def _random_allocation(self, variants: List[Dict], traffic_split: Dict[str, float]) -> str: + def _random_allocation(self, variants: list[dict], traffic_split: dict[str, float]) -> str: """随机分配""" variant_ids = [v["id"] for v in variants] weights = [traffic_split.get(v_id, 1.0 / len(variants)) for v_id in variant_ids] @@ -940,7 +941,7 @@ class GrowthManager: return random.choices(variant_ids, weights=normalized_weights, k=1)[0] def _stratified_allocation( - self, variants: List[Dict], traffic_split: Dict[str, float], user_attributes: Dict + self, variants: list[dict], traffic_split: dict[str, float], user_attributes: dict ) -> str: """分层分配(基于用户属性)""" # 简化的分层分配:根据用户 ID 哈希值分配 @@ -952,7 +953,7 @@ class GrowthManager: return self._random_allocation(variants, traffic_split) - def _targeted_allocation(self, variants: List[Dict], target_audience: Dict, user_attributes: Dict) -> Optional[str]: + def _targeted_allocation(self, variants: list[dict], target_audience: dict, user_attributes: dict) -> str | None: """定向分配(基于目标受众条件)""" # 检查用户是否符合目标受众条件 conditions = target_audience.get("conditions", []) @@ -1005,7 +1006,7 @@ class GrowthManager: ) conn.commit() - def analyze_experiment(self, experiment_id: str) -> Dict: + def analyze_experiment(self, experiment_id: str) -> dict: """分析实验结果""" experiment = self.get_experiment(experiment_id) if not experiment: @@ -1083,7 +1084,7 @@ class GrowthManager: "variant_results": results, } - def start_experiment(self, experiment_id: str) -> Optional[Experiment]: + def start_experiment(self, experiment_id: str) -> Experiment | None: """启动实验""" with self._get_db() as conn: now = datetime.now().isoformat() @@ -1099,7 +1100,7 @@ class GrowthManager: return self.get_experiment(experiment_id) - def stop_experiment(self, experiment_id: str) -> Optional[Experiment]: + def stop_experiment(self, experiment_id: str) -> Experiment | None: """停止实验""" with self._get_db() as conn: now = datetime.now().isoformat() @@ -1125,7 +1126,7 @@ class GrowthManager: subject: str, html_content: str, text_content: str = None, - variables: List[str] = None, + variables: list[str] = None, from_name: str = None, from_email: str = None, reply_to: str = None, @@ -1185,7 +1186,7 @@ class GrowthManager: return template - def get_email_template(self, template_id: str) -> Optional[EmailTemplate]: + def get_email_template(self, template_id: str) -> EmailTemplate | None: """获取邮件模板""" with self._get_db() as conn: row = conn.execute("SELECT * FROM email_templates WHERE id = ?", (template_id,)).fetchone() @@ -1194,7 +1195,7 @@ class GrowthManager: return self._row_to_email_template(row) return None - def list_email_templates(self, tenant_id: str, template_type: EmailTemplateType = None) -> List[EmailTemplate]: + def list_email_templates(self, tenant_id: str, template_type: EmailTemplateType = None) -> list[EmailTemplate]: """列出邮件模板""" query = "SELECT * FROM email_templates WHERE tenant_id = ? AND is_active = 1" params = [tenant_id] @@ -1209,7 +1210,7 @@ class GrowthManager: rows = conn.execute(query, params).fetchall() return [self._row_to_email_template(row) for row in rows] - def render_template(self, template_id: str, variables: Dict) -> Dict[str, str]: + def render_template(self, template_id: str, variables: dict) -> dict[str, str]: """渲染邮件模板""" template = self.get_email_template(template_id) if not template: @@ -1235,7 +1236,7 @@ class GrowthManager: } def create_email_campaign( - self, tenant_id: str, name: str, template_id: str, recipient_list: List[Dict], scheduled_at: datetime = None + self, tenant_id: str, name: str, template_id: str, recipient_list: list[dict], scheduled_at: datetime = None ) -> EmailCampaign: """创建邮件营销活动""" campaign_id = f"ec_{uuid.uuid4().hex[:16]}" @@ -1314,7 +1315,7 @@ class GrowthManager: return campaign - async def send_email(self, campaign_id: str, user_id: str, email: str, template_id: str, variables: Dict) -> bool: + async def send_email(self, campaign_id: str, user_id: str, email: str, template_id: str, variables: dict) -> bool: """发送单封邮件""" template = self.get_email_template(template_id) if not template: @@ -1380,7 +1381,7 @@ class GrowthManager: conn.commit() return False - async def send_campaign(self, campaign_id: str) -> Dict: + async def send_campaign(self, campaign_id: str) -> dict: """发送整个营销活动""" with self._get_db() as conn: campaign_row = conn.execute("SELECT * FROM email_campaigns WHERE id = ?", (campaign_id,)).fetchone() @@ -1432,7 +1433,7 @@ class GrowthManager: return {"campaign_id": campaign_id, "total": len(logs), "success": success_count, "failed": failed_count} - def _get_user_variables(self, tenant_id: str, user_id: str) -> Dict: + def _get_user_variables(self, tenant_id: str, user_id: str) -> dict: """获取用户变量用于邮件模板""" # 这里应该从用户服务获取用户信息 # 简化实现 @@ -1444,8 +1445,8 @@ class GrowthManager: name: str, description: str, trigger_type: WorkflowTriggerType, - trigger_conditions: Dict, - actions: List[Dict], + trigger_conditions: dict, + actions: list[dict], ) -> AutomationWorkflow: """创建自动化工作流""" workflow_id = f"aw_{uuid.uuid4().hex[:16]}" @@ -1491,7 +1492,7 @@ class GrowthManager: return workflow - async def trigger_workflow(self, workflow_id: str, event_data: Dict): + async def trigger_workflow(self, workflow_id: str, event_data: dict): """触发自动化工作流""" with self._get_db() as conn: row = conn.execute( @@ -1519,7 +1520,7 @@ class GrowthManager: return True - def _check_trigger_conditions(self, conditions: Dict, event_data: Dict) -> bool: + def _check_trigger_conditions(self, conditions: dict, event_data: dict) -> bool: """检查触发条件""" # 简化的条件检查 for key, value in conditions.items(): @@ -1527,7 +1528,7 @@ class GrowthManager: return False return True - async def _execute_action(self, action: Dict, event_data: Dict): + async def _execute_action(self, action: dict, event_data: dict): """执行工作流动作""" action_type = action.get("type") @@ -1691,7 +1692,7 @@ class GrowthManager: if not row: return code - def _get_referral_program(self, program_id: str) -> Optional[ReferralProgram]: + def _get_referral_program(self, program_id: str) -> ReferralProgram | None: """获取推荐计划""" with self._get_db() as conn: row = conn.execute("SELECT * FROM referral_programs WHERE id = ?", (program_id,)).fetchone() @@ -1746,7 +1747,7 @@ class GrowthManager: return True - def get_referral_stats(self, program_id: str) -> Dict: + def get_referral_stats(self, program_id: str) -> dict: """获取推荐统计""" with self._get_db() as conn: stats = conn.execute( @@ -1841,7 +1842,7 @@ class GrowthManager: def check_team_incentive_eligibility( self, tenant_id: str, current_tier: str, team_size: int - ) -> List[TeamIncentive]: + ) -> list[TeamIncentive]: """检查团队激励资格""" with self._get_db() as conn: now = datetime.now().isoformat() @@ -1859,7 +1860,7 @@ class GrowthManager: # ==================== 实时分析仪表板 ==================== - def get_realtime_dashboard(self, tenant_id: str) -> Dict: + def get_realtime_dashboard(self, tenant_id: str) -> dict: """获取实时分析仪表板数据""" now = datetime.now() today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) diff --git a/backend/image_processor.py b/backend/image_processor.py index ec933dc..58e514f 100644 --- a/backend/image_processor.py +++ b/backend/image_processor.py @@ -4,11 +4,10 @@ InsightFlow Image Processor - Phase 7 图片处理模块:识别白板、PPT、手写笔记等内容 """ -import os -import io -import uuid import base64 -from typing import List, Optional, Tuple +import io +import os +import uuid from dataclasses import dataclass # 尝试导入图像处理库 @@ -42,7 +41,7 @@ class ImageEntity: name: str type: str confidence: float - bbox: Optional[Tuple[int, int, int, int]] = None # (x, y, width, height) + bbox: tuple[int, int, int, int] | None = None # (x, y, width, height) @dataclass @@ -63,8 +62,8 @@ class ImageProcessingResult: image_type: str # whiteboard, ppt, handwritten, screenshot, other ocr_text: str description: str - entities: List[ImageEntity] - relations: List[ImageRelation] + entities: list[ImageEntity] + relations: list[ImageRelation] width: int height: int success: bool @@ -75,7 +74,7 @@ class ImageProcessingResult: class BatchProcessingResult: """批量图片处理结果""" - results: List[ImageProcessingResult] + results: list[ImageProcessingResult] total_count: int success_count: int failed_count: int @@ -231,7 +230,7 @@ class ImageProcessor: print(f"Image type detection error: {e}") return "other" - def perform_ocr(self, image, lang: str = "chi_sim+eng") -> Tuple[str, float]: + def perform_ocr(self, image, lang: str = "chi_sim+eng") -> tuple[str, float]: """ 对图片进行OCR识别 @@ -262,7 +261,7 @@ class ImageProcessor: print(f"OCR error: {e}") return "", 0.0 - def extract_entities_from_text(self, text: str) -> List[ImageEntity]: + def extract_entities_from_text(self, text: str) -> list[ImageEntity]: """ 从OCR文本中提取实体 @@ -322,7 +321,7 @@ class ImageProcessor: return unique_entities - def generate_description(self, image_type: str, ocr_text: str, entities: List[ImageEntity]) -> str: + def generate_description(self, image_type: str, ocr_text: str, entities: list[ImageEntity]) -> str: """ 生成图片描述 @@ -435,7 +434,7 @@ class ImageProcessor: error_message=str(e), ) - def _extract_relations(self, entities: List[ImageEntity], text: str) -> List[ImageRelation]: + def _extract_relations(self, entities: list[ImageEntity], text: str) -> list[ImageRelation]: """ 从文本中提取实体关系 @@ -475,7 +474,7 @@ class ImageProcessor: return relations - def process_batch(self, images_data: List[Tuple[bytes, str]], project_id: str = None) -> BatchProcessingResult: + def process_batch(self, images_data: list[tuple[bytes, str]], project_id: str = None) -> BatchProcessingResult: """ 批量处理图片 @@ -515,7 +514,7 @@ class ImageProcessor: """ return base64.b64encode(image_data).decode("utf-8") - def get_image_thumbnail(self, image_data: bytes, size: Tuple[int, int] = (200, 200)) -> bytes: + def get_image_thumbnail(self, image_data: bytes, size: tuple[int, int] = (200, 200)) -> bytes: """ 生成图片缩略图 diff --git a/backend/init_db.py b/backend/init_db.py index f78e41a..7cd7778 100644 --- a/backend/init_db.py +++ b/backend/init_db.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """Initialize database with schema""" -import sqlite3 import os +import sqlite3 db_path = os.path.join(os.path.dirname(__file__), "insightflow.db") schema_path = os.path.join(os.path.dirname(__file__), "schema.sql") @@ -11,7 +11,7 @@ print(f"Database path: {db_path}") print(f"Schema path: {schema_path}") # Read schema -with open(schema_path, "r") as f: +with open(schema_path) as f: schema = f.read() # Execute schema diff --git a/backend/knowledge_reasoner.py b/backend/knowledge_reasoner.py index b1d8d10..0cac3b6 100644 --- a/backend/knowledge_reasoner.py +++ b/backend/knowledge_reasoner.py @@ -4,13 +4,13 @@ InsightFlow Knowledge Reasoning - Phase 5 知识推理与问答增强模块 """ -import os import json -import httpx -from typing import List, Dict +import os from dataclasses import dataclass from enum import Enum +import httpx + KIMI_API_KEY = os.getenv("KIMI_API_KEY", "") KIMI_BASE_URL = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding") @@ -32,9 +32,9 @@ class ReasoningResult: answer: str reasoning_type: ReasoningType confidence: float - evidence: List[Dict] # 支撑证据 - related_entities: List[str] # 相关实体 - gaps: List[str] # 知识缺口 + evidence: list[dict] # 支撑证据 + related_entities: list[str] # 相关实体 + gaps: list[str] # 知识缺口 @dataclass @@ -43,7 +43,7 @@ class InferencePath: start_entity: str end_entity: str - path: List[Dict] # 路径上的节点和关系 + path: list[dict] # 路径上的节点和关系 strength: float # 路径强度 @@ -71,7 +71,7 @@ class KnowledgeReasoner: return result["choices"][0]["message"]["content"] async def enhanced_qa( - self, query: str, project_context: Dict, graph_data: Dict, reasoning_depth: str = "medium" + self, query: str, project_context: dict, graph_data: dict, reasoning_depth: str = "medium" ) -> ReasoningResult: """ 增强问答 - 结合图谱推理的问答 @@ -95,7 +95,7 @@ class KnowledgeReasoner: else: return await self._associative_reasoning(query, project_context, graph_data) - async def _analyze_question(self, query: str) -> Dict: + async def _analyze_question(self, query: str) -> dict: """分析问题类型和意图""" prompt = f"""分析以下问题的类型和意图: @@ -129,7 +129,7 @@ class KnowledgeReasoner: return {"type": "factual", "entities": [], "intent": "general", "complexity": "simple"} - async def _causal_reasoning(self, query: str, project_context: Dict, graph_data: Dict) -> ReasoningResult: + async def _causal_reasoning(self, query: str, project_context: dict, graph_data: dict) -> ReasoningResult: """因果推理 - 分析原因和影响""" # 构建因果分析提示 @@ -190,7 +190,7 @@ class KnowledgeReasoner: gaps=["无法完成因果推理"], ) - async def _comparative_reasoning(self, query: str, project_context: Dict, graph_data: Dict) -> ReasoningResult: + async def _comparative_reasoning(self, query: str, project_context: dict, graph_data: dict) -> ReasoningResult: """对比推理 - 比较实体间的异同""" prompt = f"""基于以下知识图谱进行对比分析: @@ -244,7 +244,7 @@ class KnowledgeReasoner: gaps=[], ) - async def _temporal_reasoning(self, query: str, project_context: Dict, graph_data: Dict) -> ReasoningResult: + async def _temporal_reasoning(self, query: str, project_context: dict, graph_data: dict) -> ReasoningResult: """时序推理 - 分析时间线和演变""" prompt = f"""基于以下知识图谱进行时序分析: @@ -298,7 +298,7 @@ class KnowledgeReasoner: gaps=[], ) - async def _associative_reasoning(self, query: str, project_context: Dict, graph_data: Dict) -> ReasoningResult: + async def _associative_reasoning(self, query: str, project_context: dict, graph_data: dict) -> ReasoningResult: """关联推理 - 发现实体间的隐含关联""" prompt = f"""基于以下知识图谱进行关联分析: @@ -353,8 +353,8 @@ class KnowledgeReasoner: ) def find_inference_paths( - self, start_entity: str, end_entity: str, graph_data: Dict, max_depth: int = 3 - ) -> List[InferencePath]: + self, start_entity: str, end_entity: str, graph_data: dict, max_depth: int = 3 + ) -> list[InferencePath]: """ 发现两个实体之间的推理路径 @@ -416,7 +416,7 @@ class KnowledgeReasoner: paths.sort(key=lambda p: p.strength, reverse=True) return paths - def _calculate_path_strength(self, path: List[Dict]) -> float: + def _calculate_path_strength(self, path: list[dict]) -> float: """计算路径强度""" if len(path) < 2: return 0.0 @@ -438,8 +438,8 @@ class KnowledgeReasoner: return length_factor * confidence_factor async def summarize_project( - self, project_context: Dict, graph_data: Dict, summary_type: str = "comprehensive" - ) -> Dict: + self, project_context: dict, graph_data: dict, summary_type: str = "comprehensive" + ) -> dict: """ 项目智能总结 diff --git a/backend/llm_client.py b/backend/llm_client.py index bbda740..8e5da38 100644 --- a/backend/llm_client.py +++ b/backend/llm_client.py @@ -4,12 +4,13 @@ InsightFlow LLM Client - Phase 4 用于与 Kimi API 交互,支持 RAG 问答和 Agent 功能 """ -import os import json -import httpx -from typing import List, Dict, AsyncGenerator +import os +from collections.abc import AsyncGenerator from dataclasses import dataclass +import httpx + KIMI_API_KEY = os.getenv("KIMI_API_KEY", "") KIMI_BASE_URL = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding") @@ -44,7 +45,7 @@ class LLMClient: self.base_url = base_url or KIMI_BASE_URL self.headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} - async def chat(self, messages: List[ChatMessage], temperature: float = 0.3, stream: bool = False) -> str: + async def chat(self, messages: list[ChatMessage], temperature: float = 0.3, stream: bool = False) -> str: """发送聊天请求""" if not self.api_key: raise ValueError("KIMI_API_KEY not set") @@ -64,7 +65,7 @@ class LLMClient: result = response.json() return result["choices"][0]["message"]["content"] - async def chat_stream(self, messages: List[ChatMessage], temperature: float = 0.3) -> AsyncGenerator[str, None]: + async def chat_stream(self, messages: list[ChatMessage], temperature: float = 0.3) -> AsyncGenerator[str, None]: """流式聊天请求""" if not self.api_key: raise ValueError("KIMI_API_KEY not set") @@ -96,7 +97,7 @@ class LLMClient: async def extract_entities_with_confidence( self, text: str - ) -> tuple[List[EntityExtractionResult], List[RelationExtractionResult]]: + ) -> tuple[list[EntityExtractionResult], list[RelationExtractionResult]]: """提取实体和关系,带置信度分数""" prompt = f"""从以下会议文本中提取关键实体和它们之间的关系,以 JSON 格式返回: @@ -152,7 +153,7 @@ class LLMClient: print(f"Parse extraction result failed: {e}") return [], [] - async def rag_query(self, query: str, context: str, project_context: Dict) -> str: + async def rag_query(self, query: str, context: str, project_context: dict) -> str: """RAG 问答 - 基于项目上下文回答问题""" prompt = f"""你是一个专业的项目分析助手。基于以下项目信息回答问题: @@ -174,7 +175,7 @@ class LLMClient: return await self.chat(messages, temperature=0.3) - async def agent_command(self, command: str, project_context: Dict) -> Dict: + async def agent_command(self, command: str, project_context: dict) -> dict: """Agent 指令解析 - 将自然语言指令转换为结构化操作""" prompt = f"""解析以下用户指令,转换为结构化操作: @@ -214,7 +215,7 @@ class LLMClient: except BaseException: return {"intent": "unknown", "explanation": "解析失败"} - async def analyze_entity_evolution(self, entity_name: str, mentions: List[Dict]) -> str: + async def analyze_entity_evolution(self, entity_name: str, mentions: list[dict]) -> str: """分析实体在项目中的演变/态度变化""" mentions_text = "\n".join( [f"[{m.get('created_at', '未知时间')}] {m.get('text_snippet', '')}" for m in mentions[:20]] # 限制数量 diff --git a/backend/localization_manager.py b/backend/localization_manager.py index 074103e..96c05c1 100644 --- a/backend/localization_manager.py +++ b/backend/localization_manager.py @@ -10,14 +10,14 @@ InsightFlow Phase 8 - 全球化与本地化管理模块 作者: InsightFlow Team """ -import sqlite3 import json -import uuid -from datetime import datetime -from typing import Optional, List, Dict, Any -from dataclasses import dataclass -from enum import Enum import logging +import sqlite3 +import uuid +from dataclasses import dataclass +from datetime import datetime +from enum import StrEnum +from typing import Any try: import pytz @@ -36,7 +36,7 @@ except ImportError: logger = logging.getLogger(__name__) -class LanguageCode(str, Enum): +class LanguageCode(StrEnum): """支持的语言代码""" EN = "en" @@ -53,7 +53,7 @@ class LanguageCode(str, Enum): HI = "hi" -class RegionCode(str, Enum): +class RegionCode(StrEnum): """区域代码""" GLOBAL = "global" @@ -65,7 +65,7 @@ class RegionCode(str, Enum): MIDDLE_EAST = "me" -class DataCenterRegion(str, Enum): +class DataCenterRegion(StrEnum): """数据中心区域""" US_EAST = "us-east" @@ -79,7 +79,7 @@ class DataCenterRegion(str, Enum): CN_EAST = "cn-east" -class PaymentProvider(str, Enum): +class PaymentProvider(StrEnum): """支付提供商""" STRIPE = "stripe" @@ -96,7 +96,7 @@ class PaymentProvider(str, Enum): UNIONPAY = "unionpay" -class CalendarType(str, Enum): +class CalendarType(StrEnum): """日历类型""" GREGORIAN = "gregorian" @@ -115,12 +115,12 @@ class Translation: language: str value: str namespace: str - context: Optional[str] + context: str | None created_at: datetime updated_at: datetime is_reviewed: bool - reviewed_by: Optional[str] - reviewed_at: Optional[datetime] + reviewed_by: str | None + reviewed_at: datetime | None @dataclass @@ -131,7 +131,7 @@ class LanguageConfig: is_rtl: bool is_active: bool is_default: bool - fallback_language: Optional[str] + fallback_language: str | None date_format: str time_format: str datetime_format: str @@ -150,8 +150,8 @@ class DataCenter: endpoint: str status: str priority: int - supported_regions: List[str] - capabilities: Dict[str, Any] + supported_regions: list[str] + capabilities: dict[str, Any] created_at: datetime updated_at: datetime @@ -161,7 +161,7 @@ class TenantDataCenterMapping: id: str tenant_id: str primary_dc_id: str - secondary_dc_id: Optional[str] + secondary_dc_id: str | None region_code: str data_residency: str created_at: datetime @@ -173,15 +173,15 @@ class LocalizedPaymentMethod: id: str provider: str name: str - name_local: Dict[str, str] - supported_countries: List[str] - supported_currencies: List[str] + name_local: dict[str, str] + supported_countries: list[str] + supported_currencies: list[str] is_active: bool - config: Dict[str, Any] - icon_url: Optional[str] + config: dict[str, Any] + icon_url: str | None display_order: int - min_amount: Optional[float] - max_amount: Optional[float] + min_amount: float | None + max_amount: float | None created_at: datetime updated_at: datetime @@ -191,20 +191,20 @@ class CountryConfig: code: str code3: str name: str - name_local: Dict[str, str] + name_local: dict[str, str] region: str default_language: str - supported_languages: List[str] + supported_languages: list[str] default_currency: str - supported_currencies: List[str] + supported_currencies: list[str] timezone: str calendar_type: str - date_format: Optional[str] - time_format: Optional[str] - number_format: Optional[str] - address_format: Optional[str] - phone_format: Optional[str] - vat_rate: Optional[float] + date_format: str | None + time_format: str | None + number_format: str | None + address_format: str | None + phone_format: str | None + vat_rate: float | None is_active: bool @@ -213,7 +213,7 @@ class TimezoneConfig: id: str timezone: str utc_offset: str - dst_offset: Optional[str] + dst_offset: str | None country_code: str region: str is_active: bool @@ -223,7 +223,7 @@ class TimezoneConfig: class CurrencyConfig: code: str name: str - name_local: Dict[str, str] + name_local: dict[str, str] symbol: str decimal_places: int decimal_separator: str @@ -236,13 +236,13 @@ class LocalizationSettings: id: str tenant_id: str default_language: str - supported_languages: List[str] + supported_languages: list[str] default_currency: str - supported_currencies: List[str] + supported_currencies: list[str] default_timezone: str - default_date_format: Optional[str] - default_time_format: Optional[str] - default_number_format: Optional[str] + default_date_format: str | None + default_time_format: str | None + default_number_format: str | None calendar_type: str first_day_of_week: int region_code: str @@ -940,7 +940,7 @@ class LocalizationManager: def get_translation( self, key: str, language: str, namespace: str = "common", fallback: bool = True - ) -> Optional[str]: + ) -> str | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -962,7 +962,7 @@ class LocalizationManager: self._close_if_file_db(conn) def set_translation( - self, key: str, language: str, value: str, namespace: str = "common", context: Optional[str] = None + self, key: str, language: str, value: str, namespace: str = "common", context: str | None = None ) -> Translation: conn = self._get_connection() try: @@ -985,7 +985,7 @@ class LocalizationManager: def _get_translation_internal( self, conn: sqlite3.Connection, key: str, language: str, namespace: str - ) -> Optional[Translation]: + ) -> Translation | None: cursor = conn.cursor() cursor.execute( "SELECT * FROM translations WHERE key = ? AND language = ? AND namespace = ?", (key, language, namespace) @@ -1008,8 +1008,8 @@ class LocalizationManager: self._close_if_file_db(conn) def list_translations( - self, language: Optional[str] = None, namespace: Optional[str] = None, limit: int = 1000, offset: int = 0 - ) -> List[Translation]: + self, language: str | None = None, namespace: str | None = None, limit: int = 1000, offset: int = 0 + ) -> list[Translation]: conn = self._get_connection() try: cursor = conn.cursor() @@ -1029,7 +1029,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_language_config(self, code: str) -> Optional[LanguageConfig]: + def get_language_config(self, code: str) -> LanguageConfig | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1041,7 +1041,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def list_language_configs(self, active_only: bool = True) -> List[LanguageConfig]: + def list_language_configs(self, active_only: bool = True) -> list[LanguageConfig]: conn = self._get_connection() try: cursor = conn.cursor() @@ -1055,7 +1055,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_data_center(self, dc_id: str) -> Optional[DataCenter]: + def get_data_center(self, dc_id: str) -> DataCenter | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1067,7 +1067,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_data_center_by_region(self, region_code: str) -> Optional[DataCenter]: + def get_data_center_by_region(self, region_code: str) -> DataCenter | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1079,7 +1079,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def list_data_centers(self, status: Optional[str] = None, region: Optional[str] = None) -> List[DataCenter]: + def list_data_centers(self, status: str | None = None, region: str | None = None) -> list[DataCenter]: conn = self._get_connection() try: cursor = conn.cursor() @@ -1098,7 +1098,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_tenant_data_center(self, tenant_id: str) -> Optional[TenantDataCenterMapping]: + def get_tenant_data_center(self, tenant_id: str) -> TenantDataCenterMapping | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1159,7 +1159,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_payment_method(self, provider: str) -> Optional[LocalizedPaymentMethod]: + def get_payment_method(self, provider: str) -> LocalizedPaymentMethod | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1172,8 +1172,8 @@ class LocalizationManager: self._close_if_file_db(conn) def list_payment_methods( - self, country_code: Optional[str] = None, currency: Optional[str] = None, active_only: bool = True - ) -> List[LocalizedPaymentMethod]: + self, country_code: str | None = None, currency: str | None = None, active_only: bool = True + ) -> list[LocalizedPaymentMethod]: conn = self._get_connection() try: cursor = conn.cursor() @@ -1194,7 +1194,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def get_localized_payment_methods(self, country_code: str, language: str = "en") -> List[Dict[str, Any]]: + def get_localized_payment_methods(self, country_code: str, language: str = "en") -> list[dict[str, Any]]: methods = self.list_payment_methods(country_code=country_code) result = [] for method in methods: @@ -1212,7 +1212,7 @@ class LocalizationManager: ) return result - def get_country_config(self, code: str) -> Optional[CountryConfig]: + def get_country_config(self, code: str) -> CountryConfig | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1224,7 +1224,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def list_country_configs(self, region: Optional[str] = None, active_only: bool = True) -> List[CountryConfig]: + def list_country_configs(self, region: str | None = None, active_only: bool = True) -> list[CountryConfig]: conn = self._get_connection() try: cursor = conn.cursor() @@ -1243,7 +1243,7 @@ class LocalizationManager: self._close_if_file_db(conn) def format_datetime( - self, dt: datetime, language: str = "en", timezone: Optional[str] = None, format_type: str = "datetime" + self, dt: datetime, language: str = "en", timezone: str | None = None, format_type: str = "datetime" ) -> str: try: if timezone and PYTZ_AVAILABLE: @@ -1276,7 +1276,7 @@ class LocalizationManager: logger.error(f"Error formatting datetime: {e}") return dt.strftime("%Y-%m-%d %H:%M") - def format_number(self, number: float, language: str = "en", decimal_places: Optional[int] = None) -> str: + def format_number(self, number: float, language: str = "en", decimal_places: int | None = None) -> str: try: if BABEL_AVAILABLE: try: @@ -1319,7 +1319,7 @@ class LocalizationManager: logger.error(f"Error converting timezone: {e}") return dt - def get_calendar_info(self, calendar_type: str, year: int, month: int) -> Dict[str, Any]: + def get_calendar_info(self, calendar_type: str, year: int, month: int) -> dict[str, Any]: import calendar cal = calendar.Calendar() @@ -1334,7 +1334,7 @@ class LocalizationManager: "weeks": month_days, } - def get_localization_settings(self, tenant_id: str) -> Optional[LocalizationSettings]: + def get_localization_settings(self, tenant_id: str) -> LocalizationSettings | None: conn = self._get_connection() try: cursor = conn.cursor() @@ -1350,9 +1350,9 @@ class LocalizationManager: self, tenant_id: str, default_language: str = "en", - supported_languages: Optional[List[str]] = None, + supported_languages: list[str] | None = None, default_currency: str = "USD", - supported_currencies: Optional[List[str]] = None, + supported_currencies: list[str] | None = None, default_timezone: str = "UTC", region_code: str = "global", data_residency: str = "regional", @@ -1397,7 +1397,7 @@ class LocalizationManager: finally: self._close_if_file_db(conn) - def update_localization_settings(self, tenant_id: str, **kwargs) -> Optional[LocalizationSettings]: + def update_localization_settings(self, tenant_id: str, **kwargs) -> LocalizationSettings | None: conn = self._get_connection() try: settings = self.get_localization_settings(tenant_id) @@ -1441,8 +1441,8 @@ class LocalizationManager: self._close_if_file_db(conn) def detect_user_preferences( - self, accept_language: Optional[str] = None, ip_country: Optional[str] = None - ) -> Dict[str, str]: + self, accept_language: str | None = None, ip_country: str | None = None + ) -> dict[str, str]: preferences = {"language": "en", "country": "US", "timezone": "UTC", "currency": "USD"} if accept_language: langs = accept_language.split(",") diff --git a/backend/main.py b/backend/main.py index 4d85f2b..af752dc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,21 +6,32 @@ Knowledge Growth: Multi-file fusion + Entity Alignment + Document Import ASR: 阿里云听悟 + OSS """ +import io +import json +import logging import os import sys -import json -import httpx -import uuid -import io import time -import logging +import uuid from datetime import datetime, timedelta -from typing import List, Optional, Union, Dict, Any +from typing import Any, Optional -from fastapi import FastAPI, File, UploadFile, HTTPException, Form, Depends, Header, Request, Query, Body +import httpx +from fastapi import ( + Body, + Depends, + FastAPI, + File, + Form, + Header, + HTTPException, + Query, + Request, + UploadFile, +) from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles from fastapi.responses import JSONResponse, StreamingResponse +from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field # Configure logger @@ -45,7 +56,7 @@ except ImportError: TINGWU_AVAILABLE = False try: - from db_manager import get_db_manager, Entity, EntityMention + from db_manager import Entity, EntityMention, get_db_manager DB_AVAILABLE = True except ImportError as e: print(f"DB import error: {e}") @@ -64,7 +75,7 @@ except ImportError: ALIGNER_AVAILABLE = False try: - from llm_client import get_llm_client, ChatMessage + from llm_client import ChatMessage, get_llm_client LLM_CLIENT_AVAILABLE = True except ImportError: LLM_CLIENT_AVAILABLE = False @@ -76,13 +87,13 @@ except ImportError: REASONER_AVAILABLE = False try: - from export_manager import get_export_manager, ExportEntity, ExportRelation, ExportTranscript + from export_manager import ExportEntity, ExportRelation, ExportTranscript, get_export_manager EXPORT_AVAILABLE = True except ImportError: EXPORT_AVAILABLE = False try: - from neo4j_manager import get_neo4j_manager, sync_project_to_neo4j, NEO4J_AVAILABLE + from neo4j_manager import NEO4J_AVAILABLE, get_neo4j_manager, sync_project_to_neo4j except ImportError: NEO4J_AVAILABLE = False @@ -96,7 +107,7 @@ except ImportError as e: # Phase 6: Rate Limiter try: - from rate_limiter import get_rate_limiter, RateLimitConfig + from rate_limiter import RateLimitConfig, get_rate_limiter RATE_LIMITER_AVAILABLE = True except ImportError as e: print(f"Rate Limiter import error: {e}") @@ -104,9 +115,7 @@ except ImportError as e: # Phase 7: Workflow Manager try: - from workflow_manager import ( - Workflow, WebhookConfig - ) + from workflow_manager import WebhookConfig, Workflow WORKFLOW_AVAILABLE = True except ImportError as e: print(f"Workflow Manager import error: {e}") @@ -114,27 +123,21 @@ except ImportError as e: # Phase 7: Multimodal Support try: - from multimodal_processor import ( - get_multimodal_processor - ) + from multimodal_processor import get_multimodal_processor MULTIMODAL_AVAILABLE = True except ImportError as e: print(f"Multimodal Processor import error: {e}") MULTIMODAL_AVAILABLE = False try: - from image_processor import ( - get_image_processor - ) + from image_processor import get_image_processor IMAGE_PROCESSOR_AVAILABLE = True except ImportError as e: print(f"Image Processor import error: {e}") IMAGE_PROCESSOR_AVAILABLE = False try: - from multimodal_entity_linker import ( - get_multimodal_entity_linker, EntityLink - ) + from multimodal_entity_linker import EntityLink, get_multimodal_entity_linker MULTIMODAL_LINKER_AVAILABLE = True except ImportError as e: print(f"Multimodal Entity Linker import error: {e}") @@ -143,8 +146,12 @@ except ImportError as e: # Phase 7 Task 7: Plugin Manager try: from plugin_manager import ( - get_plugin_manager, Plugin, PluginType, - PluginStatus, BotHandler, WebhookIntegration + BotHandler, + Plugin, + PluginStatus, + PluginType, + WebhookIntegration, + get_plugin_manager, ) PLUGIN_MANAGER_AVAILABLE = True except ImportError as e: @@ -153,9 +160,7 @@ except ImportError as e: # Phase 7 Task 3: Security Manager try: - from security_manager import ( - get_security_manager, MaskingRuleType - ) + from security_manager import MaskingRuleType, get_security_manager SECURITY_MANAGER_AVAILABLE = True except ImportError as e: print(f"Security Manager import error: {e}") @@ -163,9 +168,7 @@ except ImportError as e: # Phase 7 Task 4: Collaboration Manager try: - from collaboration_manager import ( - get_collaboration_manager - ) + from collaboration_manager import get_collaboration_manager COLLABORATION_AVAILABLE = True except ImportError as e: print(f"Collaboration Manager import error: {e}") @@ -180,9 +183,7 @@ except ImportError as e: # Phase 7 Task 6: Search Manager try: - from search_manager import ( - get_search_manager, SearchOperator - ) + from search_manager import SearchOperator, get_search_manager SEARCH_MANAGER_AVAILABLE = True except ImportError as e: print(f"Search Manager import error: {e}") @@ -190,9 +191,7 @@ except ImportError as e: # Phase 7 Task 8: Performance Manager try: - from performance_manager import ( - get_performance_manager - ) + from performance_manager import get_performance_manager PERFORMANCE_MANAGER_AVAILABLE = True except ImportError as e: print(f"Performance Manager import error: {e}") @@ -200,9 +199,7 @@ except ImportError as e: # Phase 8: Tenant Manager (Multi-Tenant SaaS) try: - from tenant_manager import ( - get_tenant_manager, TenantStatus, TenantTier, TenantRole - ) + from tenant_manager import TenantRole, TenantStatus, TenantTier, get_tenant_manager TENANT_MANAGER_AVAILABLE = True except ImportError as e: print(f"Tenant Manager import error: {e}") @@ -210,9 +207,7 @@ except ImportError as e: # Phase 8: Subscription Manager try: - from subscription_manager import ( - get_subscription_manager - ) + from subscription_manager import get_subscription_manager SUBSCRIPTION_MANAGER_AVAILABLE = True except ImportError as e: print(f"Subscription Manager import error: {e}") @@ -220,9 +215,7 @@ except ImportError as e: # Phase 8: Enterprise Manager try: - from enterprise_manager import ( - get_enterprise_manager - ) + from enterprise_manager import get_enterprise_manager ENTERPRISE_MANAGER_AVAILABLE = True except ImportError as e: print(f"Enterprise Manager import error: {e}") @@ -230,9 +223,7 @@ except ImportError as e: # Phase 8: Localization Manager try: - from localization_manager import ( - get_localization_manager - ) + from localization_manager import get_localization_manager LOCALIZATION_MANAGER_AVAILABLE = True except ImportError as e: print(f"Localization Manager import error: {e}") @@ -241,7 +232,11 @@ except ImportError as e: # Phase 8 Task 4: AI Manager try: from ai_manager import ( - get_ai_manager, ModelType, ModelStatus, MultimodalProvider, PredictionType + ModelStatus, + ModelType, + MultimodalProvider, + PredictionType, + get_ai_manager, ) AI_MANAGER_AVAILABLE = True except ImportError as e: @@ -251,7 +246,12 @@ except ImportError as e: # Phase 8 Task 5: Growth Manager try: from growth_manager import ( - GrowthManager, EventType, ExperimentStatus, TrafficAllocationType, EmailTemplateType, WorkflowTriggerType + EmailTemplateType, + EventType, + ExperimentStatus, + GrowthManager, + TrafficAllocationType, + WorkflowTriggerType, ) GROWTH_MANAGER_AVAILABLE = True except ImportError as e: @@ -261,7 +261,12 @@ except ImportError as e: # Phase 8 Task 8: Operations & Monitoring Manager try: from ops_manager import ( - get_ops_manager, AlertSeverity, AlertStatus, AlertChannelType, AlertRuleType, ResourceType + AlertChannelType, + AlertRuleType, + AlertSeverity, + AlertStatus, + ResourceType, + get_ops_manager, ) OPS_MANAGER_AVAILABLE = True except ImportError as e: @@ -356,7 +361,7 @@ ADMIN_PATHS = { MASTER_KEY = os.getenv("INSIGHTFLOW_MASTER_KEY", "") -async def verify_api_key(request: Request, x_api_key: Optional[str] = Header(None, alias="X-API-Key")): +async def verify_api_key(request: Request, x_api_key: str | None = Header(None, alias="X-API-Key")): """ 验证 API Key 的依赖函数 @@ -512,21 +517,21 @@ app.middleware("http")(rate_limit_middleware) class ApiKeyCreate(BaseModel): name: str = Field(..., description="API Key 名称/描述") - permissions: List[str] = Field(default=["read"], description="权限列表: read, write, delete") + permissions: list[str] = Field(default=["read"], description="权限列表: read, write, delete") rate_limit: int = Field(default=60, description="每分钟请求限制") - expires_days: Optional[int] = Field(default=None, description="过期天数(可选)") + expires_days: int | None = Field(default=None, description="过期天数(可选)") class ApiKeyResponse(BaseModel): id: str key_preview: str name: str - permissions: List[str] + permissions: list[str] rate_limit: int status: str created_at: str - expires_at: Optional[str] - last_used_at: Optional[str] + expires_at: str | None + last_used_at: str | None total_calls: int @@ -536,14 +541,14 @@ class ApiKeyCreateResponse(BaseModel): class ApiKeyListResponse(BaseModel): - keys: List[ApiKeyResponse] + keys: list[ApiKeyResponse] total: int class ApiKeyUpdate(BaseModel): - name: Optional[str] = None - permissions: Optional[List[str]] = None - rate_limit: Optional[int] = None + name: str | None = None + permissions: list[str] | None = None + rate_limit: int | None = None class ApiCallStats(BaseModel): @@ -557,8 +562,8 @@ class ApiCallStats(BaseModel): class ApiStatsResponse(BaseModel): summary: ApiCallStats - endpoints: List[Dict] - daily: List[Dict] + endpoints: list[dict] + daily: list[dict] class ApiCallLog(BaseModel): @@ -574,7 +579,7 @@ class ApiCallLog(BaseModel): class ApiLogsResponse(BaseModel): - logs: List[ApiCallLog] + logs: list[ApiCallLog] total: int @@ -590,22 +595,22 @@ class EntityModel(BaseModel): id: str name: str type: str - definition: Optional[str] = "" - aliases: List[str] = [] + definition: str | None = "" + aliases: list[str] = [] class TranscriptSegment(BaseModel): start: float end: float text: str - speaker: Optional[str] = "Speaker A" + speaker: str | None = "Speaker A" class AnalysisResult(BaseModel): transcript_id: str project_id: str - segments: List[TranscriptSegment] - entities: List[EntityModel] + segments: list[TranscriptSegment] + entities: list[EntityModel] full_text: str created_at: str @@ -616,17 +621,17 @@ class ProjectCreate(BaseModel): class EntityUpdate(BaseModel): - name: Optional[str] = None - type: Optional[str] = None - definition: Optional[str] = None - aliases: Optional[List[str]] = None + name: str | None = None + type: str | None = None + definition: str | None = None + aliases: list[str] | None = None class RelationCreate(BaseModel): source_entity_id: str target_entity_id: str relation_type: str - evidence: Optional[str] = "" + evidence: str | None = "" class TranscriptUpdate(BaseModel): @@ -649,7 +654,7 @@ class EntityMergeRequest(BaseModel): class GlossaryTermCreate(BaseModel): term: str - pronunciation: Optional[str] = "" + pronunciation: str | None = "" # ==================== Phase 7: Workflow Pydantic Models ==================== @@ -660,21 +665,21 @@ class WorkflowCreate(BaseModel): workflow_type: str = Field(..., description="工作流类型: auto_analyze, auto_align, auto_relation, scheduled_report, custom") project_id: str = Field(..., description="所属项目ID") - schedule: Optional[str] = Field(default=None, description="调度表达式(cron或分钟数)") + schedule: str | None = Field(default=None, description="调度表达式(cron或分钟数)") schedule_type: str = Field(default="manual", description="调度类型: manual, cron, interval") - config: Dict = Field(default_factory=dict, description="工作流配置") - webhook_ids: List[str] = Field(default_factory=list, description="关联的Webhook ID列表") + config: dict = Field(default_factory=dict, description="工作流配置") + webhook_ids: list[str] = Field(default_factory=list, description="关联的Webhook ID列表") class WorkflowUpdate(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - status: Optional[str] = None # active, paused, error, completed - schedule: Optional[str] = None - schedule_type: Optional[str] = None - is_active: Optional[bool] = None - config: Optional[Dict] = None - webhook_ids: Optional[List[str]] = None + name: str | None = None + description: str | None = None + status: str | None = None # active, paused, error, completed + schedule: str | None = None + schedule_type: str | None = None + is_active: bool | None = None + config: dict | None = None + webhook_ids: list[str] | None = None class WorkflowResponse(BaseModel): @@ -684,45 +689,45 @@ class WorkflowResponse(BaseModel): workflow_type: str project_id: str status: str - schedule: Optional[str] + schedule: str | None schedule_type: str - config: Dict - webhook_ids: List[str] + config: dict + webhook_ids: list[str] is_active: bool created_at: str updated_at: str - last_run_at: Optional[str] - next_run_at: Optional[str] + last_run_at: str | None + next_run_at: str | None run_count: int success_count: int fail_count: int class WorkflowListResponse(BaseModel): - workflows: List[WorkflowResponse] + workflows: list[WorkflowResponse] total: int class WorkflowTaskCreate(BaseModel): name: str = Field(..., description="任务名称") task_type: str = Field(..., description="任务类型: analyze, align, discover_relations, notify, custom") - config: Dict = Field(default_factory=dict, description="任务配置") + config: dict = Field(default_factory=dict, description="任务配置") order: int = Field(default=0, description="执行顺序") - depends_on: List[str] = Field(default_factory=list, description="依赖的任务ID列表") + depends_on: list[str] = Field(default_factory=list, description="依赖的任务ID列表") timeout_seconds: int = Field(default=300, description="超时时间(秒)") retry_count: int = Field(default=3, description="重试次数") retry_delay: int = Field(default=5, description="重试延迟(秒)") class WorkflowTaskUpdate(BaseModel): - name: Optional[str] = None - task_type: Optional[str] = None - config: Optional[Dict] = None - order: Optional[int] = None - depends_on: Optional[List[str]] = None - timeout_seconds: Optional[int] = None - retry_count: Optional[int] = None - retry_delay: Optional[int] = None + name: str | None = None + task_type: str | None = None + config: dict | None = None + order: int | None = None + depends_on: list[str] | None = None + timeout_seconds: int | None = None + retry_count: int | None = None + retry_delay: int | None = None class WorkflowTaskResponse(BaseModel): @@ -730,9 +735,9 @@ class WorkflowTaskResponse(BaseModel): workflow_id: str name: str task_type: str - config: Dict + config: dict order: int - depends_on: List[str] + depends_on: list[str] timeout_seconds: int retry_count: int retry_delay: int @@ -745,18 +750,18 @@ class WebhookCreate(BaseModel): webhook_type: str = Field(..., description="Webhook类型: feishu, dingtalk, slack, custom") url: str = Field(..., description="Webhook URL") secret: str = Field(default="", description="签名密钥") - headers: Dict = Field(default_factory=dict, description="自定义请求头") + headers: dict = Field(default_factory=dict, description="自定义请求头") template: str = Field(default="", description="消息模板") class WebhookUpdate(BaseModel): - name: Optional[str] = None - webhook_type: Optional[str] = None - url: Optional[str] = None - secret: Optional[str] = None - headers: Optional[Dict] = None - template: Optional[str] = None - is_active: Optional[bool] = None + name: str | None = None + webhook_type: str | None = None + url: str | None = None + secret: str | None = None + headers: dict | None = None + template: str | None = None + is_active: bool | None = None class WebhookResponse(BaseModel): @@ -764,49 +769,49 @@ class WebhookResponse(BaseModel): name: str webhook_type: str url: str - headers: Dict + headers: dict template: str is_active: bool created_at: str updated_at: str - last_used_at: Optional[str] + last_used_at: str | None success_count: int fail_count: int class WebhookListResponse(BaseModel): - webhooks: List[WebhookResponse] + webhooks: list[WebhookResponse] total: int class WorkflowLogResponse(BaseModel): id: str workflow_id: str - task_id: Optional[str] + task_id: str | None status: str - start_time: Optional[str] - end_time: Optional[str] + start_time: str | None + end_time: str | None duration_ms: int - input_data: Dict - output_data: Dict + input_data: dict + output_data: dict error_message: str created_at: str class WorkflowLogListResponse(BaseModel): - logs: List[WorkflowLogResponse] + logs: list[WorkflowLogResponse] total: int class WorkflowTriggerRequest(BaseModel): - input_data: Dict = Field(default_factory=dict, description="工作流输入数据") + input_data: dict = Field(default_factory=dict, description="工作流输入数据") class WorkflowTriggerResponse(BaseModel): success: bool workflow_id: str log_id: str - results: Dict + results: dict duration_ms: int @@ -816,7 +821,7 @@ class WorkflowStatsResponse(BaseModel): failed: int success_rate: float avg_duration_ms: float - daily: List[Dict] + daily: list[dict] # API Keys @@ -1036,9 +1041,9 @@ class ManualEntityCreate(BaseModel): name: str type: str = "OTHER" definition: str = "" - transcript_id: Optional[str] = None - start_pos: Optional[int] = None - end_pos: Optional[int] = None + transcript_id: str | None = None + start_pos: int | None = None + end_pos: int | None = None @app.post("/api/v1/projects/{project_id}/entities", tags=["Entities"]) @@ -1134,7 +1139,7 @@ def mock_transcribe() -> dict: } -def extract_entities_with_llm(text: str) -> tuple[List[dict], List[dict]]: +def extract_entities_with_llm(text: str) -> tuple[list[dict], list[dict]]: """使用 Kimi API 提取实体和关系 Returns: @@ -1774,9 +1779,10 @@ async def agent_query(project_id: str, query: AgentQuery, _=Depends(verify_api_k context = "\n\n".join(context_parts) if query.stream: - from fastapi.responses import StreamingResponse import json + from fastapi.responses import StreamingResponse + async def stream_response(): messages = [ ChatMessage(role="system", content="你是一个专业的项目分析助手,擅长从会议记录中提取洞察。"), @@ -2256,35 +2262,35 @@ async def project_summary(project_id: str, req: SummaryRequest, _=Depends(verify class AttributeTemplateCreate(BaseModel): name: str type: str # text, number, date, select, multiselect, boolean - options: Optional[List[str]] = None - default_value: Optional[str] = "" - description: Optional[str] = "" + options: list[str] | None = None + default_value: str | None = "" + description: str | None = "" is_required: bool = False sort_order: int = 0 class AttributeTemplateUpdate(BaseModel): - name: Optional[str] = None - type: Optional[str] = None - options: Optional[List[str]] = None - default_value: Optional[str] = None - description: Optional[str] = None - is_required: Optional[bool] = None - sort_order: Optional[int] = None + name: str | None = None + type: str | None = None + options: list[str] | None = None + default_value: str | None = None + description: str | None = None + is_required: bool | None = None + sort_order: int | None = None class EntityAttributeSet(BaseModel): name: str type: str - value: Optional[Union[str, int, float, List[str]]] = None - template_id: Optional[str] = None - options: Optional[List[str]] = None - change_reason: Optional[str] = "" + value: str | int | float | list[str] | None = None + template_id: str | None = None + options: list[str] | None = None + change_reason: str | None = "" class EntityAttributeBatchSet(BaseModel): - attributes: List[EntityAttributeSet] - change_reason: Optional[str] = "" + attributes: list[EntityAttributeSet] + change_reason: str | None = "" # 属性模板管理 API @@ -2568,7 +2574,7 @@ async def get_entity_attributes_endpoint(entity_id: str, _=Depends(verify_api_ke @app.delete("/api/v1/entities/{entity_id}/attributes/{template_id}") async def delete_entity_attribute_endpoint(entity_id: str, template_id: str, - reason: Optional[str] = "", _=Depends(verify_api_key)): + reason: str | None = "", _=Depends(verify_api_key)): """删除实体属性值""" if not DB_AVAILABLE: raise HTTPException(status_code=500, detail="Database not available") @@ -2632,7 +2638,7 @@ async def get_template_history_endpoint(template_id: str, limit: int = 50, _=Dep @app.get("/api/v1/projects/{project_id}/entities/search-by-attributes") async def search_entities_by_attributes_endpoint( project_id: str, - attribute_filter: Optional[str] = None, # JSON 格式: {"职位": "经理", "部门": "技术部"} + attribute_filter: str | None = None, # JSON 格式: {"职位": "经理", "部门": "技术部"} _=Depends(verify_api_key) ): """根据属性筛选搜索实体""" @@ -3082,7 +3088,7 @@ class PathQueryRequest(BaseModel): class GraphQueryRequest(BaseModel): - entity_ids: List[str] + entity_ids: list[str] depth: int = 1 @@ -3402,7 +3408,7 @@ async def create_api_key(request: ApiKeyCreate, _=Depends(verify_api_key)): @app.get("/api/v1/api-keys", response_model=ApiKeyListResponse, tags=["API Keys"]) async def list_api_keys( - status: Optional[str] = None, + status: str | None = None, limit: int = 100, offset: int = 0, _=Depends(verify_api_key) @@ -3758,9 +3764,9 @@ async def create_workflow_endpoint(request: WorkflowCreate, _=Depends(verify_api @app.get("/api/v1/workflows", response_model=WorkflowListResponse, tags=["Workflows"]) async def list_workflows_endpoint( - project_id: Optional[str] = None, - status: Optional[str] = None, - workflow_type: Optional[str] = None, + project_id: str | None = None, + status: str | None = None, + workflow_type: str | None = None, _=Depends(verify_api_key) ): """获取工作流列表""" @@ -3916,7 +3922,7 @@ async def trigger_workflow_endpoint( @app.get("/api/v1/workflows/{workflow_id}/logs", response_model=WorkflowLogListResponse, tags=["Workflows"]) async def get_workflow_logs_endpoint( workflow_id: str, - status: Optional[str] = None, + status: str | None = None, limit: int = 100, offset: int = 0, _=Depends(verify_api_key) @@ -4187,7 +4193,7 @@ class MultimodalAlignmentRequest(BaseModel): class MultimodalAlignmentResponse(BaseModel): project_id: str aligned_count: int - links: List[MultimodalEntityLinkResponse] + links: list[MultimodalEntityLinkResponse] message: str @@ -4197,7 +4203,7 @@ class MultimodalStatsResponse(BaseModel): image_count: int multimodal_entity_count: int cross_modal_links: int - modality_distribution: Dict[str, int] + modality_distribution: dict[str, int] @app.post("/api/v1/projects/{project_id}/upload-video", response_model=VideoUploadResponse, tags=["Multimodal"]) @@ -4456,7 +4462,7 @@ async def upload_image_endpoint( @app.post("/api/v1/projects/{project_id}/upload-images-batch", tags=["Multimodal"]) async def upload_images_batch_endpoint( project_id: str, - files: List[UploadFile] = File(...), + files: list[UploadFile] = File(...), _=Depends(verify_api_key) ): """ @@ -4894,7 +4900,7 @@ class VideoUploadResponse(BaseModel): filename: str duration: float fps: float - resolution: Dict[str, int] + resolution: dict[str, int] frames_extracted: int audio_extracted: bool ocr_text_length: int @@ -4918,7 +4924,7 @@ class MultimodalEntityLinkResponse(BaseModel): link_type: str confidence: float evidence: str - modalities: List[str] + modalities: list[str] class MultimodalProfileResponse(BaseModel): @@ -4933,13 +4939,13 @@ class PluginCreate(BaseModel): plugin_type: str = Field(..., description="插件类型: chrome_extension, feishu_bot, dingtalk_bot, zapier, make, webdav, custom") project_id: str = Field(..., description="关联项目ID") - config: Dict = Field(default_factory=dict, description="插件配置") + config: dict = Field(default_factory=dict, description="插件配置") class PluginUpdate(BaseModel): - name: Optional[str] = None - status: Optional[str] = None # active, inactive, error, pending - config: Optional[Dict] = None + name: str | None = None + status: str | None = None # active, inactive, error, pending + config: dict | None = None class PluginResponse(BaseModel): @@ -4948,32 +4954,32 @@ class PluginResponse(BaseModel): plugin_type: str project_id: str status: str - config: Dict + config: dict created_at: str updated_at: str - last_used_at: Optional[str] + last_used_at: str | None use_count: int class PluginListResponse(BaseModel): - plugins: List[PluginResponse] + plugins: list[PluginResponse] total: int class ChromeExtensionTokenCreate(BaseModel): name: str = Field(..., description="令牌名称") - project_id: Optional[str] = Field(default=None, description="关联项目ID") - permissions: List[str] = Field(default=["read"], description="权限列表: read, write, delete") - expires_days: Optional[int] = Field(default=None, description="过期天数") + project_id: str | None = Field(default=None, description="关联项目ID") + permissions: list[str] = Field(default=["read"], description="权限列表: read, write, delete") + expires_days: int | None = Field(default=None, description="过期天数") class ChromeExtensionTokenResponse(BaseModel): id: str token: str = Field(..., description="令牌(仅显示一次)") name: str - project_id: Optional[str] - permissions: List[str] - expires_at: Optional[str] + project_id: str | None + permissions: list[str] + expires_at: str | None created_at: str @@ -4982,13 +4988,13 @@ class ChromeExtensionImportRequest(BaseModel): url: str = Field(..., description="网页URL") title: str = Field(..., description="网页标题") content: str = Field(..., description="网页正文内容") - html_content: Optional[str] = Field(default=None, description="HTML内容(可选)") + html_content: str | None = Field(default=None, description="HTML内容(可选)") class BotSessionCreate(BaseModel): session_id: str = Field(..., description="群ID或会话ID") session_name: str = Field(..., description="会话名称") - project_id: Optional[str] = Field(default=None, description="关联项目ID") + project_id: str | None = Field(default=None, description="关联项目ID") webhook_url: str = Field(default="", description="Webhook URL") secret: str = Field(default="", description="签名密钥") @@ -4998,34 +5004,34 @@ class BotSessionResponse(BaseModel): bot_type: str session_id: str session_name: str - project_id: Optional[str] + project_id: str | None webhook_url: str is_active: bool created_at: str - last_message_at: Optional[str] + last_message_at: str | None message_count: int class BotMessageRequest(BaseModel): session_id: str = Field(..., description="会话ID") msg_type: str = Field(default="text", description="消息类型: text, audio, file") - content: Dict = Field(default_factory=dict, description="消息内容") + content: dict = Field(default_factory=dict, description="消息内容") class BotMessageResponse(BaseModel): success: bool response: str - error: Optional[str] = None + error: str | None = None class WebhookEndpointCreate(BaseModel): name: str = Field(..., description="端点名称") endpoint_type: str = Field(..., description="端点类型: zapier, make, custom") endpoint_url: str = Field(..., description="Webhook URL") - project_id: Optional[str] = Field(default=None, description="关联项目ID") + project_id: str | None = Field(default=None, description="关联项目ID") auth_type: str = Field(default="none", description="认证类型: none, api_key, oauth, custom") - auth_config: Dict = Field(default_factory=dict, description="认证配置") - trigger_events: List[str] = Field(default_factory=list, description="触发事件列表") + auth_config: dict = Field(default_factory=dict, description="认证配置") + trigger_events: list[str] = Field(default_factory=list, description="触发事件列表") class WebhookEndpointResponse(BaseModel): @@ -5033,12 +5039,12 @@ class WebhookEndpointResponse(BaseModel): name: str endpoint_type: str endpoint_url: str - project_id: Optional[str] + project_id: str | None auth_type: str - trigger_events: List[str] + trigger_events: list[str] is_active: bool created_at: str - last_triggered_at: Optional[str] + last_triggered_at: str | None trigger_count: int @@ -5068,7 +5074,7 @@ class WebDAVSyncResponse(BaseModel): remote_path: str sync_mode: str sync_interval: int - last_sync_at: Optional[str] + last_sync_at: str | None last_sync_status: str is_active: bool created_at: str @@ -5083,10 +5089,10 @@ class WebDAVTestResponse(BaseModel): class WebDAVSyncResult(BaseModel): success: bool message: str - entities_count: Optional[int] = None - relations_count: Optional[int] = None - remote_path: Optional[str] = None - error: Optional[str] = None + entities_count: int | None = None + relations_count: int | None = None + remote_path: str | None = None + error: str | None = None # Plugin Manager singleton @@ -5148,9 +5154,9 @@ async def create_plugin_endpoint(request: PluginCreate, _=Depends(verify_api_key @app.get("/api/v1/plugins", response_model=PluginListResponse, tags=["Plugins"]) async def list_plugins_endpoint( - project_id: Optional[str] = None, - plugin_type: Optional[str] = None, - status: Optional[str] = None, + project_id: str | None = None, + plugin_type: str | None = None, + status: str | None = None, _=Depends(verify_api_key) ): """获取插件列表""" @@ -5287,7 +5293,7 @@ async def create_chrome_token_endpoint(request: ChromeExtensionTokenCreate, _=De @app.get("/api/v1/plugins/chrome/tokens", tags=["Chrome Extension"]) async def list_chrome_tokens_endpoint( - project_id: Optional[str] = None, + project_id: str | None = None, _=Depends(verify_api_key) ): """列出 Chrome 扩展令牌""" @@ -5450,7 +5456,7 @@ async def create_dingtalk_session_endpoint(request: BotSessionCreate, _=Depends( @app.get("/api/v1/plugins/bot/{bot_type}/sessions", tags=["Bot"]) async def list_bot_sessions_endpoint( bot_type: str, - project_id: Optional[str] = None, + project_id: str | None = None, _=Depends(verify_api_key) ): """列出机器人会话""" @@ -5653,7 +5659,7 @@ async def create_make_endpoint(request: WebhookEndpointCreate, _=Depends(verify_ @app.get("/api/v1/plugins/integrations/{endpoint_type}", tags=["Integrations"]) async def list_integration_endpoints_endpoint( endpoint_type: str, - project_id: Optional[str] = None, + project_id: str | None = None, _=Depends(verify_api_key) ): """列出集成端点""" @@ -5727,7 +5733,7 @@ async def test_integration_endpoint(endpoint_id: str, _=Depends(verify_api_key)) async def trigger_integration_endpoint( endpoint_id: str, event_type: str, - data: Dict, + data: dict, _=Depends(verify_api_key) ): """手动触发集成端点""" @@ -5800,7 +5806,7 @@ async def create_webdav_sync_endpoint(request: WebDAVSyncCreate, _=Depends(verif @app.get("/api/v1/plugins/webdav", tags=["WebDAV"]) async def list_webdav_syncs_endpoint( - project_id: Optional[str] = None, + project_id: str | None = None, _=Depends(verify_api_key) ): """列出 WebDAV 同步配置""" @@ -5934,15 +5940,15 @@ if __name__ == "__main__": class PluginCreateRequest(BaseModel): name: str plugin_type: str - project_id: Optional[str] = None - config: Optional[Dict] = {} + project_id: str | None = None + config: dict | None = {} class PluginResponse(BaseModel): id: str name: str plugin_type: str - project_id: Optional[str] + project_id: str | None status: str api_key: str created_at: str @@ -5953,12 +5959,12 @@ class BotSessionResponse(BaseModel): plugin_id: str platform: str session_id: str - user_id: Optional[str] - user_name: Optional[str] - project_id: Optional[str] + user_id: str | None + user_name: str | None + project_id: str | None message_count: int created_at: str - last_message_at: Optional[str] + last_message_at: str | None class WebhookEndpointResponse(BaseModel): @@ -5967,7 +5973,7 @@ class WebhookEndpointResponse(BaseModel): name: str endpoint_path: str endpoint_type: str - target_project_id: Optional[str] + target_project_id: str | None is_active: bool trigger_count: int created_at: str @@ -5985,7 +5991,7 @@ class WebDAVSyncResponse(BaseModel): sync_mode: str auto_analyze: bool is_active: bool - last_sync_at: Optional[str] + last_sync_at: str | None created_at: str @@ -5994,8 +6000,8 @@ class ChromeClipRequest(BaseModel): title: str content: str content_type: str = "page" - meta: Optional[Dict] = {} - project_id: Optional[str] = None + meta: dict | None = {} + project_id: str | None = None class ChromeClipResponse(BaseModel): @@ -6010,23 +6016,23 @@ class ChromeClipResponse(BaseModel): class BotMessagePayload(BaseModel): platform: str session_id: str - user_id: Optional[str] = None - user_name: Optional[str] = None + user_id: str | None = None + user_name: str | None = None message_type: str content: str - project_id: Optional[str] = None + project_id: str | None = None class BotMessageResult(BaseModel): success: bool - reply: Optional[str] = None + reply: str | None = None session_id: str - action: Optional[str] = None + action: str | None = None class WebhookPayload(BaseModel): event: str - data: Dict + data: dict @app.post("/api/v1/plugins", response_model=PluginResponse, tags=["Plugins"]) @@ -6059,8 +6065,8 @@ async def create_plugin( @app.get("/api/v1/plugins", tags=["Plugins"]) async def list_plugins( - project_id: Optional[str] = None, - plugin_type: Optional[str] = None, + project_id: str | None = None, + plugin_type: str | None = None, api_key: str = Depends(verify_api_key) ): """列出插件""" @@ -6147,7 +6153,7 @@ async def regenerate_plugin_key( @app.post("/api/v1/plugins/chrome/clip", response_model=ChromeClipResponse, tags=["Chrome Extension"]) async def chrome_clip( request: ChromeClipRequest, - x_api_key: Optional[str] = Header(None, alias="X-API-Key") + x_api_key: str | None = Header(None, alias="X-API-Key") ): """Chrome 插件保存网页内容""" if not PLUGIN_MANAGER_AVAILABLE: @@ -6221,7 +6227,7 @@ URL: {request.url} async def bot_webhook( platform: str, request: Request, - x_signature: Optional[str] = Header(None, alias="X-Signature") + x_signature: str | None = Header(None, alias="X-Signature") ): """接收机器人 Webhook 消息(飞书/钉钉/Slack)""" if not PLUGIN_MANAGER_AVAILABLE: @@ -6255,10 +6261,10 @@ async def bot_webhook( ) -@app.get("/api/v1/bots/sessions", response_model=List[BotSessionResponse], tags=["Bot"]) +@app.get("/api/v1/bots/sessions", response_model=list[BotSessionResponse], tags=["Bot"]) async def list_bot_sessions( - plugin_id: Optional[str] = None, - project_id: Optional[str] = None, + plugin_id: str | None = None, + project_id: str | None = None, api_key: str = Depends(verify_api_key) ): """列出机器人会话""" @@ -6292,8 +6298,8 @@ async def create_integration_webhook_endpoint( plugin_id: str, name: str, endpoint_type: str, - target_project_id: Optional[str] = None, - allowed_events: Optional[List[str]] = None, + target_project_id: str | None = None, + allowed_events: list[str] | None = None, api_key: str = Depends(verify_api_key) ): """创建 Webhook 端点(用于 Zapier/Make 集成)""" @@ -6322,9 +6328,9 @@ async def create_integration_webhook_endpoint( ) -@app.get("/api/v1/webhook-endpoints", response_model=List[WebhookEndpointResponse], tags=["Integrations"]) +@app.get("/api/v1/webhook-endpoints", response_model=list[WebhookEndpointResponse], tags=["Integrations"]) async def list_webhook_endpoints( - plugin_id: Optional[str] = None, + plugin_id: str | None = None, api_key: str = Depends(verify_api_key) ): """列出 Webhook 端点""" @@ -6355,7 +6361,7 @@ async def receive_webhook( endpoint_type: str, token: str, request: Request, - x_signature: Optional[str] = Header(None, alias="X-Signature") + x_signature: str | None = Header(None, alias="X-Signature") ): """接收外部 Webhook 调用(Zapier/Make/Custom)""" if not PLUGIN_MANAGER_AVAILABLE: @@ -6456,9 +6462,9 @@ async def create_webdav_sync( ) -@app.get("/api/v1/webdav-syncs", response_model=List[WebDAVSyncResponse], tags=["WebDAV"]) +@app.get("/api/v1/webdav-syncs", response_model=list[WebDAVSyncResponse], tags=["WebDAV"]) async def list_webdav_syncs( - plugin_id: Optional[str] = None, + plugin_id: str | None = None, api_key: str = Depends(verify_api_key) ): """列出 WebDAV 同步配置""" @@ -6552,7 +6558,7 @@ async def trigger_webdav_sync( @app.get("/api/v1/plugins/{plugin_id}/logs", tags=["Plugins"]) async def get_plugin_logs( plugin_id: str, - activity_type: Optional[str] = None, + activity_type: str | None = None, limit: int = 100, api_key: str = Depends(verify_api_key) ): @@ -6587,13 +6593,13 @@ async def get_plugin_logs( class AuditLogResponse(BaseModel): id: str action_type: str - user_id: Optional[str] = None - user_ip: Optional[str] = None - resource_type: Optional[str] = None - resource_id: Optional[str] = None - action_details: Optional[str] = None + user_id: str | None = None + user_ip: str | None = None + resource_type: str | None = None + resource_id: str | None = None + action_details: str | None = None success: bool = True - error_message: Optional[str] = None + error_message: str | None = None created_at: str @@ -6601,7 +6607,7 @@ class AuditStatsResponse(BaseModel): total_actions: int success_count: int failure_count: int - action_breakdown: Dict[str, Dict[str, int]] + action_breakdown: dict[str, dict[str, int]] class EncryptionEnableRequest(BaseModel): @@ -6620,9 +6626,9 @@ class EncryptionConfigResponse(BaseModel): class MaskingRuleCreateRequest(BaseModel): name: str rule_type: str # phone, email, id_card, bank_card, name, address, custom - pattern: Optional[str] = None - replacement: Optional[str] = None - description: Optional[str] = None + pattern: str | None = None + replacement: str | None = None + description: str | None = None priority: int = 0 @@ -6635,30 +6641,30 @@ class MaskingRuleResponse(BaseModel): replacement: str is_active: bool priority: int - description: Optional[str] = None + description: str | None = None created_at: str updated_at: str class MaskingApplyRequest(BaseModel): text: str - rule_types: Optional[List[str]] = None + rule_types: list[str] | None = None class MaskingApplyResponse(BaseModel): original_text: str masked_text: str - applied_rules: List[str] + applied_rules: list[str] class AccessPolicyCreateRequest(BaseModel): name: str - description: Optional[str] = None - allowed_users: Optional[List[str]] = None - allowed_roles: Optional[List[str]] = None - allowed_ips: Optional[List[str]] = None - time_restrictions: Optional[Dict] = None - max_access_count: Optional[int] = None + description: str | None = None + allowed_users: list[str] | None = None + allowed_roles: list[str] | None = None + allowed_ips: list[str] | None = None + time_restrictions: dict | None = None + max_access_count: int | None = None require_approval: bool = False @@ -6666,12 +6672,12 @@ class AccessPolicyResponse(BaseModel): id: str project_id: str name: str - description: Optional[str] = None - allowed_users: Optional[List[str]] = None - allowed_roles: Optional[List[str]] = None - allowed_ips: Optional[List[str]] = None - time_restrictions: Optional[Dict] = None - max_access_count: Optional[int] = None + description: str | None = None + allowed_users: list[str] | None = None + allowed_roles: list[str] | None = None + allowed_ips: list[str] | None = None + time_restrictions: dict | None = None + max_access_count: int | None = None require_approval: bool = False is_active: bool = True created_at: str @@ -6680,7 +6686,7 @@ class AccessPolicyResponse(BaseModel): class AccessRequestCreateRequest(BaseModel): policy_id: str - request_reason: Optional[str] = None + request_reason: str | None = None expires_hours: int = 24 @@ -6688,25 +6694,25 @@ class AccessRequestResponse(BaseModel): id: str policy_id: str user_id: str - request_reason: Optional[str] = None + request_reason: str | None = None status: str - approved_by: Optional[str] = None - approved_at: Optional[str] = None - expires_at: Optional[str] = None + approved_by: str | None = None + approved_at: str | None = None + expires_at: str | None = None created_at: str # ==================== Audit Logs API ==================== -@app.get("/api/v1/audit-logs", response_model=List[AuditLogResponse], tags=["Security"]) +@app.get("/api/v1/audit-logs", response_model=list[AuditLogResponse], tags=["Security"]) async def get_audit_logs( - user_id: Optional[str] = None, - resource_type: Optional[str] = None, - resource_id: Optional[str] = None, - action_type: Optional[str] = None, - start_time: Optional[str] = None, - end_time: Optional[str] = None, - success: Optional[bool] = None, + user_id: str | None = None, + resource_type: str | None = None, + resource_id: str | None = None, + action_type: str | None = None, + start_time: str | None = None, + end_time: str | None = None, + success: bool | None = None, limit: int = 100, offset: int = 0, api_key: str = Depends(verify_api_key) @@ -6747,8 +6753,8 @@ async def get_audit_logs( @app.get("/api/v1/audit-logs/stats", response_model=AuditStatsResponse, tags=["Security"]) async def get_audit_stats( - start_time: Optional[str] = None, - end_time: Optional[str] = None, + start_time: str | None = None, + end_time: str | None = None, api_key: str = Depends(verify_api_key) ): """获取审计统计""" @@ -6894,7 +6900,7 @@ async def create_masking_rule( ) -@app.get("/api/v1/projects/{project_id}/masking-rules", response_model=List[MaskingRuleResponse], tags=["Security"]) +@app.get("/api/v1/projects/{project_id}/masking-rules", response_model=list[MaskingRuleResponse], tags=["Security"]) async def get_masking_rules( project_id: str, active_only: bool = True, @@ -6928,12 +6934,12 @@ async def get_masking_rules( @app.put("/api/v1/masking-rules/{rule_id}", response_model=MaskingRuleResponse, tags=["Security"]) async def update_masking_rule( rule_id: str, - name: Optional[str] = None, - pattern: Optional[str] = None, - replacement: Optional[str] = None, - is_active: Optional[bool] = None, - priority: Optional[int] = None, - description: Optional[str] = None, + name: str | None = None, + pattern: str | None = None, + replacement: str | None = None, + is_active: bool | None = None, + priority: int | None = None, + description: str | None = None, api_key: str = Depends(verify_api_key) ): """更新脱敏规则""" @@ -7067,7 +7073,7 @@ async def create_access_policy( ) -@app.get("/api/v1/projects/{project_id}/access-policies", response_model=List[AccessPolicyResponse], tags=["Security"]) +@app.get("/api/v1/projects/{project_id}/access-policies", response_model=list[AccessPolicyResponse], tags=["Security"]) async def get_access_policies( project_id: str, active_only: bool = True, @@ -7104,7 +7110,7 @@ async def get_access_policies( async def check_access_permission( policy_id: str, user_id: str, - user_ip: Optional[str] = None, + user_ip: str | None = None, api_key: str = Depends(verify_api_key) ): """检查访问权限""" @@ -7221,24 +7227,24 @@ async def reject_access_request( class ShareLinkCreate(BaseModel): permission: str = "read_only" # read_only, comment, edit, admin - expires_in_days: Optional[int] = None - max_uses: Optional[int] = None - password: Optional[str] = None + expires_in_days: int | None = None + max_uses: int | None = None + password: str | None = None allow_download: bool = False allow_export: bool = False class ShareLinkVerify(BaseModel): token: str - password: Optional[str] = None + password: str | None = None class CommentCreate(BaseModel): target_type: str # entity, relation, transcript, project target_id: str - parent_id: Optional[str] = None + parent_id: str | None = None content: str - mentions: Optional[List[str]] = None + mentions: list[str] | None = None class CommentUpdate(BaseModel): @@ -7345,7 +7351,7 @@ async def verify_share_link(request: ShareLinkVerify): @app.get("/api/v1/shares/{token}/access") -async def access_shared_project(token: str, password: Optional[str] = None): +async def access_shared_project(token: str, password: str | None = None): """通过分享链接访问项目""" if not COLLABORATION_AVAILABLE: raise HTTPException(status_code=503, detail="Collaboration module not available") @@ -7541,8 +7547,8 @@ async def delete_comment(comment_id: str, deleted_by: str = "current_user"): @app.get("/api/v1/projects/{project_id}/history") async def get_change_history( project_id: str, - entity_type: Optional[str] = None, - entity_id: Optional[str] = None, + entity_type: str | None = None, + entity_id: str | None = None, limit: int = 50, offset: int = 0 ): @@ -7749,7 +7755,7 @@ async def check_project_permissions(project_id: str, user_id: str = "current_use class FullTextSearchRequest(BaseModel): """全文搜索请求""" query: str - content_types: Optional[List[str]] = None + content_types: list[str] | None = None operator: str = "AND" # AND, OR, NOT limit: int = 20 @@ -7757,7 +7763,7 @@ class FullTextSearchRequest(BaseModel): class SemanticSearchRequest(BaseModel): """语义搜索请求""" query: str - content_types: Optional[List[str]] = None + content_types: list[str] | None = None threshold: float = 0.7 limit: int = 20 @@ -7967,7 +7973,7 @@ async def get_cache_stats( @app.post("/api/v1/cache/clear", tags=["Performance"]) async def clear_cache( - pattern: Optional[str] = None, + pattern: str | None = None, _=Depends(verify_api_key) ): """清除缓存""" @@ -7985,8 +7991,8 @@ async def clear_cache( @app.get("/api/v1/performance/metrics", tags=["Performance"]) async def get_performance_metrics( - metric_type: Optional[str] = None, - endpoint: Optional[str] = None, + metric_type: str | None = None, + endpoint: str | None = None, hours: int = 24, limit: int = 1000, _=Depends(verify_api_key) @@ -8068,8 +8074,8 @@ async def get_task_status( @app.get("/api/v1/tasks", tags=["Performance"]) async def list_tasks( - project_id: Optional[str] = None, - status: Optional[str] = None, + project_id: str | None = None, + status: str | None = None, limit: int = 50, _=Depends(verify_api_key) ): @@ -8140,15 +8146,15 @@ async def list_shards( class CreateTenantRequest(BaseModel): name: str - description: Optional[str] = None + description: str | None = None tier: str = "free" class UpdateTenantRequest(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - tier: Optional[str] = None - status: Optional[str] = None + name: str | None = None + description: str | None = None + tier: str | None = None + status: str | None = None class AddDomainRequest(BaseModel): @@ -8157,13 +8163,13 @@ class AddDomainRequest(BaseModel): class UpdateBrandingRequest(BaseModel): - logo_url: Optional[str] = None - favicon_url: Optional[str] = None - primary_color: Optional[str] = None - secondary_color: Optional[str] = None - custom_css: Optional[str] = None - custom_js: Optional[str] = None - login_page_bg: Optional[str] = None + logo_url: str | None = None + favicon_url: str | None = None + primary_color: str | None = None + secondary_color: str | None = None + custom_css: str | None = None + custom_js: str | None = None + login_page_bg: str | None = None class InviteMemberRequest(BaseModel): @@ -8172,7 +8178,7 @@ class InviteMemberRequest(BaseModel): class UpdateMemberRequest(BaseModel): - role: Optional[str] = None + role: str | None = None # Tenant Management APIs @@ -8512,7 +8518,7 @@ async def invite_member( @app.get("/api/v1/tenants/{tenant_id}/members", tags=["Tenants"]) async def list_members( tenant_id: str, - status: Optional[str] = None, + status: str | None = None, _=Depends(verify_api_key) ): """列出租户成员""" @@ -8704,13 +8710,13 @@ class TenantCreate(BaseModel): class TenantUpdate(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - status: Optional[str] = None - plan: Optional[str] = None - billing_email: Optional[str] = None - max_projects: Optional[int] = None - max_members: Optional[int] = None + name: str | None = None + description: str | None = None + status: str | None = None + plan: str | None = None + billing_email: str | None = None + max_projects: int | None = None + max_members: int | None = None class TenantResponse(BaseModel): @@ -8739,31 +8745,31 @@ class TenantDomainResponse(BaseModel): domain: str status: str verification_record: str - verification_expires_at: Optional[str] + verification_expires_at: str | None ssl_enabled: bool created_at: str - verified_at: Optional[str] + verified_at: str | None class TenantBrandingUpdate(BaseModel): - logo_url: Optional[str] = None - logo_dark_url: Optional[str] = None - favicon_url: Optional[str] = None - primary_color: Optional[str] = None - secondary_color: Optional[str] = None - accent_color: Optional[str] = None - background_color: Optional[str] = None - text_color: Optional[str] = None - dark_primary_color: Optional[str] = None - dark_background_color: Optional[str] = None - dark_text_color: Optional[str] = None - font_family: Optional[str] = None - custom_css: Optional[str] = None - custom_js: Optional[str] = None - app_name: Optional[str] = None - login_page_title: Optional[str] = None - login_page_description: Optional[str] = None - footer_text: Optional[str] = None + logo_url: str | None = None + logo_dark_url: str | None = None + favicon_url: str | None = None + primary_color: str | None = None + secondary_color: str | None = None + accent_color: str | None = None + background_color: str | None = None + text_color: str | None = None + dark_primary_color: str | None = None + dark_background_color: str | None = None + dark_text_color: str | None = None + font_family: str | None = None + custom_css: str | None = None + custom_js: str | None = None + app_name: str | None = None + login_page_title: str | None = None + login_page_description: str | None = None + footer_text: str | None = None class TenantMemberInvite(BaseModel): @@ -8780,17 +8786,17 @@ class TenantMemberResponse(BaseModel): name: str role: str status: str - invited_by: Optional[str] - invited_at: Optional[str] - joined_at: Optional[str] - last_active_at: Optional[str] + invited_by: str | None + invited_at: str | None + joined_at: str | None + last_active_at: str | None created_at: str class TenantRoleCreate(BaseModel): name: str = Field(..., description="角色名称") description: str = Field(default="", description="角色描述") - permissions: List[str] = Field(default_factory=list, description="权限列表") + permissions: list[str] = Field(default_factory=list, description="权限列表") class TenantRoleResponse(BaseModel): @@ -8798,7 +8804,7 @@ class TenantRoleResponse(BaseModel): tenant_id: str name: str description: str - permissions: List[str] + permissions: list[str] is_system: bool created_at: str @@ -8840,10 +8846,10 @@ async def create_tenant_endpoint(tenant: TenantCreate, request: Request, _=Depen raise HTTPException(status_code=400, detail=str(e)) -@app.get("/api/v1/tenants", response_model=List[TenantResponse], tags=["Tenants"]) +@app.get("/api/v1/tenants", response_model=list[TenantResponse], tags=["Tenants"]) async def list_tenants_endpoint( - status: Optional[str] = None, - plan: Optional[str] = None, + status: str | None = None, + plan: str | None = None, limit: int = 100, offset: int = 0, _=Depends(verify_api_key) @@ -8952,7 +8958,7 @@ async def add_tenant_domain_endpoint(tenant_id: str, domain: TenantDomainCreate, raise HTTPException(status_code=400, detail=str(e)) -@app.get("/api/v1/tenants/{tenant_id}/domains", response_model=List[TenantDomainResponse], tags=["Tenants"]) +@app.get("/api/v1/tenants/{tenant_id}/domains", response_model=list[TenantDomainResponse], tags=["Tenants"]) async def list_tenant_domains_endpoint(tenant_id: str, _=Depends(verify_api_key)): """获取租户的所有域名""" if not TENANT_MANAGER_AVAILABLE: @@ -9109,11 +9115,11 @@ async def accept_invitation_endpoint(token: str, user_id: str): return member.to_dict() -@app.get("/api/v1/tenants/{tenant_id}/members", response_model=List[TenantMemberResponse], tags=["Tenants"]) +@app.get("/api/v1/tenants/{tenant_id}/members", response_model=list[TenantMemberResponse], tags=["Tenants"]) async def list_tenant_members_endpoint( tenant_id: str, - status: Optional[str] = None, - role: Optional[str] = None, + status: str | None = None, + role: str | None = None, _=Depends(verify_api_key) ): """列出租户成员""" @@ -9190,7 +9196,7 @@ async def remove_tenant_member_endpoint( # Tenant Role API -@app.get("/api/v1/tenants/{tenant_id}/roles", response_model=List[TenantRoleResponse], tags=["Tenants"]) +@app.get("/api/v1/tenants/{tenant_id}/roles", response_model=list[TenantRoleResponse], tags=["Tenants"]) async def list_tenant_roles_endpoint(tenant_id: str, _=Depends(verify_api_key)): """列出租户角色""" if not TENANT_MANAGER_AVAILABLE: @@ -9229,7 +9235,7 @@ async def create_tenant_role_endpoint( async def update_role_permissions_endpoint( tenant_id: str, role_id: str, - permissions: List[str], + permissions: list[str], _=Depends(verify_api_key) ): """更新角色权限""" @@ -9282,9 +9288,9 @@ async def list_tenant_permissions_endpoint(_=Depends(verify_api_key)): # Tenant Resolution API @app.get("/api/v1/tenants/resolve", tags=["Tenants"]) async def resolve_tenant_endpoint( - host: Optional[str] = None, - slug: Optional[str] = None, - tenant_id: Optional[str] = None, + host: str | None = None, + slug: str | None = None, + tenant_id: str | None = None, _=Depends(verify_api_key) ): """从请求信息解析租户""" @@ -9327,7 +9333,7 @@ async def get_tenant_context_endpoint(tenant_id: str, _=Depends(verify_api_key)) class CreateSubscriptionRequest(BaseModel): plan_id: str = Field(..., description="订阅计划ID") billing_cycle: str = Field(default="monthly", description="计费周期: monthly/yearly") - payment_provider: Optional[str] = Field(default=None, description="支付提供商: stripe/alipay/wechat") + payment_provider: str | None = Field(default=None, description="支付提供商: stripe/alipay/wechat") trial_days: int = Field(default=0, description="试用天数") @@ -9344,7 +9350,7 @@ class CreatePaymentRequest(BaseModel): amount: float = Field(..., description="支付金额") currency: str = Field(default="CNY", description="货币") provider: str = Field(..., description="支付提供商: stripe/alipay/wechat") - payment_method: Optional[str] = Field(default=None, description="支付方式") + payment_method: str | None = Field(default=None, description="支付方式") class RequestRefundRequest(BaseModel): @@ -9355,14 +9361,14 @@ class RequestRefundRequest(BaseModel): class ProcessRefundRequest(BaseModel): action: str = Field(..., description="操作: approve/reject") - reason: Optional[str] = Field(default=None, description="拒绝原因(拒绝时必填)") + reason: str | None = Field(default=None, description="拒绝原因(拒绝时必填)") class RecordUsageRequest(BaseModel): resource_type: str = Field(..., description="资源类型: transcription/storage/api_call/export") quantity: float = Field(..., description="使用量") unit: str = Field(..., description="单位: minutes/mb/count/page") - description: Optional[str] = Field(default=None, description="描述") + description: str | None = Field(default=None, description="描述") class CreateCheckoutSessionRequest(BaseModel): @@ -9607,8 +9613,8 @@ async def record_usage( @app.get("/api/v1/tenants/{tenant_id}/usage", tags=["Subscriptions"]) async def get_usage_summary( tenant_id: str, - start_date: Optional[str] = Query(default=None, description="开始日期 (ISO格式)"), - end_date: Optional[str] = Query(default=None, description="结束日期 (ISO格式)"), + start_date: str | None = Query(default=None, description="开始日期 (ISO格式)"), + end_date: str | None = Query(default=None, description="结束日期 (ISO格式)"), _=Depends(verify_api_key) ): """获取用量汇总""" @@ -9629,7 +9635,7 @@ async def get_usage_summary( @app.get("/api/v1/tenants/{tenant_id}/payments", tags=["Subscriptions"]) async def list_payments( tenant_id: str, - status: Optional[str] = Query(default=None, description="支付状态过滤"), + status: str | None = Query(default=None, description="支付状态过滤"), limit: int = Query(default=100, description="返回数量限制"), offset: int = Query(default=0, description="偏移量"), _=Depends(verify_api_key) @@ -9698,7 +9704,7 @@ async def get_payment( @app.get("/api/v1/tenants/{tenant_id}/invoices", tags=["Subscriptions"]) async def list_invoices( tenant_id: str, - status: Optional[str] = Query(default=None, description="发票状态过滤"), + status: str | None = Query(default=None, description="发票状态过滤"), limit: int = Query(default=100, description="返回数量限制"), offset: int = Query(default=0, description="偏移量"), _=Depends(verify_api_key) @@ -9805,7 +9811,7 @@ async def request_refund( @app.get("/api/v1/tenants/{tenant_id}/refunds", tags=["Subscriptions"]) async def list_refunds( tenant_id: str, - status: Optional[str] = Query(default=None, description="退款状态过滤"), + status: str | None = Query(default=None, description="退款状态过滤"), limit: int = Query(default=100, description="返回数量限制"), offset: int = Query(default=0, description="偏移量"), _=Depends(verify_api_key) @@ -9888,8 +9894,8 @@ async def process_refund( @app.get("/api/v1/tenants/{tenant_id}/billing-history", tags=["Subscriptions"]) async def get_billing_history( tenant_id: str, - start_date: Optional[str] = Query(default=None, description="开始日期 (ISO格式)"), - end_date: Optional[str] = Query(default=None, description="结束日期 (ISO格式)"), + start_date: str | None = Query(default=None, description="开始日期 (ISO格式)"), + end_date: str | None = Query(default=None, description="结束日期 (ISO格式)"), limit: int = Query(default=100, description="返回数量限制"), offset: int = Query(default=0, description="偏移量"), _=Depends(verify_api_key) @@ -10058,42 +10064,42 @@ async def wechat_webhook(request: Request): class SSOConfigCreate(BaseModel): provider: str = Field(..., description="SSO 提供商: wechat_work/dingtalk/feishu/okta/azure_ad/google/custom_saml") - entity_id: Optional[str] = Field(default=None, description="SAML Entity ID") - sso_url: Optional[str] = Field(default=None, description="SAML SSO URL") - slo_url: Optional[str] = Field(default=None, description="SAML SLO URL") - certificate: Optional[str] = Field(default=None, description="SAML X.509 证书") - metadata_url: Optional[str] = Field(default=None, description="SAML 元数据 URL") - metadata_xml: Optional[str] = Field(default=None, description="SAML 元数据 XML") - client_id: Optional[str] = Field(default=None, description="OAuth Client ID") - client_secret: Optional[str] = Field(default=None, description="OAuth Client Secret") - authorization_url: Optional[str] = Field(default=None, description="OAuth 授权 URL") - token_url: Optional[str] = Field(default=None, description="OAuth Token URL") - userinfo_url: Optional[str] = Field(default=None, description="OAuth UserInfo URL") - scopes: List[str] = Field(default=["openid", "email", "profile"], description="OAuth Scopes") - attribute_mapping: Optional[Dict[str, str]] = Field(default=None, description="属性映射") + entity_id: str | None = Field(default=None, description="SAML Entity ID") + sso_url: str | None = Field(default=None, description="SAML SSO URL") + slo_url: str | None = Field(default=None, description="SAML SLO URL") + certificate: str | None = Field(default=None, description="SAML X.509 证书") + metadata_url: str | None = Field(default=None, description="SAML 元数据 URL") + metadata_xml: str | None = Field(default=None, description="SAML 元数据 XML") + client_id: str | None = Field(default=None, description="OAuth Client ID") + client_secret: str | None = Field(default=None, description="OAuth Client Secret") + authorization_url: str | None = Field(default=None, description="OAuth 授权 URL") + token_url: str | None = Field(default=None, description="OAuth Token URL") + userinfo_url: str | None = Field(default=None, description="OAuth UserInfo URL") + scopes: list[str] = Field(default=["openid", "email", "profile"], description="OAuth Scopes") + attribute_mapping: dict[str, str] | None = Field(default=None, description="属性映射") auto_provision: bool = Field(default=True, description="自动创建用户") default_role: str = Field(default="member", description="默认角色") - domain_restriction: List[str] = Field(default_factory=list, description="允许的邮箱域名") + domain_restriction: list[str] = Field(default_factory=list, description="允许的邮箱域名") class SSOConfigUpdate(BaseModel): - entity_id: Optional[str] = None - sso_url: Optional[str] = None - slo_url: Optional[str] = None - certificate: Optional[str] = None - metadata_url: Optional[str] = None - metadata_xml: Optional[str] = None - client_id: Optional[str] = None - client_secret: Optional[str] = None - authorization_url: Optional[str] = None - token_url: Optional[str] = None - userinfo_url: Optional[str] = None - scopes: Optional[List[str]] = None - attribute_mapping: Optional[Dict[str, str]] = None - auto_provision: Optional[bool] = None - default_role: Optional[str] = None - domain_restriction: Optional[List[str]] = None - status: Optional[str] = None + entity_id: str | None = None + sso_url: str | None = None + slo_url: str | None = None + certificate: str | None = None + metadata_url: str | None = None + metadata_xml: str | None = None + client_id: str | None = None + client_secret: str | None = None + authorization_url: str | None = None + token_url: str | None = None + userinfo_url: str | None = None + scopes: list[str] | None = None + attribute_mapping: dict[str, str] | None = None + auto_provision: bool | None = None + default_role: str | None = None + domain_restriction: list[str] | None = None + status: str | None = None class SCIMConfigCreate(BaseModel): @@ -10101,53 +10107,53 @@ class SCIMConfigCreate(BaseModel): scim_base_url: str = Field(..., description="SCIM 服务端地址") scim_token: str = Field(..., description="SCIM 访问令牌") sync_interval_minutes: int = Field(default=60, description="同步间隔(分钟)") - attribute_mapping: Optional[Dict[str, str]] = Field(default=None, description="属性映射") - sync_rules: Optional[Dict[str, Any]] = Field(default=None, description="同步规则") + attribute_mapping: dict[str, str] | None = Field(default=None, description="属性映射") + sync_rules: dict[str, Any] | None = Field(default=None, description="同步规则") class SCIMConfigUpdate(BaseModel): - scim_base_url: Optional[str] = None - scim_token: Optional[str] = None - sync_interval_minutes: Optional[int] = None - attribute_mapping: Optional[Dict[str, str]] = None - sync_rules: Optional[Dict[str, Any]] = None - status: Optional[str] = None + scim_base_url: str | None = None + scim_token: str | None = None + sync_interval_minutes: int | None = None + attribute_mapping: dict[str, str] | None = None + sync_rules: dict[str, Any] | None = None + status: str | None = None class AuditExportCreate(BaseModel): export_format: str = Field(..., description="导出格式: json/csv/pdf/xlsx") start_date: str = Field(..., description="开始日期 (ISO 格式)") end_date: str = Field(..., description="结束日期 (ISO 格式)") - filters: Optional[Dict[str, Any]] = Field(default_factory=dict, description="过滤条件") - compliance_standard: Optional[str] = Field(default=None, description="合规标准: soc2/iso27001/gdpr/hipaa/pci_dss") + filters: dict[str, Any] | None = Field(default_factory=dict, description="过滤条件") + compliance_standard: str | None = Field(default=None, description="合规标准: soc2/iso27001/gdpr/hipaa/pci_dss") class RetentionPolicyCreate(BaseModel): name: str = Field(..., description="策略名称") - description: Optional[str] = Field(default=None, description="策略描述") + description: str | None = Field(default=None, description="策略描述") resource_type: str = Field(..., description="资源类型: project/transcript/entity/audit_log/user_data") retention_days: int = Field(..., description="保留天数") action: str = Field(..., description="动作: archive/delete/anonymize") - conditions: Optional[Dict[str, Any]] = Field(default_factory=dict, description="触发条件") + conditions: dict[str, Any] | None = Field(default_factory=dict, description="触发条件") auto_execute: bool = Field(default=False, description="自动执行") - execute_at: Optional[str] = Field(default=None, description="执行时间 (cron 表达式)") + execute_at: str | None = Field(default=None, description="执行时间 (cron 表达式)") notify_before_days: int = Field(default=7, description="提前通知天数") - archive_location: Optional[str] = Field(default=None, description="归档位置") + archive_location: str | None = Field(default=None, description="归档位置") archive_encryption: bool = Field(default=True, description="归档加密") class RetentionPolicyUpdate(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - retention_days: Optional[int] = None - action: Optional[str] = None - conditions: Optional[Dict[str, Any]] = None - auto_execute: Optional[bool] = None - execute_at: Optional[str] = None - notify_before_days: Optional[int] = None - archive_location: Optional[str] = None - archive_encryption: Optional[bool] = None - is_active: Optional[bool] = None + name: str | None = None + description: str | None = None + retention_days: int | None = None + action: str | None = None + conditions: dict[str, Any] | None = None + auto_execute: bool | None = None + execute_at: str | None = None + notify_before_days: int | None = None + archive_location: str | None = None + archive_encryption: bool | None = None + is_active: bool | None = None # SSO/SAML APIs @@ -10688,7 +10694,7 @@ async def create_retention_policy_endpoint( @app.get("/api/v1/tenants/{tenant_id}/retention-policies", tags=["Enterprise"]) async def list_retention_policies_endpoint( tenant_id: str, - resource_type: Optional[str] = Query(default=None, description="资源类型过滤"), + resource_type: str | None = Query(default=None, description="资源类型过滤"), _=Depends(verify_api_key) ): """列出数据保留策略""" @@ -10874,32 +10880,32 @@ class TranslationCreate(BaseModel): key: str = Field(..., description="翻译键") value: str = Field(..., description="翻译值") namespace: str = Field(default="common", description="命名空间") - context: Optional[str] = Field(default=None, description="上下文说明") + context: str | None = Field(default=None, description="上下文说明") class TranslationUpdate(BaseModel): value: str = Field(..., description="翻译值") - context: Optional[str] = Field(default=None, description="上下文说明") + context: str | None = Field(default=None, description="上下文说明") class LocalizationSettingsCreate(BaseModel): default_language: str = Field(default="en", description="默认语言") - supported_languages: List[str] = Field(default=["en"], description="支持的语言列表") + supported_languages: list[str] = Field(default=["en"], description="支持的语言列表") default_currency: str = Field(default="USD", description="默认货币") - supported_currencies: List[str] = Field(default=["USD"], description="支持的货币列表") + supported_currencies: list[str] = Field(default=["USD"], description="支持的货币列表") default_timezone: str = Field(default="UTC", description="默认时区") region_code: str = Field(default="global", description="区域代码") data_residency: str = Field(default="regional", description="数据驻留策略") class LocalizationSettingsUpdate(BaseModel): - default_language: Optional[str] = None - supported_languages: Optional[List[str]] = None - default_currency: Optional[str] = None - supported_currencies: Optional[List[str]] = None - default_timezone: Optional[str] = None - region_code: Optional[str] = None - data_residency: Optional[str] = None + default_language: str | None = None + supported_languages: list[str] | None = None + default_currency: str | None = None + supported_currencies: list[str] | None = None + default_timezone: str | None = None + region_code: str | None = None + data_residency: str | None = None class DataCenterMappingRequest(BaseModel): @@ -10909,13 +10915,13 @@ class DataCenterMappingRequest(BaseModel): class FormatDateTimeRequest(BaseModel): timestamp: str = Field(..., description="ISO格式时间戳") - timezone: Optional[str] = Field(default=None, description="目标时区") + timezone: str | None = Field(default=None, description="目标时区") format_type: str = Field(default="datetime", description="格式类型: date/time/datetime") class FormatNumberRequest(BaseModel): number: float = Field(..., description="数字") - decimal_places: Optional[int] = Field(default=None, description="小数位数") + decimal_places: int | None = Field(default=None, description="小数位数") class FormatCurrencyRequest(BaseModel): @@ -11037,8 +11043,8 @@ async def delete_translation( @app.get("/api/v1/translations", tags=["Localization"]) async def list_translations( - language: Optional[str] = Query(default=None, description="语言代码"), - namespace: Optional[str] = Query(default=None, description="命名空间"), + language: str | None = Query(default=None, description="语言代码"), + namespace: str | None = Query(default=None, description="命名空间"), limit: int = Query(default=1000, description="返回数量限制"), offset: int = Query(default=0, description="偏移量"), _=Depends(verify_api_key) @@ -11131,8 +11137,8 @@ async def get_language(code: str): # Data Center APIs @app.get("/api/v1/data-centers", tags=["Localization"]) async def list_data_centers( - status: Optional[str] = Query(default=None, description="状态过滤"), - region: Optional[str] = Query(default=None, description="区域过滤") + status: str | None = Query(default=None, description="状态过滤"), + region: str | None = Query(default=None, description="区域过滤") ): """列出数据中心""" if not LOCALIZATION_MANAGER_AVAILABLE: @@ -11253,8 +11259,8 @@ async def set_tenant_data_center( # Payment Method APIs @app.get("/api/v1/payment-methods", tags=["Localization"]) async def list_payment_methods( - country_code: Optional[str] = Query(default=None, description="国家代码"), - currency: Optional[str] = Query(default=None, description="货币代码"), + country_code: str | None = Query(default=None, description="国家代码"), + currency: str | None = Query(default=None, description="货币代码"), active_only: bool = Query(default=True, description="仅返回激活的支付方式") ): """列出支付方式""" @@ -11306,7 +11312,7 @@ async def get_localized_payment_methods( # Country APIs @app.get("/api/v1/countries", tags=["Localization"]) async def list_countries( - region: Optional[str] = Query(default=None, description="区域过滤"), + region: str | None = Query(default=None, description="区域过滤"), active_only: bool = Query(default=True, description="仅返回激活的国家") ): """列出国家/地区""" @@ -11576,8 +11582,8 @@ async def convert_timezone_endpoint( @app.get("/api/v1/detect/locale", tags=["Localization"]) async def detect_locale( - accept_language: Optional[str] = Header(default=None, description="Accept-Language 头"), - ip_country: Optional[str] = Query(default=None, description="IP国家代码") + accept_language: str | None = Header(default=None, description="Accept-Language 头"), + ip_country: str | None = Query(default=None, description="IP国家代码") ): """检测用户本地化偏好""" if not LOCALIZATION_MANAGER_AVAILABLE: @@ -11616,14 +11622,14 @@ class CreateCustomModelRequest(BaseModel): name: str description: str model_type: str - training_data: Dict - hyperparameters: Dict = Field(default_factory=lambda: {"epochs": 10, "learning_rate": 0.001}) + training_data: dict + hyperparameters: dict = Field(default_factory=lambda: {"epochs": 10, "learning_rate": 0.001}) class AddTrainingSampleRequest(BaseModel): text: str - entities: List[Dict] - metadata: Dict = Field(default_factory=dict) + entities: list[dict] + metadata: dict = Field(default_factory=dict) class TrainModelRequest(BaseModel): @@ -11638,16 +11644,16 @@ class PredictRequest(BaseModel): class MultimodalAnalysisRequest(BaseModel): provider: str input_type: str - input_urls: List[str] + input_urls: list[str] prompt: str class CreateKGRAGRequest(BaseModel): name: str description: str - kg_config: Dict - retrieval_config: Dict - generation_config: Dict + kg_config: dict + retrieval_config: dict + generation_config: dict class KGRAGQueryRequest(BaseModel): @@ -11659,20 +11665,20 @@ class SmartSummaryRequest(BaseModel): source_type: str source_id: str summary_type: str - content_data: Dict + content_data: dict class CreatePredictionModelRequest(BaseModel): name: str prediction_type: str - target_entity_type: Optional[str] = None - features: List[str] - model_config: Dict + target_entity_type: str | None = None + features: list[str] + model_config: dict class PredictDataRequest(BaseModel): model_id: str - input_data: Dict + input_data: dict class PredictionFeedbackRequest(BaseModel): @@ -11718,8 +11724,8 @@ async def create_custom_model( @app.get("/api/v1/tenants/{tenant_id}/ai/custom-models", tags=["AI Enhancement"]) async def list_custom_models( tenant_id: str, - model_type: Optional[str] = Query(default=None, description="模型类型过滤"), - status: Optional[str] = Query(default=None, description="状态过滤") + model_type: str | None = Query(default=None, description="模型类型过滤"), + status: str | None = Query(default=None, description="状态过滤") ): """列出自定义模型""" if not AI_MANAGER_AVAILABLE: @@ -11904,7 +11910,7 @@ async def analyze_multimodal( @app.get("/api/v1/tenants/{tenant_id}/ai/multimodal", tags=["AI Enhancement"]) async def list_multimodal_analyses( tenant_id: str, - project_id: Optional[str] = Query(default=None, description="项目ID过滤") + project_id: str | None = Query(default=None, description="项目ID过滤") ): """获取多模态分析历史""" if not AI_MANAGER_AVAILABLE: @@ -11966,7 +11972,7 @@ async def create_kg_rag( @app.get("/api/v1/tenants/{tenant_id}/ai/kg-rag", tags=["AI Enhancement"]) async def list_kg_rags( tenant_id: str, - project_id: Optional[str] = Query(default=None, description="项目ID过滤") + project_id: str | None = Query(default=None, description="项目ID过滤") ): """列出知识图谱 RAG 配置""" if not AI_MANAGER_AVAILABLE: @@ -11993,8 +11999,8 @@ async def list_kg_rags( @app.post("/api/v1/ai/kg-rag/query", tags=["AI Enhancement"]) async def query_kg_rag( request: KGRAGQueryRequest, - project_entities: List[Dict] = Body(default=[], description="项目实体列表"), - project_relations: List[Dict] = Body(default=[], description="项目关系列表") + project_entities: list[dict] = Body(default=[], description="项目实体列表"), + project_relations: list[dict] = Body(default=[], description="项目关系列表") ): """基于知识图谱的 RAG 查询""" if not AI_MANAGER_AVAILABLE: @@ -12065,8 +12071,8 @@ async def generate_smart_summary( async def list_smart_summaries( tenant_id: str, project_id: str, - source_type: Optional[str] = Query(default=None, description="来源类型过滤"), - source_id: Optional[str] = Query(default=None, description="来源ID过滤") + source_type: str | None = Query(default=None, description="来源类型过滤"), + source_id: str | None = Query(default=None, description="来源ID过滤") ): """获取智能摘要列表""" if not AI_MANAGER_AVAILABLE: @@ -12118,7 +12124,7 @@ async def create_prediction_model( @app.get("/api/v1/tenants/{tenant_id}/ai/prediction-models", tags=["AI Enhancement"]) async def list_prediction_models( tenant_id: str, - project_id: Optional[str] = Query(default=None, description="项目ID过滤") + project_id: str | None = Query(default=None, description="项目ID过滤") ): """列出预测模型""" if not AI_MANAGER_AVAILABLE: @@ -12178,7 +12184,7 @@ async def get_prediction_model(model_id: str): @app.post("/api/v1/ai/prediction-models/{model_id}/train", tags=["AI Enhancement"]) async def train_prediction_model( model_id: str, - historical_data: List[Dict] = Body(..., description="历史训练数据") + historical_data: list[dict] = Body(..., description="历史训练数据") ): """训练预测模型""" if not AI_MANAGER_AVAILABLE: @@ -12276,38 +12282,38 @@ class TrackEventRequest(BaseModel): user_id: str event_type: str event_name: str - properties: Dict = Field(default_factory=dict) - session_id: Optional[str] = None - device_info: Dict = Field(default_factory=dict) - referrer: Optional[str] = None - utm_source: Optional[str] = None - utm_medium: Optional[str] = None - utm_campaign: Optional[str] = None + properties: dict = Field(default_factory=dict) + session_id: str | None = None + device_info: dict = Field(default_factory=dict) + referrer: str | None = None + utm_source: str | None = None + utm_medium: str | None = None + utm_campaign: str | None = None class CreateFunnelRequest(BaseModel): name: str description: str = "" - steps: List[Dict] # [{"name": "", "event_name": ""}] + steps: list[dict] # [{"name": "", "event_name": ""}] class CreateExperimentRequest(BaseModel): name: str description: str = "" hypothesis: str = "" - variants: List[Dict] # [{"id": "", "name": "", "is_control": true/false}] + variants: list[dict] # [{"id": "", "name": "", "is_control": true/false}] traffic_allocation: str = "random" # random, stratified, targeted - traffic_split: Dict[str, float] = Field(default_factory=dict) - target_audience: Dict = Field(default_factory=dict) + traffic_split: dict[str, float] = Field(default_factory=dict) + target_audience: dict = Field(default_factory=dict) primary_metric: str - secondary_metrics: List[str] = Field(default_factory=list) + secondary_metrics: list[str] = Field(default_factory=list) min_sample_size: int = 100 confidence_level: float = 0.95 class AssignVariantRequest(BaseModel): user_id: str - user_attributes: Dict = Field(default_factory=dict) + user_attributes: dict = Field(default_factory=dict) class RecordMetricRequest(BaseModel): @@ -12322,26 +12328,26 @@ class CreateEmailTemplateRequest(BaseModel): template_type: str # welcome, onboarding, feature_announcement, churn_recovery, etc. subject: str html_content: str - text_content: Optional[str] = None - variables: List[str] = Field(default_factory=list) + text_content: str | None = None + variables: list[str] = Field(default_factory=list) from_name: str = "InsightFlow" from_email: str = "noreply@insightflow.io" - reply_to: Optional[str] = None + reply_to: str | None = None class CreateCampaignRequest(BaseModel): name: str template_id: str - recipients: List[Dict] # [{"user_id": "", "email": ""}] - scheduled_at: Optional[str] = None + recipients: list[dict] # [{"user_id": "", "email": ""}] + scheduled_at: str | None = None class CreateAutomationWorkflowRequest(BaseModel): name: str description: str = "" trigger_type: str # user_signup, user_login, subscription_created, inactivity, etc. - trigger_conditions: Dict = Field(default_factory=dict) - actions: List[Dict] # [{"type": "send_email", "template_id": ""}] + trigger_conditions: dict = Field(default_factory=dict) + actions: list[dict] # [{"type": "send_email", "template_id": ""}] class CreateReferralProgramRequest(BaseModel): @@ -12438,8 +12444,8 @@ async def get_analytics_dashboard(tenant_id: str): @app.get("/api/v1/analytics/summary/{tenant_id}", tags=["Growth & Analytics"]) async def get_analytics_summary( tenant_id: str, - start_date: Optional[str] = None, - end_date: Optional[str] = None + start_date: str | None = None, + end_date: str | None = None ): """获取用户分析汇总""" if not GROWTH_MANAGER_AVAILABLE: @@ -12513,8 +12519,8 @@ async def create_funnel_endpoint(request: CreateFunnelRequest, created_by: str = @app.get("/api/v1/analytics/funnels/{funnel_id}/analyze", tags=["Growth & Analytics"]) async def analyze_funnel_endpoint( funnel_id: str, - period_start: Optional[str] = None, - period_end: Optional[str] = None + period_start: str | None = None, + period_end: str | None = None ): """分析漏斗转化率""" if not GROWTH_MANAGER_AVAILABLE: @@ -12545,7 +12551,7 @@ async def analyze_funnel_endpoint( async def calculate_retention( tenant_id: str, cohort_date: str, - periods: Optional[str] = None # JSON array: [1, 3, 7, 14, 30] + periods: str | None = None # JSON array: [1, 3, 7, 14, 30] ): """计算留存率""" if not GROWTH_MANAGER_AVAILABLE: @@ -12602,7 +12608,7 @@ async def create_experiment_endpoint(request: CreateExperimentRequest, created_b @app.get("/api/v1/experiments", tags=["Growth & Analytics"]) -async def list_experiments(status: Optional[str] = None): +async def list_experiments(status: str | None = None): """列出实验""" if not GROWTH_MANAGER_AVAILABLE: raise HTTPException(status_code=503, detail="Growth manager not available") @@ -12793,7 +12799,7 @@ async def create_email_template_endpoint(request: CreateEmailTemplateRequest): @app.get("/api/v1/email/templates", tags=["Growth & Analytics"]) -async def list_email_templates(template_type: Optional[str] = None): +async def list_email_templates(template_type: str | None = None): """列出邮件模板""" if not GROWTH_MANAGER_AVAILABLE: raise HTTPException(status_code=503, detail="Growth manager not available") @@ -12845,7 +12851,7 @@ async def get_email_template_endpoint(template_id: str): @app.post("/api/v1/email/templates/{template_id}/render", tags=["Growth & Analytics"]) -async def render_template_endpoint(template_id: str, variables: Dict): +async def render_template_endpoint(template_id: str, variables: dict): """渲染邮件模板""" if not GROWTH_MANAGER_AVAILABLE: raise HTTPException(status_code=503, detail="Growth manager not available") @@ -13087,9 +13093,13 @@ async def check_team_incentive_eligibility( # Phase 8: Developer Ecosystem Manager try: from developer_ecosystem_manager import ( - DeveloperEcosystemManager, SDKLanguage, - SDKStatus, TemplateCategory, TemplateStatus, DeveloperStatus, - PluginCategory + DeveloperEcosystemManager, + DeveloperStatus, + PluginCategory, + SDKLanguage, + SDKStatus, + TemplateCategory, + TemplateStatus, ) DEVELOPER_ECOSYSTEM_AVAILABLE = True except ImportError as e: @@ -13109,19 +13119,19 @@ class SDKReleaseCreate(BaseModel): repository_url: str = "" package_name: str min_platform_version: str = "1.0.0" - dependencies: List[Dict] = Field(default_factory=list) + dependencies: list[dict] = Field(default_factory=list) file_size: int = 0 checksum: str = "" class SDKReleaseUpdate(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - changelog: Optional[str] = None - download_url: Optional[str] = None - documentation_url: Optional[str] = None - repository_url: Optional[str] = None - status: Optional[str] = None + name: str | None = None + description: str | None = None + changelog: str | None = None + download_url: str | None = None + documentation_url: str | None = None + repository_url: str | None = None + status: str | None = None class SDKVersionCreate(BaseModel): @@ -13137,14 +13147,14 @@ class TemplateCreate(BaseModel): name: str description: str category: str - subcategory: Optional[str] = None - tags: List[str] = Field(default_factory=list) + subcategory: str | None = None + tags: list[str] = Field(default_factory=list) price: float = 0.0 currency: str = "CNY" - preview_image_url: Optional[str] = None - demo_url: Optional[str] = None - documentation_url: Optional[str] = None - download_url: Optional[str] = None + preview_image_url: str | None = None + demo_url: str | None = None + documentation_url: str | None = None + download_url: str | None = None version: str = "1.0.0" min_platform_version: str = "1.0.0" file_size: int = 0 @@ -13161,17 +13171,17 @@ class PluginCreate(BaseModel): name: str description: str category: str - tags: List[str] = Field(default_factory=list) + tags: list[str] = Field(default_factory=list) price: float = 0.0 currency: str = "CNY" pricing_model: str = "free" - preview_image_url: Optional[str] = None - demo_url: Optional[str] = None - documentation_url: Optional[str] = None - repository_url: Optional[str] = None - download_url: Optional[str] = None - webhook_url: Optional[str] = None - permissions: List[str] = Field(default_factory=list) + preview_image_url: str | None = None + demo_url: str | None = None + documentation_url: str | None = None + repository_url: str | None = None + download_url: str | None = None + webhook_url: str | None = None + permissions: list[str] = Field(default_factory=list) version: str = "1.0.0" min_platform_version: str = "1.0.0" file_size: int = 0 @@ -13187,18 +13197,18 @@ class PluginReviewCreate(BaseModel): class DeveloperProfileCreate(BaseModel): display_name: str email: str - bio: Optional[str] = None - website: Optional[str] = None - github_url: Optional[str] = None - avatar_url: Optional[str] = None + bio: str | None = None + website: str | None = None + github_url: str | None = None + avatar_url: str | None = None class DeveloperProfileUpdate(BaseModel): - display_name: Optional[str] = None - bio: Optional[str] = None - website: Optional[str] = None - github_url: Optional[str] = None - avatar_url: Optional[str] = None + display_name: str | None = None + bio: str | None = None + website: str | None = None + github_url: str | None = None + avatar_url: str | None = None class CodeExampleCreate(BaseModel): @@ -13208,25 +13218,25 @@ class CodeExampleCreate(BaseModel): category: str code: str explanation: str = "" - tags: List[str] = Field(default_factory=list) - sdk_id: Optional[str] = None - api_endpoints: List[str] = Field(default_factory=list) + tags: list[str] = Field(default_factory=list) + sdk_id: str | None = None + api_endpoints: list[str] = Field(default_factory=list) class PortalConfigCreate(BaseModel): name: str description: str = "" theme: str = "default" - custom_css: Optional[str] = None - custom_js: Optional[str] = None - logo_url: Optional[str] = None - favicon_url: Optional[str] = None + custom_css: str | None = None + custom_js: str | None = None + logo_url: str | None = None + favicon_url: str | None = None primary_color: str = "#1890ff" secondary_color: str = "#52c41a" support_email: str = "support@insightflow.io" - support_url: Optional[str] = None - github_url: Optional[str] = None - discord_url: Optional[str] = None + support_url: str | None = None + github_url: str | None = None + discord_url: str | None = None api_base_url: str = "https://api.insightflow.io" @@ -13287,9 +13297,9 @@ async def create_sdk_release_endpoint( @app.get("/api/v1/developer/sdks", tags=["Developer Ecosystem"]) async def list_sdk_releases_endpoint( - language: Optional[str] = Query(default=None, description="SDK语言过滤"), - status: Optional[str] = Query(default=None, description="状态过滤"), - search: Optional[str] = Query(default=None, description="搜索关键词") + language: str | None = Query(default=None, description="SDK语言过滤"), + status: str | None = Query(default=None, description="状态过滤"), + search: str | None = Query(default=None, description="搜索关键词") ): """列出 SDK 发布""" if not DEVELOPER_ECOSYSTEM_AVAILABLE: @@ -13507,12 +13517,12 @@ async def create_template_endpoint( @app.get("/api/v1/developer/templates", tags=["Developer Ecosystem"]) async def list_templates_endpoint( - category: Optional[str] = Query(default=None, description="分类过滤"), - status: Optional[str] = Query(default=None, description="状态过滤"), - search: Optional[str] = Query(default=None, description="搜索关键词"), - author_id: Optional[str] = Query(default=None, description="作者ID过滤"), - min_price: Optional[float] = Query(default=None, description="最低价格"), - max_price: Optional[float] = Query(default=None, description="最高价格"), + category: str | None = Query(default=None, description="分类过滤"), + status: str | None = Query(default=None, description="状态过滤"), + search: str | None = Query(default=None, description="搜索关键词"), + author_id: str | None = Query(default=None, description="作者ID过滤"), + min_price: float | None = Query(default=None, description="最低价格"), + max_price: float | None = Query(default=None, description="最高价格"), sort_by: str = Query(default="created_at", description="排序方式") ): """列出模板""" @@ -13770,10 +13780,10 @@ async def create_developer_plugin_endpoint( @app.get("/api/v1/developer/plugins", tags=["Developer Ecosystem"]) async def list_developer_plugins_endpoint( - category: Optional[str] = Query(default=None, description="分类过滤"), - status: Optional[str] = Query(default=None, description="状态过滤"), - search: Optional[str] = Query(default=None, description="搜索关键词"), - author_id: Optional[str] = Query(default=None, description="作者ID过滤"), + category: str | None = Query(default=None, description="分类过滤"), + status: str | None = Query(default=None, description="状态过滤"), + search: str | None = Query(default=None, description="搜索关键词"), + author_id: str | None = Query(default=None, description="作者ID过滤"), sort_by: str = Query(default="created_at", description="排序方式") ): """列出插件""" @@ -13979,8 +13989,8 @@ async def get_plugin_reviews_endpoint( @app.get("/api/v1/developer/revenues/{developer_id}", tags=["Developer Ecosystem"]) async def get_developer_revenues_endpoint( developer_id: str, - start_date: Optional[str] = Query(default=None, description="开始日期 (ISO格式)"), - end_date: Optional[str] = Query(default=None, description="结束日期 (ISO格式)") + start_date: str | None = Query(default=None, description="开始日期 (ISO格式)"), + end_date: str | None = Query(default=None, description="结束日期 (ISO格式)") ): """获取开发者收益记录""" if not DEVELOPER_ECOSYSTEM_AVAILABLE: @@ -14196,10 +14206,10 @@ async def create_code_example_endpoint( @app.get("/api/v1/developer/code-examples", tags=["Developer Ecosystem"]) async def list_code_examples_endpoint( - language: Optional[str] = Query(default=None, description="编程语言过滤"), - category: Optional[str] = Query(default=None, description="分类过滤"), - sdk_id: Optional[str] = Query(default=None, description="SDK ID过滤"), - search: Optional[str] = Query(default=None, description="搜索关键词") + language: str | None = Query(default=None, description="编程语言过滤"), + category: str | None = Query(default=None, description="分类过滤"), + sdk_id: str | None = Query(default=None, description="SDK ID过滤"), + search: str | None = Query(default=None, description="搜索关键词") ): """列出代码示例""" if not DEVELOPER_ECOSYSTEM_AVAILABLE: @@ -14435,9 +14445,9 @@ class AlertRuleCreate(BaseModel): threshold: float = Field(..., description="阈值") duration: int = Field(default=60, description="持续时间(秒)") evaluation_interval: int = Field(default=60, description="评估间隔(秒)") - channels: List[str] = Field(default_factory=list, description="告警渠道ID列表") - labels: Dict = Field(default_factory=dict, description="标签") - annotations: Dict = Field(default_factory=dict, description="注释") + channels: list[str] = Field(default_factory=list, description="告警渠道ID列表") + labels: dict = Field(default_factory=dict, description="标签") + annotations: dict = Field(default_factory=dict, description="注释") class AlertRuleResponse(BaseModel): @@ -14451,9 +14461,9 @@ class AlertRuleResponse(BaseModel): threshold: float duration: int evaluation_interval: int - channels: List[str] - labels: Dict - annotations: Dict + channels: list[str] + labels: dict + annotations: dict is_enabled: bool created_at: str updated_at: str @@ -14463,20 +14473,20 @@ class AlertChannelCreate(BaseModel): name: str = Field(..., description="渠道名称") channel_type: str = Field(..., description="渠道类型: pagerduty, opsgenie, feishu, dingtalk, slack, email, sms, webhook") - config: Dict = Field(default_factory=dict, description="渠道特定配置") - severity_filter: List[str] = Field(default_factory=lambda: ["p0", "p1", "p2", "p3"], description="过滤的告警级别") + config: dict = Field(default_factory=dict, description="渠道特定配置") + severity_filter: list[str] = Field(default_factory=lambda: ["p0", "p1", "p2", "p3"], description="过滤的告警级别") class AlertChannelResponse(BaseModel): id: str name: str channel_type: str - config: Dict - severity_filter: List[str] + config: dict + severity_filter: list[str] is_enabled: bool success_count: int fail_count: int - last_used_at: Optional[str] + last_used_at: str | None created_at: str @@ -14490,10 +14500,10 @@ class AlertResponse(BaseModel): metric: str value: float threshold: float - labels: Dict + labels: dict started_at: str - resolved_at: Optional[str] - acknowledged_by: Optional[str] + resolved_at: str | None + acknowledged_by: str | None suppression_count: int @@ -14502,7 +14512,7 @@ class HealthCheckCreate(BaseModel): target_type: str = Field(..., description="目标类型: service, database, api") target_id: str = Field(..., description="目标ID") check_type: str = Field(..., description="检查类型: http, tcp, ping, custom") - check_config: Dict = Field(default_factory=dict, description="检查配置") + check_config: dict = Field(default_factory=dict, description="检查配置") interval: int = Field(default=60, description="检查间隔(秒)") timeout: int = Field(default=10, description="超时时间(秒)") retry_count: int = Field(default=3, description="重试次数") @@ -14542,7 +14552,7 @@ class BackupJobCreate(BaseModel): retention_days: int = Field(default=30, description="保留天数") encryption_enabled: bool = Field(default=True, description="是否加密") compression_enabled: bool = Field(default=True, description="是否压缩") - storage_location: Optional[str] = Field(default=None, description="存储位置") + storage_location: str | None = Field(default=None, description="存储位置") # Alert Rules API @@ -14602,7 +14612,7 @@ async def create_alert_rule_endpoint( @app.get("/api/v1/ops/alert-rules", tags=["Operations & Monitoring"]) async def list_alert_rules_endpoint( tenant_id: str, - is_enabled: Optional[bool] = None, + is_enabled: bool | None = None, _=Depends(verify_api_key) ): """列出租户的告警规则""" @@ -14670,7 +14680,7 @@ async def get_alert_rule_endpoint(rule_id: str, _=Depends(verify_api_key)): @app.patch("/api/v1/ops/alert-rules/{rule_id}", response_model=AlertRuleResponse, tags=["Operations & Monitoring"]) async def update_alert_rule_endpoint( rule_id: str, - updates: Dict, + updates: dict, _=Depends(verify_api_key) ): """更新告警规则""" @@ -14801,8 +14811,8 @@ async def test_alert_channel_endpoint(channel_id: str, _=Depends(verify_api_key) @app.get("/api/v1/ops/alerts", tags=["Operations & Monitoring"]) async def list_alerts_endpoint( tenant_id: str, - status: Optional[str] = None, - severity: Optional[str] = None, + status: str | None = None, + severity: str | None = None, limit: int = 100, _=Depends(verify_api_key) ): @@ -14881,7 +14891,7 @@ async def record_resource_metric_endpoint( metric_name: str, metric_value: float, unit: str, - metadata: Dict = None, + metadata: dict = None, _=Depends(verify_api_key) ): """记录资源指标""" @@ -15077,7 +15087,7 @@ async def list_auto_scaling_policies_endpoint(tenant_id: str, _=Depends(verify_a @app.get("/api/v1/ops/scaling-events", tags=["Operations & Monitoring"]) async def list_scaling_events_endpoint( tenant_id: str, - policy_id: Optional[str] = None, + policy_id: str | None = None, limit: int = 100, _=Depends(verify_api_key) ): @@ -15270,7 +15280,7 @@ async def execute_backup_endpoint(job_id: str, _=Depends(verify_api_key)): @app.get("/api/v1/ops/backup-records", tags=["Operations & Monitoring"]) async def list_backup_records_endpoint( tenant_id: str, - job_id: Optional[str] = None, + job_id: str | None = None, limit: int = 100, _=Depends(verify_api_key) ): @@ -15381,7 +15391,7 @@ async def generate_cost_optimization_suggestions_endpoint( @app.get("/api/v1/ops/cost-optimization-suggestions", tags=["Operations & Monitoring"]) async def list_cost_optimization_suggestions_endpoint( tenant_id: str, - is_applied: Optional[bool] = None, + is_applied: bool | None = None, _=Depends(verify_api_key) ): """获取成本优化建议列表""" diff --git a/backend/multimodal_entity_linker.py b/backend/multimodal_entity_linker.py index 541649e..141f779 100644 --- a/backend/multimodal_entity_linker.py +++ b/backend/multimodal_entity_linker.py @@ -5,7 +5,6 @@ InsightFlow Multimodal Entity Linker - Phase 7 """ import uuid -from typing import List, Dict, Optional, Tuple, Set from dataclasses import dataclass from difflib import SequenceMatcher @@ -28,7 +27,7 @@ class MultimodalEntity: source_id: str mention_context: str confidence: float - modality_features: Dict = None # 模态特定特征 + modality_features: dict = None # 模态特定特征 def __post_init__(self): if self.modality_features is None: @@ -55,7 +54,7 @@ class AlignmentResult: """对齐结果""" entity_id: str - matched_entity_id: Optional[str] + matched_entity_id: str | None similarity: float match_type: str # exact, fuzzy, embedding confidence: float @@ -66,9 +65,9 @@ class FusionResult: """知识融合结果""" canonical_entity_id: str - merged_entity_ids: List[str] - fused_properties: Dict - source_modalities: List[str] + merged_entity_ids: list[str] + fused_properties: dict + source_modalities: list[str] confidence: float @@ -117,7 +116,7 @@ class MultimodalEntityLinker: # 编辑距离相似度 return SequenceMatcher(None, s1, s2).ratio() - def calculate_entity_similarity(self, entity1: Dict, entity2: Dict) -> Tuple[float, str]: + def calculate_entity_similarity(self, entity1: dict, entity2: dict) -> tuple[float, str]: """ 计算两个实体的综合相似度 @@ -159,8 +158,8 @@ class MultimodalEntityLinker: return combined_sim, "none" def find_matching_entity( - self, query_entity: Dict, candidate_entities: List[Dict], exclude_ids: Set[str] = None - ) -> Optional[AlignmentResult]: + self, query_entity: dict, candidate_entities: list[dict], exclude_ids: set[str] = None + ) -> AlignmentResult | None: """ 在候选实体中查找匹配的实体 @@ -201,11 +200,11 @@ class MultimodalEntityLinker: def align_cross_modal_entities( self, project_id: str, - audio_entities: List[Dict], - video_entities: List[Dict], - image_entities: List[Dict], - document_entities: List[Dict], - ) -> List[EntityLink]: + audio_entities: list[dict], + video_entities: list[dict], + image_entities: list[dict], + document_entities: list[dict], + ) -> list[EntityLink]: """ 跨模态实体对齐 @@ -259,7 +258,7 @@ class MultimodalEntityLinker: return links def fuse_entity_knowledge( - self, entity_id: str, linked_entities: List[Dict], multimodal_mentions: List[Dict] + self, entity_id: str, linked_entities: list[dict], multimodal_mentions: list[dict] ) -> FusionResult: """ 融合多模态实体知识 @@ -331,7 +330,7 @@ class MultimodalEntityLinker: confidence=min(1.0, len(linked_entities) * 0.2 + 0.5), ) - def detect_entity_conflicts(self, entities: List[Dict]) -> List[Dict]: + def detect_entity_conflicts(self, entities: list[dict]) -> list[dict]: """ 检测实体冲突(同名但不同义) @@ -380,7 +379,7 @@ class MultimodalEntityLinker: return conflicts - def suggest_entity_merges(self, entities: List[Dict], existing_links: List[EntityLink] = None) -> List[Dict]: + def suggest_entity_merges(self, entities: list[dict], existing_links: list[EntityLink] = None) -> list[dict]: """ 建议实体合并 @@ -464,7 +463,7 @@ class MultimodalEntityLinker: confidence=confidence, ) - def analyze_modality_distribution(self, multimodal_entities: List[MultimodalEntity]) -> Dict: + def analyze_modality_distribution(self, multimodal_entities: list[MultimodalEntity]) -> dict: """ 分析模态分布 diff --git a/backend/multimodal_processor.py b/backend/multimodal_processor.py index 2de94e7..868198e 100644 --- a/backend/multimodal_processor.py +++ b/backend/multimodal_processor.py @@ -4,12 +4,11 @@ InsightFlow Multimodal Processor - Phase 7 视频处理模块:提取音频、关键帧、OCR识别 """ -import os import json -import uuid -import tempfile +import os import subprocess -from typing import List, Dict, Tuple +import tempfile +import uuid from dataclasses import dataclass from pathlib import Path @@ -48,7 +47,7 @@ class VideoFrame: frame_path: str ocr_text: str = "" ocr_confidence: float = 0.0 - entities_detected: List[Dict] = None + entities_detected: list[dict] = None def __post_init__(self): if self.entities_detected is None: @@ -72,7 +71,7 @@ class VideoInfo: transcript_id: str = "" status: str = "pending" error_message: str = "" - metadata: Dict = None + metadata: dict = None def __post_init__(self): if self.metadata is None: @@ -85,8 +84,8 @@ class VideoProcessingResult: video_id: str audio_path: str - frames: List[VideoFrame] - ocr_results: List[Dict] + frames: list[VideoFrame] + ocr_results: list[dict] full_text: str # 整合的文本(音频转录 + OCR文本) success: bool error_message: str = "" @@ -114,7 +113,7 @@ class MultimodalProcessor: os.makedirs(self.frames_dir, exist_ok=True) os.makedirs(self.audio_dir, exist_ok=True) - def extract_video_info(self, video_path: str) -> Dict: + def extract_video_info(self, video_path: str) -> dict: """ 提取视频基本信息 @@ -215,7 +214,7 @@ class MultimodalProcessor: print(f"Error extracting audio: {e}") raise - def extract_keyframes(self, video_path: str, video_id: str, interval: int = None) -> List[str]: + def extract_keyframes(self, video_path: str, video_id: str, interval: int = None) -> list[str]: """ 从视频中提取关键帧 @@ -275,7 +274,7 @@ class MultimodalProcessor: return frame_paths - def perform_ocr(self, image_path: str) -> Tuple[str, float]: + def perform_ocr(self, image_path: str) -> tuple[str, float]: """ 对图片进行OCR识别 diff --git a/backend/neo4j_manager.py b/backend/neo4j_manager.py index 406c285..c39fa99 100644 --- a/backend/neo4j_manager.py +++ b/backend/neo4j_manager.py @@ -5,10 +5,9 @@ Phase 5: Neo4j 图数据库集成 支持数据同步、复杂图查询和图算法分析 """ -import os import json import logging -from typing import List, Dict, Optional +import os from dataclasses import dataclass logger = logging.getLogger(__name__) @@ -20,7 +19,7 @@ NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "password") # 延迟导入,避免未安装时出错 try: - from neo4j import GraphDatabase, Driver + from neo4j import Driver, GraphDatabase NEO4J_AVAILABLE = True except ImportError: @@ -37,8 +36,8 @@ class GraphEntity: name: str type: str definition: str = "" - aliases: List[str] = None - properties: Dict = None + aliases: list[str] = None + properties: dict = None def __post_init__(self): if self.aliases is None: @@ -56,7 +55,7 @@ class GraphRelation: target_id: str relation_type: str evidence: str = "" - properties: Dict = None + properties: dict = None def __post_init__(self): if self.properties is None: @@ -67,8 +66,8 @@ class GraphRelation: class PathResult: """路径查询结果""" - nodes: List[Dict] - relationships: List[Dict] + nodes: list[dict] + relationships: list[dict] length: int total_weight: float = 0.0 @@ -78,7 +77,7 @@ class CommunityResult: """社区发现结果""" community_id: int - nodes: List[Dict] + nodes: list[dict] size: int density: float = 0.0 @@ -100,7 +99,7 @@ class Neo4jManager: self.uri = uri or NEO4J_URI self.user = user or NEO4J_USER self.password = password or NEO4J_PASSWORD - self._driver: Optional["Driver"] = None + self._driver: Driver | None = None if not NEO4J_AVAILABLE: logger.error("Neo4j driver not available. Please install: pip install neo4j") @@ -226,7 +225,7 @@ class Neo4jManager: properties=json.dumps(entity.properties), ) - def sync_entities_batch(self, entities: List[GraphEntity]): + def sync_entities_batch(self, entities: list[GraphEntity]): """批量同步实体到 Neo4j""" if not self._driver or not entities: return @@ -287,7 +286,7 @@ class Neo4jManager: properties=json.dumps(relation.properties), ) - def sync_relations_batch(self, relations: List[GraphRelation]): + def sync_relations_batch(self, relations: list[GraphRelation]): """批量同步关系到 Neo4j""" if not self._driver or not relations: return @@ -350,7 +349,7 @@ class Neo4jManager: # ==================== 复杂图查询 ==================== - def find_shortest_path(self, source_id: str, target_id: str, max_depth: int = 10) -> Optional[PathResult]: + def find_shortest_path(self, source_id: str, target_id: str, max_depth: int = 10) -> PathResult | None: """ 查找两个实体之间的最短路径 @@ -399,7 +398,7 @@ class Neo4jManager: return PathResult(nodes=nodes, relationships=relationships, length=len(path.relationships)) - def find_all_paths(self, source_id: str, target_id: str, max_depth: int = 5, limit: int = 10) -> List[PathResult]: + def find_all_paths(self, source_id: str, target_id: str, max_depth: int = 5, limit: int = 10) -> list[PathResult]: """ 查找两个实体之间的所有路径 @@ -449,7 +448,7 @@ class Neo4jManager: return paths - def find_neighbors(self, entity_id: str, relation_type: str = None, limit: int = 50) -> List[Dict]: + def find_neighbors(self, entity_id: str, relation_type: str = None, limit: int = 50) -> list[dict]: """ 查找实体的邻居节点 @@ -502,7 +501,7 @@ class Neo4jManager: return neighbors - def find_common_neighbors(self, entity_id1: str, entity_id2: str) -> List[Dict]: + def find_common_neighbors(self, entity_id1: str, entity_id2: str) -> list[dict]: """ 查找两个实体的共同邻居(潜在关联) @@ -533,7 +532,7 @@ class Neo4jManager: # ==================== 图算法分析 ==================== - def calculate_pagerank(self, project_id: str, top_n: int = 20) -> List[CentralityResult]: + def calculate_pagerank(self, project_id: str, top_n: int = 20) -> list[CentralityResult]: """ 计算 PageRank 中心性 @@ -619,7 +618,7 @@ class Neo4jManager: return rankings - def calculate_betweenness(self, project_id: str, top_n: int = 20) -> List[CentralityResult]: + def calculate_betweenness(self, project_id: str, top_n: int = 20) -> list[CentralityResult]: """ 计算 Betweenness 中心性(桥梁作用) @@ -663,7 +662,7 @@ class Neo4jManager: return rankings - def detect_communities(self, project_id: str) -> List[CommunityResult]: + def detect_communities(self, project_id: str) -> list[CommunityResult]: """ 社区发现(使用 Louvain 算法) @@ -733,7 +732,7 @@ class Neo4jManager: results.sort(key=lambda x: x.size, reverse=True) return results - def find_central_entities(self, project_id: str, metric: str = "degree") -> List[CentralityResult]: + def find_central_entities(self, project_id: str, metric: str = "degree") -> list[CentralityResult]: """ 查找中心实体 @@ -791,7 +790,7 @@ class Neo4jManager: # ==================== 图统计 ==================== - def get_graph_stats(self, project_id: str) -> Dict: + def get_graph_stats(self, project_id: str) -> dict: """ 获取项目的图统计信息 @@ -870,7 +869,7 @@ class Neo4jManager: "density": round(relation_count / (entity_count * (entity_count - 1)), 4) if entity_count > 1 else 0, } - def get_subgraph(self, entity_ids: List[str], depth: int = 1) -> Dict: + def get_subgraph(self, entity_ids: list[str], depth: int = 1) -> dict: """ 获取指定实体的子图 @@ -959,7 +958,7 @@ def close_neo4j_manager(): # 便捷函数 -def sync_project_to_neo4j(project_id: str, project_name: str, entities: List[Dict], relations: List[Dict]): +def sync_project_to_neo4j(project_id: str, project_name: str, entities: list[dict], relations: list[dict]): """ 同步整个项目到 Neo4j diff --git a/backend/ops_manager.py b/backend/ops_manager.py index 6ec92ce..b444b45 100644 --- a/backend/ops_manager.py +++ b/backend/ops_manager.py @@ -10,26 +10,27 @@ InsightFlow Operations & Monitoring Manager - Phase 8 Task 8 作者: InsightFlow Team """ -import os -import json -import sqlite3 -import httpx import asyncio import hashlib -import uuid +import json +import os import re -import time +import sqlite3 import statistics -from typing import List, Dict, Optional, Tuple, Callable +import time +import uuid +from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta -from enum import Enum +from enum import StrEnum + +import httpx # Database path DB_PATH = os.path.join(os.path.dirname(__file__), "insightflow.db") -class AlertSeverity(str, Enum): +class AlertSeverity(StrEnum): """告警严重级别 P0-P3""" P0 = "p0" # 紧急 - 系统不可用,需要立即处理 @@ -38,7 +39,7 @@ class AlertSeverity(str, Enum): P3 = "p3" # 轻微 - 非核心功能问题,24小时内处理 -class AlertStatus(str, Enum): +class AlertStatus(StrEnum): """告警状态""" FIRING = "firing" # 正在告警 @@ -47,7 +48,7 @@ class AlertStatus(str, Enum): SUPPRESSED = "suppressed" # 已抑制 -class AlertChannelType(str, Enum): +class AlertChannelType(StrEnum): """告警渠道类型""" PAGERDUTY = "pagerduty" @@ -60,7 +61,7 @@ class AlertChannelType(str, Enum): WEBHOOK = "webhook" -class AlertRuleType(str, Enum): +class AlertRuleType(StrEnum): """告警规则类型""" THRESHOLD = "threshold" # 阈值告警 @@ -69,7 +70,7 @@ class AlertRuleType(str, Enum): COMPOSITE = "composite" # 复合告警 -class ResourceType(str, Enum): +class ResourceType(StrEnum): """资源类型""" CPU = "cpu" @@ -82,7 +83,7 @@ class ResourceType(str, Enum): QUEUE = "queue" -class ScalingAction(str, Enum): +class ScalingAction(StrEnum): """扩缩容动作""" SCALE_UP = "scale_up" # 扩容 @@ -90,7 +91,7 @@ class ScalingAction(str, Enum): MAINTAIN = "maintain" # 保持 -class HealthStatus(str, Enum): +class HealthStatus(StrEnum): """健康状态""" HEALTHY = "healthy" @@ -99,7 +100,7 @@ class HealthStatus(str, Enum): UNKNOWN = "unknown" -class BackupStatus(str, Enum): +class BackupStatus(StrEnum): """备份状态""" PENDING = "pending" @@ -124,9 +125,9 @@ class AlertRule: threshold: float duration: int # 持续时间(秒) evaluation_interval: int # 评估间隔(秒) - channels: List[str] # 告警渠道ID列表 - labels: Dict[str, str] # 标签 - annotations: Dict[str, str] # 注释 + channels: list[str] # 告警渠道ID列表 + labels: dict[str, str] # 标签 + annotations: dict[str, str] # 注释 is_enabled: bool created_at: str updated_at: str @@ -141,12 +142,12 @@ class AlertChannel: tenant_id: str name: str channel_type: AlertChannelType - config: Dict # 渠道特定配置 - severity_filter: List[str] # 过滤的告警级别 + config: dict # 渠道特定配置 + severity_filter: list[str] # 过滤的告警级别 is_enabled: bool success_count: int fail_count: int - last_used_at: Optional[str] + last_used_at: str | None created_at: str updated_at: str @@ -165,13 +166,13 @@ class Alert: metric: str value: float threshold: float - labels: Dict[str, str] - annotations: Dict[str, str] + labels: dict[str, str] + annotations: dict[str, str] started_at: str - resolved_at: Optional[str] - acknowledged_by: Optional[str] - acknowledged_at: Optional[str] - notification_sent: Dict[str, bool] # 渠道发送状态 + resolved_at: str | None + acknowledged_by: str | None + acknowledged_at: str | None + notification_sent: dict[str, bool] # 渠道发送状态 suppression_count: int # 抑制计数 @@ -182,11 +183,11 @@ class AlertSuppressionRule: id: str tenant_id: str name: str - matchers: Dict[str, str] # 匹配条件 + matchers: dict[str, str] # 匹配条件 duration: int # 抑制持续时间(秒) is_regex: bool # 是否使用正则匹配 created_at: str - expires_at: Optional[str] + expires_at: str | None @dataclass @@ -196,7 +197,7 @@ class AlertGroup: id: str tenant_id: str group_key: str # 聚合键 - alerts: List[str] # 告警ID列表 + alerts: list[str] # 告警ID列表 created_at: str updated_at: str @@ -213,7 +214,7 @@ class ResourceMetric: metric_value: float unit: str timestamp: str - metadata: Dict + metadata: dict @dataclass @@ -267,8 +268,8 @@ class ScalingEvent: triggered_by: str # 触发来源: manual, auto, scheduled status: str # pending, in_progress, completed, failed started_at: str - completed_at: Optional[str] - error_message: Optional[str] + completed_at: str | None + error_message: str | None @dataclass @@ -281,7 +282,7 @@ class HealthCheck: target_type: str # service, database, api, etc. target_id: str check_type: str # http, tcp, ping, custom - check_config: Dict # 检查配置 + check_config: dict # 检查配置 interval: int # 检查间隔(秒) timeout: int # 超时时间(秒) retry_count: int @@ -302,7 +303,7 @@ class HealthCheckResult: status: HealthStatus response_time: float # 响应时间(毫秒) message: str - details: Dict + details: dict checked_at: str @@ -314,7 +315,7 @@ class FailoverConfig: tenant_id: str name: str primary_region: str - secondary_regions: List[str] # 备用区域列表 + secondary_regions: list[str] # 备用区域列表 failover_trigger: str # 触发条件 auto_failover: bool failover_timeout: int # 故障转移超时(秒) @@ -336,8 +337,8 @@ class FailoverEvent: reason: str status: str # initiated, in_progress, completed, failed, rolled_back started_at: str - completed_at: Optional[str] - rolled_back_at: Optional[str] + completed_at: str | None + rolled_back_at: str | None @dataclass @@ -371,9 +372,9 @@ class BackupRecord: size_bytes: int checksum: str started_at: str - completed_at: Optional[str] - verified_at: Optional[str] - error_message: Optional[str] + completed_at: str | None + verified_at: str | None + error_message: str | None storage_path: str @@ -386,9 +387,9 @@ class CostReport: report_period: str # YYYY-MM total_cost: float currency: str - breakdown: Dict[str, float] # 按资源类型分解 - trends: Dict # 趋势数据 - anomalies: List[Dict] # 异常检测 + breakdown: dict[str, float] # 按资源类型分解 + trends: dict # 趋势数据 + anomalies: list[dict] # 异常检测 created_at: str @@ -405,7 +406,7 @@ class ResourceUtilization: avg_utilization: float idle_time_percent: float report_date: str - recommendations: List[str] + recommendations: list[str] @dataclass @@ -438,11 +439,11 @@ class CostOptimizationSuggestion: currency: str confidence: float difficulty: str # easy, medium, hard - implementation_steps: List[str] + implementation_steps: list[str] risk_level: str # low, medium, high is_applied: bool created_at: str - applied_at: Optional[str] + applied_at: str | None class OpsManager: @@ -450,7 +451,7 @@ class OpsManager: def __init__(self, db_path: str = DB_PATH): self.db_path = db_path - self._alert_evaluators: Dict[str, Callable] = {} + self._alert_evaluators: dict[str, Callable] = {} self._running = False self._evaluator_thread = None self._register_default_evaluators() @@ -481,9 +482,9 @@ class OpsManager: threshold: float, duration: int, evaluation_interval: int, - channels: List[str], - labels: Dict, - annotations: Dict, + channels: list[str], + labels: dict, + annotations: dict, created_by: str, ) -> AlertRule: """创建告警规则""" @@ -545,7 +546,7 @@ class OpsManager: return rule - def get_alert_rule(self, rule_id: str) -> Optional[AlertRule]: + def get_alert_rule(self, rule_id: str) -> AlertRule | None: """获取告警规则""" with self._get_db() as conn: row = conn.execute("SELECT * FROM alert_rules WHERE id = ?", (rule_id,)).fetchone() @@ -554,7 +555,7 @@ class OpsManager: return self._row_to_alert_rule(row) return None - def list_alert_rules(self, tenant_id: str, is_enabled: Optional[bool] = None) -> List[AlertRule]: + def list_alert_rules(self, tenant_id: str, is_enabled: bool | None = None) -> list[AlertRule]: """列出租户的所有告警规则""" query = "SELECT * FROM alert_rules WHERE tenant_id = ?" params = [tenant_id] @@ -569,7 +570,7 @@ class OpsManager: rows = conn.execute(query, params).fetchall() return [self._row_to_alert_rule(row) for row in rows] - def update_alert_rule(self, rule_id: str, **kwargs) -> Optional[AlertRule]: + def update_alert_rule(self, rule_id: str, **kwargs) -> AlertRule | None: """更新告警规则""" allowed_fields = [ "name", @@ -619,7 +620,7 @@ class OpsManager: # ==================== 告警渠道管理 ==================== def create_alert_channel( - self, tenant_id: str, name: str, channel_type: AlertChannelType, config: Dict, severity_filter: List[str] = None + self, tenant_id: str, name: str, channel_type: AlertChannelType, config: dict, severity_filter: list[str] = None ) -> AlertChannel: """创建告警渠道""" channel_id = f"ac_{uuid.uuid4().hex[:16]}" @@ -667,7 +668,7 @@ class OpsManager: return channel - def get_alert_channel(self, channel_id: str) -> Optional[AlertChannel]: + def get_alert_channel(self, channel_id: str) -> AlertChannel | None: """获取告警渠道""" with self._get_db() as conn: row = conn.execute("SELECT * FROM alert_channels WHERE id = ?", (channel_id,)).fetchone() @@ -676,7 +677,7 @@ class OpsManager: return self._row_to_alert_channel(row) return None - def list_alert_channels(self, tenant_id: str) -> List[AlertChannel]: + def list_alert_channels(self, tenant_id: str) -> list[AlertChannel]: """列出租户的所有告警渠道""" with self._get_db() as conn: rows = conn.execute( @@ -715,7 +716,7 @@ class OpsManager: # ==================== 告警评估与触发 ==================== - def _evaluate_threshold_rule(self, rule: AlertRule, metrics: List[ResourceMetric]) -> bool: + def _evaluate_threshold_rule(self, rule: AlertRule, metrics: list[ResourceMetric]) -> bool: """评估阈值告警规则""" if not metrics: return False @@ -746,7 +747,7 @@ class OpsManager: return False - def _evaluate_anomaly_rule(self, rule: AlertRule, metrics: List[ResourceMetric]) -> bool: + def _evaluate_anomaly_rule(self, rule: AlertRule, metrics: list[ResourceMetric]) -> bool: """评估异常检测规则(基于标准差)""" if len(metrics) < 10: return False @@ -764,7 +765,7 @@ class OpsManager: return z_score > 3.0 - def _evaluate_predictive_rule(self, rule: AlertRule, metrics: List[ResourceMetric]) -> bool: + def _evaluate_predictive_rule(self, rule: AlertRule, metrics: list[ResourceMetric]) -> bool: """评估预测性告警规则(基于线性趋势)""" if len(metrics) < 5: return False @@ -814,7 +815,7 @@ class OpsManager: # 触发告警 await self._trigger_alert(rule, metrics[-1] if metrics else None) - async def _trigger_alert(self, rule: AlertRule, metric: Optional[ResourceMetric]): + async def _trigger_alert(self, rule: AlertRule, metric: ResourceMetric | None): """触发告警""" # 检查是否已有相同告警在触发中 existing = self.get_active_alert_by_rule(rule.id) @@ -1166,7 +1167,7 @@ class OpsManager: # ==================== 告警查询与管理 ==================== - def get_active_alert_by_rule(self, rule_id: str) -> Optional[Alert]: + def get_active_alert_by_rule(self, rule_id: str) -> Alert | None: """获取规则对应的活跃告警""" with self._get_db() as conn: row = conn.execute( @@ -1180,7 +1181,7 @@ class OpsManager: return self._row_to_alert(row) return None - def get_alert(self, alert_id: str) -> Optional[Alert]: + def get_alert(self, alert_id: str) -> Alert | None: """获取告警详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM alerts WHERE id = ?", (alert_id,)).fetchone() @@ -1192,10 +1193,10 @@ class OpsManager: def list_alerts( self, tenant_id: str, - status: Optional[AlertStatus] = None, - severity: Optional[AlertSeverity] = None, + status: AlertStatus | None = None, + severity: AlertSeverity | None = None, limit: int = 100, - ) -> List[Alert]: + ) -> list[Alert]: """列出租户的告警""" query = "SELECT * FROM alerts WHERE tenant_id = ?" params = [tenant_id] @@ -1214,7 +1215,7 @@ class OpsManager: rows = conn.execute(query, params).fetchall() return [self._row_to_alert(row) for row in rows] - def acknowledge_alert(self, alert_id: str, user_id: str) -> Optional[Alert]: + def acknowledge_alert(self, alert_id: str, user_id: str) -> Alert | None: """确认告警""" now = datetime.now().isoformat() @@ -1231,7 +1232,7 @@ class OpsManager: return self.get_alert(alert_id) - def resolve_alert(self, alert_id: str) -> Optional[Alert]: + def resolve_alert(self, alert_id: str) -> Alert | None: """解决告警""" now = datetime.now().isoformat() @@ -1306,10 +1307,10 @@ class OpsManager: self, tenant_id: str, name: str, - matchers: Dict[str, str], + matchers: dict[str, str], duration: int, is_regex: bool = False, - expires_at: Optional[str] = None, + expires_at: str | None = None, ) -> AlertSuppressionRule: """创建告警抑制规则""" rule_id = f"sr_{uuid.uuid4().hex[:16]}" @@ -1394,7 +1395,7 @@ class OpsManager: metric_name: str, metric_value: float, unit: str, - metadata: Dict = None, + metadata: dict = None, ) -> ResourceMetric: """记录资源指标""" metric_id = f"rm_{uuid.uuid4().hex[:16]}" @@ -1436,7 +1437,7 @@ class OpsManager: return metric - def get_recent_metrics(self, tenant_id: str, metric_name: str, seconds: int = 3600) -> List[ResourceMetric]: + def get_recent_metrics(self, tenant_id: str, metric_name: str, seconds: int = 3600) -> list[ResourceMetric]: """获取最近的指标数据""" cutoff_time = (datetime.now() - timedelta(seconds=seconds)).isoformat() @@ -1458,7 +1459,7 @@ class OpsManager: metric_name: str, start_time: str, end_time: str, - ) -> List[ResourceMetric]: + ) -> list[ResourceMetric]: """获取指定资源的指标数据""" with self._get_db() as conn: rows = conn.execute( @@ -1549,7 +1550,7 @@ class OpsManager: return plan - def _calculate_trend(self, values: List[float]) -> float: + def _calculate_trend(self, values: list[float]) -> float: """计算趋势(增长率)""" if len(values) < 2: return 0.0 @@ -1576,7 +1577,7 @@ class OpsManager: return slope / mean_y return 0.0 - def get_capacity_plans(self, tenant_id: str) -> List[CapacityPlan]: + def get_capacity_plans(self, tenant_id: str) -> list[CapacityPlan]: """获取容量规划列表""" with self._get_db() as conn: rows = conn.execute( @@ -1653,7 +1654,7 @@ class OpsManager: return policy - def get_auto_scaling_policy(self, policy_id: str) -> Optional[AutoScalingPolicy]: + def get_auto_scaling_policy(self, policy_id: str) -> AutoScalingPolicy | None: """获取自动扩缩容策略""" with self._get_db() as conn: row = conn.execute("SELECT * FROM auto_scaling_policies WHERE id = ?", (policy_id,)).fetchone() @@ -1662,7 +1663,7 @@ class OpsManager: return self._row_to_auto_scaling_policy(row) return None - def list_auto_scaling_policies(self, tenant_id: str) -> List[AutoScalingPolicy]: + def list_auto_scaling_policies(self, tenant_id: str) -> list[AutoScalingPolicy]: """列出租户的自动扩缩容策略""" with self._get_db() as conn: rows = conn.execute( @@ -1672,7 +1673,7 @@ class OpsManager: def evaluate_scaling_policy( self, policy_id: str, current_instances: int, current_utilization: float - ) -> Optional[ScalingEvent]: + ) -> ScalingEvent | None: """评估扩缩容策略""" policy = self.get_auto_scaling_policy(policy_id) if not policy or not policy.is_enabled: @@ -1754,7 +1755,7 @@ class OpsManager: return event - def get_last_scaling_event(self, policy_id: str) -> Optional[ScalingEvent]: + def get_last_scaling_event(self, policy_id: str) -> ScalingEvent | None: """获取最近的扩缩容事件""" with self._get_db() as conn: row = conn.execute( @@ -1770,7 +1771,7 @@ class OpsManager: def update_scaling_event_status( self, event_id: str, status: str, error_message: str = None - ) -> Optional[ScalingEvent]: + ) -> ScalingEvent | None: """更新扩缩容事件状态""" now = datetime.now().isoformat() @@ -1797,7 +1798,7 @@ class OpsManager: return self.get_scaling_event(event_id) - def get_scaling_event(self, event_id: str) -> Optional[ScalingEvent]: + def get_scaling_event(self, event_id: str) -> ScalingEvent | None: """获取扩缩容事件""" with self._get_db() as conn: row = conn.execute("SELECT * FROM scaling_events WHERE id = ?", (event_id,)).fetchone() @@ -1806,7 +1807,7 @@ class OpsManager: return self._row_to_scaling_event(row) return None - def list_scaling_events(self, tenant_id: str, policy_id: str = None, limit: int = 100) -> List[ScalingEvent]: + def list_scaling_events(self, tenant_id: str, policy_id: str = None, limit: int = 100) -> list[ScalingEvent]: """列出租户的扩缩容事件""" query = "SELECT * FROM scaling_events WHERE tenant_id = ?" params = [tenant_id] @@ -1831,7 +1832,7 @@ class OpsManager: target_type: str, target_id: str, check_type: str, - check_config: Dict, + check_config: dict, interval: int = 60, timeout: int = 10, retry_count: int = 3, @@ -1889,7 +1890,7 @@ class OpsManager: return check - def get_health_check(self, check_id: str) -> Optional[HealthCheck]: + def get_health_check(self, check_id: str) -> HealthCheck | None: """获取健康检查配置""" with self._get_db() as conn: row = conn.execute("SELECT * FROM health_checks WHERE id = ?", (check_id,)).fetchone() @@ -1898,7 +1899,7 @@ class OpsManager: return self._row_to_health_check(row) return None - def list_health_checks(self, tenant_id: str) -> List[HealthCheck]: + def list_health_checks(self, tenant_id: str) -> list[HealthCheck]: """列出租户的健康检查""" with self._get_db() as conn: rows = conn.execute( @@ -1958,7 +1959,7 @@ class OpsManager: return result - async def _check_http_health(self, check: HealthCheck) -> Tuple[HealthStatus, float, str]: + async def _check_http_health(self, check: HealthCheck) -> tuple[HealthStatus, float, str]: """HTTP 健康检查""" config = check.check_config url = config.get("url") @@ -1980,7 +1981,7 @@ class OpsManager: except Exception as e: return HealthStatus.UNHEALTHY, (time.time() - start_time) * 1000, str(e) - async def _check_tcp_health(self, check: HealthCheck) -> Tuple[HealthStatus, float, str]: + async def _check_tcp_health(self, check: HealthCheck) -> tuple[HealthStatus, float, str]: """TCP 健康检查""" config = check.check_config host = config.get("host") @@ -1996,12 +1997,12 @@ class OpsManager: writer.close() await writer.wait_closed() return HealthStatus.HEALTHY, response_time, "TCP connection successful" - except asyncio.TimeoutError: + except TimeoutError: return HealthStatus.UNHEALTHY, (time.time() - start_time) * 1000, "Connection timeout" except Exception as e: return HealthStatus.UNHEALTHY, (time.time() - start_time) * 1000, str(e) - async def _check_ping_health(self, check: HealthCheck) -> Tuple[HealthStatus, float, str]: + async def _check_ping_health(self, check: HealthCheck) -> tuple[HealthStatus, float, str]: """Ping 健康检查(模拟)""" config = check.check_config host = config.get("host") @@ -2013,7 +2014,7 @@ class OpsManager: # 这里模拟成功 return HealthStatus.HEALTHY, 10.0, "Ping successful" - def get_health_check_results(self, check_id: str, limit: int = 100) -> List[HealthCheckResult]: + def get_health_check_results(self, check_id: str, limit: int = 100) -> list[HealthCheckResult]: """获取健康检查历史结果""" with self._get_db() as conn: rows = conn.execute( @@ -2031,7 +2032,7 @@ class OpsManager: tenant_id: str, name: str, primary_region: str, - secondary_regions: List[str], + secondary_regions: list[str], failover_trigger: str, auto_failover: bool = False, failover_timeout: int = 300, @@ -2083,7 +2084,7 @@ class OpsManager: return config - def get_failover_config(self, config_id: str) -> Optional[FailoverConfig]: + def get_failover_config(self, config_id: str) -> FailoverConfig | None: """获取故障转移配置""" with self._get_db() as conn: row = conn.execute("SELECT * FROM failover_configs WHERE id = ?", (config_id,)).fetchone() @@ -2092,7 +2093,7 @@ class OpsManager: return self._row_to_failover_config(row) return None - def list_failover_configs(self, tenant_id: str) -> List[FailoverConfig]: + def list_failover_configs(self, tenant_id: str) -> list[FailoverConfig]: """列出租户的故障转移配置""" with self._get_db() as conn: rows = conn.execute( @@ -2100,7 +2101,7 @@ class OpsManager: ).fetchall() return [self._row_to_failover_config(row) for row in rows] - def initiate_failover(self, config_id: str, reason: str) -> Optional[FailoverEvent]: + def initiate_failover(self, config_id: str, reason: str) -> FailoverEvent | None: """发起故障转移""" config = self.get_failover_config(config_id) if not config or not config.is_enabled: @@ -2150,7 +2151,7 @@ class OpsManager: return event - def update_failover_status(self, event_id: str, status: str) -> Optional[FailoverEvent]: + def update_failover_status(self, event_id: str, status: str) -> FailoverEvent | None: """更新故障转移状态""" now = datetime.now().isoformat() @@ -2186,7 +2187,7 @@ class OpsManager: return self.get_failover_event(event_id) - def get_failover_event(self, event_id: str) -> Optional[FailoverEvent]: + def get_failover_event(self, event_id: str) -> FailoverEvent | None: """获取故障转移事件""" with self._get_db() as conn: row = conn.execute("SELECT * FROM failover_events WHERE id = ?", (event_id,)).fetchone() @@ -2195,7 +2196,7 @@ class OpsManager: return self._row_to_failover_event(row) return None - def list_failover_events(self, tenant_id: str, limit: int = 100) -> List[FailoverEvent]: + def list_failover_events(self, tenant_id: str, limit: int = 100) -> list[FailoverEvent]: """列出租户的故障转移事件""" with self._get_db() as conn: rows = conn.execute( @@ -2272,7 +2273,7 @@ class OpsManager: return job - def get_backup_job(self, job_id: str) -> Optional[BackupJob]: + def get_backup_job(self, job_id: str) -> BackupJob | None: """获取备份任务""" with self._get_db() as conn: row = conn.execute("SELECT * FROM backup_jobs WHERE id = ?", (job_id,)).fetchone() @@ -2281,7 +2282,7 @@ class OpsManager: return self._row_to_backup_job(row) return None - def list_backup_jobs(self, tenant_id: str) -> List[BackupJob]: + def list_backup_jobs(self, tenant_id: str) -> list[BackupJob]: """列出租户的备份任务""" with self._get_db() as conn: rows = conn.execute( @@ -2289,7 +2290,7 @@ class OpsManager: ).fetchall() return [self._row_to_backup_job(row) for row in rows] - def execute_backup(self, job_id: str) -> Optional[BackupRecord]: + def execute_backup(self, job_id: str) -> BackupRecord | None: """执行备份""" job = self.get_backup_job(job_id) if not job or not job.is_enabled: @@ -2354,7 +2355,7 @@ class OpsManager: ) conn.commit() - def get_backup_record(self, record_id: str) -> Optional[BackupRecord]: + def get_backup_record(self, record_id: str) -> BackupRecord | None: """获取备份记录""" with self._get_db() as conn: row = conn.execute("SELECT * FROM backup_records WHERE id = ?", (record_id,)).fetchone() @@ -2363,7 +2364,7 @@ class OpsManager: return self._row_to_backup_record(row) return None - def list_backup_records(self, tenant_id: str, job_id: str = None, limit: int = 100) -> List[BackupRecord]: + def list_backup_records(self, tenant_id: str, job_id: str = None, limit: int = 100) -> list[BackupRecord]: """列出租户的备份记录""" query = "SELECT * FROM backup_records WHERE tenant_id = ?" params = [tenant_id] @@ -2452,7 +2453,7 @@ class OpsManager: return report - def _detect_cost_anomalies(self, utilizations: List[ResourceUtilization]) -> List[Dict]: + def _detect_cost_anomalies(self, utilizations: list[ResourceUtilization]) -> list[dict]: """检测成本异常""" anomalies = [] @@ -2483,7 +2484,7 @@ class OpsManager: return anomalies - def _calculate_cost_trends(self, tenant_id: str, year: int, month: int) -> Dict: + def _calculate_cost_trends(self, tenant_id: str, year: int, month: int) -> dict: """计算成本趋势""" # 简化实现:返回模拟趋势 return {"month_over_month": 0.05, "year_over_year": 0.15, "forecast_next_month": 1.05} # 5% 增长 # 15% 增长 @@ -2498,7 +2499,7 @@ class OpsManager: avg_utilization: float, idle_time_percent: float, report_date: str, - recommendations: List[str] = None, + recommendations: list[str] = None, ) -> ResourceUtilization: """记录资源利用率""" util_id = f"ru_{uuid.uuid4().hex[:16]}" @@ -2541,7 +2542,7 @@ class OpsManager: return util - def get_resource_utilizations(self, tenant_id: str, report_period: str) -> List[ResourceUtilization]: + def get_resource_utilizations(self, tenant_id: str, report_period: str) -> list[ResourceUtilization]: """获取资源利用率列表""" with self._get_db() as conn: rows = conn.execute( @@ -2552,7 +2553,7 @@ class OpsManager: ).fetchall() return [self._row_to_resource_utilization(row) for row in rows] - def detect_idle_resources(self, tenant_id: str) -> List[IdleResource]: + def detect_idle_resources(self, tenant_id: str) -> list[IdleResource]: """检测闲置资源""" idle_resources = [] @@ -2615,7 +2616,7 @@ class OpsManager: return idle_resources - def get_idle_resources(self, tenant_id: str) -> List[IdleResource]: + def get_idle_resources(self, tenant_id: str) -> list[IdleResource]: """获取闲置资源列表""" with self._get_db() as conn: rows = conn.execute( @@ -2623,7 +2624,7 @@ class OpsManager: ).fetchall() return [self._row_to_idle_resource(row) for row in rows] - def generate_cost_optimization_suggestions(self, tenant_id: str) -> List[CostOptimizationSuggestion]: + def generate_cost_optimization_suggestions(self, tenant_id: str) -> list[CostOptimizationSuggestion]: """生成成本优化建议""" suggestions = [] @@ -2691,7 +2692,7 @@ class OpsManager: def get_cost_optimization_suggestions( self, tenant_id: str, is_applied: bool = None - ) -> List[CostOptimizationSuggestion]: + ) -> list[CostOptimizationSuggestion]: """获取成本优化建议""" query = "SELECT * FROM cost_optimization_suggestions WHERE tenant_id = ?" params = [tenant_id] @@ -2706,7 +2707,7 @@ class OpsManager: rows = conn.execute(query, params).fetchall() return [self._row_to_cost_optimization_suggestion(row) for row in rows] - def apply_cost_optimization_suggestion(self, suggestion_id: str) -> Optional[CostOptimizationSuggestion]: + def apply_cost_optimization_suggestion(self, suggestion_id: str) -> CostOptimizationSuggestion | None: """应用成本优化建议""" now = datetime.now().isoformat() @@ -2723,7 +2724,7 @@ class OpsManager: return self.get_cost_optimization_suggestion(suggestion_id) - def get_cost_optimization_suggestion(self, suggestion_id: str) -> Optional[CostOptimizationSuggestion]: + def get_cost_optimization_suggestion(self, suggestion_id: str) -> CostOptimizationSuggestion | None: """获取成本优化建议详情""" with self._get_db() as conn: row = conn.execute("SELECT * FROM cost_optimization_suggestions WHERE id = ?", (suggestion_id,)).fetchone() diff --git a/backend/oss_uploader.py b/backend/oss_uploader.py index 04c8791..af72403 100644 --- a/backend/oss_uploader.py +++ b/backend/oss_uploader.py @@ -6,6 +6,7 @@ OSS 上传工具 - 用于阿里听悟音频上传 import os import uuid from datetime import datetime + import oss2 diff --git a/backend/performance_manager.py b/backend/performance_manager.py index 69b031a..3a177ac 100644 --- a/backend/performance_manager.py +++ b/backend/performance_manager.py @@ -9,18 +9,19 @@ Phase 7 Task 8: Performance Optimization & Scaling 4. PerformanceMonitor - 性能监控(API响应、查询性能、缓存命中率) """ -import os -import json -import time import hashlib +import json +import os import sqlite3 import threading -from dataclasses import dataclass, field -from typing import Dict, List, Optional, Any, Callable, Tuple -from datetime import datetime -from collections import OrderedDict -from functools import wraps +import time import uuid +from collections import OrderedDict +from collections.abc import Callable +from dataclasses import dataclass, field +from datetime import datetime +from functools import wraps +from typing import Any # 尝试导入 Redis try: @@ -67,7 +68,7 @@ class CacheEntry: key: str value: Any created_at: float - expires_at: Optional[float] + expires_at: float | None access_count: int = 0 last_accessed: float = 0 size_bytes: int = 0 @@ -79,12 +80,12 @@ class PerformanceMetric: id: str metric_type: str # api_response, db_query, cache_operation - endpoint: Optional[str] + endpoint: str | None duration_ms: float timestamp: str - metadata: Dict = field(default_factory=dict) + metadata: dict = field(default_factory=dict) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "id": self.id, "metric_type": self.metric_type, @@ -102,16 +103,16 @@ class TaskInfo: id: str task_type: str status: str # pending, running, success, failed, retrying - payload: Dict + payload: dict created_at: str - started_at: Optional[str] = None - completed_at: Optional[str] = None - result: Optional[Any] = None - error_message: Optional[str] = None + started_at: str | None = None + completed_at: str | None = None + result: Any | None = None + error_message: str | None = None retry_count: int = 0 max_retries: int = 3 - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "id": self.id, "task_type": self.task_type, @@ -132,7 +133,7 @@ class ShardInfo: """分片信息数据模型""" shard_id: str - shard_key_range: Tuple[str, str] # (start, end) + shard_key_range: tuple[str, str] # (start, end) db_path: str entity_count: int = 0 is_active: bool = True @@ -160,7 +161,7 @@ class CacheManager: def __init__( self, - redis_url: Optional[str] = None, + redis_url: str | None = None, max_memory_size: int = 100 * 1024 * 1024, # 100MB default_ttl: int = 3600, # 1小时 db_path: str = "insightflow.db", @@ -242,7 +243,7 @@ class CacheManager: self.current_memory_size -= oldest_entry.size_bytes self.stats.evictions += 1 - def get(self, key: str) -> Optional[Any]: + def get(self, key: str) -> Any | None: """ 获取缓存值 @@ -291,7 +292,7 @@ class CacheManager: self.stats.misses += 1 return None - def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool: + def set(self, key: str, value: Any, ttl: int | None = None) -> bool: """ 设置缓存值 @@ -373,7 +374,7 @@ class CacheManager: self.current_memory_size = 0 return True - def get_many(self, keys: List[str]) -> Dict[str, Any]: + def get_many(self, keys: list[str]) -> dict[str, Any]: """批量获取缓存""" results = {} @@ -397,7 +398,7 @@ class CacheManager: return results - def set_many(self, mapping: Dict[str, Any], ttl: Optional[int] = None) -> bool: + def set_many(self, mapping: dict[str, Any], ttl: int | None = None) -> bool: """批量设置缓存""" ttl = ttl or self.default_ttl @@ -417,7 +418,7 @@ class CacheManager: self.set(key, value, ttl) return True - def get_stats(self) -> Dict: + def get_stats(self) -> dict: """获取缓存统计""" self.stats.update_hit_rate() @@ -468,7 +469,7 @@ class CacheManager: conn.commit() conn.close() - def warm_up(self, project_id: str) -> Dict: + def warm_up(self, project_id: str) -> dict: """ 缓存预热 - 加载项目的热点数据 @@ -612,7 +613,7 @@ class DatabaseSharding: os.makedirs(shard_db_dir, exist_ok=True) # 分片映射 - self.shard_map: Dict[str, ShardInfo] = {} + self.shard_map: dict[str, ShardInfo] = {} # 初始化分片 self._init_shards() @@ -708,7 +709,7 @@ class DatabaseSharding: return conn - def get_all_shards(self) -> List[ShardInfo]: + def get_all_shards(self) -> list[ShardInfo]: """获取所有分片信息""" return list(self.shard_map.values()) @@ -805,7 +806,7 @@ class DatabaseSharding: conn.close() - def cross_shard_query(self, query_func: Callable) -> List[Dict]: + def cross_shard_query(self, query_func: Callable) -> list[dict]: """ 跨分片查询 @@ -831,7 +832,7 @@ class DatabaseSharding: return results - def get_shard_stats(self) -> List[Dict]: + def get_shard_stats(self) -> list[dict]: """获取所有分片的统计信息""" stats = [] @@ -852,7 +853,7 @@ class DatabaseSharding: return stats - def rebalance_shards(self) -> Dict: + def rebalance_shards(self) -> dict: """ 重新平衡分片 @@ -899,15 +900,15 @@ class TaskQueue: - 任务状态追踪和重试机制 """ - def __init__(self, redis_url: Optional[str] = None, db_path: str = "insightflow.db"): + def __init__(self, redis_url: str | None = None, db_path: str = "insightflow.db"): self.db_path = db_path self.redis_url = redis_url self.celery_app = None self.use_celery = False # 内存任务存储(非 Celery 模式) - self.tasks: Dict[str, TaskInfo] = {} - self.task_handlers: Dict[str, Callable] = {} + self.tasks: dict[str, TaskInfo] = {} + self.task_handlers: dict[str, Callable] = {} self.task_lock = threading.RLock() # 初始化任务队列表 @@ -956,7 +957,7 @@ class TaskQueue: """注册任务处理器""" self.task_handlers[task_type] = handler - def submit(self, task_type: str, payload: Dict, max_retries: int = 3) -> str: + def submit(self, task_type: str, payload: dict, max_retries: int = 3) -> str: """ 提交任务 @@ -1112,7 +1113,7 @@ class TaskQueue: conn.commit() conn.close() - def get_status(self, task_id: str) -> Optional[TaskInfo]: + def get_status(self, task_id: str) -> TaskInfo | None: """获取任务状态""" if self.use_celery: try: @@ -1143,8 +1144,8 @@ class TaskQueue: return self.tasks.get(task_id) def list_tasks( - self, status: Optional[str] = None, task_type: Optional[str] = None, limit: int = 100 - ) -> List[TaskInfo]: + self, status: str | None = None, task_type: str | None = None, limit: int = 100 + ) -> list[TaskInfo]: """列出任务""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row @@ -1233,7 +1234,7 @@ class TaskQueue: self._update_task_status(task) return True - def get_stats(self) -> Dict: + def get_stats(self) -> dict: """获取任务队列统计""" conn = sqlite3.connect(self.db_path) @@ -1290,15 +1291,15 @@ class PerformanceMonitor: self.alert_threshold = alert_threshold # 内存中的指标缓存 - self.metrics_buffer: List[PerformanceMetric] = [] + self.metrics_buffer: list[PerformanceMetric] = [] self.buffer_lock = threading.RLock() self.buffer_size = 100 # 告警回调 - self.alert_handlers: List[Callable] = [] + self.alert_handlers: list[Callable] = [] def record_metric( - self, metric_type: str, duration_ms: float, endpoint: Optional[str] = None, metadata: Optional[Dict] = None + self, metric_type: str, duration_ms: float, endpoint: str | None = None, metadata: dict | None = None ): """ 记录性能指标 @@ -1385,7 +1386,7 @@ class PerformanceMonitor: """注册告警处理器""" self.alert_handlers.append(handler) - def get_stats(self, hours: int = 24) -> Dict: + def get_stats(self, hours: int = 24) -> dict: """ 获取性能统计 @@ -1504,7 +1505,7 @@ class PerformanceMonitor: ], } - def get_api_performance(self, endpoint: Optional[str] = None, hours: int = 24) -> Dict: + def get_api_performance(self, endpoint: str | None = None, hours: int = 24) -> dict: """获取 API 性能详情""" self._flush_metrics() @@ -1584,7 +1585,7 @@ class PerformanceMonitor: # ==================== 性能装饰器 ==================== -def cached(cache_manager: CacheManager, key_prefix: str = "", ttl: int = 3600, key_func: Optional[Callable] = None): +def cached(cache_manager: CacheManager, key_prefix: str = "", ttl: int = 3600, key_func: Callable | None = None): """ 缓存装饰器 @@ -1624,7 +1625,7 @@ def cached(cache_manager: CacheManager, key_prefix: str = "", ttl: int = 3600, k return decorator -def monitored(monitor: PerformanceMonitor, metric_type: str, endpoint: Optional[str] = None): +def monitored(monitor: PerformanceMonitor, metric_type: str, endpoint: str | None = None): """ 性能监控装饰器 @@ -1662,7 +1663,7 @@ class PerformanceManager: 整合缓存管理、数据库分片、任务队列和性能监控功能 """ - def __init__(self, db_path: str = "insightflow.db", redis_url: Optional[str] = None, enable_sharding: bool = False): + def __init__(self, db_path: str = "insightflow.db", redis_url: str | None = None, enable_sharding: bool = False): self.db_path = db_path # 初始化各模块 @@ -1674,7 +1675,7 @@ class PerformanceManager: self.monitor = PerformanceMonitor(db_path=db_path) - def get_health_status(self) -> Dict: + def get_health_status(self) -> dict: """获取系统健康状态""" return { "cache": { @@ -1698,7 +1699,7 @@ class PerformanceManager: }, } - def get_full_stats(self) -> Dict: + def get_full_stats(self) -> dict: """获取完整统计信息""" stats = { "cache": self.cache.get_stats(), @@ -1717,7 +1718,7 @@ _performance_manager = None def get_performance_manager( - db_path: str = "insightflow.db", redis_url: Optional[str] = None, enable_sharding: bool = False + db_path: str = "insightflow.db", redis_url: str | None = None, enable_sharding: bool = False ) -> PerformanceManager: """获取性能管理器单例""" global _performance_manager diff --git a/backend/plugin_manager.py b/backend/plugin_manager.py index 00c3c77..d330716 100644 --- a/backend/plugin_manager.py +++ b/backend/plugin_manager.py @@ -4,20 +4,21 @@ InsightFlow Plugin Manager - Phase 7 Task 7 插件与集成系统:Chrome插件、飞书/钉钉机器人、Zapier/Make集成、WebDAV同步 """ -import os -import json +import base64 import hashlib import hmac -import base64 -import time -import uuid -import httpx -import urllib.parse -from datetime import datetime -from typing import Dict, List, Optional, Any -from dataclasses import dataclass, field -from enum import Enum +import json +import os import sqlite3 +import time +import urllib.parse +import uuid +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +from typing import Any + +import httpx # WebDAV 支持 try: @@ -58,10 +59,10 @@ class Plugin: plugin_type: str project_id: str status: str = "active" - config: Dict = field(default_factory=dict) + config: dict = field(default_factory=dict) created_at: str = "" updated_at: str = "" - last_used_at: Optional[str] = None + last_used_at: str | None = None use_count: int = 0 @@ -86,13 +87,13 @@ class BotSession: bot_type: str # feishu, dingtalk session_id: str # 群ID或会话ID session_name: str - project_id: Optional[str] = None + project_id: str | None = None webhook_url: str = "" secret: str = "" is_active: bool = True created_at: str = "" updated_at: str = "" - last_message_at: Optional[str] = None + last_message_at: str | None = None message_count: int = 0 @@ -104,14 +105,14 @@ class WebhookEndpoint: name: str endpoint_type: str # zapier, make, custom endpoint_url: str - project_id: Optional[str] = None + project_id: str | None = None auth_type: str = "none" # none, api_key, oauth, custom - auth_config: Dict = field(default_factory=dict) - trigger_events: List[str] = field(default_factory=list) + auth_config: dict = field(default_factory=dict) + trigger_events: list[str] = field(default_factory=list) is_active: bool = True created_at: str = "" updated_at: str = "" - last_triggered_at: Optional[str] = None + last_triggered_at: str | None = None trigger_count: int = 0 @@ -128,7 +129,7 @@ class WebDAVSync: remote_path: str = "/insightflow" sync_mode: str = "bidirectional" # bidirectional, upload_only, download_only sync_interval: int = 3600 # 秒 - last_sync_at: Optional[str] = None + last_sync_at: str | None = None last_sync_status: str = "pending" # pending, success, failed last_sync_error: str = "" is_active: bool = True @@ -143,13 +144,13 @@ class ChromeExtensionToken: id: str token: str - user_id: Optional[str] = None - project_id: Optional[str] = None + user_id: str | None = None + project_id: str | None = None name: str = "" - permissions: List[str] = field(default_factory=lambda: ["read", "write"]) - expires_at: Optional[str] = None + permissions: list[str] = field(default_factory=lambda: ["read", "write"]) + expires_at: str | None = None created_at: str = "" - last_used_at: Optional[str] = None + last_used_at: str | None = None use_count: int = 0 is_revoked: bool = False @@ -171,7 +172,7 @@ class PluginManager: self._handlers[PluginType.MAKE] = WebhookIntegration(self, "make") self._handlers[PluginType.WEBDAV] = WebDAVSyncManager(self) - def get_handler(self, plugin_type: PluginType) -> Optional[Any]: + def get_handler(self, plugin_type: PluginType) -> Any | None: """获取插件处理器""" return self._handlers.get(plugin_type) @@ -205,7 +206,7 @@ class PluginManager: plugin.updated_at = now return plugin - def get_plugin(self, plugin_id: str) -> Optional[Plugin]: + def get_plugin(self, plugin_id: str) -> Plugin | None: """获取插件""" conn = self.db.get_conn() row = conn.execute("SELECT * FROM plugins WHERE id = ?", (plugin_id,)).fetchone() @@ -215,7 +216,7 @@ class PluginManager: return self._row_to_plugin(row) return None - def list_plugins(self, project_id: str = None, plugin_type: str = None, status: str = None) -> List[Plugin]: + def list_plugins(self, project_id: str = None, plugin_type: str = None, status: str = None) -> list[Plugin]: """列出插件""" conn = self.db.get_conn() @@ -239,7 +240,7 @@ class PluginManager: return [self._row_to_plugin(row) for row in rows] - def update_plugin(self, plugin_id: str, **kwargs) -> Optional[Plugin]: + def update_plugin(self, plugin_id: str, **kwargs) -> Plugin | None: """更新插件""" conn = self.db.get_conn() @@ -341,7 +342,7 @@ class PluginManager: updated_at=now, ) - def get_plugin_config(self, plugin_id: str, key: str) -> Optional[str]: + def get_plugin_config(self, plugin_id: str, key: str) -> str | None: """获取插件配置""" conn = self.db.get_conn() row = conn.execute( @@ -351,7 +352,7 @@ class PluginManager: return row["config_value"] if row else None - def get_all_plugin_configs(self, plugin_id: str) -> Dict[str, str]: + def get_all_plugin_configs(self, plugin_id: str) -> dict[str, str]: """获取插件所有配置""" conn = self.db.get_conn() rows = conn.execute( @@ -396,7 +397,7 @@ class ChromeExtensionHandler: name: str, user_id: str = None, project_id: str = None, - permissions: List[str] = None, + permissions: list[str] = None, expires_days: int = None, ) -> ChromeExtensionToken: """创建 Chrome 扩展令牌""" @@ -448,7 +449,7 @@ class ChromeExtensionHandler: created_at=now, ) - def validate_token(self, token: str) -> Optional[ChromeExtensionToken]: + def validate_token(self, token: str) -> ChromeExtensionToken | None: """验证 Chrome 扩展令牌""" token_hash = hashlib.sha256(token.encode()).hexdigest() @@ -501,7 +502,7 @@ class ChromeExtensionHandler: return cursor.rowcount > 0 - def list_tokens(self, user_id: str = None, project_id: str = None) -> List[ChromeExtensionToken]: + def list_tokens(self, user_id: str = None, project_id: str = None) -> list[ChromeExtensionToken]: """列出令牌""" conn = self.pm.db.get_conn() @@ -544,7 +545,7 @@ class ChromeExtensionHandler: async def import_webpage( self, token: ChromeExtensionToken, url: str, title: str, content: str, html_content: str = None - ) -> Dict: + ) -> dict: """导入网页内容""" if not token.project_id: return {"success": False, "error": "Token not associated with any project"} @@ -617,7 +618,7 @@ class BotHandler: updated_at=now, ) - def get_session(self, session_id: str) -> Optional[BotSession]: + def get_session(self, session_id: str) -> BotSession | None: """获取会话""" conn = self.pm.db.get_conn() row = conn.execute( @@ -631,7 +632,7 @@ class BotHandler: return self._row_to_session(row) return None - def list_sessions(self, project_id: str = None) -> List[BotSession]: + def list_sessions(self, project_id: str = None) -> list[BotSession]: """列出会话""" conn = self.pm.db.get_conn() @@ -652,7 +653,7 @@ class BotHandler: return [self._row_to_session(row) for row in rows] - def update_session(self, session_id: str, **kwargs) -> Optional[BotSession]: + def update_session(self, session_id: str, **kwargs) -> BotSession | None: """更新会话""" conn = self.pm.db.get_conn() @@ -709,7 +710,7 @@ class BotHandler: message_count=row["message_count"], ) - async def handle_message(self, session: BotSession, message: Dict) -> Dict: + async def handle_message(self, session: BotSession, message: dict) -> dict: """处理收到的消息""" now = datetime.now().isoformat() @@ -740,7 +741,7 @@ class BotHandler: return {"success": False, "error": "Unsupported message type"} - async def _handle_text_message(self, session: BotSession, text: str, raw_message: Dict) -> Dict: + async def _handle_text_message(self, session: BotSession, text: str, raw_message: dict) -> dict: """处理文本消息""" # 简单命令处理 if text.startswith("/help"): @@ -772,7 +773,7 @@ class BotHandler: # 默认回复 return {"success": True, "response": f"收到消息:{text[:100]}...\n\n使用 /help 查看可用命令"} - async def _handle_audio_message(self, session: BotSession, message: Dict) -> Dict: + async def _handle_audio_message(self, session: BotSession, message: dict) -> dict: """处理音频消息""" if not session.project_id: return {"success": False, "error": "Session not bound to any project"} @@ -802,7 +803,7 @@ class BotHandler: except Exception as e: return {"success": False, "error": f"Failed to process audio: {str(e)}"} - async def _handle_file_message(self, session: BotSession, message: Dict) -> Dict: + async def _handle_file_message(self, session: BotSession, message: dict) -> dict: """处理文件消息""" return {"success": True, "response": "📎 收到文件,正在处理中..."} @@ -825,8 +826,8 @@ class BotHandler: async def _send_feishu_message(self, session: BotSession, message: str, msg_type: str) -> bool: """发送飞书消息""" - import hashlib import base64 + import hashlib timestamp = str(int(time.time())) @@ -850,8 +851,8 @@ class BotHandler: async def _send_dingtalk_message(self, session: BotSession, message: str, msg_type: str) -> bool: """发送钉钉消息""" - import hashlib import base64 + import hashlib timestamp = str(round(time.time() * 1000)) @@ -890,8 +891,8 @@ class WebhookIntegration: endpoint_url: str, project_id: str = None, auth_type: str = "none", - auth_config: Dict = None, - trigger_events: List[str] = None, + auth_config: dict = None, + trigger_events: list[str] = None, ) -> WebhookEndpoint: """创建 Webhook 端点""" endpoint_id = str(uuid.uuid4())[:8] @@ -935,7 +936,7 @@ class WebhookIntegration: updated_at=now, ) - def get_endpoint(self, endpoint_id: str) -> Optional[WebhookEndpoint]: + def get_endpoint(self, endpoint_id: str) -> WebhookEndpoint | None: """获取端点""" conn = self.pm.db.get_conn() row = conn.execute( @@ -947,7 +948,7 @@ class WebhookIntegration: return self._row_to_endpoint(row) return None - def list_endpoints(self, project_id: str = None) -> List[WebhookEndpoint]: + def list_endpoints(self, project_id: str = None) -> list[WebhookEndpoint]: """列出端点""" conn = self.pm.db.get_conn() @@ -968,7 +969,7 @@ class WebhookIntegration: return [self._row_to_endpoint(row) for row in rows] - def update_endpoint(self, endpoint_id: str, **kwargs) -> Optional[WebhookEndpoint]: + def update_endpoint(self, endpoint_id: str, **kwargs) -> WebhookEndpoint | None: """更新端点""" conn = self.pm.db.get_conn() @@ -1034,7 +1035,7 @@ class WebhookIntegration: trigger_count=row["trigger_count"], ) - async def trigger(self, endpoint: WebhookEndpoint, event_type: str, data: Dict) -> bool: + async def trigger(self, endpoint: WebhookEndpoint, event_type: str, data: dict) -> bool: """触发 Webhook""" if not endpoint.is_active: return False @@ -1079,7 +1080,7 @@ class WebhookIntegration: print(f"Failed to trigger webhook: {e}") return False - async def test_endpoint(self, endpoint: WebhookEndpoint) -> Dict: + async def test_endpoint(self, endpoint: WebhookEndpoint) -> dict: """测试端点""" test_data = { "message": "This is a test event from InsightFlow", @@ -1160,7 +1161,7 @@ class WebDAVSyncManager: updated_at=now, ) - def get_sync(self, sync_id: str) -> Optional[WebDAVSync]: + def get_sync(self, sync_id: str) -> WebDAVSync | None: """获取同步配置""" conn = self.pm.db.get_conn() row = conn.execute("SELECT * FROM webdav_syncs WHERE id = ?", (sync_id,)).fetchone() @@ -1170,7 +1171,7 @@ class WebDAVSyncManager: return self._row_to_sync(row) return None - def list_syncs(self, project_id: str = None) -> List[WebDAVSync]: + def list_syncs(self, project_id: str = None) -> list[WebDAVSync]: """列出同步配置""" conn = self.pm.db.get_conn() @@ -1185,7 +1186,7 @@ class WebDAVSyncManager: return [self._row_to_sync(row) for row in rows] - def update_sync(self, sync_id: str, **kwargs) -> Optional[WebDAVSync]: + def update_sync(self, sync_id: str, **kwargs) -> WebDAVSync | None: """更新同步配置""" conn = self.pm.db.get_conn() @@ -1252,7 +1253,7 @@ class WebDAVSyncManager: sync_count=row["sync_count"], ) - async def test_connection(self, sync: WebDAVSync) -> Dict: + async def test_connection(self, sync: WebDAVSync) -> dict: """测试 WebDAV 连接""" if not WEBDAV_AVAILABLE: return {"success": False, "error": "WebDAV library not available"} @@ -1268,7 +1269,7 @@ class WebDAVSyncManager: except Exception as e: return {"success": False, "error": str(e)} - async def sync_project(self, sync: WebDAVSync) -> Dict: + async def sync_project(self, sync: WebDAVSync) -> dict: """同步项目到 WebDAV""" if not WEBDAV_AVAILABLE: return {"success": False, "error": "WebDAV library not available"} diff --git a/backend/rate_limiter.py b/backend/rate_limiter.py index 254b91d..5e01b0b 100644 --- a/backend/rate_limiter.py +++ b/backend/rate_limiter.py @@ -5,11 +5,11 @@ API 限流中间件 支持基于内存的滑动窗口限流 """ -import time import asyncio -from typing import Dict, Optional, Callable -from dataclasses import dataclass +import time from collections import defaultdict +from collections.abc import Callable +from dataclasses import dataclass from functools import wraps @@ -37,7 +37,7 @@ class SlidingWindowCounter: def __init__(self, window_size: int = 60): self.window_size = window_size - self.requests: Dict[int, int] = defaultdict(int) # 秒级计数 + self.requests: dict[int, int] = defaultdict(int) # 秒级计数 self._lock = asyncio.Lock() self._cleanup_lock = asyncio.Lock() @@ -69,13 +69,13 @@ class RateLimiter: def __init__(self): # key -> SlidingWindowCounter - self.counters: Dict[str, SlidingWindowCounter] = {} + self.counters: dict[str, SlidingWindowCounter] = {} # key -> RateLimitConfig - self.configs: Dict[str, RateLimitConfig] = {} + self.configs: dict[str, RateLimitConfig] = {} self._lock = asyncio.Lock() self._cleanup_lock = asyncio.Lock() - async def is_allowed(self, key: str, config: Optional[RateLimitConfig] = None) -> RateLimitInfo: + async def is_allowed(self, key: str, config: RateLimitConfig | None = None) -> RateLimitInfo: """ 检查是否允许请求 @@ -143,7 +143,7 @@ class RateLimiter: retry_after=max(0, config.window_size) if current_count >= config.requests_per_minute else 0, ) - def reset(self, key: Optional[str] = None): + def reset(self, key: str | None = None): """重置限流计数器""" if key: self.counters.pop(key, None) @@ -154,7 +154,7 @@ class RateLimiter: # 全局限流器实例 -_rate_limiter: Optional[RateLimiter] = None +_rate_limiter: RateLimiter | None = None def get_rate_limiter() -> RateLimiter: @@ -166,7 +166,7 @@ def get_rate_limiter() -> RateLimiter: # 限流装饰器(用于函数级别限流) -def rate_limit(requests_per_minute: int = 60, key_func: Optional[Callable] = None): +def rate_limit(requests_per_minute: int = 60, key_func: Callable | None = None): """ 限流装饰器 diff --git a/backend/search_manager.py b/backend/search_manager.py index f92564c..ce25840 100644 --- a/backend/search_manager.py +++ b/backend/search_manager.py @@ -9,15 +9,14 @@ Phase 7 Task 6: Advanced Search & Discovery 4. KnowledgeGapDetection - 知识缺口识别 """ -import re +import hashlib import json import math +import re import sqlite3 -import hashlib -from dataclasses import dataclass, field -from typing import List, Dict, Optional, Tuple, Set -from datetime import datetime from collections import defaultdict +from dataclasses import dataclass, field +from datetime import datetime from enum import Enum @@ -46,10 +45,10 @@ class SearchResult: content_type: str # transcript, entity, relation project_id: str score: float - highlights: List[Tuple[int, int]] = field(default_factory=list) # 高亮位置 - metadata: Dict = field(default_factory=dict) + highlights: list[tuple[int, int]] = field(default_factory=list) # 高亮位置 + metadata: dict = field(default_factory=dict) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "id": self.id, "content": self.content, @@ -69,10 +68,10 @@ class SemanticSearchResult: content_type: str project_id: str similarity: float - embedding: Optional[List[float]] = None - metadata: Dict = field(default_factory=dict) + embedding: list[float] | None = None + metadata: dict = field(default_factory=dict) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: result = { "id": self.id, "content": self.content[:500] + "..." if len(self.content) > 500 else self.content, @@ -95,12 +94,12 @@ class EntityPath: target_entity_id: str target_entity_name: str path_length: int - nodes: List[Dict] # 路径上的节点 - edges: List[Dict] # 路径上的边 + nodes: list[dict] # 路径上的节点 + edges: list[dict] # 路径上的边 confidence: float path_description: str - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "path_id": self.path_id, "source_entity_id": self.source_entity_id, @@ -120,15 +119,15 @@ class KnowledgeGap: """知识缺口数据模型""" gap_id: str gap_type: str # missing_attribute, sparse_relation, isolated_entity, incomplete_entity - entity_id: Optional[str] - entity_name: Optional[str] + entity_id: str | None + entity_name: str | None description: str severity: str # high, medium, low - suggestions: List[str] - related_entities: List[str] - metadata: Dict = field(default_factory=dict) + suggestions: list[str] + related_entities: list[str] + metadata: dict = field(default_factory=dict) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "gap_id": self.gap_id, "gap_type": self.gap_type, @@ -149,8 +148,8 @@ class SearchIndex: content_id: str content_type: str project_id: str - tokens: List[str] - token_positions: Dict[str, List[int]] # 词 -> 位置列表 + tokens: list[str] + token_positions: dict[str, list[int]] # 词 -> 位置列表 created_at: str updated_at: str @@ -162,7 +161,7 @@ class TextEmbedding: content_id: str content_type: str project_id: str - embedding: List[float] + embedding: list[float] model_name: str created_at: str @@ -231,7 +230,7 @@ class FullTextSearch: conn.commit() conn.close() - def _tokenize(self, text: str) -> List[str]: + def _tokenize(self, text: str) -> list[str]: """ 中文分词(简化版) @@ -243,7 +242,7 @@ class FullTextSearch: tokens = re.findall(r'[\u4e00-\u9fa5]+|[a-z]+|\d+', text) return tokens - def _extract_positions(self, text: str, tokens: List[str]) -> Dict[str, List[int]]: + def _extract_positions(self, text: str, tokens: list[str]) -> dict[str, list[int]]: """提取每个词在文本中的位置""" positions = defaultdict(list) text_lower = text.lower() @@ -326,9 +325,9 @@ class FullTextSearch: print(f"索引创建失败: {e}") return False - def search(self, query: str, project_id: Optional[str] = None, - content_types: Optional[List[str]] = None, - limit: int = 20, offset: int = 0) -> List[SearchResult]: + def search(self, query: str, project_id: str | None = None, + content_types: list[str] | None = None, + limit: int = 20, offset: int = 0) -> list[SearchResult]: """ 全文搜索 @@ -358,7 +357,7 @@ class FullTextSearch: return scored_results[offset:offset + limit] - def _parse_boolean_query(self, query: str) -> Dict: + def _parse_boolean_query(self, query: str) -> dict: """ 解析布尔查询 @@ -401,9 +400,9 @@ class FullTextSearch: "phrases": phrases } - def _execute_boolean_search(self, parsed_query: Dict, - project_id: Optional[str] = None, - content_types: Optional[List[str]] = None) -> List[Dict]: + def _execute_boolean_search(self, parsed_query: dict, + project_id: str | None = None, + content_types: list[str] | None = None) -> list[dict]: """执行布尔搜索""" conn = self._get_conn() @@ -503,7 +502,7 @@ class FullTextSearch: return results def _get_content_by_id(self, conn: sqlite3.Connection, - content_id: str, content_type: str) -> Optional[str]: + content_id: str, content_type: str) -> str | None: """根据ID获取内容""" try: if content_type == "transcript": @@ -542,7 +541,7 @@ class FullTextSearch: return None def _get_project_id(self, conn: sqlite3.Connection, - content_id: str, content_type: str) -> Optional[str]: + content_id: str, content_type: str) -> str | None: """获取内容所属的项目ID""" try: if content_type == "transcript": @@ -567,7 +566,7 @@ class FullTextSearch: except Exception: return None - def _score_results(self, results: List[Dict], parsed_query: Dict) -> List[SearchResult]: + def _score_results(self, results: list[dict], parsed_query: dict) -> list[SearchResult]: """计算搜索结果的相关性分数""" scored = [] all_terms = parsed_query["and"] + parsed_query["or"] + parsed_query["phrases"] @@ -689,7 +688,7 @@ class FullTextSearch: print(f"删除索引失败: {e}") return False - def reindex_project(self, project_id: str) -> Dict: + def reindex_project(self, project_id: str) -> dict: """重新索引整个项目""" conn = self._get_conn() stats = {"transcripts": 0, "entities": 0, "relations": 0, "errors": 0} @@ -808,7 +807,7 @@ class SemanticSearch: """检查语义搜索是否可用""" return self.model is not None and SENTENCE_TRANSFORMERS_AVAILABLE - def generate_embedding(self, text: str) -> Optional[List[float]]: + def generate_embedding(self, text: str) -> list[float] | None: """ 生成文本的 embedding 向量 @@ -878,9 +877,9 @@ class SemanticSearch: print(f"索引 embedding 失败: {e}") return False - def search(self, query: str, project_id: Optional[str] = None, - content_types: Optional[List[str]] = None, - top_k: int = 10, threshold: float = 0.5) -> List[SemanticSearchResult]: + def search(self, query: str, project_id: str | None = None, + content_types: list[str] | None = None, + top_k: int = 10, threshold: float = 0.5) -> list[SemanticSearchResult]: """ 语义搜索 @@ -959,7 +958,7 @@ class SemanticSearch: results.sort(key=lambda x: x.similarity, reverse=True) return results[:top_k] - def _get_content_text(self, content_id: str, content_type: str) -> Optional[str]: + def _get_content_text(self, content_id: str, content_type: str) -> str | None: """获取内容文本""" conn = self._get_conn() @@ -1002,7 +1001,7 @@ class SemanticSearch: return None def find_similar_content(self, content_id: str, content_type: str, - top_k: int = 5) -> List[SemanticSearchResult]: + top_k: int = 5) -> list[SemanticSearchResult]: """ 查找与指定内容相似的内容 @@ -1107,7 +1106,7 @@ class EntityPathDiscovery: def find_shortest_path(self, source_entity_id: str, target_entity_id: str, - max_depth: int = 5) -> Optional[EntityPath]: + max_depth: int = 5) -> EntityPath | None: """ 查找两个实体之间的最短路径(BFS算法) @@ -1181,7 +1180,7 @@ class EntityPathDiscovery: def find_all_paths(self, source_entity_id: str, target_entity_id: str, max_depth: int = 4, - max_paths: int = 10) -> List[EntityPath]: + max_paths: int = 10) -> list[EntityPath]: """ 查找两个实体之间的所有路径(限制数量和深度) @@ -1211,7 +1210,7 @@ class EntityPathDiscovery: paths = [] def dfs(current_id: str, target_id: str, - path: List[str], visited: Set[str], depth: int): + path: list[str], visited: set[str], depth: int): if depth > max_depth: return @@ -1247,7 +1246,7 @@ class EntityPathDiscovery: # 构建路径对象 return [self._build_path_object(path, project_id) for path in paths] - def _build_path_object(self, entity_ids: List[str], + def _build_path_object(self, entity_ids: list[str], project_id: str) -> EntityPath: """构建路径对象""" conn = self._get_conn() @@ -1312,7 +1311,7 @@ class EntityPathDiscovery: ) def find_multi_hop_relations(self, entity_id: str, - max_hops: int = 3) -> List[Dict]: + max_hops: int = 3) -> list[dict]: """ 查找实体的多跳关系 @@ -1394,7 +1393,7 @@ class EntityPathDiscovery: return relations def _get_path_to_entity(self, source_id: str, target_id: str, - project_id: str, conn: sqlite3.Connection) -> List[str]: + project_id: str, conn: sqlite3.Connection) -> list[str]: """获取从源实体到目标实体的路径(简化版)""" # BFS 找路径 visited = {source_id} @@ -1428,7 +1427,7 @@ class EntityPathDiscovery: return [] - def generate_path_visualization(self, path: EntityPath) -> Dict: + def generate_path_visualization(self, path: EntityPath) -> dict: """ 生成路径可视化数据 @@ -1467,7 +1466,7 @@ class EntityPathDiscovery: "confidence": path.confidence } - def analyze_path_centrality(self, project_id: str) -> List[Dict]: + def analyze_path_centrality(self, project_id: str) -> list[dict]: """ 分析项目中实体的路径中心性(桥接程度) @@ -1558,7 +1557,7 @@ class KnowledgeGapDetection: conn.row_factory = sqlite3.Row return conn - def analyze_project(self, project_id: str) -> List[KnowledgeGap]: + def analyze_project(self, project_id: str) -> list[KnowledgeGap]: """ 分析项目中的知识缺口 @@ -1591,7 +1590,7 @@ class KnowledgeGapDetection: return gaps - def _check_entity_attribute_completeness(self, project_id: str) -> List[KnowledgeGap]: + def _check_entity_attribute_completeness(self, project_id: str) -> list[KnowledgeGap]: """检查实体属性完整性""" conn = self._get_conn() gaps = [] @@ -1661,7 +1660,7 @@ class KnowledgeGapDetection: conn.close() return gaps - def _check_relation_sparsity(self, project_id: str) -> List[KnowledgeGap]: + def _check_relation_sparsity(self, project_id: str) -> list[KnowledgeGap]: """检查关系稀疏度""" conn = self._get_conn() gaps = [] @@ -1720,7 +1719,7 @@ class KnowledgeGapDetection: conn.close() return gaps - def _check_isolated_entities(self, project_id: str) -> List[KnowledgeGap]: + def _check_isolated_entities(self, project_id: str) -> list[KnowledgeGap]: """检查孤立实体(没有任何关系)""" conn = self._get_conn() gaps = [] @@ -1756,7 +1755,7 @@ class KnowledgeGapDetection: conn.close() return gaps - def _check_incomplete_entities(self, project_id: str) -> List[KnowledgeGap]: + def _check_incomplete_entities(self, project_id: str) -> list[KnowledgeGap]: """检查不完整实体(缺少名称、类型或定义)""" conn = self._get_conn() gaps = [] @@ -1788,7 +1787,7 @@ class KnowledgeGapDetection: conn.close() return gaps - def _check_missing_key_entities(self, project_id: str) -> List[KnowledgeGap]: + def _check_missing_key_entities(self, project_id: str) -> list[KnowledgeGap]: """检查可能缺失的关键实体""" conn = self._get_conn() gaps = [] @@ -1841,7 +1840,7 @@ class KnowledgeGapDetection: conn.close() return gaps[:10] # 限制数量 - def generate_completeness_report(self, project_id: str) -> Dict: + def generate_completeness_report(self, project_id: str) -> dict: """ 生成知识完整性报告 @@ -1898,7 +1897,7 @@ class KnowledgeGapDetection: "recommendations": self._generate_recommendations(gaps) } - def _generate_recommendations(self, gaps: List[KnowledgeGap]) -> List[str]: + def _generate_recommendations(self, gaps: list[KnowledgeGap]) -> list[str]: """生成改进建议""" recommendations = [] @@ -1941,8 +1940,8 @@ class SearchManager: self.path_discovery = EntityPathDiscovery(db_path) self.gap_detection = KnowledgeGapDetection(db_path) - def hybrid_search(self, query: str, project_id: Optional[str] = None, - limit: int = 20) -> Dict: + def hybrid_search(self, query: str, project_id: str | None = None, + limit: int = 20) -> dict: """ 混合搜索(全文 + 语义) @@ -2014,7 +2013,7 @@ class SearchManager: "results": results[:limit] } - def index_project(self, project_id: str) -> Dict: + def index_project(self, project_id: str) -> dict: """ 为项目建立所有索引 @@ -2071,7 +2070,7 @@ class SearchManager: "semantic": semantic_stats } - def get_search_stats(self, project_id: Optional[str] = None) -> Dict: + def get_search_stats(self, project_id: str | None = None) -> dict: """获取搜索统计信息""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row @@ -2126,28 +2125,28 @@ def get_search_manager(db_path: str = "insightflow.db") -> SearchManager: # 便捷函数 -def fulltext_search(query: str, project_id: Optional[str] = None, - limit: int = 20) -> List[SearchResult]: +def fulltext_search(query: str, project_id: str | None = None, + limit: int = 20) -> list[SearchResult]: """全文搜索便捷函数""" manager = get_search_manager() return manager.fulltext_search.search(query, project_id, limit=limit) -def semantic_search(query: str, project_id: Optional[str] = None, - top_k: int = 10) -> List[SemanticSearchResult]: +def semantic_search(query: str, project_id: str | None = None, + top_k: int = 10) -> list[SemanticSearchResult]: """语义搜索便捷函数""" manager = get_search_manager() return manager.semantic_search.search(query, project_id, top_k=top_k) def find_entity_path(source_id: str, target_id: str, - max_depth: int = 5) -> Optional[EntityPath]: + max_depth: int = 5) -> EntityPath | None: """查找实体路径便捷函数""" manager = get_search_manager() return manager.path_discovery.find_shortest_path(source_id, target_id, max_depth) -def detect_knowledge_gaps(project_id: str) -> List[KnowledgeGap]: +def detect_knowledge_gaps(project_id: str) -> list[KnowledgeGap]: """知识缺口检测便捷函数""" manager = get_search_manager() return manager.gap_detection.analyze_project(project_id) diff --git a/backend/security_manager.py b/backend/security_manager.py index 080afde..777bb5b 100644 --- a/backend/security_manager.py +++ b/backend/security_manager.py @@ -3,16 +3,16 @@ InsightFlow Phase 7 Task 3: 数据安全与合规模块 Security Manager - 端到端加密、数据脱敏、审计日志 """ -import json -import hashlib -import secrets import base64 +import hashlib +import json import re -from datetime import datetime, timedelta -from typing import List, Optional, Dict, Any, Tuple -from dataclasses import dataclass, field, asdict -from enum import Enum +import secrets import sqlite3 +from dataclasses import asdict, dataclass, field +from datetime import datetime, timedelta +from enum import Enum +from typing import Any # 加密相关 try: @@ -71,19 +71,19 @@ class AuditLog: """审计日志条目""" id: str action_type: str - user_id: Optional[str] = None - user_ip: Optional[str] = None - user_agent: Optional[str] = None - resource_type: Optional[str] = None # project, entity, transcript, etc. - resource_id: Optional[str] = None - action_details: Optional[str] = None # JSON string - before_value: Optional[str] = None - after_value: Optional[str] = None + user_id: str | None = None + user_ip: str | None = None + user_agent: str | None = None + resource_type: str | None = None # project, entity, transcript, etc. + resource_id: str | None = None + action_details: str | None = None # JSON string + before_value: str | None = None + after_value: str | None = None success: bool = True - error_message: Optional[str] = None + error_message: str | None = None created_at: str = field(default_factory=lambda: datetime.now().isoformat()) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return asdict(self) @@ -95,12 +95,12 @@ class EncryptionConfig: is_enabled: bool = False encryption_type: str = "aes-256-gcm" # aes-256-gcm, chacha20-poly1305 key_derivation: str = "pbkdf2" # pbkdf2, argon2 - master_key_hash: Optional[str] = None # 主密钥哈希(用于验证) - salt: Optional[str] = None + master_key_hash: str | None = None # 主密钥哈希(用于验证) + salt: str | None = None created_at: str = field(default_factory=lambda: datetime.now().isoformat()) updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return asdict(self) @@ -115,11 +115,11 @@ class MaskingRule: replacement: str # 替换模板,如 "****" is_active: bool = True priority: int = 0 - description: Optional[str] = None + description: str | None = None created_at: str = field(default_factory=lambda: datetime.now().isoformat()) updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return asdict(self) @@ -129,18 +129,18 @@ class DataAccessPolicy: id: str project_id: str name: str - description: Optional[str] = None - allowed_users: Optional[str] = None # JSON array of user IDs - allowed_roles: Optional[str] = None # JSON array of roles - allowed_ips: Optional[str] = None # JSON array of IP patterns - time_restrictions: Optional[str] = None # JSON: {"start_time": "09:00", "end_time": "18:00"} - max_access_count: Optional[int] = None # 最大访问次数 + description: str | None = None + allowed_users: str | None = None # JSON array of user IDs + allowed_roles: str | None = None # JSON array of roles + allowed_ips: str | None = None # JSON array of IP patterns + time_restrictions: str | None = None # JSON: {"start_time": "09:00", "end_time": "18:00"} + max_access_count: int | None = None # 最大访问次数 require_approval: bool = False is_active: bool = True created_at: str = field(default_factory=lambda: datetime.now().isoformat()) updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return asdict(self) @@ -150,14 +150,14 @@ class AccessRequest: id: str policy_id: str user_id: str - request_reason: Optional[str] = None + request_reason: str | None = None status: str = "pending" # pending, approved, rejected, expired - approved_by: Optional[str] = None - approved_at: Optional[str] = None - expires_at: Optional[str] = None + approved_by: str | None = None + approved_at: str | None = None + expires_at: str | None = None created_at: str = field(default_factory=lambda: datetime.now().isoformat()) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return asdict(self) @@ -196,7 +196,7 @@ class SecurityManager: self.db_path = db_path self.db_path = db_path # 预编译正则缓存 - self._compiled_patterns: Dict[str, re.Pattern] = {} + self._compiled_patterns: dict[str, re.Pattern] = {} self._local = {} self._init_db() @@ -317,16 +317,16 @@ class SecurityManager: def log_audit( self, action_type: AuditActionType, - user_id: Optional[str] = None, - user_ip: Optional[str] = None, - user_agent: Optional[str] = None, - resource_type: Optional[str] = None, - resource_id: Optional[str] = None, - action_details: Optional[Dict] = None, - before_value: Optional[str] = None, - after_value: Optional[str] = None, + user_id: str | None = None, + user_ip: str | None = None, + user_agent: str | None = None, + resource_type: str | None = None, + resource_id: str | None = None, + action_details: dict | None = None, + before_value: str | None = None, + after_value: str | None = None, success: bool = True, - error_message: Optional[str] = None + error_message: str | None = None ) -> AuditLog: """记录审计日志""" log = AuditLog( @@ -364,16 +364,16 @@ class SecurityManager: def get_audit_logs( self, - user_id: Optional[str] = None, - resource_type: Optional[str] = None, - resource_id: Optional[str] = None, - action_type: Optional[str] = None, - start_time: Optional[str] = None, - end_time: Optional[str] = None, - success: Optional[bool] = None, + user_id: str | None = None, + resource_type: str | None = None, + resource_id: str | None = None, + action_type: str | None = None, + start_time: str | None = None, + end_time: str | None = None, + success: bool | None = None, limit: int = 100, offset: int = 0 - ) -> List[AuditLog]: + ) -> list[AuditLog]: """查询审计日志""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -438,9 +438,9 @@ class SecurityManager: def get_audit_stats( self, - start_time: Optional[str] = None, - end_time: Optional[str] = None - ) -> Dict[str, Any]: + start_time: str | None = None, + end_time: str | None = None + ) -> dict[str, Any]: """获取审计统计""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -633,7 +633,7 @@ class SecurityManager: return key_hash == stored_hash - def get_encryption_config(self, project_id: str) -> Optional[EncryptionConfig]: + def get_encryption_config(self, project_id: str) -> EncryptionConfig | None: """获取加密配置""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -664,8 +664,8 @@ class SecurityManager: self, data: str, password: str, - salt: Optional[str] = None - ) -> Tuple[str, str]: + salt: str | None = None + ) -> tuple[str, str]: """加密数据""" if not CRYPTO_AVAILABLE: raise RuntimeError("cryptography library not available") @@ -702,9 +702,9 @@ class SecurityManager: project_id: str, name: str, rule_type: MaskingRuleType, - pattern: Optional[str] = None, - replacement: Optional[str] = None, - description: Optional[str] = None, + pattern: str | None = None, + replacement: str | None = None, + description: str | None = None, priority: int = 0 ) -> MaskingRule: """创建脱敏规则""" @@ -756,7 +756,7 @@ class SecurityManager: self, project_id: str, active_only: bool = True - ) -> List[MaskingRule]: + ) -> list[MaskingRule]: """获取脱敏规则""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -795,7 +795,7 @@ class SecurityManager: self, rule_id: str, **kwargs - ) -> Optional[MaskingRule]: + ) -> MaskingRule | None: """更新脱敏规则""" allowed_fields = ["name", "pattern", "replacement", "is_active", "priority", "description"] @@ -868,7 +868,7 @@ class SecurityManager: self, text: str, project_id: str, - rule_types: Optional[List[MaskingRuleType]] = None + rule_types: list[MaskingRuleType] | None = None ) -> str: """应用脱敏规则到文本""" rules = self.get_masking_rules(project_id) @@ -897,9 +897,9 @@ class SecurityManager: def apply_masking_to_entity( self, - entity_data: Dict[str, Any], + entity_data: dict[str, Any], project_id: str - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """对实体数据应用脱敏""" masked_data = entity_data.copy() @@ -918,12 +918,12 @@ class SecurityManager: self, project_id: str, name: str, - description: Optional[str] = None, - allowed_users: Optional[List[str]] = None, - allowed_roles: Optional[List[str]] = None, - allowed_ips: Optional[List[str]] = None, - time_restrictions: Optional[Dict] = None, - max_access_count: Optional[int] = None, + description: str | None = None, + allowed_users: list[str] | None = None, + allowed_roles: list[str] | None = None, + allowed_ips: list[str] | None = None, + time_restrictions: dict | None = None, + max_access_count: int | None = None, require_approval: bool = False ) -> DataAccessPolicy: """创建数据访问策略""" @@ -966,7 +966,7 @@ class SecurityManager: self, project_id: str, active_only: bool = True - ) -> List[DataAccessPolicy]: + ) -> list[DataAccessPolicy]: """获取数据访问策略""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -1005,8 +1005,8 @@ class SecurityManager: self, policy_id: str, user_id: str, - user_ip: Optional[str] = None - ) -> Tuple[bool, Optional[str]]: + user_ip: str | None = None + ) -> tuple[bool, str | None]: """检查访问权限""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -1107,7 +1107,7 @@ class SecurityManager: self, policy_id: str, user_id: str, - request_reason: Optional[str] = None, + request_reason: str | None = None, expires_hours: int = 24 ) -> AccessRequest: """创建访问请求""" @@ -1142,7 +1142,7 @@ class SecurityManager: request_id: str, approved_by: str, expires_hours: int = 24 - ) -> Optional[AccessRequest]: + ) -> AccessRequest | None: """批准访问请求""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -1182,7 +1182,7 @@ class SecurityManager: self, request_id: str, rejected_by: str - ) -> Optional[AccessRequest]: + ) -> AccessRequest | None: """拒绝访问请求""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() diff --git a/backend/subscription_manager.py b/backend/subscription_manager.py index 0c11721..1f0a80f 100644 --- a/backend/subscription_manager.py +++ b/backend/subscription_manager.py @@ -10,19 +10,19 @@ InsightFlow Phase 8 - 订阅与计费系统模块 作者: InsightFlow Team """ -import sqlite3 import json -import uuid -from datetime import datetime, timedelta -from typing import Optional, List, Dict, Any -from dataclasses import dataclass -from enum import Enum import logging +import sqlite3 +import uuid +from dataclasses import dataclass +from datetime import datetime, timedelta +from enum import StrEnum +from typing import Any logger = logging.getLogger(__name__) -class SubscriptionStatus(str, Enum): +class SubscriptionStatus(StrEnum): """订阅状态""" ACTIVE = "active" # 活跃 CANCELLED = "cancelled" # 已取消 @@ -32,7 +32,7 @@ class SubscriptionStatus(str, Enum): PENDING = "pending" # 待支付 -class PaymentProvider(str, Enum): +class PaymentProvider(StrEnum): """支付提供商""" STRIPE = "stripe" # Stripe ALIPAY = "alipay" # 支付宝 @@ -40,7 +40,7 @@ class PaymentProvider(str, Enum): BANK_TRANSFER = "bank_transfer" # 银行转账 -class PaymentStatus(str, Enum): +class PaymentStatus(StrEnum): """支付状态""" PENDING = "pending" # 待支付 PROCESSING = "processing" # 处理中 @@ -50,7 +50,7 @@ class PaymentStatus(str, Enum): PARTIAL_REFUNDED = "partial_refunded" # 部分退款 -class InvoiceStatus(str, Enum): +class InvoiceStatus(StrEnum): """发票状态""" DRAFT = "draft" # 草稿 ISSUED = "issued" # 已开具 @@ -60,7 +60,7 @@ class InvoiceStatus(str, Enum): CREDIT_NOTE = "credit_note" # 贷项通知单 -class RefundStatus(str, Enum): +class RefundStatus(StrEnum): """退款状态""" PENDING = "pending" # 待处理 APPROVED = "approved" # 已批准 @@ -79,12 +79,12 @@ class SubscriptionPlan: price_monthly: float # 月付价格 price_yearly: float # 年付价格 currency: str # CNY/USD - features: List[str] # 功能列表 - limits: Dict[str, Any] # 资源限制 + features: list[str] # 功能列表 + limits: dict[str, Any] # 资源限制 is_active: bool created_at: datetime updated_at: datetime - metadata: Dict[str, Any] + metadata: dict[str, Any] @dataclass @@ -97,14 +97,14 @@ class Subscription: current_period_start: datetime current_period_end: datetime cancel_at_period_end: bool - canceled_at: Optional[datetime] - trial_start: Optional[datetime] - trial_end: Optional[datetime] - payment_provider: Optional[str] - provider_subscription_id: Optional[str] # 支付提供商的订阅ID + canceled_at: datetime | None + trial_start: datetime | None + trial_end: datetime | None + payment_provider: str | None + provider_subscription_id: str | None # 支付提供商的订阅ID created_at: datetime updated_at: datetime - metadata: Dict[str, Any] + metadata: dict[str, Any] @dataclass @@ -117,8 +117,8 @@ class UsageRecord: unit: str # minutes/mb/count recorded_at: datetime cost: float # 费用 - description: Optional[str] - metadata: Dict[str, Any] + description: str | None + metadata: dict[str, Any] @dataclass @@ -126,18 +126,18 @@ class Payment: """支付记录数据类""" id: str tenant_id: str - subscription_id: Optional[str] - invoice_id: Optional[str] + subscription_id: str | None + invoice_id: str | None amount: float currency: str provider: str - provider_payment_id: Optional[str] + provider_payment_id: str | None status: str - payment_method: Optional[str] - payment_details: Dict[str, Any] - paid_at: Optional[datetime] - failed_at: Optional[datetime] - failure_reason: Optional[str] + payment_method: str | None + payment_details: dict[str, Any] + paid_at: datetime | None + failed_at: datetime | None + failure_reason: str | None created_at: datetime updated_at: datetime @@ -147,7 +147,7 @@ class Invoice: """发票数据类""" id: str tenant_id: str - subscription_id: Optional[str] + subscription_id: str | None invoice_number: str status: str amount_due: float @@ -156,11 +156,11 @@ class Invoice: period_start: datetime period_end: datetime description: str - line_items: List[Dict[str, Any]] + line_items: list[dict[str, Any]] due_date: datetime - paid_at: Optional[datetime] - voided_at: Optional[datetime] - void_reason: Optional[str] + paid_at: datetime | None + voided_at: datetime | None + void_reason: str | None created_at: datetime updated_at: datetime @@ -171,18 +171,18 @@ class Refund: id: str tenant_id: str payment_id: str - invoice_id: Optional[str] + invoice_id: str | None amount: float currency: str reason: str status: str requested_by: str requested_at: datetime - approved_by: Optional[str] - approved_at: Optional[str] - completed_at: Optional[datetime] - provider_refund_id: Optional[str] - metadata: Dict[str, Any] + approved_by: str | None + approved_at: str | None + completed_at: datetime | None + provider_refund_id: str | None + metadata: dict[str, Any] created_at: datetime updated_at: datetime @@ -199,7 +199,7 @@ class BillingHistory: reference_id: str # 关联的订阅/支付/退款ID balance_after: float # 操作后余额 created_at: datetime - metadata: Dict[str, Any] + metadata: dict[str, Any] class SubscriptionManager: @@ -542,7 +542,7 @@ class SubscriptionManager: # ==================== 订阅计划管理 ==================== - def get_plan(self, plan_id: str) -> Optional[SubscriptionPlan]: + def get_plan(self, plan_id: str) -> SubscriptionPlan | None: """获取订阅计划""" conn = self._get_connection() try: @@ -557,7 +557,7 @@ class SubscriptionManager: finally: conn.close() - def get_plan_by_tier(self, tier: str) -> Optional[SubscriptionPlan]: + def get_plan_by_tier(self, tier: str) -> SubscriptionPlan | None: """通过层级获取订阅计划""" conn = self._get_connection() try: @@ -572,7 +572,7 @@ class SubscriptionManager: finally: conn.close() - def list_plans(self, include_inactive: bool = False) -> List[SubscriptionPlan]: + def list_plans(self, include_inactive: bool = False) -> list[SubscriptionPlan]: """列出所有订阅计划""" conn = self._get_connection() try: @@ -591,8 +591,8 @@ class SubscriptionManager: def create_plan(self, name: str, tier: str, description: str, price_monthly: float, price_yearly: float, - currency: str = "CNY", features: List[str] = None, - limits: Dict[str, Any] = None) -> SubscriptionPlan: + currency: str = "CNY", features: list[str] = None, + limits: dict[str, Any] = None) -> SubscriptionPlan: """创建新订阅计划""" conn = self._get_connection() try: @@ -639,7 +639,7 @@ class SubscriptionManager: finally: conn.close() - def update_plan(self, plan_id: str, **kwargs) -> Optional[SubscriptionPlan]: + def update_plan(self, plan_id: str, **kwargs) -> SubscriptionPlan | None: """更新订阅计划""" conn = self._get_connection() try: @@ -685,7 +685,7 @@ class SubscriptionManager: # ==================== 订阅管理 ==================== def create_subscription(self, tenant_id: str, plan_id: str, - payment_provider: Optional[str] = None, + payment_provider: str | None = None, trial_days: int = 0, billing_cycle: str = "monthly") -> Subscription: """创建新订阅""" @@ -785,7 +785,7 @@ class SubscriptionManager: finally: conn.close() - def get_subscription(self, subscription_id: str) -> Optional[Subscription]: + def get_subscription(self, subscription_id: str) -> Subscription | None: """获取订阅信息""" conn = self._get_connection() try: @@ -800,7 +800,7 @@ class SubscriptionManager: finally: conn.close() - def get_tenant_subscription(self, tenant_id: str) -> Optional[Subscription]: + def get_tenant_subscription(self, tenant_id: str) -> Subscription | None: """获取租户的当前订阅""" conn = self._get_connection() try: @@ -819,7 +819,7 @@ class SubscriptionManager: finally: conn.close() - def update_subscription(self, subscription_id: str, **kwargs) -> Optional[Subscription]: + def update_subscription(self, subscription_id: str, **kwargs) -> Subscription | None: """更新订阅""" conn = self._get_connection() try: @@ -862,7 +862,7 @@ class SubscriptionManager: conn.close() def cancel_subscription(self, subscription_id: str, - at_period_end: bool = True) -> Optional[Subscription]: + at_period_end: bool = True) -> Subscription | None: """取消订阅""" conn = self._get_connection() try: @@ -904,7 +904,7 @@ class SubscriptionManager: conn.close() def change_plan(self, subscription_id: str, new_plan_id: str, - prorate: bool = True) -> Optional[Subscription]: + prorate: bool = True) -> Subscription | None: """更改订阅计划""" conn = self._get_connection() try: @@ -950,8 +950,8 @@ class SubscriptionManager: def record_usage(self, tenant_id: str, resource_type: str, quantity: float, unit: str, - description: Optional[str] = None, - metadata: Optional[Dict] = None) -> UsageRecord: + description: str | None = None, + metadata: dict | None = None) -> UsageRecord: """记录用量""" conn = self._get_connection() try: @@ -989,8 +989,8 @@ class SubscriptionManager: conn.close() def get_usage_summary(self, tenant_id: str, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None) -> Dict[str, Any]: + start_date: datetime | None = None, + end_date: datetime | None = None) -> dict[str, Any]: """获取用量汇总""" conn = self._get_connection() try: @@ -1061,10 +1061,10 @@ class SubscriptionManager: # ==================== 支付管理 ==================== def create_payment(self, tenant_id: str, amount: float, currency: str, - provider: str, subscription_id: Optional[str] = None, - invoice_id: Optional[str] = None, - payment_method: Optional[str] = None, - payment_details: Optional[Dict] = None) -> Payment: + provider: str, subscription_id: str | None = None, + invoice_id: str | None = None, + payment_method: str | None = None, + payment_details: dict | None = None) -> Payment: """创建支付记录""" conn = self._get_connection() try: @@ -1113,7 +1113,7 @@ class SubscriptionManager: conn.close() def confirm_payment(self, payment_id: str, - provider_payment_id: Optional[str] = None) -> Optional[Payment]: + provider_payment_id: str | None = None) -> Payment | None: """确认支付完成""" conn = self._get_connection() try: @@ -1160,7 +1160,7 @@ class SubscriptionManager: finally: conn.close() - def fail_payment(self, payment_id: str, reason: str) -> Optional[Payment]: + def fail_payment(self, payment_id: str, reason: str) -> Payment | None: """标记支付失败""" conn = self._get_connection() try: @@ -1179,7 +1179,7 @@ class SubscriptionManager: finally: conn.close() - def get_payment(self, payment_id: str) -> Optional[Payment]: + def get_payment(self, payment_id: str) -> Payment | None: """获取支付记录""" conn = self._get_connection() try: @@ -1187,8 +1187,8 @@ class SubscriptionManager: finally: conn.close() - def list_payments(self, tenant_id: str, status: Optional[str] = None, - limit: int = 100, offset: int = 0) -> List[Payment]: + def list_payments(self, tenant_id: str, status: str | None = None, + limit: int = 100, offset: int = 0) -> list[Payment]: """列出支付记录""" conn = self._get_connection() try: @@ -1212,7 +1212,7 @@ class SubscriptionManager: finally: conn.close() - def _get_payment_internal(self, conn: sqlite3.Connection, payment_id: str) -> Optional[Payment]: + def _get_payment_internal(self, conn: sqlite3.Connection, payment_id: str) -> Payment | None: """内部方法:获取支付记录""" cursor = conn.cursor() cursor.execute("SELECT * FROM payments WHERE id = ?", (payment_id,)) @@ -1225,10 +1225,10 @@ class SubscriptionManager: # ==================== 发票管理 ==================== def _create_invoice_internal(self, conn: sqlite3.Connection, tenant_id: str, - subscription_id: Optional[str], amount: float, + subscription_id: str | None, amount: float, currency: str, period_start: datetime, period_end: datetime, description: str, - line_items: Optional[List[Dict]] = None) -> Invoice: + line_items: list[dict] | None = None) -> Invoice: """内部方法:创建发票""" invoice_id = str(uuid.uuid4()) invoice_number = self._generate_invoice_number() @@ -1275,7 +1275,7 @@ class SubscriptionManager: return invoice - def get_invoice(self, invoice_id: str) -> Optional[Invoice]: + def get_invoice(self, invoice_id: str) -> Invoice | None: """获取发票""" conn = self._get_connection() try: @@ -1290,7 +1290,7 @@ class SubscriptionManager: finally: conn.close() - def get_invoice_by_number(self, invoice_number: str) -> Optional[Invoice]: + def get_invoice_by_number(self, invoice_number: str) -> Invoice | None: """通过发票号获取发票""" conn = self._get_connection() try: @@ -1305,8 +1305,8 @@ class SubscriptionManager: finally: conn.close() - def list_invoices(self, tenant_id: str, status: Optional[str] = None, - limit: int = 100, offset: int = 0) -> List[Invoice]: + def list_invoices(self, tenant_id: str, status: str | None = None, + limit: int = 100, offset: int = 0) -> list[Invoice]: """列出发票""" conn = self._get_connection() try: @@ -1330,7 +1330,7 @@ class SubscriptionManager: finally: conn.close() - def void_invoice(self, invoice_id: str, reason: str) -> Optional[Invoice]: + def void_invoice(self, invoice_id: str, reason: str) -> Invoice | None: """作废发票""" conn = self._get_connection() try: @@ -1442,7 +1442,7 @@ class SubscriptionManager: finally: conn.close() - def approve_refund(self, refund_id: str, approved_by: str) -> Optional[Refund]: + def approve_refund(self, refund_id: str, approved_by: str) -> Refund | None: """批准退款""" conn = self._get_connection() try: @@ -1469,7 +1469,7 @@ class SubscriptionManager: conn.close() def complete_refund(self, refund_id: str, - provider_refund_id: Optional[str] = None) -> Optional[Refund]: + provider_refund_id: str | None = None) -> Refund | None: """完成退款""" conn = self._get_connection() try: @@ -1507,7 +1507,7 @@ class SubscriptionManager: finally: conn.close() - def reject_refund(self, refund_id: str, reason: str) -> Optional[Refund]: + def reject_refund(self, refund_id: str, reason: str) -> Refund | None: """拒绝退款""" conn = self._get_connection() try: @@ -1530,7 +1530,7 @@ class SubscriptionManager: finally: conn.close() - def get_refund(self, refund_id: str) -> Optional[Refund]: + def get_refund(self, refund_id: str) -> Refund | None: """获取退款记录""" conn = self._get_connection() try: @@ -1538,8 +1538,8 @@ class SubscriptionManager: finally: conn.close() - def list_refunds(self, tenant_id: str, status: Optional[str] = None, - limit: int = 100, offset: int = 0) -> List[Refund]: + def list_refunds(self, tenant_id: str, status: str | None = None, + limit: int = 100, offset: int = 0) -> list[Refund]: """列出退款记录""" conn = self._get_connection() try: @@ -1563,7 +1563,7 @@ class SubscriptionManager: finally: conn.close() - def _get_refund_internal(self, conn: sqlite3.Connection, refund_id: str) -> Optional[Refund]: + def _get_refund_internal(self, conn: sqlite3.Connection, refund_id: str) -> Refund | None: """内部方法:获取退款记录""" cursor = conn.cursor() cursor.execute("SELECT * FROM refunds WHERE id = ?", (refund_id,)) @@ -1593,9 +1593,9 @@ class SubscriptionManager: )) def get_billing_history(self, tenant_id: str, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, - limit: int = 100, offset: int = 0) -> List[BillingHistory]: + start_date: datetime | None = None, + end_date: datetime | None = None, + limit: int = 100, offset: int = 0) -> list[BillingHistory]: """获取账单历史""" conn = self._get_connection() try: @@ -1626,7 +1626,7 @@ class SubscriptionManager: def create_stripe_checkout_session(self, tenant_id: str, plan_id: str, success_url: str, cancel_url: str, - billing_cycle: str = "monthly") -> Dict[str, Any]: + billing_cycle: str = "monthly") -> dict[str, Any]: """创建 Stripe Checkout 会话(占位实现)""" # 这里应该集成 Stripe SDK # 简化实现,返回模拟数据 @@ -1638,7 +1638,7 @@ class SubscriptionManager: } def create_alipay_order(self, tenant_id: str, plan_id: str, - billing_cycle: str = "monthly") -> Dict[str, Any]: + billing_cycle: str = "monthly") -> dict[str, Any]: """创建支付宝订单(占位实现)""" # 这里应该集成支付宝 SDK plan = self.get_plan(plan_id) @@ -1654,7 +1654,7 @@ class SubscriptionManager: } def create_wechat_order(self, tenant_id: str, plan_id: str, - billing_cycle: str = "monthly") -> Dict[str, Any]: + billing_cycle: str = "monthly") -> dict[str, Any]: """创建微信支付订单(占位实现)""" # 这里应该集成微信支付 SDK plan = self.get_plan(plan_id) @@ -1669,7 +1669,7 @@ class SubscriptionManager: "provider": "wechat" } - def handle_webhook(self, provider: str, payload: Dict[str, Any]) -> bool: + def handle_webhook(self, provider: str, payload: dict[str, Any]) -> bool: """处理支付提供商的 Webhook(占位实现)""" # 这里应该实现实际的 Webhook 处理逻辑 logger.info(f"Received webhook from {provider}: {payload.get('event_type', 'unknown')}") diff --git a/backend/tenant_manager.py b/backend/tenant_manager.py index e6ed4dc..5b71a25 100644 --- a/backend/tenant_manager.py +++ b/backend/tenant_manager.py @@ -11,16 +11,16 @@ InsightFlow Phase 8 - 多租户 SaaS 架构管理模块 作者: InsightFlow Team """ -import sqlite3 -import json -import uuid import hashlib -import re -from datetime import datetime -from typing import Optional, List, Dict, Any, Tuple -from dataclasses import dataclass, asdict -from enum import Enum +import json import logging +import re +import sqlite3 +import uuid +from dataclasses import asdict, dataclass +from datetime import datetime +from enum import StrEnum +from typing import Any logger = logging.getLogger(__name__) @@ -44,7 +44,7 @@ class TenantLimits: UNLIMITED = -1 -class TenantStatus(str, Enum): +class TenantStatus(StrEnum): """租户状态""" ACTIVE = "active" # 活跃 SUSPENDED = "suspended" # 暂停 @@ -53,14 +53,14 @@ class TenantStatus(str, Enum): PENDING = "pending" # 待激活 -class TenantTier(str, Enum): +class TenantTier(StrEnum): """租户订阅层级""" FREE = "free" # 免费版 PRO = "pro" # 专业版 ENTERPRISE = "enterprise" # 企业版 -class TenantRole(str, Enum): +class TenantRole(StrEnum): """租户角色""" OWNER = "owner" # 所有者 ADMIN = "admin" # 管理员 @@ -68,7 +68,7 @@ class TenantRole(str, Enum): VIEWER = "viewer" # 查看者 -class DomainStatus(str, Enum): +class DomainStatus(StrEnum): """域名状态""" PENDING = "pending" # 待验证 VERIFIED = "verified" # 已验证 @@ -82,16 +82,16 @@ class Tenant: id: str name: str slug: str # URL 友好的唯一标识 - description: Optional[str] + description: str | None tier: str # free/pro/enterprise status: str # active/suspended/trial/expired/pending owner_id: str # 所有者用户ID created_at: datetime updated_at: datetime - expires_at: Optional[datetime] # 订阅过期时间 - settings: Dict[str, Any] # 租户级设置 - resource_limits: Dict[str, Any] # 资源限制 - metadata: Dict[str, Any] # 元数据 + expires_at: datetime | None # 订阅过期时间 + settings: dict[str, Any] # 租户级设置 + resource_limits: dict[str, Any] # 资源限制 + metadata: dict[str, Any] # 元数据 @dataclass @@ -103,12 +103,12 @@ class TenantDomain: status: str # pending/verified/failed/expired verification_token: str # 验证令牌 verification_method: str # dns/file - verified_at: Optional[datetime] + verified_at: datetime | None created_at: datetime updated_at: datetime is_primary: bool # 是否主域名 ssl_enabled: bool # SSL 是否启用 - ssl_expires_at: Optional[datetime] + ssl_expires_at: datetime | None @dataclass @@ -116,14 +116,14 @@ class TenantBranding: """租户品牌配置数据类""" id: str tenant_id: str - logo_url: Optional[str] # Logo URL - favicon_url: Optional[str] # Favicon URL - primary_color: Optional[str] # 主题主色 - secondary_color: Optional[str] # 主题次色 - custom_css: Optional[str] # 自定义 CSS - custom_js: Optional[str] # 自定义 JS - login_page_bg: Optional[str] # 登录页背景 - email_template: Optional[str] # 邮件模板 + logo_url: str | None # Logo URL + favicon_url: str | None # Favicon URL + primary_color: str | None # 主题主色 + secondary_color: str | None # 主题次色 + custom_css: str | None # 自定义 CSS + custom_js: str | None # 自定义 JS + login_page_bg: str | None # 登录页背景 + email_template: str | None # 邮件模板 created_at: datetime updated_at: datetime @@ -136,11 +136,11 @@ class TenantMember: user_id: str email: str role: str # owner/admin/member/viewer - permissions: List[str] # 具体权限列表 - invited_by: Optional[str] # 邀请者 + permissions: list[str] # 具体权限列表 + invited_by: str | None # 邀请者 invited_at: datetime - joined_at: Optional[datetime] - last_active_at: Optional[datetime] + joined_at: datetime | None + last_active_at: datetime | None status: str # active/pending/suspended @@ -151,10 +151,10 @@ class TenantPermission: tenant_id: str name: str # 权限名称 code: str # 权限代码 - description: Optional[str] + description: str | None resource_type: str # project/entity/api/etc - actions: List[str] # create/read/update/delete/etc - conditions: Optional[Dict] # 条件限制 + actions: list[str] # create/read/update/delete/etc + conditions: dict | None # 条件限制 created_at: datetime @@ -381,8 +381,8 @@ class TenantManager: def create_tenant(self, name: str, owner_id: str, tier: str = "free", - description: Optional[str] = None, - settings: Optional[Dict] = None) -> Tenant: + description: str | None = None, + settings: dict | None = None) -> Tenant: """创建新租户""" conn = self._get_connection() try: @@ -436,7 +436,7 @@ class TenantManager: finally: conn.close() - def get_tenant(self, tenant_id: str) -> Optional[Tenant]: + def get_tenant(self, tenant_id: str) -> Tenant | None: """获取租户信息""" conn = self._get_connection() try: @@ -451,7 +451,7 @@ class TenantManager: finally: conn.close() - def get_tenant_by_slug(self, slug: str) -> Optional[Tenant]: + def get_tenant_by_slug(self, slug: str) -> Tenant | None: """通过 slug 获取租户""" conn = self._get_connection() try: @@ -466,7 +466,7 @@ class TenantManager: finally: conn.close() - def get_tenant_by_domain(self, domain: str) -> Optional[Tenant]: + def get_tenant_by_domain(self, domain: str) -> Tenant | None: """通过自定义域名获取租户""" conn = self._get_connection() try: @@ -486,11 +486,11 @@ class TenantManager: conn.close() def update_tenant(self, tenant_id: str, - name: Optional[str] = None, - description: Optional[str] = None, - tier: Optional[str] = None, - status: Optional[str] = None, - settings: Optional[Dict] = None) -> Optional[Tenant]: + name: str | None = None, + description: str | None = None, + tier: str | None = None, + status: str | None = None, + settings: dict | None = None) -> Tenant | None: """更新租户信息""" conn = self._get_connection() try: @@ -548,9 +548,9 @@ class TenantManager: finally: conn.close() - def list_tenants(self, status: Optional[str] = None, - tier: Optional[str] = None, - limit: int = 100, offset: int = 0) -> List[Tenant]: + def list_tenants(self, status: str | None = None, + tier: str | None = None, + limit: int = 100, offset: int = 0) -> list[Tenant]: """列出租户""" conn = self._get_connection() try: @@ -689,7 +689,7 @@ class TenantManager: finally: conn.close() - def get_domain_verification_instructions(self, domain_id: str) -> Dict[str, Any]: + def get_domain_verification_instructions(self, domain_id: str) -> dict[str, Any]: """获取域名验证指导""" conn = self._get_connection() try: @@ -739,7 +739,7 @@ class TenantManager: finally: conn.close() - def list_domains(self, tenant_id: str) -> List[TenantDomain]: + def list_domains(self, tenant_id: str) -> list[TenantDomain]: """列出租户的所有域名""" conn = self._get_connection() try: @@ -758,7 +758,7 @@ class TenantManager: # ==================== 品牌白标管理 ==================== - def get_branding(self, tenant_id: str) -> Optional[TenantBranding]: + def get_branding(self, tenant_id: str) -> TenantBranding | None: """获取租户品牌配置""" conn = self._get_connection() try: @@ -774,14 +774,14 @@ class TenantManager: conn.close() def update_branding(self, tenant_id: str, - logo_url: Optional[str] = None, - favicon_url: Optional[str] = None, - primary_color: Optional[str] = None, - secondary_color: Optional[str] = None, - custom_css: Optional[str] = None, - custom_js: Optional[str] = None, - login_page_bg: Optional[str] = None, - email_template: Optional[str] = None) -> TenantBranding: + logo_url: str | None = None, + favicon_url: str | None = None, + primary_color: str | None = None, + secondary_color: str | None = None, + custom_css: str | None = None, + custom_js: str | None = None, + login_page_bg: str | None = None, + email_template: str | None = None) -> TenantBranding: """更新租户品牌配置""" conn = self._get_connection() try: @@ -890,7 +890,7 @@ class TenantManager: # ==================== 成员与权限管理 ==================== def invite_member(self, tenant_id: str, email: str, role: str, - invited_by: str, permissions: Optional[List[str]] = None) -> TenantMember: + invited_by: str, permissions: list[str] | None = None) -> TenantMember: """邀请成员加入租户""" conn = self._get_connection() try: @@ -967,7 +967,7 @@ class TenantManager: conn.close() def update_member_role(self, tenant_id: str, member_id: str, - role: str, permissions: Optional[List[str]] = None) -> bool: + role: str, permissions: list[str] | None = None) -> bool: """更新成员角色""" conn = self._get_connection() try: @@ -988,7 +988,7 @@ class TenantManager: finally: conn.close() - def list_members(self, tenant_id: str, status: Optional[str] = None) -> List[TenantMember]: + def list_members(self, tenant_id: str, status: str | None = None) -> list[TenantMember]: """列出租户成员""" conn = self._get_connection() try: @@ -1042,7 +1042,7 @@ class TenantManager: finally: conn.close() - def get_user_tenants(self, user_id: str) -> List[Dict[str, Any]]: + def get_user_tenants(self, user_id: str) -> list[dict[str, Any]]: """获取用户所属的所有租户""" conn = self._get_connection() try: @@ -1108,8 +1108,8 @@ class TenantManager: conn.close() def get_usage_stats(self, tenant_id: str, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None) -> Dict[str, Any]: + start_date: datetime | None = None, + end_date: datetime | None = None) -> dict[str, Any]: """获取使用统计""" conn = self._get_connection() try: @@ -1165,7 +1165,7 @@ class TenantManager: finally: conn.close() - def check_resource_limit(self, tenant_id: str, resource_type: str) -> Tuple[bool, int, int]: + def check_resource_limit(self, tenant_id: str, resource_type: str) -> tuple[bool, int, int]: """检查资源是否超限 Returns: @@ -1288,7 +1288,7 @@ class TenantManager: def _add_member_internal(self, conn: sqlite3.Connection, tenant_id: str, user_id: str, email: str, role: TenantRole, - invited_by: Optional[str]): + invited_by: str | None): """内部方法:添加成员""" cursor = conn.cursor() member_id = str(uuid.uuid4()) @@ -1416,8 +1416,8 @@ class TenantManager: class TenantContext: """租户上下文管理器 - 用于请求级别的租户隔离""" - _current_tenant_id: Optional[str] = None - _current_user_id: Optional[str] = None + _current_tenant_id: str | None = None + _current_user_id: str | None = None @classmethod def set_current_tenant(cls, tenant_id: str): @@ -1425,7 +1425,7 @@ class TenantContext: cls._current_tenant_id = tenant_id @classmethod - def get_current_tenant(cls) -> Optional[str]: + def get_current_tenant(cls) -> str | None: """获取当前租户ID""" return cls._current_tenant_id @@ -1435,7 +1435,7 @@ class TenantContext: cls._current_user_id = user_id @classmethod - def get_current_user(cls) -> Optional[str]: + def get_current_user(cls) -> str | None: """获取当前用户ID""" return cls._current_user_id diff --git a/backend/test_multimodal.py b/backend/test_multimodal.py index a5af2ff..d4ff38e 100644 --- a/backend/test_multimodal.py +++ b/backend/test_multimodal.py @@ -4,8 +4,8 @@ InsightFlow Multimodal Module Test Script 测试多模态支持模块 """ -import sys import os +import sys # 添加 backend 目录到路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -18,25 +18,19 @@ print("=" * 60) print("\n1. 测试模块导入...") try: - from multimodal_processor import ( - get_multimodal_processor - ) + from multimodal_processor import get_multimodal_processor print(" ✓ multimodal_processor 导入成功") except ImportError as e: print(f" ✗ multimodal_processor 导入失败: {e}") try: - from image_processor import ( - get_image_processor - ) + from image_processor import get_image_processor print(" ✓ image_processor 导入成功") except ImportError as e: print(f" ✗ image_processor 导入失败: {e}") try: - from multimodal_entity_linker import ( - get_multimodal_entity_linker - ) + from multimodal_entity_linker import get_multimodal_entity_linker print(" ✓ multimodal_entity_linker 导入成功") except ImportError as e: print(f" ✗ multimodal_entity_linker 导入失败: {e}") diff --git a/backend/test_phase7_task6_8.py b/backend/test_phase7_task6_8.py index 7cddc03..b43a755 100644 --- a/backend/test_phase7_task6_8.py +++ b/backend/test_phase7_task6_8.py @@ -4,19 +4,19 @@ InsightFlow Phase 7 Task 6 & 8 测试脚本 测试高级搜索与发现、性能优化与扩展功能 """ -from performance_manager import ( - get_performance_manager, CacheManager, - TaskQueue, PerformanceMonitor -) -from search_manager import ( - get_search_manager, FullTextSearch, - SemanticSearch, EntityPathDiscovery, - KnowledgeGapDetection -) import os import sys import time +from performance_manager import CacheManager, PerformanceMonitor, TaskQueue, get_performance_manager +from search_manager import ( + EntityPathDiscovery, + FullTextSearch, + KnowledgeGapDetection, + SemanticSearch, + get_search_manager, +) + # 添加 backend 到路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) diff --git a/backend/test_phase8_task1.py b/backend/test_phase8_task1.py index 0c33bf0..e723e27 100644 --- a/backend/test_phase8_task1.py +++ b/backend/test_phase8_task1.py @@ -10,11 +10,11 @@ InsightFlow Phase 8 Task 1 - 多租户 SaaS 架构测试脚本 5. 资源使用统计 """ -from tenant_manager import ( - get_tenant_manager -) -import sys import os +import sys + +from tenant_manager import get_tenant_manager + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) diff --git a/backend/test_phase8_task2.py b/backend/test_phase8_task2.py index 95b353b..ff376a7 100644 --- a/backend/test_phase8_task2.py +++ b/backend/test_phase8_task2.py @@ -3,13 +3,12 @@ InsightFlow Phase 8 Task 2 测试脚本 - 订阅与计费系统 """ -from subscription_manager import ( - SubscriptionManager, PaymentProvider -) -import sys import os +import sys import tempfile +from subscription_manager import PaymentProvider, SubscriptionManager + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) diff --git a/backend/test_phase8_task4.py b/backend/test_phase8_task4.py index a57b08c..06b1a8c 100644 --- a/backend/test_phase8_task4.py +++ b/backend/test_phase8_task4.py @@ -4,12 +4,11 @@ InsightFlow Phase 8 Task 4 测试脚本 测试 AI 能力增强功能 """ -from ai_manager import ( - get_ai_manager, ModelType, PredictionType -) import asyncio -import sys import os +import sys + +from ai_manager import ModelType, PredictionType, get_ai_manager # Add backend directory to path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) diff --git a/backend/test_phase8_task5.py b/backend/test_phase8_task5.py index 1d721e5..be65bd6 100644 --- a/backend/test_phase8_task5.py +++ b/backend/test_phase8_task5.py @@ -13,14 +13,20 @@ InsightFlow Phase 8 Task 5 - 运营与增长工具测试脚本 python test_phase8_task5.py """ -from growth_manager import ( - GrowthManager, EventType, ExperimentStatus, TrafficAllocationType, EmailTemplateType, WorkflowTriggerType -) import asyncio -import sys import os +import sys from datetime import datetime, timedelta +from growth_manager import ( + EmailTemplateType, + EventType, + ExperimentStatus, + GrowthManager, + TrafficAllocationType, + WorkflowTriggerType, +) + # 添加 backend 目录到路径 backend_dir = os.path.dirname(os.path.abspath(__file__)) if backend_dir not in sys.path: diff --git a/backend/test_phase8_task6.py b/backend/test_phase8_task6.py index 4b025cd..9b30561 100644 --- a/backend/test_phase8_task6.py +++ b/backend/test_phase8_task6.py @@ -10,17 +10,20 @@ InsightFlow Phase 8 Task 6: Developer Ecosystem Test Script 4. 开发者文档与示例代码 """ -from developer_ecosystem_manager import ( - DeveloperEcosystemManager, - SDKLanguage, TemplateCategory, - PluginCategory, PluginStatus, - DeveloperStatus -) -import sys import os +import sys import uuid from datetime import datetime +from developer_ecosystem_manager import ( + DeveloperEcosystemManager, + DeveloperStatus, + PluginCategory, + PluginStatus, + SDKLanguage, + TemplateCategory, +) + # Add backend directory to path backend_dir = os.path.dirname(os.path.abspath(__file__)) if backend_dir not in sys.path: diff --git a/backend/test_phase8_task8.py b/backend/test_phase8_task8.py index 1237bee..0587a9a 100644 --- a/backend/test_phase8_task8.py +++ b/backend/test_phase8_task8.py @@ -10,15 +10,20 @@ InsightFlow Phase 8 Task 8: Operations & Monitoring Test Script 4. 成本优化 """ -from ops_manager import ( - get_ops_manager, AlertSeverity, AlertStatus, AlertChannelType, AlertRuleType, - ResourceType -) +import json import os import sys -import json from datetime import datetime, timedelta +from ops_manager import ( + AlertChannelType, + AlertRuleType, + AlertSeverity, + AlertStatus, + ResourceType, + get_ops_manager, +) + # Add backend directory to path backend_dir = os.path.dirname(os.path.abspath(__file__)) if backend_dir not in sys.path: diff --git a/backend/tingwu_client.py b/backend/tingwu_client.py index 44c4b91..fab65c7 100644 --- a/backend/tingwu_client.py +++ b/backend/tingwu_client.py @@ -6,7 +6,7 @@ import os import time from datetime import datetime -from typing import Dict, Any +from typing import Any class TingwuClient: @@ -18,7 +18,7 @@ class TingwuClient: if not self.access_key or not self.secret_key: raise ValueError("ALI_ACCESS_KEY and ALI_SECRET_KEY required") - def _sign_request(self, method: str, uri: str, query: str = "", body: str = "") -> Dict[str, str]: + def _sign_request(self, method: str, uri: str, query: str = "", body: str = "") -> dict[str, str]: """阿里云签名 V3""" timestamp = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') @@ -35,9 +35,9 @@ class TingwuClient: def create_task(self, audio_url: str, language: str = "zh") -> str: """创建听悟任务""" try: + from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_tingwu20230930 import models as tingwu_models from alibabacloud_tingwu20230930.client import Client as TingwuSDKClient - from alibabacloud_tea_openapi import models as open_api_models config = open_api_models.Config( access_key_id=self.access_key, @@ -74,12 +74,12 @@ class TingwuClient: print(f"Tingwu API error: {e}") return f"mock_task_{int(time.time())}" - def get_task_result(self, task_id: str, max_retries: int = 60, interval: int = 5) -> Dict[str, Any]: + def get_task_result(self, task_id: str, max_retries: int = 60, interval: int = 5) -> dict[str, Any]: """获取任务结果""" try: + from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_tingwu20230930 import models as tingwu_models from alibabacloud_tingwu20230930.client import Client as TingwuSDKClient - from alibabacloud_tea_openapi import models as open_api_models config = open_api_models.Config( access_key_id=self.access_key, @@ -114,7 +114,7 @@ class TingwuClient: raise TimeoutError(f"Task {task_id} timeout") - def _parse_result(self, data) -> Dict[str, Any]: + def _parse_result(self, data) -> dict[str, Any]: """解析结果""" result = data.result transcription = result.transcription @@ -140,7 +140,7 @@ class TingwuClient: "segments": segments } - def _mock_result(self) -> Dict[str, Any]: + def _mock_result(self) -> dict[str, Any]: """Mock 结果""" return { "full_text": "这是一个示例转录文本,包含 Project Alpha 和 K8s 等术语。", @@ -149,7 +149,7 @@ class TingwuClient: ] } - def transcribe(self, audio_url: str, language: str = "zh") -> Dict[str, Any]: + def transcribe(self, audio_url: str, language: str = "zh") -> dict[str, Any]: """一键转录""" task_id = self.create_task(audio_url, language) print(f"Tingwu task: {task_id}") diff --git a/backend/workflow_manager.py b/backend/workflow_manager.py index a1ba454..2404140 100644 --- a/backend/workflow_manager.py +++ b/backend/workflow_manager.py @@ -9,20 +9,21 @@ InsightFlow Workflow Manager - Phase 7 - 工作流配置管理 """ -import json -import uuid import asyncio -import httpx +import json import logging -from datetime import datetime, timedelta -from typing import List, Dict, Optional, Callable, Any +import uuid +from collections.abc import Callable from dataclasses import dataclass, field +from datetime import datetime, timedelta from enum import Enum +from typing import Any +import httpx +from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.interval import IntervalTrigger -from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR # Configure logging logging.basicConfig(level=logging.INFO) @@ -70,9 +71,9 @@ class WorkflowTask: workflow_id: str name: str task_type: str # analyze, align, discover_relations, notify, custom - config: Dict = field(default_factory=dict) + config: dict = field(default_factory=dict) order: int = 0 - depends_on: List[str] = field(default_factory=list) + depends_on: list[str] = field(default_factory=list) timeout_seconds: int = 300 retry_count: int = 3 retry_delay: int = 5 @@ -94,12 +95,12 @@ class WebhookConfig: webhook_type: str # feishu, dingtalk, slack, custom url: str secret: str = "" # 用于签名验证 - headers: Dict = field(default_factory=dict) + headers: dict = field(default_factory=dict) template: str = "" # 消息模板 is_active: bool = True created_at: str = "" updated_at: str = "" - last_used_at: Optional[str] = None + last_used_at: str | None = None success_count: int = 0 fail_count: int = 0 @@ -119,15 +120,15 @@ class Workflow: workflow_type: str project_id: str status: str = "active" - schedule: Optional[str] = None # cron expression or interval + schedule: str | None = None # cron expression or interval schedule_type: str = "manual" # manual, cron, interval - config: Dict = field(default_factory=dict) - webhook_ids: List[str] = field(default_factory=list) + config: dict = field(default_factory=dict) + webhook_ids: list[str] = field(default_factory=list) is_active: bool = True created_at: str = "" updated_at: str = "" - last_run_at: Optional[str] = None - next_run_at: Optional[str] = None + last_run_at: str | None = None + next_run_at: str | None = None run_count: int = 0 success_count: int = 0 fail_count: int = 0 @@ -144,13 +145,13 @@ class WorkflowLog: """工作流执行日志""" id: str workflow_id: str - task_id: Optional[str] = None + task_id: str | None = None status: str = "pending" # pending, running, success, failed, cancelled - start_time: Optional[str] = None - end_time: Optional[str] = None + start_time: str | None = None + end_time: str | None = None duration_ms: int = 0 - input_data: Dict = field(default_factory=dict) - output_data: Dict = field(default_factory=dict) + input_data: dict = field(default_factory=dict) + output_data: dict = field(default_factory=dict) error_message: str = "" created_at: str = "" @@ -165,7 +166,7 @@ class WebhookNotifier: def __init__(self): self.http_client = httpx.AsyncClient(timeout=30.0) - async def send(self, config: WebhookConfig, message: Dict) -> bool: + async def send(self, config: WebhookConfig, message: dict) -> bool: """发送 Webhook 通知""" try: webhook_type = WebhookType(config.webhook_type) @@ -179,14 +180,14 @@ class WebhookNotifier: else: return await self._send_custom(config, message) - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Webhook send failed: {e}") return False - async def _send_feishu(self, config: WebhookConfig, message: Dict) -> bool: + async def _send_feishu(self, config: WebhookConfig, message: dict) -> bool: """发送飞书通知""" - import hashlib import base64 + import hashlib import hmac timestamp = str(int(datetime.now().timestamp())) @@ -252,10 +253,10 @@ class WebhookNotifier: return result.get("code") == 0 - async def _send_dingtalk(self, config: WebhookConfig, message: Dict) -> bool: + async def _send_dingtalk(self, config: WebhookConfig, message: dict) -> bool: """发送钉钉通知""" - import hashlib import base64 + import hashlib import hmac import urllib.parse @@ -314,7 +315,7 @@ class WebhookNotifier: return result.get("errcode") == 0 - async def _send_slack(self, config: WebhookConfig, message: Dict) -> bool: + async def _send_slack(self, config: WebhookConfig, message: dict) -> bool: """发送 Slack 通知""" # Slack 直接支持标准 webhook 格式 payload = { @@ -341,7 +342,7 @@ class WebhookNotifier: return response.text == "ok" - async def _send_custom(self, config: WebhookConfig, message: Dict) -> bool: + async def _send_custom(self, config: WebhookConfig, message: dict) -> bool: """发送自定义 Webhook 通知""" headers = { "Content-Type": "application/json", @@ -374,8 +375,8 @@ class WorkflowManager: self.db = db_manager self.scheduler = AsyncIOScheduler() self.notifier = WebhookNotifier() - self._task_handlers: Dict[str, Callable] = {} - self._running_tasks: Dict[str, asyncio.Task] = {} + self._task_handlers: dict[str, Callable] = {} + self._running_tasks: dict[str, asyncio.Task] = {} self._setup_default_handlers() # 添加调度器事件监听 @@ -421,7 +422,7 @@ class WorkflowManager: for workflow in workflows: if workflow.schedule and workflow.is_active: self._schedule_workflow(workflow) - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Failed to load workflows: {e}") def _schedule_workflow(self, workflow: Workflow): @@ -458,7 +459,7 @@ class WorkflowManager: """调度器调用的工作流执行函数""" try: await self.execute_workflow(workflow_id) - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Scheduled workflow execution failed: {e}") def _on_job_executed(self, event): @@ -497,7 +498,7 @@ class WorkflowManager: finally: conn.close() - def get_workflow(self, workflow_id: str) -> Optional[Workflow]: + def get_workflow(self, workflow_id: str) -> Workflow | None: """获取工作流""" conn = self.db.get_conn() try: @@ -514,7 +515,7 @@ class WorkflowManager: conn.close() def list_workflows(self, project_id: str = None, status: str = None, - workflow_type: str = None) -> List[Workflow]: + workflow_type: str = None) -> list[Workflow]: """列出工作流""" conn = self.db.get_conn() try: @@ -542,7 +543,7 @@ class WorkflowManager: finally: conn.close() - def update_workflow(self, workflow_id: str, **kwargs) -> Optional[Workflow]: + def update_workflow(self, workflow_id: str, **kwargs) -> Workflow | None: """更新工作流""" conn = self.db.get_conn() try: @@ -648,7 +649,7 @@ class WorkflowManager: finally: conn.close() - def get_task(self, task_id: str) -> Optional[WorkflowTask]: + def get_task(self, task_id: str) -> WorkflowTask | None: """获取任务""" conn = self.db.get_conn() try: @@ -664,7 +665,7 @@ class WorkflowManager: finally: conn.close() - def list_tasks(self, workflow_id: str) -> List[WorkflowTask]: + def list_tasks(self, workflow_id: str) -> list[WorkflowTask]: """列出工作流的所有任务""" conn = self.db.get_conn() try: @@ -677,7 +678,7 @@ class WorkflowManager: finally: conn.close() - def update_task(self, task_id: str, **kwargs) -> Optional[WorkflowTask]: + def update_task(self, task_id: str, **kwargs) -> WorkflowTask | None: """更新任务""" conn = self.db.get_conn() try: @@ -758,7 +759,7 @@ class WorkflowManager: finally: conn.close() - def get_webhook(self, webhook_id: str) -> Optional[WebhookConfig]: + def get_webhook(self, webhook_id: str) -> WebhookConfig | None: """获取 Webhook 配置""" conn = self.db.get_conn() try: @@ -774,7 +775,7 @@ class WorkflowManager: finally: conn.close() - def list_webhooks(self) -> List[WebhookConfig]: + def list_webhooks(self) -> list[WebhookConfig]: """列出所有 Webhook 配置""" conn = self.db.get_conn() try: @@ -786,7 +787,7 @@ class WorkflowManager: finally: conn.close() - def update_webhook(self, webhook_id: str, **kwargs) -> Optional[WebhookConfig]: + def update_webhook(self, webhook_id: str, **kwargs) -> WebhookConfig | None: """更新 Webhook 配置""" conn = self.db.get_conn() try: @@ -889,7 +890,7 @@ class WorkflowManager: finally: conn.close() - def update_log(self, log_id: str, **kwargs) -> Optional[WorkflowLog]: + def update_log(self, log_id: str, **kwargs) -> WorkflowLog | None: """更新工作流日志""" conn = self.db.get_conn() try: @@ -918,7 +919,7 @@ class WorkflowManager: finally: conn.close() - def get_log(self, log_id: str) -> Optional[WorkflowLog]: + def get_log(self, log_id: str) -> WorkflowLog | None: """获取日志""" conn = self.db.get_conn() try: @@ -935,7 +936,7 @@ class WorkflowManager: conn.close() def list_logs(self, workflow_id: str = None, task_id: str = None, - status: str = None, limit: int = 100, offset: int = 0) -> List[WorkflowLog]: + status: str = None, limit: int = 100, offset: int = 0) -> list[WorkflowLog]: """列出工作流日志""" conn = self.db.get_conn() try: @@ -966,7 +967,7 @@ class WorkflowManager: finally: conn.close() - def get_workflow_stats(self, workflow_id: str, days: int = 30) -> Dict: + def get_workflow_stats(self, workflow_id: str, days: int = 30) -> dict: """获取工作流统计""" conn = self.db.get_conn() try: @@ -1037,7 +1038,7 @@ class WorkflowManager: # ==================== Workflow Execution ==================== - async def execute_workflow(self, workflow_id: str, input_data: Dict = None) -> Dict: + async def execute_workflow(self, workflow_id: str, input_data: dict = None) -> dict: """执行工作流""" workflow = self.get_workflow(workflow_id) if not workflow: @@ -1100,7 +1101,7 @@ class WorkflowManager: "duration_ms": duration } - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Workflow {workflow_id} execution failed: {e}") # 更新日志为失败 @@ -1122,8 +1123,8 @@ class WorkflowManager: raise - async def _execute_tasks_with_deps(self, tasks: List[WorkflowTask], - input_data: Dict, log_id: str) -> Dict: + async def _execute_tasks_with_deps(self, tasks: list[WorkflowTask], + input_data: dict, log_id: str) -> dict: """按依赖顺序执行任务""" results = {} completed_tasks = set() @@ -1158,7 +1159,7 @@ class WorkflowManager: try: result = await self._execute_single_task(task, task_input, log_id) break - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Task {task.id} retry {attempt + 1} failed: {e}") if attempt == task.retry_count - 1: raise @@ -1171,7 +1172,7 @@ class WorkflowManager: return results async def _execute_single_task(self, task: WorkflowTask, - input_data: Dict, log_id: str) -> Any: + input_data: dict, log_id: str) -> Any: """执行单个任务""" handler = self._task_handlers.get(task.task_type) if not handler: @@ -1205,7 +1206,7 @@ class WorkflowManager: return result - except asyncio.TimeoutError: + except TimeoutError: self.update_log( task_log.id, status=TaskStatus.FAILED.value, @@ -1224,7 +1225,7 @@ class WorkflowManager: raise async def _execute_default_workflow(self, workflow: Workflow, - input_data: Dict) -> Dict: + input_data: dict) -> dict: """执行默认工作流(根据类型)""" workflow_type = WorkflowType(workflow.workflow_type) @@ -1241,7 +1242,7 @@ class WorkflowManager: # ==================== Default Task Handlers ==================== - async def _handle_analyze_task(self, task: WorkflowTask, input_data: Dict) -> Dict: + async def _handle_analyze_task(self, task: WorkflowTask, input_data: dict) -> dict: """处理分析任务""" project_id = input_data.get("project_id") file_ids = input_data.get("file_ids", []) @@ -1258,7 +1259,7 @@ class WorkflowManager: "status": "completed" } - async def _handle_align_task(self, task: WorkflowTask, input_data: Dict) -> Dict: + async def _handle_align_task(self, task: WorkflowTask, input_data: dict) -> dict: """处理实体对齐任务""" project_id = input_data.get("project_id") threshold = task.config.get("threshold", 0.85) @@ -1276,7 +1277,7 @@ class WorkflowManager: } async def _handle_discover_relations_task(self, task: WorkflowTask, - input_data: Dict) -> Dict: + input_data: dict) -> dict: """处理关系发现任务""" project_id = input_data.get("project_id") @@ -1291,7 +1292,7 @@ class WorkflowManager: "status": "completed" } - async def _handle_notify_task(self, task: WorkflowTask, input_data: Dict) -> Dict: + async def _handle_notify_task(self, task: WorkflowTask, input_data: dict) -> dict: """处理通知任务""" webhook_id = task.config.get("webhook_id") message = task.config.get("message", {}) @@ -1319,7 +1320,7 @@ class WorkflowManager: "success": success } - async def _handle_custom_task(self, task: WorkflowTask, input_data: Dict) -> Dict: + async def _handle_custom_task(self, task: WorkflowTask, input_data: dict) -> dict: """处理自定义任务""" # 自定义任务的具体逻辑由外部处理器实现 return { @@ -1331,7 +1332,7 @@ class WorkflowManager: # ==================== Default Workflow Implementations ==================== - async def _auto_analyze_files(self, workflow: Workflow, input_data: Dict) -> Dict: + async def _auto_analyze_files(self, workflow: Workflow, input_data: dict) -> dict: """自动分析新上传的文件""" project_id = workflow.project_id @@ -1346,7 +1347,7 @@ class WorkflowManager: "status": "completed" } - async def _auto_align_entities(self, workflow: Workflow, input_data: Dict) -> Dict: + async def _auto_align_entities(self, workflow: Workflow, input_data: dict) -> dict: """自动实体对齐""" project_id = workflow.project_id threshold = workflow.config.get("threshold", 0.85) @@ -1359,7 +1360,7 @@ class WorkflowManager: "status": "completed" } - async def _auto_discover_relations(self, workflow: Workflow, input_data: Dict) -> Dict: + async def _auto_discover_relations(self, workflow: Workflow, input_data: dict) -> dict: """自动关系发现""" project_id = workflow.project_id @@ -1370,7 +1371,7 @@ class WorkflowManager: "status": "completed" } - async def _generate_scheduled_report(self, workflow: Workflow, input_data: Dict) -> Dict: + async def _generate_scheduled_report(self, workflow: Workflow, input_data: dict) -> dict: """生成定时报告""" project_id = workflow.project_id report_type = workflow.config.get("report_type", "summary") @@ -1385,7 +1386,7 @@ class WorkflowManager: # ==================== Notification ==================== async def _send_workflow_notification(self, workflow: Workflow, - results: Dict, success: bool = True): + results: dict, success: bool = True): """发送工作流执行通知""" if not workflow.webhook_ids: return @@ -1414,11 +1415,11 @@ class WorkflowManager: try: result = await self.notifier.send(webhook, message) self.update_webhook_stats(webhook_id, result) - except (httpx.HTTPError, asyncio.TimeoutError) as e: + except (TimeoutError, httpx.HTTPError) as e: logger.error(f"Failed to send notification to {webhook_id}: {e}") - def _build_feishu_message(self, workflow: Workflow, results: Dict, - success: bool) -> Dict: + def _build_feishu_message(self, workflow: Workflow, results: dict, + success: bool) -> dict: """构建飞书消息""" status_text = "✅ 成功" if success else "❌ 失败" @@ -1431,8 +1432,8 @@ class WorkflowManager: ] } - def _build_dingtalk_message(self, workflow: Workflow, results: Dict, - success: bool) -> Dict: + def _build_dingtalk_message(self, workflow: Workflow, results: dict, + success: bool) -> dict: """构建钉钉消息""" status_text = "✅ 成功" if success else "❌ 失败" @@ -1453,8 +1454,8 @@ class WorkflowManager: """ } - def _build_slack_message(self, workflow: Workflow, results: Dict, - success: bool) -> Dict: + def _build_slack_message(self, workflow: Workflow, results: dict, + success: bool) -> dict: """构建 Slack 消息""" color = "#36a64f" if success else "#ff0000" status_text = "Success" if success else "Failed"