无法对非表 SQLModel 进行验证

问题描述 投票:0回答:1

我无法在使用或不使用 SQLModel 的情况下进行 Pydantic 验证,并且我不确定我做错了什么。我不确定我错过了什么:要点是:验证似乎没有在我期望的时候发生。到目前为止,我在这个主题上看到的唯一问答集中在 table=True 模型如何需要这种确切的行为,这不是我的问题;我还没有看到有人遇到我所遇到的问题。

我有一个简单的(非表格)模型,我已将其归结为一个用于测试的字段,并且我有一个用于验证的简单正则表达式。

LOGIN_NAME_REGEX = r"^[a-z0-9_]{1,25}$"

class User(SQLModel, table=False):  # explicitly set table because it's been a long weekend
    login_name: str

最初,我尝试使用

constr
来实现这一点(尽管使用
regex
而不是
pattern
作为关键字),但似乎不再起作用。老实说,似乎它完全被忽略了。其他遇到此问题的人似乎正在使用 table=True 模型执行此操作,但我没有这样做;此行为发生在表模型继承的基本模型上。

LOGIN_NAME_REGEX = r"^[a-z0-9_]{1,25}$"

class User(SQLModel, table=False):
    login_name: constr(pattern=LOGIN_NAME_REGEX)

还有:

class User(SQLModel, table=False):
    login_name: str = Field(..., regex=TWITCH_LOGIN_NAME_REGEX, index=True)

我为此在文档页面上上下功夫,并尝试实施几种替代策略,但没有成功。

到目前为止我已经尝试过,但没有成功:

  • Pydantic 的
    constr
    似乎没有任何效果
  • 使用
    @validator
    的自定义验证器,它似乎从未被调用过。
  • 使用
    @field_validator
    的自定义验证器,它似乎从未被调用过。
  • 使用 WrapValidator 的近似方法 来自文档

这是我努力的一个例子:

def matches_regex(value: Any, pattern: str) -> str:
    """Check if the given value matches the provided regex pattern."""
    print(">>> In the matcher", flush=True)
    if not isinstance(value, str) or not bool(re.match(pattern, value)):
        raise ValueError
    return value

class User(SQLModel, table=False):
    login_name: str

    @field_validator("login_name")
    @classmethod
    def validate_login_name(cls, v: str) -> str:
        print(f">>> In the field validator with {v=}", flush=True)
        return matches_regex(v, pattern=LOGIN_NAME_REGEX)

matches_regex
中的打印不打印对我来说是铁证如山;我做了一些微小的单元测试,在之前和之后打印,并且 those 确实打印,所以这不是冲洗问题;也就是说我还是添加了
flush=True
,因为我已经没有想法了。

我建立了一个小型单元测试,这样我就可以快速启动它来查看每个更改,也许错误就在那里并且我的检查不正确?

def test_create_invalid_username():
    invalid_data = {
        "login_name": "invalid!username!",  # Invalid due to exclamation marks
    }

    with pytest.raises((ValueError, ValidationError)):
        print(f"\n > > > {invalid_data=} < < < \n")  # this print fires
        vs = User(**invalid_data)
        print(f"\n > > > {vs.login_name=} < < < \n")  # this print also fires

    assert vs.login_name != invalid_data["login_name"]  # sanity check

这是测试结果示例:

 > > > invalid_data={'login_name': 'invalid!username!'} < < <


 > > > vs.login_name='invalid!username!' < < <

FAILED tests/validation/test_user_validation.py::test_create_invalid_username - Failed: DID NOT RAISE (<class 'ValueError'>, <class 'pydantic_core._pydantic_core.ValidationError'>)

我还尝试了

WrapValidator
尽我所能地重复文档中的示例,但是,再一次,结果是没有结果;似乎验证没有发生,这就是我想使用 Pydantic 的全部原因。

我仍在梳理文档并在 GitHub 上寻找示例,令人沮丧的是,在我看来,我的代码与我见过的示例相匹配。我的 SQLModel 和 Pydantic 版本是最新的。我尝试仅使用 Pydantic 的 BaseModel,结果完全相同:这些打印不会发生,field_validator 和 matches_regex 方法不会被调用。我对自己做错的事感到不知所措,但显然我做错了。

我非常感谢更多人关注这个问题;预先感谢您的时间和精力。

编辑:添加了我尝试使用基于字段的验证(相同的非结果)。

python validation pydantic sqlmodel pydantic-v2
1个回答
0
投票

我发现我的错误,它是 100% P.E.B.C.A.K.所以我来这里是为了吃我的乌鸦。

验证 100% 有效;问题是失败的测试被

pytest.mark.parametrize
装饰,我在疲劳后 100% 误读了错误报告。今天,我坐下来敲定了一系列单元测试,试图找出错误所在,但没有发现任何错误,验证一直在进行。这是供后代使用的单元测试。

#/tests/validation/test_sanity.py
from uuid import uuid4

import pytest
from pydantic import BaseModel, ValidationError, field_validator
from sqlmodel import Field, SQLModel

from app.models. import UserBase, UserCreate


class TestSubject(BaseModel):
    name: str

    @field_validator("name")
    def validate_name(cls, v: str) -> str:
        if not isinstance(v, str):
            raise ValueError("nope")
        return v


class TestModel(SQLModel):
    name: str

    @field_validator("name")
    def validate_name(cls, v: str) -> str:
        if not isinstance(v, str):
            raise ValueError("nope")
        return v


class TestModelWithFieldAttrib(SQLModel):
    name: str = Field(...)

    @field_validator("name")
    def validate_name(cls, v: str) -> str:
        if not isinstance(v, str):
            raise ValueError("nope")
        return v


def test_base_model():
    TestSubject(name="hello")

    with pytest.raises((ValidationError, ValueError)):
        TestSubject(name=123)


def test_sqlmodel():
    TestModel(name="ohai")

    with pytest.raises((ValidationError, ValueError)):
        TestModel(name=321)


def test_sqlmodel_with_attributes():
    TestModelWithFieldAttrib(name="ohai")

    with pytest.raises((ValidationError, ValueError)):
        TestModelWithFieldAttrib(name=321)


def test_user_base():
    UserBase(login_name="howdy")

    with pytest.raises((ValidationError, ValueError)):
        UserBase(login_name=123)

    with pytest.raises((ValidationError, ValueError)):
        UserBase(viewer_login_name="hilo")


def test_user_create():
    UserCreate(login_name="yessir")

    with pytest.raises((ValidationError, ValueError)):
        UserCreate(viewer_login_name=123)

    with pytest.raises((ValidationError, ValueError)):
        UserCreate(login_name="weeee")

    with pytest.raises((ValidationError, ValueError)):
        UserCreate(**{"login_name": "nope!!")

    # name of just numbers is technically legal, this wasn't raising an error
    UserCreate(**{"login_name": "123"})

用户名 123 有一个参数化测试用例,我已将其标记为预期失败,这就是错误。

再次感谢您阅读本文。有时我们只需要睡觉。

© www.soinside.com 2019 - 2024. All rights reserved.