Files
test/app/schema/advanced_schema.py
T
2026-09-21 19:03:31 +08:00

112 lines
4.1 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.
"""通用高级筛选器出入参(需求 2.7.1)。
请求体形如:
{
"model": "student",
"rules": [
{"field": "age", "operator": ">", "value": 25},
{"field": "gender", "operator": "=", "value": "男"},
{"logic": "OR", "sub_rules": [
{"field": "salary", "operator": ">=", "value": 15000},
{"field": "class_name", "operator": "like", "value": "Java"}
]}
],
"order_by": "age",
"order": "desc",
"page": 1,
"page_size": 10
}
"""
from __future__ import annotations
from typing import Any, Literal
from pydantic import BaseModel, Field, field_validator, model_validator
from app.schema.common import SortOrder
class FilterRule(BaseModel):
"""一条规则 = 一个条件 或 一组子规则。"""
field: str | None = Field(None, description="字段名,与 sub_rules 二选一")
operator: str | None = Field(
None,
description="操作符 > < = != >= <= like in not_like not_in between is_null not_null",
)
value: Any = Field(None, description="比较值;in/not_in 传数组,between 传 [a, b]")
logic: Literal["AND", "OR", "and", "or"] | None = Field(None, description="子规则之间的逻辑,AND / OR")
sub_rules: list["FilterRule"] | None = Field(None, description="嵌套子规则,可多层")
@model_validator(mode="after")
def _check(self):
is_group = bool(self.sub_rules)
if is_group:
if self.logic is None:
self.logic = "AND"
return self
if not self.field or not self.operator:
raise ValueError("叶子规则必须同时提供 field 和 operator")
return self
@field_validator("operator")
@classmethod
def _lower(cls, v): # noqa: ANN001, ANN201
return v.strip().lower() if isinstance(v, str) else v
FilterRule.model_rebuild()
class QueryRequest(BaseModel):
model: str = Field("student", description="目标模型:student / class / teacher / score / employment / advisor")
rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树")
order_by: str | None = Field(None, description="排序字段(同样受白名单限制)")
order: SortOrder = Field(SortOrder.ASC, description="排序方向 asc / desc")
page: int = Field(1, ge=1)
page_size: int = Field(10, ge=1, le=200)
fields: list[str] | None = Field(None, description="指定返回字段,不传返回全部可读字段")
with_explain: bool = Field(True, description="是否返回规则解析后的说明,方便调试规则")
class MetricSpec(BaseModel):
func: Literal["count", "count_distinct", "avg", "sum", "max", "min"] = Field(..., description="聚合函数")
field: str | None = Field(None, description="聚合字段;count 时可省略")
alias: str | None = Field(None, description="结果列名")
class AggregationParams(BaseModel):
model: str = Field(..., description="目标模型")
group_by: list[str] = Field(default_factory=list, description="分组字段,可为空=整体聚合")
metrics: list[MetricSpec] = Field(..., min_length=1, description="聚合指标")
rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树")
having: list[FilterRule] = Field(default_factory=list, description="对聚合结果的过滤(HAVING)")
order_by: str | None = Field(None, description="按结果列名排序")
order: SortOrder = SortOrder.DESC
limit: int = Field(50, ge=1, le=500)
class AggregationResult(BaseModel):
columns: list[str] = Field(default_factory=list)
rows: list[dict[str, Any]] = Field(default_factory=list)
total: int = 0
sql: str | None = Field(None, description="实际执行的 SQL(已参数化,便于讲解与排查)")
class FieldMeta(BaseModel):
field: str
type: str
desc: str
sortable: bool = True
filterable: bool = True
class ModelMeta(BaseModel):
model: str
label: str
fields: list[FieldMeta] = Field(default_factory=list)
relations: list[str] = Field(default_factory=list, description="可通过 JOIN 使用的关联字段")