first commit

This commit is contained in:
strepsiades
2026-03-09 16:31:42 +08:00
commit 4a9c444b10
486 changed files with 52479 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
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