Невозможно заставить проверку работать для SQLModel, не являющегося таблицей

Я не могу заставить проверку 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, который, похоже, никогда не вызывался.
  • пример использования 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 не печатался как дымящийся пистолет; Я сделал крошечные модульные тесты, которые печатаются до и после, и они печатаются, так что это не проблема со сбросом; тем не менее, я все равно добавил 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 не вызываются. Я в недоумении, что я сделал не так, но очевидно, что я делаю что-то не так.

Я глубоко ценю дополнительный взгляд на проблему; заранее спасибо за ваше время и усилия.

Обновлено: добавлена ​​моя попытка использовать проверку на основе полей (тот же результат).

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел свою ошибку, это 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, который я пометил как ожидаемый провальный, и ЭТО была ошибка.

Еще раз спасибо, если вы это читаете. Иногда нам просто нужно поспать на нем.

Другие вопросы по теме