관리-도구
편집 파일: functional_validators.py
"""This module contains related classes and functions for validation.""" from __future__ import annotations as _annotations import dataclasses import sys from functools import partialmethod from types import FunctionType from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast, overload from pydantic_core import core_schema from pydantic_core import core_schema as _core_schema from typing_extensions import Annotated, Literal, TypeAlias from . import GetCoreSchemaHandler as _GetCoreSchemaHandler from ._internal import _core_metadata, _decorators, _generics, _internal_dataclass from .annotated_handlers import GetCoreSchemaHandler from .errors import PydanticUserError if sys.version_info < (3, 11): from typing_extensions import Protocol else: from typing import Protocol _inspect_validator = _decorators.inspect_validator @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class AfterValidator: '''Usage docs: https://docs.pydantic.dev/2.2/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **after** the inner validation logic. Attributes: func: The validator function. Example: ```py from typing import Annotated from pydantic import BaseModel, AfterValidator, ValidationError MyInt = Annotated[int, AfterValidator(lambda v: v + 1)] class Model(BaseModel): a: MyInt print(Model(a=1).a) # > 2 try: Model(a='a') except ValidationError as e: print(e.json(indent=2)) """ [ { "type": "int_parsing", "loc": [ "a" ], "msg": "Input should be a valid integer, unable to parse string as an integer", "input": "a", "url": "https://errors.pydantic.dev/0.38.0/v/int_parsing" } ] """ ``` ''' func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) info_arg = _inspect_validator(self.func, 'after') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) return core_schema.with_info_after_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) return core_schema.no_info_after_validator_function(func, schema=schema) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class BeforeValidator: """Usage docs: https://docs.pydantic.dev/2.4/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **before** the inner validation logic. Attributes: func: The validator function. Example: ```py from typing_extensions import Annotated from pydantic import BaseModel, BeforeValidator MyInt = Annotated[int, BeforeValidator(lambda v: v + 1)] class Model(BaseModel): a: MyInt print(Model(a=1).a) #> 2 try: Model(a='a') except TypeError as e: print(e) #> can only concatenate str (not "int") to str ``` """ func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) info_arg = _inspect_validator(self.func, 'before') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) return core_schema.with_info_before_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) return core_schema.no_info_before_validator_function(func, schema=schema) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class PlainValidator: """Usage docs: https://docs.pydantic.dev/2.4/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **instead** of the inner validation logic. Attributes: func: The validator function. Example: ```py from typing_extensions import Annotated from pydantic import BaseModel, PlainValidator MyInt = Annotated[int, PlainValidator(lambda v: int(v) + 1)] class Model(BaseModel): a: MyInt print(Model(a='1').a) #> 2 ``` """ func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: info_arg = _inspect_validator(self.func, 'plain') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) return core_schema.with_info_plain_validator_function(func, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) return core_schema.no_info_plain_validator_function(func) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class WrapValidator: """Usage docs: https://docs.pydantic.dev/2.4/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **around** the inner validation logic. Attributes: func: The validator function. ```py from datetime import datetime from typing_extensions import Annotated from pydantic import BaseModel, ValidationError, WrapValidator def validate_timestamp(v, handler): if v == 'now': # we don't want to bother with further validation, just return the new value return datetime.now() try: return handler(v) except ValidationError: # validation failed, in this case we want to return a default value return datetime(2000, 1, 1) MyTimestamp = Annotated[datetime, WrapValidator(validate_timestamp)] class Model(BaseModel): a: MyTimestamp print(Model(a='now').a) #> 2032-01-02 03:04:05.000006 print(Model(a='invalid').a) #> 2000-01-01 00:00:00 ``` """ func: core_schema.NoInfoWrapValidatorFunction | core_schema.WithInfoWrapValidatorFunction def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) info_arg = _inspect_validator(self.func, 'wrap') if info_arg: func = cast(core_schema.WithInfoWrapValidatorFunction, self.func) return core_schema.with_info_wrap_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoWrapValidatorFunction, self.func) return core_schema.no_info_wrap_validator_function(func, schema=schema) if TYPE_CHECKING: class _OnlyValueValidatorClsMethod(Protocol): def __call__(self, __cls: Any, __value: Any) -> Any: ... class _V2ValidatorClsMethod(Protocol): def __call__(self, __cls: Any, __input_value: Any, __info: _core_schema.ValidationInfo) -> Any: ... class _V2WrapValidatorClsMethod(Protocol): def __call__( self, __cls: Any, __input_value: Any, __validator: _core_schema.ValidatorFunctionWrapHandler, __info: _core_schema.ValidationInfo, ) -> Any: ... _V2Validator = Union[ _V2ValidatorClsMethod, _core_schema.WithInfoValidatorFunction, _OnlyValueValidatorClsMethod, _core_schema.NoInfoValidatorFunction, ] _V2WrapValidator = Union[ _V2WrapValidatorClsMethod, _core_schema.WithInfoWrapValidatorFunction, ] _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] _V2BeforeAfterOrPlainValidatorType = TypeVar( '_V2BeforeAfterOrPlainValidatorType', _V2Validator, _PartialClsOrStaticMethod, ) _V2WrapValidatorType = TypeVar('_V2WrapValidatorType', _V2WrapValidator, _PartialClsOrStaticMethod) @overload def field_validator( __field: str, *fields: str, mode: Literal['before', 'after', 'plain'] = ..., check_fields: bool | None = ..., ) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: ... @overload def field_validator( __field: str, *fields: str, mode: Literal['wrap'], check_fields: bool | None = ..., ) -> Callable[[_V2WrapValidatorType], _V2WrapValidatorType]: ... FieldValidatorModes: TypeAlias = Literal['before', 'after', 'wrap', 'plain'] def field_validator( __field: str, *fields: str, mode: FieldValidatorModes = 'after', check_fields: bool | None = None, ) -> Callable[[Any], Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/validators/#field-validators Decorate methods on the class indicating that they should be used to validate fields. Args: __field: The first field the `field_validator` should be called on; this is separate from `fields` to ensure an error is raised if you don't pass at least one. *fields: Additional field(s) the `field_validator` should be called on. mode: Specifies whether to validate the fields before or after validation. check_fields: Whether to check that the fields actually exist on the model. Returns: A decorator that can be used to decorate a function to be used as a field_validator. Raises: PydanticUserError: - If `@field_validator` is used bare (with no fields). - If the args passed to `@field_validator` as fields are not strings. - If `@field_validator` applied to instance methods. """ if isinstance(__field, FunctionType): raise PydanticUserError( '`@field_validator` should be used with fields and keyword arguments, not bare. ' "E.g. usage should be `@validator('<field_name>', ...)`", code='validator-no-fields', ) fields = __field, *fields if not all(isinstance(field, str) for field in fields): raise PydanticUserError( '`@field_validator` fields should be passed as separate string args. ' "E.g. usage should be `@validator('<field_name_1>', '<field_name_2>', ...)`", code='validator-invalid-fields', ) def dec( f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any] ) -> _decorators.PydanticDescriptorProxy[Any]: if _decorators.is_instance_method_from_sig(f): raise PydanticUserError( '`@field_validator` cannot be applied to instance methods', code='validator-instance-method' ) # auto apply the @classmethod decorator f = _decorators.ensure_classmethod_based_on_signature(f) dec_info = _decorators.FieldValidatorDecoratorInfo(fields=fields, mode=mode, check_fields=check_fields) return _decorators.PydanticDescriptorProxy(f, dec_info) return dec _ModelType = TypeVar('_ModelType') _ModelTypeCo = TypeVar('_ModelTypeCo', covariant=True) class ModelWrapValidatorHandler(_core_schema.ValidatorFunctionWrapHandler, Protocol[_ModelTypeCo]): """@model_validator decorated function handler argument type. This is used when `mode='wrap'`.""" def __call__( # noqa: D102 self, input_value: Any, outer_location: str | int | None = None ) -> _ModelTypeCo: # pragma: no cover ... class ModelWrapValidatorWithoutInfo(Protocol[_ModelType]): """A @model_validator decorated function signature. This is used when `mode='wrap'` and the function does not have info argument. """ def __call__( # noqa: D102 self, cls: type[_ModelType], # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases __value: Any, __handler: ModelWrapValidatorHandler[_ModelType], ) -> _ModelType: ... class ModelWrapValidator(Protocol[_ModelType]): """A @model_validator decorated function signature. This is used when `mode='wrap'`.""" def __call__( # noqa: D102 self, cls: type[_ModelType], # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases __value: Any, __handler: ModelWrapValidatorHandler[_ModelType], __info: _core_schema.ValidationInfo, ) -> _ModelType: ... class ModelBeforeValidatorWithoutInfo(Protocol): """A @model_validator decorated function signature. This is used when `mode='before'` and the function does not have info argument. """ def __call__( # noqa: D102 self, cls: Any, # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases __value: Any, ) -> Any: ... class ModelBeforeValidator(Protocol): """A `@model_validator` decorated function signature. This is used when `mode='before'`.""" def __call__( # noqa: D102 self, cls: Any, # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases __value: Any, __info: _core_schema.ValidationInfo, ) -> Any: ... ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType] """A `@model_validator` decorated function signature. This is used when `mode='after'` and the function does not have info argument. """ ModelAfterValidator = Callable[[_ModelType, _core_schema.ValidationInfo], _ModelType] """A `@model_validator` decorated function signature. This is used when `mode='after'`.""" _AnyModelWrapValidator = Union[ModelWrapValidator[_ModelType], ModelWrapValidatorWithoutInfo[_ModelType]] _AnyModeBeforeValidator = Union[ModelBeforeValidator, ModelBeforeValidatorWithoutInfo] _AnyModelAfterValidator = Union[ModelAfterValidator[_ModelType], ModelAfterValidatorWithoutInfo[_ModelType]] @overload def model_validator( *, mode: Literal['wrap'], ) -> Callable[ [_AnyModelWrapValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] ]: ... @overload def model_validator( *, mode: Literal['before'], ) -> Callable[[_AnyModeBeforeValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]]: ... @overload def model_validator( *, mode: Literal['after'], ) -> Callable[ [_AnyModelAfterValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] ]: ... def model_validator( *, mode: Literal['wrap', 'before', 'after'], ) -> Any: """Decorate model methods for validation purposes. Args: mode: A required string literal that specifies the validation mode. It can be one of the following: 'wrap', 'before', or 'after'. Returns: A decorator that can be used to decorate a function to be used as a model validator. """ def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]: # auto apply the @classmethod decorator f = _decorators.ensure_classmethod_based_on_signature(f) dec_info = _decorators.ModelValidatorDecoratorInfo(mode=mode) return _decorators.PydanticDescriptorProxy(f, dec_info) return dec AnyType = TypeVar('AnyType') if TYPE_CHECKING: # If we add configurable attributes to IsInstance, we'd probably need to stop hiding it from type checkers like this InstanceOf = Annotated[AnyType, ...] # `IsInstance[Sequence]` will be recognized by type checkers as `Sequence` else: @dataclasses.dataclass(**_internal_dataclass.slots_true) class InstanceOf: '''Generic type for annotating a type that is an instance of a given class. Example: ```py from pydantic import BaseModel, InstanceOf class Foo: ... class Bar(BaseModel): foo: InstanceOf[Foo] Bar(foo=Foo()) try: Bar(foo=42) except ValidationError as e: print(e) """ [ │ { │ │ 'type': 'is_instance_of', │ │ 'loc': ('foo',), │ │ 'msg': 'Input should be an instance of Foo', │ │ 'input': 42, │ │ 'ctx': {'class': 'Foo'}, │ │ 'url': 'https://errors.pydantic.dev/0.38.0/v/is_instance_of' │ } ] """ ``` ''' @classmethod def __class_getitem__(cls, item: AnyType) -> AnyType: return Annotated[item, cls()] @classmethod def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: from pydantic import PydanticSchemaGenerationError # use the generic _origin_ as the second argument to isinstance when appropriate instance_of_schema = core_schema.is_instance_schema(_generics.get_origin(source) or source) try: # Try to generate the "standard" schema, which will be used when loading from JSON original_schema = handler(source) except PydanticSchemaGenerationError: # If that fails, just produce a schema that can validate from python return instance_of_schema else: # Use the "original" approach to serialization instance_of_schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( function=lambda v, h: h(v), schema=original_schema ) return core_schema.json_or_python_schema(python_schema=instance_of_schema, json_schema=original_schema) __hash__ = object.__hash__ if TYPE_CHECKING: SkipValidation = Annotated[AnyType, ...] # SkipValidation[list[str]] will be treated by type checkers as list[str] else: @dataclasses.dataclass(**_internal_dataclass.slots_true) class SkipValidation: """If this is applied as an annotation (e.g., via `x: Annotated[int, SkipValidation]`), validation will be skipped. You can also use `SkipValidation[int]` as a shorthand for `Annotated[int, SkipValidation]`. This can be useful if you want to use a type annotation for documentation/IDE/type-checking purposes, and know that it is safe to skip validation for one or more of the fields. Because this converts the validation schema to `any_schema`, subsequent annotation-applied transformations may not have the expected effects. Therefore, when used, this annotation should generally be the final annotation applied to a type. """ def __class_getitem__(cls, item: Any) -> Any: return Annotated[item, SkipValidation()] @classmethod def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: original_schema = handler(source) metadata = _core_metadata.build_metadata_dict(js_annotation_functions=[lambda _c, h: h(original_schema)]) return core_schema.any_schema( metadata=metadata, serialization=core_schema.wrap_serializer_function_ser_schema( function=lambda v, h: h(v), schema=original_schema ), ) __hash__ = object.__hash__