I have a file test.py
like this:
from pydantic import root_validator
from sqlmodel import SQLModel
from typing import List
from devtools import debug
class Base(SQLModel):
@root_validator(pre=True)
def validate(cls, values):
debug(values, type(values))
for k, v in values.items():
# here we perform the validation
# for this example we do nothing
pass
debug("EXIT")
return values
class A(Base):
id: int
name: str
class B(Base):
id: int
others: List[A]
Now if I do
$ python3
Python 3.10.5 (v3.10.5:f377153967, Jun 6 2022, 12:36:10) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from test import A
>>> from test import B
>>> x = A(id=1, name="john")
test.py:11 Base.validate
values: {
'id': 1,
'name': 'john',
} (dict) len=2
type(values): <class 'dict'> (type)
test.py:16 Base.validate
'EXIT' (str) len=4
>>> y = B(id=42, others=[])
test.py:11 Base.validate
values: {
'id': 42,
'others': [],
} (dict) len=2
type(values): <class 'dict'> (type)
test.py:16 Base.validate
'EXIT' (str) len=4
>>> z = B(id=100, others=[x])
test.py:11 Base.validate
values: {
'id': 42,
'others': [
A(
id=1,
name='john',
),
],
} (dict) len=2
type(values): <class 'dict'> (type)
test.py:16 Base.validate
'EXIT' (str) len=4
test.py:11 Base.validate
values: A(
id=1,
name='john',
) (A)
type(values): <class 'test.A'> (SQLModelMetaclass)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/yuri/Desktop/exact/fractal/fractal-common/venv/lib/python3.10/site-packages/sqlmodel/main.py", line 498, in __init__
values, fields_set, validation_error = validate_model(
File "pydantic/main.py", line 1077, in pydantic.main.validate_model
File "pydantic/fields.py", line 895, in pydantic.fields.ModelField.validate
File "pydantic/fields.py", line 928, in pydantic.fields.ModelField._validate_sequence_like
File "pydantic/fields.py", line 1094, in pydantic.fields.ModelField._validate_singleton
File "pydantic/fields.py", line 884, in pydantic.fields.ModelField.validate
File "pydantic/fields.py", line 1101, in pydantic.fields.ModelField._validate_singleton
File "pydantic/fields.py", line 1148, in pydantic.fields.ModelField._apply_validators
File "pydantic/class_validators.py", line 318, in pydantic.class_validators._generic_validator_basic.lambda13
File "/Users/yuri/Desktop/exact/fractal/fractal-common/test.py", line 12, in validate
for k, v in values.items():
AttributeError: 'A' object has no attribute 'items'
I don't undestand what is happening with z
. Why after calling the validator for class B
, the constructor call the validator also for class A
but this time values
is not a dict but a <class 'test.A'> (SQLModelMetaclass)
.
This has nothing to do with SQLModel per se, but is an issue that SQLModel
inherits from Pydantic's BaseModel
.
You are breaking regular model validation by overriding the BaseModel.validate
method. It serves a special purpose. When you have a model field annotated with another model, like your B.others
being of type list[A]
, the outer (B
) model will call the inner (A
) model's validate
method.
That method is designed as a "normal" validator method, i.e. the value to be validated will be passed to it as the first positional argument. In this case that value is your A
model instance x
that you passed for in the list for others
. That is why it shows <class 'test.A'>
as the type of values
. (Don't know why it mentions the metaclass as well, but I guess that is just what this debug
function does.)
The solution is very simple: Name your root validator method something else that does not override a built-in method of BaseModel
. This is also a good idea from a semantic perspective because validate
is not very descriptive. Try and indicate what it does on top of normal model validation in its name.
But even just changing the method name to foo
will fix your error.