Модель pydantic неправильно применяет проверку

У меня есть модель, определенная следующим образом;

class SomeModel(BaseModel):
    name: str

class SomeOtherModel(BaseModel):
    name: int

class MyModel(BaseModel):
    items: List[Union[SomeModel, SomeOtherModel]]

    @validator("items", always=True)
    def validate(cls, value):
        by_type = list(filter(lambda v: isinstance(v, SomeModel), value))
        if len(by_type) < 1:
            raise ValueError("we need at least one SomeModel")

Подмодели не важны, по сути мне нужен список различных подмоделей, и этот список должен содержать хотя бы одну из первого типа. Все хорошо.

В другом месте моего кода я имею в виду эту модель (контекст: для целей сохранения пользовательских настроек не имеет значения для этого вопроса).

class ComposerModels(BaseModel):
    user: List[MyModel] = []
    system: List[MyModel] = []

class ComposerSettings(BaseModel):
    models: ComposerModels

class UserSettings(BaseModel):
    composer: ComposerSettings

Моя программа должна иметь возможность сохранять новые модели в модели UserSettings, примерно так;

my_model = MyModel(items=[SomeModel(name = "a"), SomeOtherModel(name=1)])

user_settings = UserSettings(
    composer=ComposerSettings(
        models=ComposerModels(
            user=[my_model]
        )
    )
)

Однако это вызывает ошибку;

Traceback (most recent call last):
  File ".../pydantic/shenanigans.py", line 38, in <module>
    user=[my_model]
  File "pydantic/main.py", line 342, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ComposerModels
user -> 0
  we need at least one SomeModel (type=value_error)

Вот тут-то это и становится странным.

По какой-то причине при попытке создать экземпляр UserSettings он проходит проверку поля items внутри MyModel, но вместо того, чтобы передать список подмоделей, как ожидалось, каким-то образом вместо этого он получает список экземпляров MyModel. Очевидно, что это не проходит проверку и вызывает ошибку, указанную выше. Однако, если я закомментирую код проверки, он сработает. Никаких ошибок, и модель пользовательских настроек содержит ожидаемый MyModel.

Я не могу просто отключить эту проверку, она мне нужна где-то в моей программе... Есть идеи, что здесь происходит? Я в тупике...

Я использую Python 3.7 с pydantic 1.10 на Centos 7. Я не могу легко обновить какие-либо версии, потому что моя компания не верит в DevOps, поэтому, если это известная ошибка с pydantic в этой версии, мне придется подумать чего-то другого.

сегодня я пробовал pydantic 2.5.3 (потому что мы всего лишь py37), и похоже, что это на самом деле ошибка в pydantic 1.10. Однако, как уже упоминалось, обновление pydantic будет огромной проблемой... поэтому альтернативное решение было бы идеальным.

Edward Spencer 20.05.2024 15:05
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Должно быть, это какая-то ошибка с pydantic v1.10. Неудивительно, что они упразднили validator в v2.

Я сделал небольшую модификацию, чтобы все работало. Думаю, для вас не составит труда инициализировать класс UserSettings с пустыми пользователем и системой.

my_model = MyModel(items=[SomeModel(name = "a"), SomeOtherModel(name=1)])

user_settings = UserSettings(
    composer=ComposerSettings(
        models=ComposerModels(
        )
    )
)

user_settings.composer.models.user = [my_model]
print(user_settings.__dict__)

Надеюсь, это тот результат, которого вы ожидали:

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

Используйте другое имя функции для валидатора. Метод validate — это существующий метод BaseModel, и вы его переопределяете. Всякий раз, когда создается новый объект, pydantic проверяет поля с соответствующими типами, используя метод validate модели. Поскольку вы переопределили метод validate, он будет использоваться для проверки модели.

Поток кода будет следующим:

my_model = MyModel(items=[SomeModel(name = "a"), SomeOtherModel(name=1)])

user_settings = UserSettings(
    composer=ComposerSettings(
        models=ComposerModels(
            user=[my_model]  # Validate user with List[MyModel]
        )
    )
)

Теперь, чтобы проверить user, сначала нужно проверить my_model с помощью MyModel Внутри он вызовет что-то похожее на MyModel.validate(my_model) (вы можете проверить точный источник здесь https://github.com/pydantic/pydantic/blob/547925887071a8cedf4642c4eb16ed749bf802cc/pydantic/v1/main.py#L1074), и поскольку вы переопределить метод validate, для которого проверка isinstance(v, SomeModel) не будет удовлетворена my_model, и она завершится неудачно и вызовет исключение..

Поэтому решение состоит в том, чтобы просто использовать другое имя функции.

class MyModel(BaseModel):
    items: List[Union[SomeModel, SomeOtherModel]]

    @validator("items", always=True)
    def validate_items(cls, value):  # Changed to validate_items
        by_type = list(filter(lambda v: isinstance(v, SomeModel), value))
        if len(by_type) < 1:
            raise ValueError("we need at least one SomeModel")

Это будет работать так, как ожидалось.

Святой Бог из машины, ты только что спас мне жизнь

Edward Spencer 22.05.2024 14:05

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