from pydantic import BaseModel, Field, ValidationError, model_validator from typing import Any, Dict from typing import Any, Optional, TypeVar, Generic, List from pydantic import BaseModel, Field, model_serializer class BaseResponseWithID(BaseModel): """ Mixin: 统一处理输入中的 id/_id -> id 约束: - 若 id 与 _id 同时出现且值不同 -> 报错 - 若仅 _id 出现 -> 转成 id - 若两者都缺失 -> 报错 - 最终保证存在 id: str """ id: str = Field(..., description="资源唯一标识ID") @model_validator(mode="before") @classmethod def normalize_id(cls, data: Any) -> Any: # 仅处理 dict-like 输入;其他情况交给 Pydantic 默认流程 if not isinstance(data, dict): return data has_id = "id" in data has__id = "_id" in data if has_id and has__id: if str(data["id"]) != str(data["_id"]): raise ValidationError([ { "type": "value_error", "loc": ("id",), "msg": "id and _id both provided but not equal", "input": data.get("id"), } ], cls) # 相同则优先保留 id,去掉 _id data = {**data} data.pop("_id", None) return data if has__id and not has_id: # 将 _id 统一映射为 id data = {**data} data["id"] = str(data["_id"]) data.pop("_id", None) return data if not has_id and not has__id: raise ValidationError([ { "type": "value_error.missing", "loc": ("id",), "msg": "id or _id is required", "input": data, } ], cls) # 默认返回(已有 id) return data class RequestApprovalMixin(BaseModel): """ 请求审批相关的基础模型 """ pass T = TypeVar("T") # def _build_full_response_dict(code: int, message: Optional[str], data: Any) -> dict: # """构建与 __orjson__ 完全一致的响应字典""" # # 处理 data 的序列化(与 __orjson__ 逻辑同步) # if data is None: # clean_data = {} # elif isinstance(data, BaseModel): # clean_data = data.model_dump() # else: # clean_data = data # return { # "code": code, # "message": message, # "data": clean_data # } class Success(BaseModel, Generic[T]): code: int = 200 message: Optional[str] = "OK" data: Optional[T] = None # def __init__(self, **kwargs): # super().__init__(**kwargs) # # 自动捕获完整响应结构到 push_log.result # push_log = current_push_log.get() # if push_log is not None: # full_response = _build_full_response_dict(self.code, self.message, self.data) # push_log.result = full_response # class Fail(BaseModel): code: int = 400 message: Optional[str] = None data: Optional[Any] = Field(default_factory=dict) class PaginatedList(BaseModel, Generic[T]): list: List[T] = Field(default_factory=list) total: int = 0 page: int = 1 page_size: int = 20