You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SQLModel中定义带约束的Optional[Decimal]字段并生成合规JSON Schema

在SQLModel中为可选Decimal字段保留约束并实现字段级自定义正则

我在SQLModel里定义基类和表模型时,碰到个麻烦事:要把Decimal字段设为可选,同时得保住所有约束——数据库层面的精度限制、Pydantic的数值校验、还有JSON Schema里的规则,试了好几种方法都有坑:

  • 直接在Field里加max_digits和decimal_places,会炸出ValueError: Unknown constraint max_digits
  • 照搬Pydantic的Annotated方案,在带table=True的模型里会触发TypeError: issubclass() arg 1 must be a class
  • 用sa_column指定数据库约束的临时办法,虽然Pydantic的ge/le校验能正常工作,但生成的JSON Schema丢了所有约束,导致用jsf生成测试假数据全失败

后来拆分基类和表模型的字段定义,解决了约束保留的问题,但自定义JSON Schema生成器时,所有Decimal字段会共用同一个正则,没法给单个字段指定专属规则。下面是解决这个问题的具体方案:


1. 字段定义时通过metadata传递自定义正则

在基类的Annotated Field里,用metadata参数给每个Decimal字段存专属的正则表达式,表模型里单独配置数据库层面的DECIMAL约束:

from sqlmodel import SQLModel, Field, Column, DECIMAL
from decimal import Decimal
from typing import Optional, Annotated
from pydantic import core_schema, GenerateJsonSchema, JsonSchemaValue

class ItemBase(SQLModel):
    # 给price字段指定最多4位小数的正则
    price: Optional[Annotated[Decimal, Field(
        default=None, multiple_of=0.0001, ge=0, le=100,
        metadata={"decimal_regex": r"^\d+(\.\d{1,4})?$"}
    )]] = None
    
    # 给tax字段指定最多2位小数的正则,和price区分开
    tax: Optional[Annotated[Decimal, Field(
        default=None, multiple_of=0.01, ge=0, le=10,
        metadata={"decimal_regex": r"^\d+(\.\d{1,2})?$"}
    )]] = None

class ItemTable(ItemBase, table=True):
    __tablename__ = "item"
    id: Optional[int] = Field(default=None, primary_key=True)
    # 表模型里单独配置数据库层面的精度约束
    price: Optional[Decimal] = Field(
        default=None, ge=0, le=100, sa_column=Column(DECIMAL(7,4))
    )
    tax: Optional[Decimal] = Field(
        default=None, ge=0, le=10, sa_column=Column(DECIMAL(5,2))
    )

2. 修改Schema生成器,读取字段专属正则

自定义GenerateJsonSchema的子类,在处理Decimal字段时,从schema的metadata里取出对应的正则,实现字段级的差异化配置:

class CustomDecimalSchemaGenerator(GenerateJsonSchema):
    def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue:
        # 先拿到默认的number类型schema(带ge/le/multiple_of约束)
        number_schema = super().decimal_schema(schema)
        # 读取当前字段的自定义正则,没指定就用默认规则
        custom_regex = schema.metadata.get(
            "decimal_regex", 
            r"^[-+]?\d+((?:\.\d+)|(?:[eE][-+]?\d+))?$"
        )
        # 构建带正则的string类型schema
        string_schema = self.str_schema(core_schema.str_schema(pattern=custom_regex))
        # 合并number、string、null三种选项(因为字段是Optional)
        return {
            "anyOf": [number_schema, string_schema, {"type": "null"}],
            "default": schema.default,
            "title": schema.title
        }

3. 生成带字段级正则的JSON Schema

调用模型的model_json_schema方法时传入自定义生成器,就能得到每个Decimal字段带专属正则的Schema:

schema = ItemBase.model_json_schema(schema_generator=CustomDecimalSchemaGenerator)

# 查看price字段的Schema(带4位小数的正则)
print(schema["properties"]["price"])
# 输出:
# {'anyOf': [{'maximum': 100.0, 'minimum': 0.0, 'multipleOf': 0.0001, 'type': 'number'}, {'pattern': '^\\d+(\\.\\d{1,4})?$', 'type': 'string'}, {'type': 'null'}], 'default': None, 'title': 'Price'}

# 查看tax字段的Schema(带2位小数的正则)
print(schema["properties"]["tax"])
# 输出:
# {'anyOf': [{'maximum': 10.0, 'minimum': 0.0, 'multipleOf': 0.01, 'type': 'number'}, {'pattern': '^\\d+(\\.\\d{1,2})?$', 'type': 'string'}, {'type': 'null'}], 'default': None, 'title': 'Tax'}

原理说明

  • 基类用Annotated[Decimal, Field(...)]保留Pydantic的数值校验规则,同时通过metadata存储字段专属的正则;
  • 表模型重写字段,用sa_column=Column(DECIMAL(...))指定数据库层面的精度约束,避免SQLModel混淆Pydantic和SQLAlchemy的参数;
  • 自定义Schema生成器通过读取schema.metadata里的正则,为每个Decimal字段生成专属的string类型约束,解决了全局正则的问题。

内容的提问来源于stack exchange,提问作者Joachim Huet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 05:50:18