Search code examples
pythonpython-3.xpydantic-v2

What is the json_schema attribute of core_schema.json_or_python_schema used for in pydantic


Assume I have an external Class OriginalExternalClass whose code I can not be modified. I want to use that external class as a field of another class called PydanticClassWithExternalClassMember which derives from pydantic BaseModel.

I want circumvent using the arbitrary types flag, as I want to implement type checking and serialization capabilities for the OriginalExternalClass field. To code works and looks the following:

from typing import Any
from pydantic_core import core_schema
import pydantic


class OriginalExternalClass:
    def __init__(self, a: int, b: str) -> None:
        self.a = a
        self.b = b


class PydanticExternalClass(OriginalExternalClass):
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: pydantic.GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        def json_schema(value: dict) -> OriginalExternalClass:
            # NEVER CALLED!!!
            print("json_schema called")
            result = OriginalExternalClass(**value)

            return result

        def python_schema(
            value: OriginalExternalClass,
        ) -> OriginalExternalClass:
            print("python_schema called")
            if isinstance(value, dict):
                value = OriginalExternalClass(**value)

            if not isinstance(value, OriginalExternalClass):
                raise pydantic.ValidationError(
                    msg="Expected a `ExternalClass` instance",
                    loc=("third_party_type",),
                    type=OriginalExternalClass,
                )

            return value

        def serialization(value: OriginalExternalClass) -> dict:
            print("serialization called")
            return {"a": value.a, "b": value.b}

        return core_schema.json_or_python_schema(
            json_schema=core_schema.no_info_plain_validator_function(json_schema),
            python_schema=core_schema.no_info_plain_validator_function(python_schema),
            serialization=core_schema.plain_serializer_function_ser_schema(
                serialization
            ),
        )


class PydanticClassWithExternalClassMember(pydantic.BaseModel):
    x: PydanticExternalClass

However, what I do not understand, is when is the json_schema attribute of JsonOrPythonSchema genereated by json_schema=core_schema.no_info_plain_validator_function(json_schema) used? What is it needed for? The function json_schema is never called when executing:

print(
    "m1 = PydanticClassWithExternalClassMember(x=PydanticExternalClass(1, 'basgsadf'))"
)
m1 = PydanticClassWithExternalClassMember(x=PydanticExternalClass(1, "basgsadf"))
print("d = m1.model_dump()")
d = m1.model_dump()
print("m1 = PydanticClassWithExternalClassMember(**d)")
m1 = PydanticClassWithExternalClassMember(**d)

And the output is

m1 = PydanticClassWithExternalClassMember(x=PydanticExternalClass(1, 'basgsadf'))
python_schema called
d = m1.model_dump()
serialization called
m1 = PydanticClassWithExternalClassMember(**d)
python_schema called

Solution

  • That's because you're not testing JSON deserialisation there.

    If you did this, though:

    adapter = pydantic.TypeAdapter(PydanticClassWithExternalClassMember)
    value = adapter.validate_json("""{"x": {"a": 1, "b": "basgsadf"}}""")
    print("Got", value.model_dump())
    

    ...it does. See output:

    m1 = PydanticClassWithExternalClassMember(x=PydanticExternalClass(1, 'basgsadf'))
    python_schema called
    json_schema called
    serialization called
    Got {'x': {'a': 1, 'b': 'basgsadf'}}
    d = m1.model_dump()
    serialization called
    m1 = PydanticClassWithExternalClassMember(**d)
    python_schema called