"""只按明确的源驱动错误分类,不把含凭据的驱动文本写入响应。""" from sqlalchemy.exc import TimeoutError as PoolTimeoutError TRANSIENT_CODES = frozenset({"SOURCE_CONNECTION_LOST", "SOURCE_TIMEOUT"}) MESSAGES = { "TASK_AUTHORIZATION_REVOKED": "请求账号已停用或权限已撤销,请重新授权后创建任务", "SOURCE_CONNECTION_LOST": "源连接中断,请检查网络和只读连接", "SOURCE_TIMEOUT": "源连接或查询超时", "SOURCE_AUTH_ERROR": "源账号认证失败,请核对只读账号配置", "SOURCE_SCHEMA_ERROR": "源表或字段不匹配,请核对版本与映射", "SOURCE_CONFIG_ERROR": "源连接配置不完整或任务排队后发生变化", "SOURCE_DRIVER_ERROR": "源数据库驱动不可用,请检查已验证的ODBC驱动", "QUERY_FILE_MISSING": "缺少源查询文件,请核对部署包", } def classify_source_error(exc, fallback="READ_ERROR"): from app.core.exceptions import ForbiddenError if isinstance(exc, ForbiddenError): return "TASK_AUTHORIZATION_REVOKED" if isinstance(exc, (TimeoutError, PoolTimeoutError)): return "SOURCE_TIMEOUT" if isinstance(exc, ModuleNotFoundError): return "SOURCE_DRIVER_ERROR" if isinstance(exc, FileNotFoundError): return "QUERY_FILE_MISSING" original = getattr(exc, "orig", exc) args = getattr(original, "args", ()) state = str(args[0]).upper() if args else "" if state in {"HYT00", "HYT01"}: return "SOURCE_TIMEOUT" if state in {"08S01", "08001", "08003", "08006"}: return "SOURCE_CONNECTION_LOST" if state == "28000": return "SOURCE_AUTH_ERROR" if state in {"42S02", "42S22", "07002"}: return "SOURCE_SCHEMA_ERROR" return fallback