我无法在使用或不使用 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)
我为此在文档页面上上下功夫,并尝试实施几种替代策略,但没有成功。
到目前为止我已经尝试过,但没有成功:
constr
似乎没有任何效果@validator
的自定义验证器,它似乎从未被调用过。@field_validator
的自定义验证器,它似乎从未被调用过。这是我努力的一个例子:
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 方法不会被调用。我对自己做错的事感到不知所措,但显然我做错了。
我非常感谢更多人关注这个问题;预先感谢您的时间和精力。
编辑:添加了我尝试使用基于字段的验证(相同的非结果)。
我发现我的错误,它是 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 有一个参数化测试用例,我已将其标记为预期失败,这就是错误。
再次感谢您阅读本文。有时我们只需要睡觉。