-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Bug
Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":
pydantic version: 1.6.1
pydantic compiled: True
install path: /Users/jakub/.virtualenvs/server/lib/python3.8/site-packages/pydantic
python version: 3.8.1 (default, Mar 13 2020, 20:31:03) [Clang 11.0.0 (clang-1100.0.33.17)]
platform: macOS-10.15.5-x86_64-i386-64bit
optional deps. installed: ['typing-extensions']
Methods decorated with root_validator that are defined in a subclass do not override the method defined in the superclass. For methods decorated with validator they do override the method defined in the superclass properly.
Context:
I want to have a model that declares a bunch of fields and some default validators for these fields, which should be applied in every subclass, unless overridden. That works for fields decorated with validator, but for fields decorated with root_validator the validator in superclass gets called first.
I couldn't find anything in the docs to disable calling the superclass validator when using the root_validator decorator. Also, I looked into the generic models (it seems that it would fit composition better than inheritance) and reusing validators (doesn't seem to be a good candidate to validate field contents because in my case the validation depends on the exercise_type and I would still have to define something like _normalized_choices in every subclass).
from __future__ import annotations
from pydantic import BaseModel, root_validator, validator
from typing import Union, List, Optional
class ExerciseBase(BaseModel):
text: Optional[str] = None
choices: Optional[List[List[str]]] = None
@root_validator
def validate_choices(cls, values):
print("ExerciseBase validate_choices")
choices = values.get('choices')
assert choices is None, 'Choices not allowed for this exercise type'
return values
class FillBlanksExercise(ExerciseBase):
@root_validator
def validate_choices(cls, values):
print("FillBlanksExercise validate_choices")
choices = values.get('choices')
if choices is not None:
text = values.get('text')
number_of_blanks = text.count('____')
assert {len(choice) for choice in choices} == {number_of_blanks}, 'Each choice must match number of blanks'
return values
EXERCISE_TYPES_MAPPING = {
'FillBlanksExercise': FillBlanksExercise,
'ChoiceExercise': ChoiceExercise,
}
exercise_data = {
'text': 'Python is a type of ____?',
'exercise_type': 'FillBlanksExercise',
'choices': [['snake'], ['bird'], ['fish'], ['dinosaur']],
}
exercise = FillBlanksExercise(**exercise_data)
# ExerciseBase validate_choices
# FillBlanksExercise validate_choices
# Traceback (most recent call last):
# File "/Users/jakub/Library/Application Support/JetBrains/PyCharm2020.2/scratches/scratch_2.py", line 85, in <module>
# exercise = ExerciseBase.build(**exercise_data)
# File "/Users/jakub/Library/Application Support/JetBrains/PyCharm2020.2/scratches/scratch_2.py", line 22, in build
# return _class(**kwargs)
# File "pydantic/main.py", line 346, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for FillBlanksExercise
# __root__
# Choices not allowed for this exercise type (type=assertion_error)