BaseModel.model_validate()

class BaseModel(metaclass=_model_construction.ModelMetaclass):
    ...
    __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]  
    """The `pydantic-core` `SchemaValidator` used to validate instances of the model."""
    ...
    @classmethod
    def model_validate(
        cls,
        obj: Any,
        *,
        strict: bool | None = None,
        from_attributes: bool | None = None,
        context: Any | None = None,
    ) -> Self:
        ...
        return cls.__pydantic_validator__.validate_python(
            obj, strict=strict, from_attributes=from_attributes, context=context
        )
        

Note

  • model_validate()cls.__pydantic_validator__.validate_python() を呼び出すだけ
  • cls.__pydantic_validator__SchemaValidator クラスのインスタンス

complete_model_class()関数で SchemaValidator のインスタンス化

def complete_model_class(
    cls: type[BaseModel],
    cls_name: str,
    config_wrapper: ConfigWrapper,
    *,
    raise_errors: bool = True,
    types_namespace: dict[str, Any] | None,
    create_model_module: str | None = None,
) -> bool:
    ...
    # debug(schema)
    cls.__pydantic_core_schema__ = schema

    cls.__pydantic_validator__ = create_schema_validator(
        schema,
        cls,
        create_model_module or cls.__module__,
        cls.__qualname__,
        'create_model' if create_model_module else 'BaseModel',
        core_config,
        config_wrapper.plugin_settings,
    )
    ...

Note

  • complete_model_class()BaseModelのサブクラスを定義したときに、メタクラスを通じて実行される
  • create_schema_validator()return SchemaValidator(schema, config) とほぼ同等

SchemaValidatorクラス

下記はREADME内のサンプルコード。

from pydantic_core import SchemaValidator, ValidationError


v = SchemaValidator(
    {
        'type': 'typed-dict',
        'fields': {
            'name': {
                'type': 'typed-dict-field',
                'schema': {
                    'type': 'str',
                },
            },
            'age': {
                'type': 'typed-dict-field',
                'schema': {
                    'type': 'int',
                    'ge': 18,
                },
            },
            'is_developer': {
                'type': 'typed-dict-field',
                'schema': {
                    'type': 'default',
                    'schema': {'type': 'bool'},
                    'default': True,
                },
            },
        },
    }
)

r1 = v.validate_python({'name': 'Samuel', 'age': 35})
assert r1 == {'name': 'Samuel', 'age': 35, 'is_developer': True}

# pydantic-core can also validate JSON directly
r2 = v.validate_json('{"name": "Samuel", "age": 35}')
assert r1 == r2

try:
    v.validate_python({'name': 'Samuel', 'age': 11})
except ValidationError as e:
    print(e)
    """
    1 validation error for model
    age
      Input should be greater than or equal to 18
      [type=greater_than_equal, context={ge: 18}, input_value=11, input_type=int]
    """

Note

  • SchemaValitatorクラスはイニシャライザでデータ構造を表現した辞書を受け取る
  • validate_python()メソッドはこの辞書を元にバリデーションを実行する

schemaオブジェクト

class Item(BaseModel):
   item_id: int
   name: str
   price: PositiveInt
   kind: Kind = Kind.DRINK

上記 Item クラスを定義したときの schema オブジェクト。

