DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
RackNerd;$21.99/年 · 1GB KVM
晚安云;香港 CN2 ¥15/月

Pydantic strict 模式的整数 Literal 仍接受 2.0

Pydantic 2.14 的整数 Literal 可走 Python 等值查找,浮点 2.0 可能匹配整数 2。协议要求区分类型时,在 before 验证器中检查原始类型,再保留 Literal 的取值范围。

模型设置了 strict=True,value: Literal[2] 却仍接受 JSON 的 2.0、2e0,验证后还变成整数 2。如果协议要求输入必须是整数,不能只凭最终结果的类型判断原始输入是否合格。

Pydantic 2.14.0 有这个具体表现。当前 2.14.1 配套的 core 2.50.1 正式实现中,Literal 查找仍包含 Python 哈希与相等比较的回退;Python 中 2 == 2.0,会影响这种匹配。模型级 strict 并不等价于给整数 Literal 再叠加一次原始类型检查。

类型与允许值分开检查

下面限定只接受 Python 的普通 int,再由 Literal 保留允许值集合:

from typing import Literal
from pydantic import BaseModel, ConfigDict, field_validator

class Request(BaseModel):
    model_config = ConfigDict(strict=True)
    value: Literal[2, 4, 6]

    @field_validator("value", mode="before")
    @classmethod
    def require_integer_input(cls, value):
        if type(value) is not int:
            raise ValueError("value 必须是整数输入")
        return value

这会拒绝 2.0、字符串 "2" 和布尔值,而 3 虽是整数,仍由 Literal 拒绝。选择 type(value) is int 是这里的明确契约:它连 bool 与自定义整数子类都不接受。如果应用确实允许某些子类,需要另行定义规则,不能悄悄放宽成所有与整数相等的对象。

mode="before" 很关键;等值匹配完成后再检查,收到的可能已经是模型保存的整数 2,原始浮点类型便无法判断。这里使用 ValueError 形成字段校验错误,避免把验证器自身的程序错误当成正常业务输入。

不要丢掉闭集约束

单独改成 StrictInt 可以表达严格整数,却会让 3、5 等原来不允许的整数通过。若采用 StrictInt,也必须另外保留允许值校验。直接 int(value) 更不合适,会转换浮点、字符串,甚至截断非整数值,与严格协议相反。

JSON 解码后 2.0 与 2e0 都会走浮点输入分支。如果业务还要求区分 2.0 与 2e0 的原始文本写法,普通字段验证不能提供这个层面的词法信息,必须在协议解析边界另行设计。

复核应分别送入整数、相等浮点、非整数浮点、字符串、布尔、空值与不在集合内的整数,同时比较 Python 与 JSON 两个验证入口。成功条件是接受范围符合协议,不是错误数量变少。