Files
2026-06-09 21:18:33 +08:00

309 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
表单数据操作 — 细粒度异常体系
异常层次:
FormDataException (所有表单数据异常的基类)
├── FormNotFoundException (表单不存在)
├── RecordNotFoundException (记录不存在)
├── FormDataValidationError (数据校验失败的基类)
│ ├── UniqueConstraintError (唯一约束冲突)
│ ├── NotNullConstraintError (非空约束违反)
│ ├── CheckConstraintError (CHECK 约束违反)
│ ├── ForeignKeyConstraintError (外键约束违反)
│ ├── DataTypeMismatchError (数据类型不匹配)
│ └── DataTooLongError (数据超长)
├── FormDataSchemaError (表结构/配置错误的基类)
│ ├── TableNotFoundError (表不存在)
│ └── ColumnNotFoundError (列不存在)
├── FormDataConnectionError (连接相关错误的基类)
│ ├── DatabaseConnectionError (连接失败/断开)
│ ├── ConnectionPoolExhaustedError (连接池耗尽)
│ └── ConnectionTimeoutError (连接超时)
├── FormDataConcurrencyError (并发相关错误的基类)
│ ├── DeadlockError (死锁)
│ └── LockTimeoutError (锁等待超时)
├── QueryError (SQL 查询构建/执行错误)
└── InternalDatabaseError (其他数据库内部错误)
每个异常类携带 error_code 和结构化的上下文信息 (context),
方便 API 层映射到 HTTP 响应 + 前端根据 error_code 做差异化处理。
"""
from __future__ import annotations
from typing import Any, Dict, Optional
# =====================================================================
# 基类
# =====================================================================
class FormDataException(Exception):
"""表单数据操作异常基类"""
error_code: str = "FORM_DATA_ERROR"
http_status: int = 400
def __init__(
self,
message: str,
*,
error_code: Optional[str] = None,
http_status: Optional[int] = None,
context: Optional[Dict[str, Any]] = None,
):
super().__init__(message)
if error_code is not None:
self.error_code = error_code
if http_status is not None:
self.http_status = http_status
self.context: Dict[str, Any] = context or {}
# =====================================================================
# 资源不存在
# =====================================================================
class FormNotFoundException(FormDataException):
"""表单定义不存在"""
error_code = "FORM_NOT_FOUND"
http_status = 404
def __init__(self, form_code: str):
super().__init__(
f"表单不存在: {form_code}",
context={"form_code": form_code},
)
class RecordNotFoundException(FormDataException):
"""记录不存在"""
error_code = "RECORD_NOT_FOUND"
http_status = 404
def __init__(self, pk: Any, *, table: str = ""):
super().__init__(
f"数据不存在: {pk}",
context={"pk": str(pk), "table": table},
)
# =====================================================================
# 数据校验 / 约束违反
# =====================================================================
class FormDataValidationError(FormDataException):
"""数据校验失败的基类"""
error_code = "VALIDATION_ERROR"
http_status = 400
class UniqueConstraintError(FormDataValidationError):
"""唯一约束冲突(含重复数据信息)"""
error_code = "UNIQUE_CONSTRAINT_VIOLATION"
def __init__(
self,
message: str = "数据重复,违反唯一约束",
*,
fields: Optional[list[str]] = None,
constraint_name: str = "",
):
super().__init__(
message,
context={"fields": fields or [], "constraint_name": constraint_name},
)
class NotNullConstraintError(FormDataValidationError):
"""非空约束违反"""
error_code = "NOT_NULL_VIOLATION"
def __init__(self, column: str = ""):
msg = f"字段 '{column}' 不能为空" if column else "存在必填字段为空"
super().__init__(msg, context={"column": column})
class CheckConstraintError(FormDataValidationError):
"""CHECK 约束违反"""
error_code = "CHECK_CONSTRAINT_VIOLATION"
def __init__(self, constraint_name: str = "", detail: str = ""):
msg = "数据不满足校验规则"
if constraint_name:
msg += f" ({constraint_name})"
super().__init__(msg, context={"constraint_name": constraint_name, "detail": detail})
class ForeignKeyConstraintError(FormDataValidationError):
"""外键约束违反(删除/更新时关联数据存在)"""
error_code = "FOREIGN_KEY_VIOLATION"
def __init__(
self,
message: str = "",
*,
detail: str = "",
referenced_table: str = "",
constraint_name: str = "",
):
if not message:
message = "操作失败,该数据被其他数据引用"
if referenced_table:
message = (
f"无法删除,该数据已被「{referenced_table}」引用,"
"请先删除或解除关联数据"
)
elif detail:
message += f"{detail}"
super().__init__(
message,
context={
"detail": detail,
"referenced_table": referenced_table,
"constraint_name": constraint_name,
},
)
class DataTypeMismatchError(FormDataValidationError):
"""数据类型不匹配"""
error_code = "DATA_TYPE_MISMATCH"
def __init__(self, detail: str = ""):
msg = "数据类型不匹配,请检查输入数据格式"
if detail:
msg += f"{detail}"
super().__init__(msg, context={"detail": detail})
class DataTooLongError(FormDataValidationError):
"""数据长度超出字段限制"""
error_code = "DATA_TOO_LONG"
def __init__(self, column: str = "", max_length: int = 0):
msg = f"字段 '{column}' 的数据过长" if column else "数据长度超出限制"
if max_length:
msg += f"(最大 {max_length} 个字符)"
super().__init__(
msg,
context={"column": column, "max_length": max_length},
)
# =====================================================================
# 表结构 / 配置错误
# =====================================================================
class FormDataSchemaError(FormDataException):
"""表结构或配置相关错误基类"""
error_code = "SCHEMA_ERROR"
http_status = 400
class TableNotFoundError(FormDataSchemaError):
"""数据库表不存在"""
error_code = "TABLE_NOT_FOUND"
def __init__(self, table: str = ""):
msg = f"数据库表 {table} 不存在,请检查数据源配置" if table else "数据库表不存在"
super().__init__(msg, context={"table": table})
class ColumnNotFoundError(FormDataSchemaError):
"""数据库列不存在"""
error_code = "COLUMN_NOT_FOUND"
def __init__(self, column: str = "", table: str = ""):
msg = f"数据库字段 '{column}' 不存在" if column else "数据库字段不存在"
msg += ",请检查表单配置与数据库表结构是否一致"
super().__init__(msg, context={"column": column, "table": table})
# =====================================================================
# 连接相关
# =====================================================================
class FormDataConnectionError(FormDataException):
"""数据库连接相关错误基类"""
error_code = "CONNECTION_ERROR"
http_status = 503
class DatabaseConnectionError(FormDataConnectionError):
"""数据库连接失败或断开"""
error_code = "DB_CONNECTION_FAILED"
def __init__(self, detail: str = ""):
msg = "数据库连接异常,请稍后重试"
super().__init__(msg, context={"detail": detail})
class ConnectionPoolExhaustedError(FormDataConnectionError):
"""连接池耗尽"""
error_code = "CONNECTION_POOL_EXHAUSTED"
def __init__(self):
super().__init__(
"服务器繁忙(数据库连接池已满),请稍后重试",
)
class ConnectionTimeoutError(FormDataConnectionError):
"""连接超时"""
error_code = "CONNECTION_TIMEOUT"
def __init__(self):
super().__init__("数据库连接超时,请稍后重试")
# =====================================================================
# 并发相关
# =====================================================================
class FormDataConcurrencyError(FormDataException):
"""并发相关错误基类"""
error_code = "CONCURRENCY_ERROR"
http_status = 409
class DeadlockError(FormDataConcurrencyError):
"""死锁"""
error_code = "DEADLOCK"
def __init__(self):
super().__init__("操作冲突(死锁),请重试")
class LockTimeoutError(FormDataConcurrencyError):
"""锁等待超时"""
error_code = "LOCK_TIMEOUT"
def __init__(self):
super().__init__("操作等待超时,请稍后重试")
# =====================================================================
# 查询 / 其他
# =====================================================================
class QueryError(FormDataException):
"""SQL 查询构建或执行错误"""
error_code = "QUERY_ERROR"
http_status = 400
def __init__(self, detail: str = ""):
msg = "数据库查询异常,请检查表单配置是否正确"
super().__init__(msg, context={"detail": detail})
class InternalDatabaseError(FormDataException):
"""其他未分类的数据库内部错误"""
error_code = "INTERNAL_DB_ERROR"
http_status = 500
def __init__(self, detail: str = ""):
msg = "数据库操作失败,请联系管理员"
super().__init__(msg, context={"detail": detail})