1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pydantic v2 モデルに v1 モデルのフィールドをつくる

Last updated at Posted at 2025-05-31

課題

基本的に v1/v2 のモデルを混合して使うことはできないのですが、Pydantic v1 → v2 のマイグレーションの都合上どうしても v1/v2 を共存させる必要がありました。

例えば以下のように v2 モデルの中に v1 モデルのフィールドを作るとエラーが発生します。

from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1


class Child(BaseModelV1):
    name: str


class Parent(BaseModel):
    child: Child


if __name__ == "__main__":
    print("Parent schema  :", Parent.model_json_schema())
    print("Parent instance:", parent := Parent.model_validate({"child": {"name": "John"}}))
    print("Parent dumped  :", parent.model_dump())
...
    raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}')
pydantic.errors.PydanticInvalidForJsonSchema: Cannot generate a JsonSchema for core_schema.PlainValidatorFunctionSchema ({'type': 'with-info', 'function': <bound method BaseModel.validate of <class '__main__.Child'>>})

For further information visit https://errors.pydantic.dev/2.10/u/invalid-for-json-schema

v1 と v2 では validation/serialization の挙動が大きく変わっていることもあり、公式にはこれらを併用する手段が提供されていません。

解決策

Pydantic v2 ではモデルに __get_pydantic_core_schema__/__get_pydantic_json_schema__ メソッドを実装することで独自のスキーマ情報を渡すことができます1
今回は Pydantic v1 のモデルにこれらのメソッドを追加して併用可能にすることにします。

例えば以下のような関数を実装します。

from typing import Any, TypeVar

from pydantic import BaseModel, GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic_core import core_schema

_TV1 = TypeVar("_TV1", bound=BaseModelV1 | type[BaseModelV1])


def _get_pydantic_core_schema(
    cls: type[BaseModelV1],
    source_type: Any,
    handler: GetCoreSchemaHandler,
) -> core_schema.CoreSchema:
    del source_type, handler
    # NOTE: ちゃんと実装すれば v1 モデルから適切な CoreSchema を生成できそうな気がしますが、
    # 非常に複雑になるためひとまず AnySchema  を返しています。
    return core_schema.no_info_before_validator_function(
        function=lambda value: value if isinstance(value, cls) else cls.parse_obj(value),
        schema=core_schema.any_schema(),
    )


def _get_pydantic_json_schema(
    cls: type[BaseModelV1],
    source_type: Any,
    handler: GetJsonSchemaHandler,
) -> JsonSchemaValue:
    del source_type, handler
    return cls.schema()


def pydantic_v2_compatible(model: _TV1) -> _TV1:
    given = model
    cls = given.__class__ if isinstance(given, BaseModel) else given
    setattr(cls, "__get_pydantic_core_schema__", classmethod(_get_pydantic_core_schema))
    setattr(cls, "__get_pydantic_json_schema__", classmethod(_get_pydantic_json_schema))
    return given

...

@pydantic_v2_compatible
class Child(BaseModelV1):
    name: str

これを使って先述のコードを再度実行します。

Parent schema  : {'properties': {'child': {'properties': {'name': {'title': 'Name', 'type': 'string'}}, 'required': ['name'], 'title': 'Child', 'type': 'object'}}, 'required': ['child'], 'title': 'Parent', 'type': 'object'}
Parent instance: child=Child(name='John')
Parent dumped  : {'child': Child(name='John')}

JSON Schema の生成と validation はうまく動作しています。

model_dump の結果を辞書型にする場合は serializer を設定する必要があります2 (→ 追記)

def serialize_model(model: BaseModelV1, info: core_schema.SerializationInfo) -> dict[str, Any]:
    return model.dict(
        include=info.include,
        exclude=info.exclude,
        by_alias=info.by_alias,
        exclude_defaults=info.exclude_defaults,
        exclude_none=info.exclude_none,
        exclude_unset=info.exclude_unset,
    )

class Parent(BaseModel):
    child: Annotated[Child, PlainSerializer(serialize_model)]
Parent schema  : {'properties': {'child': {'properties': {'name': {'title': 'Name', 'type': 'string'}, 'age': {'title': 'Age', 'type': 'integer'}}, 'required': ['name'], 'title': 'Child', 'type': 'object'}}, 'required': ['child'], 'title': 'Parent', 'type': 'object'}
Parent instance: child=Child(name='John')
Parent dumped  : {'child': {'name': 'John'}}

おわりに

用途によっては他にも変更が必要かもしれませんが、これでひとまず基本的なユースケースでは問題なく動作するようになりました。

追記 (2025-06-07)

CoreSchema を設定する際に serialization 引数を通して serialization の挙動を設定できます。

    return core_schema.no_info_after_validator_function(
        lambda v: v if isinstance(v, cls) else cls.parse_obj(v),
        core_schema.any_schema(
            serialization=core_schema.plain_serializer_function_ser_schema(
                function=_serialize_model,
                info_arg=True,
            )
        ),
    )
  1. https://docs.pydantic.dev/latest/concepts/types/#customizing-validation-with-__get_pydantic_core_schema__

  2. https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?