functional_validators.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. """This module contains related classes and functions for validation."""
  2. from __future__ import annotations as _annotations
  3. import dataclasses
  4. import sys
  5. from functools import partialmethod
  6. from types import FunctionType
  7. from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast, overload
  8. from pydantic_core import core_schema
  9. from pydantic_core import core_schema as _core_schema
  10. from typing_extensions import Annotated, Literal, TypeAlias
  11. from . import GetCoreSchemaHandler as _GetCoreSchemaHandler
  12. from ._internal import _core_metadata, _decorators, _generics, _internal_dataclass
  13. from .annotated_handlers import GetCoreSchemaHandler
  14. from .errors import PydanticUserError
  15. if sys.version_info < (3, 11):
  16. from typing_extensions import Protocol
  17. else:
  18. from typing import Protocol
  19. _inspect_validator = _decorators.inspect_validator
  20. @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true)
  21. class AfterValidator:
  22. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators
  23. A metadata class that indicates that a validation should be applied **after** the inner validation logic.
  24. Attributes:
  25. func: The validator function.
  26. Example:
  27. ```py
  28. from typing_extensions import Annotated
  29. from pydantic import AfterValidator, BaseModel, ValidationError
  30. MyInt = Annotated[int, AfterValidator(lambda v: v + 1)]
  31. class Model(BaseModel):
  32. a: MyInt
  33. print(Model(a=1).a)
  34. #> 2
  35. try:
  36. Model(a='a')
  37. except ValidationError as e:
  38. print(e.json(indent=2))
  39. '''
  40. [
  41. {
  42. "type": "int_parsing",
  43. "loc": [
  44. "a"
  45. ],
  46. "msg": "Input should be a valid integer, unable to parse string as an integer",
  47. "input": "a",
  48. "url": "https://errors.pydantic.dev/2/v/int_parsing"
  49. }
  50. ]
  51. '''
  52. ```
  53. """
  54. func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction
  55. def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema:
  56. schema = handler(source_type)
  57. info_arg = _inspect_validator(self.func, 'after')
  58. if info_arg:
  59. func = cast(core_schema.WithInfoValidatorFunction, self.func)
  60. return core_schema.with_info_after_validator_function(func, schema=schema, field_name=handler.field_name)
  61. else:
  62. func = cast(core_schema.NoInfoValidatorFunction, self.func)
  63. return core_schema.no_info_after_validator_function(func, schema=schema)
  64. @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true)
  65. class BeforeValidator:
  66. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators
  67. A metadata class that indicates that a validation should be applied **before** the inner validation logic.
  68. Attributes:
  69. func: The validator function.
  70. Example:
  71. ```py
  72. from typing_extensions import Annotated
  73. from pydantic import BaseModel, BeforeValidator
  74. MyInt = Annotated[int, BeforeValidator(lambda v: v + 1)]
  75. class Model(BaseModel):
  76. a: MyInt
  77. print(Model(a=1).a)
  78. #> 2
  79. try:
  80. Model(a='a')
  81. except TypeError as e:
  82. print(e)
  83. #> can only concatenate str (not "int") to str
  84. ```
  85. """
  86. func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction
  87. def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema:
  88. schema = handler(source_type)
  89. info_arg = _inspect_validator(self.func, 'before')
  90. if info_arg:
  91. func = cast(core_schema.WithInfoValidatorFunction, self.func)
  92. return core_schema.with_info_before_validator_function(func, schema=schema, field_name=handler.field_name)
  93. else:
  94. func = cast(core_schema.NoInfoValidatorFunction, self.func)
  95. return core_schema.no_info_before_validator_function(func, schema=schema)
  96. @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true)
  97. class PlainValidator:
  98. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators
  99. A metadata class that indicates that a validation should be applied **instead** of the inner validation logic.
  100. Attributes:
  101. func: The validator function.
  102. Example:
  103. ```py
  104. from typing_extensions import Annotated
  105. from pydantic import BaseModel, PlainValidator
  106. MyInt = Annotated[int, PlainValidator(lambda v: int(v) + 1)]
  107. class Model(BaseModel):
  108. a: MyInt
  109. print(Model(a='1').a)
  110. #> 2
  111. ```
  112. """
  113. func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction
  114. def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema:
  115. info_arg = _inspect_validator(self.func, 'plain')
  116. if info_arg:
  117. func = cast(core_schema.WithInfoValidatorFunction, self.func)
  118. return core_schema.with_info_plain_validator_function(func, field_name=handler.field_name)
  119. else:
  120. func = cast(core_schema.NoInfoValidatorFunction, self.func)
  121. return core_schema.no_info_plain_validator_function(func)
  122. @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true)
  123. class WrapValidator:
  124. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators
  125. A metadata class that indicates that a validation should be applied **around** the inner validation logic.
  126. Attributes:
  127. func: The validator function.
  128. ```py
  129. from datetime import datetime
  130. from typing_extensions import Annotated
  131. from pydantic import BaseModel, ValidationError, WrapValidator
  132. def validate_timestamp(v, handler):
  133. if v == 'now':
  134. # we don't want to bother with further validation, just return the new value
  135. return datetime.now()
  136. try:
  137. return handler(v)
  138. except ValidationError:
  139. # validation failed, in this case we want to return a default value
  140. return datetime(2000, 1, 1)
  141. MyTimestamp = Annotated[datetime, WrapValidator(validate_timestamp)]
  142. class Model(BaseModel):
  143. a: MyTimestamp
  144. print(Model(a='now').a)
  145. #> 2032-01-02 03:04:05.000006
  146. print(Model(a='invalid').a)
  147. #> 2000-01-01 00:00:00
  148. ```
  149. """
  150. func: core_schema.NoInfoWrapValidatorFunction | core_schema.WithInfoWrapValidatorFunction
  151. def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema:
  152. schema = handler(source_type)
  153. info_arg = _inspect_validator(self.func, 'wrap')
  154. if info_arg:
  155. func = cast(core_schema.WithInfoWrapValidatorFunction, self.func)
  156. return core_schema.with_info_wrap_validator_function(func, schema=schema, field_name=handler.field_name)
  157. else:
  158. func = cast(core_schema.NoInfoWrapValidatorFunction, self.func)
  159. return core_schema.no_info_wrap_validator_function(func, schema=schema)
  160. if TYPE_CHECKING:
  161. class _OnlyValueValidatorClsMethod(Protocol):
  162. def __call__(self, __cls: Any, __value: Any) -> Any:
  163. ...
  164. class _V2ValidatorClsMethod(Protocol):
  165. def __call__(self, __cls: Any, __input_value: Any, __info: _core_schema.ValidationInfo) -> Any:
  166. ...
  167. class _V2WrapValidatorClsMethod(Protocol):
  168. def __call__(
  169. self,
  170. __cls: Any,
  171. __input_value: Any,
  172. __validator: _core_schema.ValidatorFunctionWrapHandler,
  173. __info: _core_schema.ValidationInfo,
  174. ) -> Any:
  175. ...
  176. _V2Validator = Union[
  177. _V2ValidatorClsMethod,
  178. _core_schema.WithInfoValidatorFunction,
  179. _OnlyValueValidatorClsMethod,
  180. _core_schema.NoInfoValidatorFunction,
  181. ]
  182. _V2WrapValidator = Union[
  183. _V2WrapValidatorClsMethod,
  184. _core_schema.WithInfoWrapValidatorFunction,
  185. ]
  186. _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]]
  187. _V2BeforeAfterOrPlainValidatorType = TypeVar(
  188. '_V2BeforeAfterOrPlainValidatorType',
  189. _V2Validator,
  190. _PartialClsOrStaticMethod,
  191. )
  192. _V2WrapValidatorType = TypeVar('_V2WrapValidatorType', _V2WrapValidator, _PartialClsOrStaticMethod)
  193. @overload
  194. def field_validator(
  195. __field: str,
  196. *fields: str,
  197. mode: Literal['before', 'after', 'plain'] = ...,
  198. check_fields: bool | None = ...,
  199. ) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]:
  200. ...
  201. @overload
  202. def field_validator(
  203. __field: str,
  204. *fields: str,
  205. mode: Literal['wrap'],
  206. check_fields: bool | None = ...,
  207. ) -> Callable[[_V2WrapValidatorType], _V2WrapValidatorType]:
  208. ...
  209. FieldValidatorModes: TypeAlias = Literal['before', 'after', 'wrap', 'plain']
  210. def field_validator(
  211. __field: str,
  212. *fields: str,
  213. mode: FieldValidatorModes = 'after',
  214. check_fields: bool | None = None,
  215. ) -> Callable[[Any], Any]:
  216. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#field-validators
  217. Decorate methods on the class indicating that they should be used to validate fields.
  218. Example usage:
  219. ```py
  220. from typing import Any
  221. from pydantic import (
  222. BaseModel,
  223. ValidationError,
  224. field_validator,
  225. )
  226. class Model(BaseModel):
  227. a: str
  228. @field_validator('a')
  229. @classmethod
  230. def ensure_foobar(cls, v: Any):
  231. if 'foobar' not in v:
  232. raise ValueError('"foobar" not found in a')
  233. return v
  234. print(repr(Model(a='this is foobar good')))
  235. #> Model(a='this is foobar good')
  236. try:
  237. Model(a='snap')
  238. except ValidationError as exc_info:
  239. print(exc_info)
  240. '''
  241. 1 validation error for Model
  242. a
  243. Value error, "foobar" not found in a [type=value_error, input_value='snap', input_type=str]
  244. '''
  245. ```
  246. For more in depth examples, see [Field Validators](../concepts/validators.md#field-validators).
  247. Args:
  248. __field: The first field the `field_validator` should be called on; this is separate
  249. from `fields` to ensure an error is raised if you don't pass at least one.
  250. *fields: Additional field(s) the `field_validator` should be called on.
  251. mode: Specifies whether to validate the fields before or after validation.
  252. check_fields: Whether to check that the fields actually exist on the model.
  253. Returns:
  254. A decorator that can be used to decorate a function to be used as a field_validator.
  255. Raises:
  256. PydanticUserError:
  257. - If `@field_validator` is used bare (with no fields).
  258. - If the args passed to `@field_validator` as fields are not strings.
  259. - If `@field_validator` applied to instance methods.
  260. """
  261. if isinstance(__field, FunctionType):
  262. raise PydanticUserError(
  263. '`@field_validator` should be used with fields and keyword arguments, not bare. '
  264. "E.g. usage should be `@validator('<field_name>', ...)`",
  265. code='validator-no-fields',
  266. )
  267. fields = __field, *fields
  268. if not all(isinstance(field, str) for field in fields):
  269. raise PydanticUserError(
  270. '`@field_validator` fields should be passed as separate string args. '
  271. "E.g. usage should be `@validator('<field_name_1>', '<field_name_2>', ...)`",
  272. code='validator-invalid-fields',
  273. )
  274. def dec(
  275. f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any],
  276. ) -> _decorators.PydanticDescriptorProxy[Any]:
  277. if _decorators.is_instance_method_from_sig(f):
  278. raise PydanticUserError(
  279. '`@field_validator` cannot be applied to instance methods', code='validator-instance-method'
  280. )
  281. # auto apply the @classmethod decorator
  282. f = _decorators.ensure_classmethod_based_on_signature(f)
  283. dec_info = _decorators.FieldValidatorDecoratorInfo(fields=fields, mode=mode, check_fields=check_fields)
  284. return _decorators.PydanticDescriptorProxy(f, dec_info)
  285. return dec
  286. _ModelType = TypeVar('_ModelType')
  287. _ModelTypeCo = TypeVar('_ModelTypeCo', covariant=True)
  288. class ModelWrapValidatorHandler(_core_schema.ValidatorFunctionWrapHandler, Protocol[_ModelTypeCo]):
  289. """@model_validator decorated function handler argument type. This is used when `mode='wrap'`."""
  290. def __call__( # noqa: D102
  291. self, input_value: Any, outer_location: str | int | None = None
  292. ) -> _ModelTypeCo: # pragma: no cover
  293. ...
  294. class ModelWrapValidatorWithoutInfo(Protocol[_ModelType]):
  295. """A @model_validator decorated function signature.
  296. This is used when `mode='wrap'` and the function does not have info argument.
  297. """
  298. def __call__( # noqa: D102
  299. self,
  300. cls: type[_ModelType],
  301. # this can be a dict, a model instance
  302. # or anything else that gets passed to validate_python
  303. # thus validators _must_ handle all cases
  304. __value: Any,
  305. __handler: ModelWrapValidatorHandler[_ModelType],
  306. ) -> _ModelType:
  307. ...
  308. class ModelWrapValidator(Protocol[_ModelType]):
  309. """A @model_validator decorated function signature. This is used when `mode='wrap'`."""
  310. def __call__( # noqa: D102
  311. self,
  312. cls: type[_ModelType],
  313. # this can be a dict, a model instance
  314. # or anything else that gets passed to validate_python
  315. # thus validators _must_ handle all cases
  316. __value: Any,
  317. __handler: ModelWrapValidatorHandler[_ModelType],
  318. __info: _core_schema.ValidationInfo,
  319. ) -> _ModelType:
  320. ...
  321. class ModelBeforeValidatorWithoutInfo(Protocol):
  322. """A @model_validator decorated function signature.
  323. This is used when `mode='before'` and the function does not have info argument.
  324. """
  325. def __call__( # noqa: D102
  326. self,
  327. cls: Any,
  328. # this can be a dict, a model instance
  329. # or anything else that gets passed to validate_python
  330. # thus validators _must_ handle all cases
  331. __value: Any,
  332. ) -> Any:
  333. ...
  334. class ModelBeforeValidator(Protocol):
  335. """A `@model_validator` decorated function signature. This is used when `mode='before'`."""
  336. def __call__( # noqa: D102
  337. self,
  338. cls: Any,
  339. # this can be a dict, a model instance
  340. # or anything else that gets passed to validate_python
  341. # thus validators _must_ handle all cases
  342. __value: Any,
  343. __info: _core_schema.ValidationInfo,
  344. ) -> Any:
  345. ...
  346. ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType]
  347. """A `@model_validator` decorated function signature. This is used when `mode='after'` and the function does not
  348. have info argument.
  349. """
  350. ModelAfterValidator = Callable[[_ModelType, _core_schema.ValidationInfo], _ModelType]
  351. """A `@model_validator` decorated function signature. This is used when `mode='after'`."""
  352. _AnyModelWrapValidator = Union[ModelWrapValidator[_ModelType], ModelWrapValidatorWithoutInfo[_ModelType]]
  353. _AnyModeBeforeValidator = Union[ModelBeforeValidator, ModelBeforeValidatorWithoutInfo]
  354. _AnyModelAfterValidator = Union[ModelAfterValidator[_ModelType], ModelAfterValidatorWithoutInfo[_ModelType]]
  355. @overload
  356. def model_validator(
  357. *,
  358. mode: Literal['wrap'],
  359. ) -> Callable[
  360. [_AnyModelWrapValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]
  361. ]:
  362. ...
  363. @overload
  364. def model_validator(
  365. *,
  366. mode: Literal['before'],
  367. ) -> Callable[[_AnyModeBeforeValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]]:
  368. ...
  369. @overload
  370. def model_validator(
  371. *,
  372. mode: Literal['after'],
  373. ) -> Callable[
  374. [_AnyModelAfterValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]
  375. ]:
  376. ...
  377. def model_validator(
  378. *,
  379. mode: Literal['wrap', 'before', 'after'],
  380. ) -> Any:
  381. """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#model-validators
  382. Decorate model methods for validation purposes.
  383. Example usage:
  384. ```py
  385. from typing import Optional
  386. from pydantic import BaseModel, ValidationError, model_validator
  387. class Square(BaseModel):
  388. width: float
  389. height: float
  390. @model_validator(mode='after')
  391. def verify_square(self) -> 'Rectangle':
  392. if self.width != self.height:
  393. raise ValueError('width and height do not match')
  394. return self
  395. s = Square(width=1, height=1)
  396. print(repr(s))
  397. #> Square(width=1.0, height=1.0)
  398. try:
  399. Square(width=1, height=2)
  400. except ValidationError as e:
  401. print(e)
  402. '''
  403. 1 validation error for Square
  404. __root__
  405. width and height do not match (type=value_error)
  406. '''
  407. ```
  408. For more in depth examples, see [Model Validators](../concepts/validators.md#model-validators).
  409. Args:
  410. mode: A required string literal that specifies the validation mode.
  411. It can be one of the following: 'wrap', 'before', or 'after'.
  412. Returns:
  413. A decorator that can be used to decorate a function to be used as a model validator.
  414. """
  415. def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]:
  416. # auto apply the @classmethod decorator
  417. f = _decorators.ensure_classmethod_based_on_signature(f)
  418. dec_info = _decorators.ModelValidatorDecoratorInfo(mode=mode)
  419. return _decorators.PydanticDescriptorProxy(f, dec_info)
  420. return dec
  421. AnyType = TypeVar('AnyType')
  422. if TYPE_CHECKING:
  423. # If we add configurable attributes to IsInstance, we'd probably need to stop hiding it from type checkers like this
  424. InstanceOf = Annotated[AnyType, ...] # `IsInstance[Sequence]` will be recognized by type checkers as `Sequence`
  425. else:
  426. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  427. class InstanceOf:
  428. '''Generic type for annotating a type that is an instance of a given class.
  429. Example:
  430. ```py
  431. from pydantic import BaseModel, InstanceOf
  432. class Foo:
  433. ...
  434. class Bar(BaseModel):
  435. foo: InstanceOf[Foo]
  436. Bar(foo=Foo())
  437. try:
  438. Bar(foo=42)
  439. except ValidationError as e:
  440. print(e)
  441. """
  442. [
  443. │ {
  444. │ │ 'type': 'is_instance_of',
  445. │ │ 'loc': ('foo',),
  446. │ │ 'msg': 'Input should be an instance of Foo',
  447. │ │ 'input': 42,
  448. │ │ 'ctx': {'class': 'Foo'},
  449. │ │ 'url': 'https://errors.pydantic.dev/0.38.0/v/is_instance_of'
  450. │ }
  451. ]
  452. """
  453. ```
  454. '''
  455. @classmethod
  456. def __class_getitem__(cls, item: AnyType) -> AnyType:
  457. return Annotated[item, cls()]
  458. @classmethod
  459. def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
  460. from pydantic import PydanticSchemaGenerationError
  461. # use the generic _origin_ as the second argument to isinstance when appropriate
  462. instance_of_schema = core_schema.is_instance_schema(_generics.get_origin(source) or source)
  463. try:
  464. # Try to generate the "standard" schema, which will be used when loading from JSON
  465. original_schema = handler(source)
  466. except PydanticSchemaGenerationError:
  467. # If that fails, just produce a schema that can validate from python
  468. return instance_of_schema
  469. else:
  470. # Use the "original" approach to serialization
  471. instance_of_schema['serialization'] = core_schema.wrap_serializer_function_ser_schema(
  472. function=lambda v, h: h(v), schema=original_schema
  473. )
  474. return core_schema.json_or_python_schema(python_schema=instance_of_schema, json_schema=original_schema)
  475. __hash__ = object.__hash__
  476. if TYPE_CHECKING:
  477. SkipValidation = Annotated[AnyType, ...] # SkipValidation[list[str]] will be treated by type checkers as list[str]
  478. else:
  479. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  480. class SkipValidation:
  481. """If this is applied as an annotation (e.g., via `x: Annotated[int, SkipValidation]`), validation will be
  482. skipped. You can also use `SkipValidation[int]` as a shorthand for `Annotated[int, SkipValidation]`.
  483. This can be useful if you want to use a type annotation for documentation/IDE/type-checking purposes,
  484. and know that it is safe to skip validation for one or more of the fields.
  485. Because this converts the validation schema to `any_schema`, subsequent annotation-applied transformations
  486. may not have the expected effects. Therefore, when used, this annotation should generally be the final
  487. annotation applied to a type.
  488. """
  489. def __class_getitem__(cls, item: Any) -> Any:
  490. return Annotated[item, SkipValidation()]
  491. @classmethod
  492. def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
  493. original_schema = handler(source)
  494. metadata = _core_metadata.build_metadata_dict(js_annotation_functions=[lambda _c, h: h(original_schema)])
  495. return core_schema.any_schema(
  496. metadata=metadata,
  497. serialization=core_schema.wrap_serializer_function_ser_schema(
  498. function=lambda v, h: h(v), schema=original_schema
  499. ),
  500. )
  501. __hash__ = object.__hash__