Я не могу заставить проверку Pydantic работать с SQLModel и без нее, и я не уверен, что делаю неправильно. Я не уверен, что мне не хватает: изюминка в том, что проверка, похоже, не происходит тогда, когда я этого ожидаю. До сих пор единственные вопросы и ответы, которые я видел по этой теме, касаются того, насколько желательно именно такое поведение для моделей 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
не печатался как дымящийся пистолет; Я сделал крошечные модульные тесты, которые печатаются до и после, и они печатаются, так что это не проблема со сбросом; тем не менее, я все равно добавил 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 обновлены. Я пробовал использовать только BaseModel Pydantic, и результаты были точно такими же: этих отпечатков не происходит, методы field_validator и match_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, который я пометил как ожидаемый провальный, и ЭТО была ошибка.
Еще раз спасибо, если вы это читаете. Иногда нам просто нужно поспать на нем.