{'cls': <class '__main__.Item'>,
 'config': {'title': 'Item'},
 'custom_init': False,
 'metadata': {'pydantic_js_annotation_functions': [],
              'pydantic_js_functions': [...]},
 'ref': '__main__.Item:4321551040',
 'root_model': False,
 'schema': {'computed_fields': [],
            'fields': {'item_id': {'metadata': {'pydantic_js_annotation_functions': [...],
                                                'pydantic_js_functions': []},
                                   'schema': {'type': 'int'},
                                   'type': 'model-field'},
                       'kind': {'metadata': {'pydantic_js_annotation_functions': [...],
                                             'pydantic_js_functions': []},
                                'schema': {'default': <Kind.DRINK: 'DRINK'>,
                                           'schema': {'cls': <enum 'Kind'>,
                                                      'members': [<Kind.DRINK: 'DRINK'>,
                                                                  <Kind.FOOD: 'FOOD'>],
                                                      'metadata': {'pydantic_js_functions': [...},
                                                      'ref': '__main__.Kind:4321431408',
                                                      'sub_type': 'str',
                                                      'type': 'enum'},
                                           'type': 'default'},
                                'type': 'model-field'},
                       'name': {'metadata': {'pydantic_js_annotation_functions': [...],
                                             'pydantic_js_functions': []},
                                'schema': {'type': 'str'},
                                'type': 'model-field'},
                       'price': {'metadata': {'pydantic_js_annotation_functions': [...],
                                              'pydantic_js_functions': []},
                                 'schema': {'gt': 0, 'type': 'int'},
                                 'type': 'model-field'}},
            'model_name': 'Item',
            'type': 'model-fields'},
 'type': 'model'}

Note

  • この辞書は CoreSchema と呼ばれている

collect_model_fields()関数による cls.model_fields のセット

def collect_model_fields(  # noqa: C901  
    cls: type[BaseModel],  
    bases: tuple[type[Any], ...],
    config_wrapper: ConfigWrapper,
    types_namespace: dict[str, Any] | None,  
    *,
    typevars_map: dict[Any, Any] | None = None,  
) -> tuple[dict[str, FieldInfo], set[str]]:
    ...
    BaseModel = import_cached_base_model()
    FieldInfo_ = import_cached_field_info()

    # __mro__ を逆順に辿りながら ann = base.__dict__.get('__annotations__') の結果を1つの辞書に集約する
    type_hints = get_cls_type_hints_lenient(cls, types_namespace)

    # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
    # annotations is only used for finding fields in parent classes
    annotations = cls.__dict__.get('__annotations__', {})  
    fields: dict[str, FieldInfo] = {}
    ...
    return fields, class_vars

Note

import inspect
print(inspect.get_annotations(Item))
{'item_id': <class 'int'>, 'name': <class 'str'>, 'price': typing.Annotated[int, Gt(gt=0)], 'kind': <enum 'Kind'>}

fields_schemaオブジェクトの生成

cls.model_fieldsオブジェクト

{'item_id': FieldInfo(annotation=int, required=True),
 'kind': FieldInfo(annotation=Kind, required=False, default=<Kind.DRINK: 'DRINK'>),
 'name': FieldInfo(annotation=str, required=True),
 'price': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)])}

この内包表記部分で

{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()}

下記の形式に変換

{'item_id': {'metadata': {'pydantic_js_annotation_functions': [...],
                          'pydantic_js_functions': []},
             'schema': {'type': 'int'},
             'type': 'model-field'},
 'kind': {'metadata': {'pydantic_js_annotation_functions': [...],
                       'pydantic_js_functions': []},
          'schema': {'default': <Kind.DRINK: 'DRINK'>,
                     'schema': {'schema_ref': '__main__.Kind:5057056880',
                                'type': 'definition-ref'},
                     'type': 'default'},
          'type': 'model-field'},
 'name': {'metadata': {'pydantic_js_annotation_functions': [...],
                       'pydantic_js_functions': []},
          'schema': {'type': 'str'},
          'type': 'model-field'},
 'price': {'metadata': {'pydantic_js_annotation_functions': [...],
                        'pydantic_js_functions': []},
           'schema': {'gt': 0, 'type': 'int'},
           'type': 'model-field'}}

Note

  • フィールドごとに下記の呼び出し
  • _generate_md_field_schema()
    • _common_field_schema()
      • _apply_annotations()
        • ...
          • match_type()

GenerateSchema.match_type()

