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
)
model_validate()
は cls.__pydantic_validator__.validate_python()
を呼び出すだけcls.__pydantic_validator__
は 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,
)
...
complete_model_class()
はBaseModel
のサブクラスを定義したときに、メタクラスを通じて実行されるcreate_schema_validator()
は return SchemaValidator(schema, config)
とほぼ同等ref. README.md
ref. pydantic_core._pydantic_core
ref. src/validators/mod.rs
下記は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]
"""
SchemaValitator
クラスはイニシャライザでデータ構造を表現した辞書を受け取るvalidate_python()
メソッドはこの辞書を元にバリデーションを実行する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'}
CoreSchema
と呼ばれている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
complete_model_class()
を呼び出す前に実行されているFieldInfo
に変換import inspect
print(inspect.get_annotations(Item))
{'item_id': <class 'int'>, 'name': <class 'str'>, 'price': typing.Annotated[int, Gt(gt=0)], 'kind': <enum 'Kind'>}
{'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'}}
_generate_md_field_schema()
_common_field_schema()
_apply_annotations()
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)
CoreSchema
に変換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,
)
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)
core_schema.CoreSchema
型のschemaオブジェクトを生成