Files
ecm_sync_system/schemas/common/base.py
T
strepsiades 8ee0131d47 修改schema
2026-03-24 10:27:14 +08:00

141 lines
4.0 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.
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
def _get_plan_node_date(node: Any) -> Any:
if isinstance(node, dict):
return node.get("date")
return getattr(node, "date", None)
def trim_plan_nodes_by_boundary_dates(
plan_nodes: Optional[List[Any]],
start_date: Optional[Any] = None,
end_date: Optional[Any] = None,
) -> List[Any]:
if not plan_nodes:
return []
cleaned_nodes = list(plan_nodes)
while (
start_date is not None
and cleaned_nodes
and str(_get_plan_node_date(cleaned_nodes[0])) == str(start_date)
):
cleaned_nodes.pop(0)
while (
end_date is not None
and cleaned_nodes
and str(_get_plan_node_date(cleaned_nodes[-1])) == str(end_date)
):
cleaned_nodes.pop()
return cleaned_nodes
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