class GenerateSchema:
    ...
    def match_type(self, obj: Any) -> core_schema.CoreSchema:  # noqa: C901
        if obj is str:
            return self.str_schema()
        elif obj is bytes:
            return core_schema.bytes_schema()
        elif obj is int:
            return core_schema.int_schema()
        elif obj is float:
            return core_schema.float_schema()
        elif obj is bool:
            return core_schema.bool_schema()
        elif obj is complex:
            return core_schema.complex_schema()
        elif obj is Any or obj is object:
            return core_schema.any_schema()
        elif obj is datetime.date:
            return core_schema.date_schema()
        elif obj is datetime.datetime:
            return core_schema.datetime_schema()
        elif obj is datetime.time:
            return core_schema.time_schema()
        elif obj is datetime.timedelta:
            return core_schema.timedelta_schema()
        elif obj is Decimal:
            return core_schema.decimal_schema()
        elif obj is UUID:
            return core_schema.uuid_schema()
        elif obj is Url:
            return core_schema.url_schema()
        elif obj is MultiHostUrl:
            return core_schema.multi_host_url_schema()
        elif obj is None or obj is _typing_extra.NoneType:
            return core_schema.none_schema()
        elif obj in IP_TYPES:
            return self._ip_schema(obj)
        elif obj in TUPLE_TYPES:
            return self._tuple_schema(obj)
        elif obj in LIST_TYPES:
            return self._list_schema(Any)
        elif obj in SET_TYPES:
            return self._set_schema(Any)
        elif obj in FROZEN_SET_TYPES:
            return self._frozenset_schema(Any)
        elif obj in SEQUENCE_TYPES:
            return self._sequence_schema(Any)
        elif obj in DICT_TYPES:
            return self._dict_schema(Any, Any)
        elif isinstance(obj, TypeAliasType):
            return self._type_alias_type_schema(obj)
        elif obj is type:
            return self._type_schema()
        elif _typing_extra.is_callable_type(obj):
            return core_schema.callable_schema()
        elif _typing_extra.is_literal_type(obj):
            return self._literal_schema(obj)
        elif is_typeddict(obj):
            return self._typed_dict_schema(obj, None)
        elif _typing_extra.is_namedtuple(obj):
            return self._namedtuple_schema(obj, None)
        elif _typing_extra.is_new_type(obj):
            # NewType, can't use isinstance because it fails <3.10
            return self.generate_schema(obj.__supertype__)
        elif obj == re.Pattern:
            return self._pattern_schema(obj)
        elif obj is collections.abc.Hashable or obj is typing.Hashable:
            return self._hashable_schema()
        elif isinstance(obj, typing.TypeVar):
            return self._unsubstituted_typevar_schema(obj)
        elif is_finalvar(obj):
            if obj is Final:
                return core_schema.any_schema()
            return self.generate_schema(
                self._get_first_arg_or_any(obj),
            )
        elif isinstance(obj, (FunctionType, LambdaType, MethodType, partial)):
            return self._callable_schema(obj)
        elif inspect.isclass(obj) and issubclass(obj, Enum):
            return self._enum_schema(obj)
        elif is_zoneinfo_type(obj):
            return self._zoneinfo_schema()

        if _typing_extra.is_dataclass(obj):
            return self._dataclass_schema(obj, None)

        origin = get_origin(obj)
        if origin is not None:
            return self._match_generic_type(obj, origin)

        res = self._get_prepare_pydantic_annotations_for_known_type(obj, ())
        if res is not None:
            source_type, annotations = res
            return self._apply_annotations(source_type, annotations)

        if self._arbitrary_types:
            return self._arbitrary_type_schema(obj)
        return self._unknown_type_schema(obj)

Note

  • 巨大な if 文で実装されている
  • pydantic-core パッケージが提供する関数を使って型ごとに CoreSchema に変換

core_schema.int_schema()

class IntSchema(TypedDict, total=False):
    type: Required[Literal['int']]
    multiple_of: int
    le: int
    ge: int
    lt: int
    gt: int
    strict: bool
    ref: str
    metadata: Dict[str, Any]
    serialization: SerSchema


def int_schema(
    *,
    multiple_of: int | None = None,
    le: int | None = None,
    ge: int | None = None,
    lt: int | None = None,
    gt: int | None = None,
    strict: bool | None = None,
    ref: str | None = None,
    metadata: Dict[str, Any] | None = None,
    serialization: SerSchema | None = None,
) -> IntSchema:
    return _dict_not_none(
        type='int',
        multiple_of=multiple_of,
        le=le,
        ge=ge,
        lt=lt,
        gt=gt,
        strict=strict,
        ref=ref,
        metadata=metadata,
        serialization=serialization,
    )

Itemクラスの場合の schema オブジェクトの生成箇所

    def _model_schema(self, cls: type[BaseModel]) -> core_schema.CoreSchema:
        """Generate schema for a Pydantic model."""
            ...
            fields = cls.model_fields
            ...
                if cls.__pydantic_root_model__:
                    ...

                else:
                    # フィールド単位の schema オブジェクトの生成
                    fields_schema: core_schema.CoreSchema = core_schema.model_fields_schema(
                        {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
                        computed_fields=[
                            self._computed_field_schema(d, decorators.field_serializers)
                            for d in computed_fields.values()
                        ],
                        extras_schema=extras_schema,
                        model_name=cls.__name__,
                    )
                    inner_schema = apply_validators(fields_schema, decorators.root_validators.values(), None)
                    ...
                    model_schema = core_schema.model_schema(
                        cls,
                        inner_schema,
                        custom_init=getattr(cls, '__pydantic_custom_init__', None),
                        root_model=False,
                        post_init=getattr(cls, '__pydantic_post_init__', None),
                        config=core_config,
                        ref=model_ref,
                        metadata=metadata,
                    )

                schema = self._apply_model_serializers(model_schema, decorators.model_serializers.values())
                schema = apply_model_validators(schema, model_validators, 'outer')
                self.defs.definitions[model_ref] = schema
                return core_schema.definition_reference_schema(model_ref)

Note

  • 各フィールドを走査してベースとなるcore_schema.CoreSchema 型のschemaオブジェクトを生成
  • 生成したschemaオブジェクトに追加の情報を付与