-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Closed
Labels
Description
Initial Checks
- I confirm that I'm using Pydantic V2
Description
I have a model which validates from and serializes to a string. The Thing
model in the example code below is intended to be a reduced/simplified case with trivial validation logic, the original is a bit more involved (actual parser, etc).
Note: some code elided in this explanation, full example code is below at the bottom of this post.
class Thing(BaseModel):
value: str
@classmethod
def _validate(cls, value: str) -> Thing: ...
@classmethod
def __get_pydantic_core_schema__(cls, source, handler, /) -> CoreSchema:
string_schema = cs.chain_schema([
cs.str_schema(),
cs.no_info_plain_validator_function(cls._validate)
])
string_serializer = cs.plain_serializer_function_ser_schema(lambda t: t.value)
return cs.json_or_python_schema(
python_schema=cs.union_schema([string_schema, handler(cls)]), # validate from string, or fall back on default
json_schema=string_schema,
serialization=string_serializer
)
What I'd expect is that if I nest Thing
inside another model, I can validate that model from string values. This works swimmingly when there is only one field of type Thing
nested inside another model:
class Foo(BaseModel):
thing1: Thing
foo = Foo.model_validate({
"thing1": "hello"
})
I'd then expect this to work identically when I add a second field of type Thing
... however, as soon as there are multiple, validation fails:
class Bar(BaseModel):
thing1: Thing
thing2: Thing
bar = Bar.model_validate({
"thing1": "hello",
"thing2": "world"
})
pydantic_core._pydantic_core.ValidationError: 1 validation error for Bar
thing2
Input should be a valid dictionary or instance of Thing [type=model_type, input_value='world', input_type=str]
For further information visit https://errors.pydantic.dev/2.8/v/model_type
Example Code
from __future__ import annotations
from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import CoreSchema
from pydantic_core import core_schema as cs
class Thing(BaseModel):
value: str
@classmethod
def _validate(cls, value: str) -> 'Thing':
if value != value.lower():
raise ValueError("Value must be lowercase")
return Thing.model_construct(value=value)
@classmethod
def __get_pydantic_core_schema__(cls, source: type[BaseModel], handler: GetCoreSchemaHandler, /) -> CoreSchema:
string_schema = cs.chain_schema([
cs.str_schema(),
cs.no_info_plain_validator_function(cls._validate)
])
string_serializer = cs.plain_serializer_function_ser_schema(lambda t: t.value)
return cs.json_or_python_schema(
python_schema=cs.union_schema([string_schema, handler(cls)]),
json_schema=string_schema,
serialization=string_serializer
)
class Foo(BaseModel):
thing1: Thing
class Bar(BaseModel):
thing1: Thing
thing2: Thing
# OK
foo = Foo.model_validate({
"thing1": "hello"
})
print(foo)
# Errors
bar = Bar.model_validate({
"thing1": "hello",
"thing2": "world"
})
print(bar)
Python, Pydantic & OS Version
pydantic version: 2.8.2
pydantic-core version: 2.20.1
pydantic-core build: profile=release pgo=true
install path:
.../lib/python3.12/site-packages/pydantic
python version: 3.12.3 (main, Jun 4 2024, 16:53:26) [Clang
15.0.0 (clang-1500.3.9.4)]
platform: macOS-14.4.1-arm64-arm-64bit
related packages: typing_extensions-4.12.1 pydantic-settings-2.3.1
mypy-1.10.1 pyright-1.1.366 fastapi-0.111.0
commit: unknown