108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
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 |