ForcePilot/backend/package/yuxi/channels/contract/errors/server.py
Kris 08617091dc refactor: 整理项目包结构与导入路径
- 新增多个业务域的__init__.py模块文件,规范包导出结构
- 调整多个DTO文件的导入路径,统一模块组织方式
- 移除测试文件中多余的空行与导入语句
- 优化部分业务模块的包层级划分
2026-07-18 02:04:03 +08:00

248 lines
8.2 KiB
Python
Raw 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.

"""服务端错误类型定义。
定义 ``ServerError`` 抽象基类及其 4 个具体子类,表示由服务端引起的错误
5xx 语义):内部错误、依赖故障、超时、未实现。``ServerError`` 额外携带
``cause`` 字段记录原始错误,便于追踪。
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from yuxi.channels.contract.errors.base import Error
if TYPE_CHECKING:
from yuxi.channels.contract.errors.transport import TransportErrorCategory
class ServerError(Error):
"""服务端错误抽象基类。
表示由服务端引起的错误5xx 语义)。额外携带 ``cause`` 字段记录原始错误,
便于跨链路追踪。子类包括 ``InternalError`` / ``DependencyError`` /
``TimeoutError``。
"""
error_code = "SERVER_ERROR"
def __init__(
self,
message: str,
*,
trace_id: str | None = None,
cause: Error | Exception | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
super().__init__(message, trace_id=trace_id, category_hint=category_hint)
self.cause = cause
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
if self.cause is None:
data["cause"] = None
elif isinstance(self.cause, Error):
data["cause"] = self.cause.to_dict()
else:
# 原生 Exception 无 to_dict(),仅记录类型与信息以便追踪
data["cause"] = {
"type": type(self.cause).__name__,
"message": str(self.cause),
}
return data
class InternalError(ServerError):
"""内部错误。
服务端内部异常导致的错误HTTP 500。``cause`` 可选:当原始错误为
``Error`` 子类时传入以保留结构化错误链;当原始错误为原生 ``Exception``
时可不传 ``cause``,通过 ``message`` 携带异常信息。
``category_hint`` 透传仅供 API 一致性:内部错误默认 ``None``(翻译器按
``isinstance`` 映射为 ``permanent``),透传仅供显式覆盖默认分类。
"""
error_code = "INTERNAL"
def __init__(
self,
cause: Error | Exception | None = None,
*,
trace_id: str | None = None,
message: str | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
super().__init__(
message or "Internal error",
trace_id=trace_id,
cause=cause,
category_hint=category_hint,
)
class DependencyError(ServerError):
"""依赖故障错误。
外部依赖如数据库、Redis、第三方服务故障时抛出HTTP 502
``cause`` 可选:当存在真实原始异常(如 ``except Exception as exc`` 捕获的
异常)时传入以保留结构化错误链;当无真实异常(如"缓存未命中""状态非法"
等业务条件)时应通过 ``message`` 携带诊断信息,不构造占位 ``Exception``。
``retry_after_ms`` 携带上游建议的重试等待时间(毫秒),用于 503 等
场景解析 ``Retry-After`` header 后透传给全局错误处理器M10
默认 ``None`` 表示无明确重试建议,保持向后兼容。
"""
error_code = "DEPENDENCY"
def __init__(
self,
dep: str,
*,
cause: Error | Exception | None = None,
message: str | None = None,
trace_id: str | None = None,
category_hint: TransportErrorCategory | None = None,
retry_after_ms: int | None = None,
) -> None:
super().__init__(
message or f"Dependency {dep} failed",
trace_id=trace_id,
cause=cause,
category_hint=category_hint,
)
self.dep = dep
self.retry_after_ms = retry_after_ms
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["dep"] = self.dep
if self.retry_after_ms is not None:
data["retry_after_ms"] = self.retry_after_ms
return data
class OperationTimeoutError(ServerError):
"""操作超时错误。
操作超时时抛出HTTP 504error_code=``TIMEOUT``)。命名为
``OperationTimeoutError`` 以避免与 Python 内置 ``TimeoutError`` 冲突,
遵循 ``PermissionDeniedError`` 的同一命名约定。
"""
error_code = "TIMEOUT"
def __init__(
self,
timeout_ms: int,
*,
message: str | None = None,
trace_id: str | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
"""初始化超时错误。
Args:
timeout_ms: 超时毫秒数,写入 ``details`` 供客户端参考。
message: 自定义错误信息。为 ``None`` 时使用默认模板
``"Operation timed out after {timeout_ms}ms"``。
trace_id: 调用链路追踪 ID用于跨链路关联。
category_hint: 传输分类建议供翻译器读取F-06
"""
super().__init__(
message or f"Operation timed out after {timeout_ms}ms",
trace_id=trace_id,
category_hint=category_hint,
)
self.timeout_ms = timeout_ms
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["timeout_ms"] = self.timeout_ms
return data
class NotImplementedError(ServerError):
"""未实现错误。
请求的操作尚未实现时抛出HTTP 501。用于标记尚未落地的功能分支
避免静默返回空结果掩盖功能缺失。
不透传 ``category_hint``:未实现属功能缺失语义,不涉及传输分类映射
(翻译器按 ``error_code`` 映射 HTTP 501无需显式覆盖传输分类。
"""
error_code = "NOT_IMPLEMENTED"
def __init__(
self,
operation: str,
*,
message: str | None = None,
trace_id: str | None = None,
details: dict[str, Any] | None = None,
) -> None:
"""初始化未实现错误。
Args:
operation: 未实现的操作名称,写入 ``details`` 供客户端参考。
message: 自定义错误信息。为 ``None`` 时使用默认模板
``"Operation not implemented: {operation}"``。
trace_id: 调用链路追踪 ID用于跨链路关联。
details: 额外业务字段,如 ``{"reason": "directory_disabled"}``
用于客户端区分同一 HTTP 状态码下的不同语义。
"""
super().__init__(
message or f"Operation not implemented: {operation}",
trace_id=trace_id,
)
self.operation = operation
self.extra_details = details or {}
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["operation"] = self.operation
data.update(self.extra_details)
return data
class ServiceAccountCreationError(InternalError):
"""服务账号创建失败错误。
为渠道账户自动创建服务账号(``User.user_type='service'``)失败时抛出
HTTP 500。``details`` 包含 ``channel_type``、``account_id`` 与
``reason``,便于定位创建失败的根因。
适配器层抛出时应使用 ``raise ServiceAccountCreationError(...) from exc``
保留原始异常 traceback原始异常通过 ``cause`` 参数传递至
``InternalError.cause`` 字段。
"""
error_code = "CHANNEL_SERVICE_ACCOUNT_CREATE_FAILED"
def __init__(
self,
channel_type: str,
account_id: str,
reason: str,
*,
cause: Error | Exception | None = None,
trace_id: str | None = None,
) -> None:
super().__init__(
cause=cause,
trace_id=trace_id,
message=f"failed to create service account for {channel_type}:{account_id}: {reason}",
)
self.channel_type = channel_type
self.account_id = account_id
self.reason = reason
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["channel_type"] = self.channel_type
data["account_id"] = self.account_id
data["reason"] = self.reason
return data