diff --git a/docs/migration.md b/docs/migration.md index e5fe2572de..cf328982ea 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -280,14 +280,14 @@ being validated. Some of these arguments have been removed from `@field_validato to index into `cls.model_fields` ```python -from pydantic import BaseModel, FieldValidationInfo, field_validator +from pydantic import BaseModel, ValidationInfo, field_validator class Model(BaseModel): x: int @field_validator('x') - def val_x(cls, v: int, info: FieldValidationInfo) -> int: + def val_x(cls, v: int, info: ValidationInfo) -> int: assert info.config is not None print(info.config.get('title')) #> Model diff --git a/docs/usage/serialization.md b/docs/usage/serialization.md index 26fc23a1a3..56775cc966 100644 --- a/docs/usage/serialization.md +++ b/docs/usage/serialization.md @@ -365,14 +365,14 @@ class DayThisYear(date): def __get_pydantic_core_schema__( cls, source: Type[Any], handler: GetCoreSchemaHandler ) -> core_schema.CoreSchema: - return core_schema.general_after_validator_function( + return core_schema.no_info_after_validator_function( cls.validate, core_schema.int_schema(), serialization=core_schema.format_ser_schema('%Y-%m-%d'), ) @classmethod - def validate(cls, v: int, _info): + def validate(cls, v: int): return date.today().replace(month=1, day=1) + timedelta(days=v) diff --git a/docs/usage/types/custom.md b/docs/usage/types/custom.md index 62cadcc0b1..53e4a805d3 100644 --- a/docs/usage/types/custom.md +++ b/docs/usage/types/custom.md @@ -1,5 +1,3 @@ - - You can also define your own custom data types. There are several ways to achieve it. ## Composing types via `Annotated` @@ -775,8 +773,8 @@ class MySequence(Sequence[T]): else: sequence_t_schema = handler.generate_schema(Sequence) - non_instance_schema = core_schema.general_after_validator_function( - lambda v, i: MySequence(v), sequence_t_schema + non_instance_schema = core_schema.no_info_after_validator_function( + MySequence, sequence_t_schema ) return core_schema.union_schema([instance_schema, non_instance_schema]) @@ -807,11 +805,80 @@ except ValidationError as exc: 2 validation errors for M s1.is-instance[MySequence] Input should be an instance of MySequence [type=is_instance_of, input_value=['a'], input_type=list] - s1.function-after[(), json-or-python[json=list[int],python=chain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0 + s1.function-after[MySequence(), json-or-python[json=list[int],python=chain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0 Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] """ ``` +### Access to field name + +!!!note + This was not possible with Pydantic V2 to V2.3, it was [re-added](https://github.com/pydantic/pydantic/pull/7542) in Pydantic V2.4. + +As of Pydantic V2.4, you can access the field name via the `handler.field_name` within `__get_pydantic_core_schema__` +and thereby set the field name which will be available from `info.field_name`. + +```python +from typing import Any + +from pydantic_core import core_schema + +from pydantic import BaseModel, GetCoreSchemaHandler, ValidationInfo + + +class CustomType: + """Custom type that stores the field it was used in.""" + + def __init__(self, value: int, field_name: str): + self.value = value + self.field_name = field_name + + def __repr__(self): + return f'CustomType<{self.value} {self.field_name!r}>' + + @classmethod + def validate(cls, value: int, info: ValidationInfo): + return cls(value, info.field_name) + + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + return core_schema.with_info_after_validator_function( + cls.validate, handler(int), field_name=handler.field_name + ) + + +class MyModel(BaseModel): + my_field: CustomType + + +m = MyModel(my_field=1) +print(m.my_field) +#> CustomType<1 'my_field'> +``` + +You can also access `field_name` from the markers used with `Annotated`, like [`AfterValidator`][pydantic.functional_validators.AfterValidator]. + +```python +from typing_extensions import Annotated + +from pydantic import AfterValidator, BaseModel, ValidationInfo + + +def my_validators(value: int, info: ValidationInfo): + return f'<{value} {info.field_name!r}>' + + +class MyModel(BaseModel): + my_field: Annotated[int, AfterValidator(my_validators)] + + +m = MyModel(my_field=1) +print(m.my_field) +#> <1 'my_field'> +``` + [PEP 593]: https://peps.python.org/pep-0593/ [PEP 695]: https://peps.python.org/pep-0695/ [typing-extensions]: https://github.com/python/typing_extensions diff --git a/docs/usage/validators.md b/docs/usage/validators.md index 074d761b6b..9ed6bc2c69 100644 --- a/docs/usage/validators.md +++ b/docs/usage/validators.md @@ -305,8 +305,8 @@ If you want to attach a validator to a specific field of a model you can use the ```py from pydantic import ( BaseModel, - FieldValidationInfo, ValidationError, + ValidationInfo, field_validator, ) @@ -325,7 +325,7 @@ class UserModel(BaseModel): # you can select multiple fields, or use '*' to select all fields @field_validator('id', 'name') @classmethod - def check_alphanumeric(cls, v: str, info: FieldValidationInfo) -> str: + def check_alphanumeric(cls, v: str, info: ValidationInfo) -> str: if isinstance(v, str): # info.field_name is the name of the field being validated is_alphanumeric = v.replace(' ', '').isalnum() @@ -372,7 +372,7 @@ A few things to note on validators: * `@field_validator`s are "class methods", so the first argument value they receive is the `UserModel` class, not an instance of `UserModel`. We recommend you use the `@classmethod` decorator on them below the `@field_validator` decorator to get proper type checking. * the second argument is the field value to validate; it can be named as you please -* the third argument, if present, is an instance of `pydantic.FieldValidationInfo` +* the third argument, if present, is an instance of `pydantic.ValidationInfo` * validators should either return the parsed value or raise a `ValueError` or `AssertionError` (``assert`` statements may be used). * A single validator can be applied to multiple fields by passing it multiple field names. * A single validator can also be called on *all* fields by passing the special value `'*'`. @@ -382,8 +382,8 @@ A few things to note on validators: Python with the [`-O` optimization flag](https://docs.python.org/3/using/cmdline.html#cmdoption-o) disables `assert` statements, and **validators will stop working**. -If you want to access values from another field inside a `@field_validator`, this may be possible using `FieldValidationInfo.data`, which is a dict of field name to field value. -Validation is done in the order fields are defined, so you have to be careful when using `FieldValidationInfo.data` to not access a field that has not yet been validated/populated — in the code above, for example, you would not be able to access `info.data['id']` from within `name_must_contain_space`. +If you want to access values from another field inside a `@field_validator`, this may be possible using `ValidationInfo.data`, which is a dict of field name to field value. +Validation is done in the order fields are defined, so you have to be careful when using `ValidationInfo.data` to not access a field that has not yet been validated/populated — in the code above, for example, you would not be able to access `info.data['id']` from within `name_must_contain_space`. However, in most cases where you want to perform validation using multiple field values, it is better to use `@model_validator` which is discussed in the section below. ## Model validators @@ -604,7 +604,7 @@ You can pass a context object to the validation methods which can be accessed fr argument to decorated validator functions: ```python -from pydantic import BaseModel, FieldValidationInfo, field_validator +from pydantic import BaseModel, ValidationInfo, field_validator class Model(BaseModel): @@ -612,7 +612,7 @@ class Model(BaseModel): @field_validator('text') @classmethod - def remove_stopwords(cls, v: str, info: FieldValidationInfo): + def remove_stopwords(cls, v: str, info: ValidationInfo): context = info.context if context: stopwords = context.get('stopwords', set()) @@ -638,8 +638,8 @@ from typing import Any, Dict, List from pydantic import ( BaseModel, - FieldValidationInfo, ValidationError, + ValidationInfo, field_validator, ) @@ -660,7 +660,7 @@ class Model(BaseModel): @field_validator('choice') @classmethod - def validate_choice(cls, v: str, info: FieldValidationInfo): + def validate_choice(cls, v: str, info: ValidationInfo): allowed_choices = info.context.get('allowed_choices') if allowed_choices and v not in allowed_choices: raise ValueError(f'choice must be one of {allowed_choices}') @@ -702,7 +702,7 @@ from contextlib import contextmanager from contextvars import ContextVar from typing import Any, Dict, Iterator -from pydantic import BaseModel, FieldValidationInfo, field_validator +from pydantic import BaseModel, ValidationInfo, field_validator _init_context_var = ContextVar('_init_context_var', default=None) @@ -728,9 +728,7 @@ class Model(BaseModel): @field_validator('my_number') @classmethod - def multiply_with_context( - cls, value: int, info: FieldValidationInfo - ) -> int: + def multiply_with_context(cls, value: int, info: ValidationInfo) -> int: if info.context: multiplier = info.context.get('multiplier', 1) value = value * multiplier diff --git a/pdm.lock b/pdm.lock index 1cc6e576a5..5604843943 100644 --- a/pdm.lock +++ b/pdm.lock @@ -6,7 +6,7 @@ groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "t cross_platform = true static_urls = false lock_version = "4.3" -content_hash = "sha256:78bf42bb65b252ed3899213fec08411730335eef7b67e5d6cbc66cd284daee34" +content_hash = "sha256:2f4bdc1fb8542b8abb90350685fbde0b5857a2d2debff2b7a0cdc935a09f09f2" [[package]] name = "annotated-types" @@ -1070,119 +1070,119 @@ files = [ [[package]] name = "pydantic-core" -version = "2.9.0" +version = "2.10.0" requires_python = ">=3.7" summary = "" dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.9.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:ae2a40cbd62e61b5e9001003dba5f563b090f5f1c3a0573367213f83b3edf508"}, - {file = "pydantic_core-2.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e3a862b99717695e7c3356bbfb0446abc3a76f0886fa8a7383c1be30025df3"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90227628c4baa0cc67752ca8c0c13adaa811304bfd82cdc43a2a202bfcc93c8b"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a90406916a3f9e1c12c0b3d9164a74f32b563fb91dfae1bda54564d902e0a143"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96f4de28cc4e1d7fb96b957a77067afd63a7ff92258124f3a35c0d7b392be410"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79033b1864d96493b0e2bf5611e783e2e95ada9c13e6e3c6ac39a1653e4023a2"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee4569fd9a4a28888020e85b1ad945ea91db38b32d29ee0896ed094625e95afd"}, - {file = "pydantic_core-2.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:542cef8d285f5c3d41dd0f738a69168d0301c65af250fc838927936bbdb1552c"}, - {file = "pydantic_core-2.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:69c94a747f37665859f376cfef93cf8bad994253f358a918aaa1eec450a70d38"}, - {file = "pydantic_core-2.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b6eda52a5675945ec1784bce90be87da22b7c43cee6d732238503354295cdc3d"}, - {file = "pydantic_core-2.9.0-cp310-none-win32.whl", hash = "sha256:eaf387cdd8eb3fb0a4b1ffbbc49fca83ca6c32f12b2fd189f06da4c0ec6f613b"}, - {file = "pydantic_core-2.9.0-cp310-none-win_amd64.whl", hash = "sha256:380c7563f26b3afec415e2bb25e2c4bc3c7abc23c1c947722f3a2bade29d4e1b"}, - {file = "pydantic_core-2.9.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:02452ec7536a687b7b0847bbe06f251de331678dfd8915d61c2956b169f3c3f2"}, - {file = "pydantic_core-2.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ebe477f75502145f91e0632f2fd8501cd121a32b89dd19f76f4a9b5c6dce0e1"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d6966bcbffe476ac84c37409ca7bcd9447a4042eef9b8651b4fa2a5095ec610"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d84bd4230f0cd334071443f117e629ee762767c1004a56f4783003ce6a87660"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:091e2295a9b1049be97b3d835630c8bd79a7085bc6884497bccc2614527a3deb"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:623f4b0eb2f9ee4b2e9ceb3c7cc5c5db877982d2b032c7493abb9daf62a8eeee"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fdc8ac8b73d672bbce52fb6394dc53b84dc174499742328da8f1cc623112f3e"}, - {file = "pydantic_core-2.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32b9de267634078b069b637d06f5b1e6e1b1ff1554ccf8ff0359750966c91118"}, - {file = "pydantic_core-2.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ba53bc51d759e52cbeadcd06dab608a705583a5034b5a764a11ab0dacfaccbf"}, - {file = "pydantic_core-2.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:158c570388215c3b1a138dda4fec31e090b3be89ab7bcdf6cbe205333888ccbd"}, - {file = "pydantic_core-2.9.0-cp311-none-win32.whl", hash = "sha256:42f6b7b84108de79191538b2e33dcb4fb68dd2ac751c7f6af6f4b2b8cd4df3b3"}, - {file = "pydantic_core-2.9.0-cp311-none-win_amd64.whl", hash = "sha256:ae212959a0ec235e5bad1cda9973490db21d52f8af9dc74cde4ba02543ab09b7"}, - {file = "pydantic_core-2.9.0-cp311-none-win_arm64.whl", hash = "sha256:1787a7cca418cb1c767cba6ef4b81ef4c87c488d439229059b687a219efe9383"}, - {file = "pydantic_core-2.9.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:a6d73f1529a49f1851877c9dc5cd5ef00eec0d6b22c6cb71545ebc4167e7f7f3"}, - {file = "pydantic_core-2.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf54e5fb167e32e53348d915e09d059e063b40a3c49a77cfa7a98ae62277b172"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96990d693825efcffc78bca28f2cda98ebb64fb3dcf7e525a8abcdc1ab4e06fe"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a9f56ac2218e1c316a2636c75e580ecc5c995de34aa794e5633e105bf875f0a"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:036dd39ac0c613469045f5fa4c90ca9038fe905316aa7b02961a5be99aea060e"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59a7f18f4b7ae71cfde914cd221900ac0c48f1b3dae442d75b95e4e3013dd3e"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d30e464b5cbed5907a9b5c05513740353ab40b99efc0a2ffbe67ad841ecb31b"}, - {file = "pydantic_core-2.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:190d3753fc6cec6fb666beccd5c9b1c3434c86d02278f7b551d144999e2a6793"}, - {file = "pydantic_core-2.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9c40fcc3e3a98c6b30ba646315c0005b02ec7c6b5c7007858b6606cfaa37aff"}, - {file = "pydantic_core-2.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:85496c0df1102d9a18ed6a2149e36e954f57062c7982a54f987fa881a7c788d9"}, - {file = "pydantic_core-2.9.0-cp312-none-win32.whl", hash = "sha256:21e119619ea10a7600120a1a56e595b7df2d2d0c40f3c468aac964bfcf248d47"}, - {file = "pydantic_core-2.9.0-cp312-none-win_amd64.whl", hash = "sha256:5e652d24db195ee6ded9776f14edd75160b52849a077eab818d095bfe07089ee"}, - {file = "pydantic_core-2.9.0-cp312-none-win_arm64.whl", hash = "sha256:17cec7ca295ceabc2ea707334c6f1bba9eb9d4b90c60b88742adc424e2e6bcdf"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:c5dca9cb65eb68cdf0138942e87bb22d840ac7cd6e0dd55b730765692ebc11b1"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:2d6ac059a1df33cdaab750c00f7eccbb5ab88c20a772c3829d2046fdc2448fa4"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d9891a83971a347207c60a46d000ddbc8788ae9669f58941bf5d7aff5cf692"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9cde5f19ce949cacecbb766f6037438aeee0c0f2bae656370fc98f20ec4c9731"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f172a5dbc13841021e3376cdfd64d45d348635ccf2146b6d065ae9def292f82f"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e18f2d5a9ff628c8ee391f1352908c93125e1fc972a303a4d25726ea01266d0d"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f0d32373601e016c8c2d2c3b6382c8f7cfe91f1f43d695cdb12970fdc2e766f"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b107b2b7287080fc6954e8587016c935cc66df5bcda2aebc0b2f01e6591cd571"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:09395cd083f4d48cb73ed7fa1a6d6009595c5414fcd647c4d5b74b505650ef9e"}, - {file = "pydantic_core-2.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da1288267185e4d4cbdb2320e17a5206d1feee83b464156664d436b3605a16f5"}, - {file = "pydantic_core-2.9.0-cp37-none-win32.whl", hash = "sha256:6fc70f08f3c43baf0b74dc5579f71aa5bd3aed1e4d05862b6db2b457708bf53e"}, - {file = "pydantic_core-2.9.0-cp37-none-win_amd64.whl", hash = "sha256:52902bd4a766e0c383ee30a4bb645436c297b3e2927fb883144058fa89a10d36"}, - {file = "pydantic_core-2.9.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:edc960a54a85a6cf49227c57cd431f03c30f477fa985dfd896d110770b3fe516"}, - {file = "pydantic_core-2.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae53063e6291395e7e5524de979d20c27452a37bcd59ddcff55cd7dd9337d3e"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09073a93b608d452473b800bdb6c1bf040ebfd46ebef11a2661907d092862ba3"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:360a7154f38ff3d58fcb79715040ffb5d4100747311502089729f8f8f4259a83"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd05a7fe37bb409db89d658f16a9243a34aa63a05613fc0cabff42cd5664ea5"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11fa68f33ce9c0e2ee519e8a494a13f9c6ff21d7c590b25817fd50c5fff656fb"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0fe00b62d4ef6258e7dbaa7e57d2c2ffcaba0ef62892e8b61891bb3cd5459f"}, - {file = "pydantic_core-2.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e19ea5b407b84ab0e99ad1a33909d10c14f337898077a515d4b861776e339a5"}, - {file = "pydantic_core-2.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:90dcd94f8b2a4a211b922542f2c9a257a7d658e5dd1a38b4daefb14824ad307f"}, - {file = "pydantic_core-2.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:896980bb09b52bb4cfb594783adf6e9e02603151c4882295c79f2c24e6c00efb"}, - {file = "pydantic_core-2.9.0-cp38-none-win32.whl", hash = "sha256:a4630f1f88e4488adec3b3bab172324f6c2704d020836304697cc775982d0eef"}, - {file = "pydantic_core-2.9.0-cp38-none-win_amd64.whl", hash = "sha256:97f02ad9d5a016dd6d068f2ce2fd53bdf357f250b48d457b9be8511fb3e158e0"}, - {file = "pydantic_core-2.9.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:5ceec3c88c15de6280c228ab27ee3bc4df93f5e76b4b5ba1cc8097cfb152457e"}, - {file = "pydantic_core-2.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7a0b9aa61c05d62af76ec8f87797d3c56268df970f72a6f4a7130e2931ddb1f"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9bd3600dcbeebaed925818e9bfe98132622c650d6f97dee1ac036eabaa1917"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2564650ebe5015b3c99dbf5540c9d6789aaa725545401d2003eb8acecff45a9"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b8f444979d438259097e8695a9f95f0320f3c7fe6d187eeda8f4d2db601ce1f"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dda5f119870b117134cef060c50fe637106ba7fa4a569d1ba4ba08322d6e86ac"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a371aa3cdf7af31b40a5091ecfd2ef614632a1c4fa75d76dbe9ccf63d03ca5c"}, - {file = "pydantic_core-2.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11d73fe73152364ee1a5b062f58311544c38c30666b0179ef8470e6a98b20add"}, - {file = "pydantic_core-2.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c3403b3b1b75cce67a9e5b5c0e0361fbeeede2bc4e0660cc897885a0bdff13a"}, - {file = "pydantic_core-2.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8036236645e155cd853c45fe4931a08728a11efdfee04b046786044aa0f01d4a"}, - {file = "pydantic_core-2.9.0-cp39-none-win32.whl", hash = "sha256:4384d234417a754ebede9ff400dcfc021c78c781d96aa1e4f0e691e9f89d9a55"}, - {file = "pydantic_core-2.9.0-cp39-none-win_amd64.whl", hash = "sha256:60ec60fb42e43718ee6255f6fbcd032163d6771afc47c91a33381b5ce73e1b22"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:880b15af12ff845536cae84ab939bc246097716ce6b708a9fc0f71a1873b7cc1"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6cbb7a410cc9e153fafe56d8ba570e711b3a651648525dc416402d2fefd790a4"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a191dab33b5575d79b98ddbf44dc77214ce27603c99c2a6268cc259b0f5c4fbb"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d731bd93d39b7126f7b94ff602507ebd70c03c012610f02cd10d95918a41fc9c"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab99b277577ae8029e074699dfd65e60f6abd82edc20a89a3770859217e7dbda"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e6cc0d88aab12db83e6564e88871af68a219f0df650f4bda086a6bdaa1d19577"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:15fa39220ec5ca2dea38c6664ec55f0b67dff16340006f4162b5808ba9ed66fb"}, - {file = "pydantic_core-2.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d980715f7b37e9d7b93ba76473a7dcc5fe52cd39c4e1f57f6d57e88a5bfe2f50"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8e00141c00c3aa544eefb3955e9758b5892da2673521275b433aa95c385a1442"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:527f5d4249e563c376d03d75a21f9f2047c7e43ef3891c2e312cd8e890054fbc"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274d3057fd649583cc7192595e0a2c270b12becf76a9ed34f96ccdd02888e3b0"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bd4194af8e337a8103f05481badb97dc386f73e970d3a18c7fd1e88a71d3213"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:937818ddb644fb1302b1ad5062ec241b46c9a0465b07207aace69ce8b52511fa"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:735d888cbca9d382f409b796f51a4c4bd9cabadf312a129a04e59faa13829add"}, - {file = "pydantic_core-2.9.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:218459ed47e43a5721460dc2fcff2a484b5592b5249646bdcfaec0beddfd1d8a"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:17da825a7d882249a5be3bf1aa8776ca7703f6f92a23b8d354800e1b30b98982"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:317496d7048f03a5b21d73cd65838ee77de8d9d4c0fdaaf5df8dcccfe4430e30"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9e56e0d182274274ad06b7f8d96dc07ed14c39d6cc63fb4e43f9d0b57921a22"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6b0eeea78adf5890666ac48239f800c2f82a77cc44029211ee6bb825271db3"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d5b392fa509e2dc0dd4b231fb33e37bb86d20b4a39695ae383076e4ee61d0d8"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:17e0fac3ad76aa95c281aaa8c01bdf985c22aa66aa79f82464fbe705b308905d"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f8551d9b3c24df9b9878ae032de38b7c8accb1b1bea411e958a4964b9cdce97d"}, - {file = "pydantic_core-2.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0b5ebba7d71585173f6abaf1ef0313733a05ffbb75977d4d5cf65cbd1ad3ab7e"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:836e42758e1a208a5ad5779043b577a34381ff9a693082d6e9d66f4dd6b77362"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a8ce1073d69e80f0942760d59ae3d3f8737f974a3040ec052524d3923c07b31b"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0da92988b57723298afecdaaf8fb27a3ca7385693e039f2b09144ade424d33"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a5bdeacffc2c94c0dde3337a8e269ce17db6b6120d9b25b840082f326a0ba70"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:571685d75feadb24a5186145b6caa8a41900be48a7b2f15995737dc5f66a7bcb"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:194435b3e1849f8df122e61c48864a9c4b58d99f46aa7ef6f747a48d317b6017"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3ff26c8af620d3a008d4988956758b4936436210b5a0334485190a7cdf45fefd"}, - {file = "pydantic_core-2.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5c10df3f827c046594ccb89447aee28e0768aa5db1e20274640864a2887ebffc"}, - {file = "pydantic_core-2.9.0.tar.gz", hash = "sha256:3afee35fceaeea03e91714adbe5ba0ac1b1b949702b1af283bad175f55665c66"}, + {file = "pydantic_core-2.10.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:daea90360d99ad06a3f686b3e628222ac3aa953b1982f13be5b69b2648c5e6bb"}, + {file = "pydantic_core-2.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f76cb8d68d87fd05e56aba392c841d98eeb3ad378bcf5331b42bac7afee0d66"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e816d042f80dd630aaedbc75c21084da9e1d7ea5918619b8089c7edaedd57e8"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7192492b09c1e4ad103e5cb98eb397f9b61a9037fce03e94cafe3238404dbe0f"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:312831c5bf50d9d432c11baf9bbd8d8961740608ccbc66fb1290d532aff21b18"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:873db84afcbcf3f1ed0040ed9c5534bc1af5d647d13c04be12f3568421f5dd3e"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aa8bdc2d78afadd191148726f094be81d5e4b76011f8fa9300f317e06a1b732"}, + {file = "pydantic_core-2.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7cbf77664099345a25932ebe25d7bf9a330fc29acd9a909e8751ac0c42097fb3"}, + {file = "pydantic_core-2.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a537e87ca600e59e532fbc770a60f9f3a5ebcff9bae8c60aceeec5beb326e1b8"}, + {file = "pydantic_core-2.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed5f8d6cb834c80fb813d233f9bfb60d3453b7450c80c0814b8e78c23d1ea8bf"}, + {file = "pydantic_core-2.10.0-cp310-none-win32.whl", hash = "sha256:0e210107faf47d5965fcebc294c41891573adab36e5cf70731c57d0068fc7c5c"}, + {file = "pydantic_core-2.10.0-cp310-none-win_amd64.whl", hash = "sha256:9527cf9c25fd655617620c8d6cb43216c0ce5779871ab7f83175421267b85199"}, + {file = "pydantic_core-2.10.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:ab2d56dfa13244164f0ba8125d8315c799fa0150459b88fc42ed5c1e3c04d47a"}, + {file = "pydantic_core-2.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1e79893a20207ff671f13f5562c1f0aaece030e6e30252683f536286ba89864"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030ba2f59e78c8732445d8c9f093579674f2b5b93b3960945face14ec2e82682"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:705fad71297dfedc5c9e3c935702864aa0cc7812be11ac544f152677ba6ea430"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394a8ce4a7495af8dbf33038daf57a6170be15f8d1d92a7b63c6f2211527d950"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19c7aa3c0ff08ddc91597d8af08f8c4de59b27fe752b3bd1db9a67f6f08c4020"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb204346d3eda4e0c63cbeeec6398a52682ac51f9cf7379a13505863e47d3186"}, + {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1fefe63baa04f1d9dd5b4564b1e73d133e1c745589933d7ef9718235915cc81"}, + {file = "pydantic_core-2.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fa4bd88165d860111e860e8b43efd97afd137a9165cf24eb3cfb2371f57452bf"}, + {file = "pydantic_core-2.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e21ab9c49cc58282c228ff89fb4a5e4b447233ccd53acb7f333d1cde58df37b"}, + {file = "pydantic_core-2.10.0-cp311-none-win32.whl", hash = "sha256:2a6f28e2b2a5cef3b52b5ac6c6d64fe810ca51ec57081554f447c818778eea09"}, + {file = "pydantic_core-2.10.0-cp311-none-win_amd64.whl", hash = "sha256:f94539aa4265ab5528d8c3dc4505a19369083c29d0713b8ed536f93b9bc1e94f"}, + {file = "pydantic_core-2.10.0-cp311-none-win_arm64.whl", hash = "sha256:2352f7cb8ef0cd21fbc582abe2a14105d7e8400f97a551ca2e3b05dee77525d2"}, + {file = "pydantic_core-2.10.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:c2a126c7271a9421005a0f57cf71294ad49c375e4d0a9198b93665796f49e7f7"}, + {file = "pydantic_core-2.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7440933341f655a64456065211cf7657c3cf3524d5b0b02f5d9b63ef5a7e0d49"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85d8225cd08aacb8a2843cf0a0a72f1c403c6ac6f18d4cfeecabe050f80c9ea3"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:573e89b3da5908f564ae54b6284e20b490158681e91e1776a59dfda17ec0a6a8"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0061965942489e6da23f0399b1136fd10eff0a4f0cefae13369eba1776e22a6"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:725f0276402773a6b61b6f67bf9562f37ba08a8bfebdfb9990eea786ed5711b2"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25cacd12689b1a357ae6212c7f5980ebf487720db5bbf1bb5d91085226b6a962"}, + {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e70c6c882ab101a72010c8f91e87db211fa2aaf6aa51acc7160fe5649630ed75"}, + {file = "pydantic_core-2.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e079540fd4c45c23de4465cafb20cddcd8befe3b5f46505a2eb28e49b9d13ee2"}, + {file = "pydantic_core-2.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:98474284adb71c8738e5efb71ccb1382d8d66f042ad0875018c78bcb38ac0f47"}, + {file = "pydantic_core-2.10.0-cp312-none-win32.whl", hash = "sha256:ab1fa046ef9058ceef941b576c5e7711bab3d99be00a304fb4726cf4b94e05ff"}, + {file = "pydantic_core-2.10.0-cp312-none-win_amd64.whl", hash = "sha256:b4df023610af081d6da85328411fed7aacf19e939fe955bb31f29212f8dcf306"}, + {file = "pydantic_core-2.10.0-cp312-none-win_arm64.whl", hash = "sha256:f1a70f99d1a7270d4f321a8824e87d5b88acd64c2af6049915b7fd8215437e04"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:f622778eb180cf7eba25e65d2fe37a57a0eadd8403df4c44606b56d204f686de"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:fb513fc74bdf5f649e6e855fc87ed9b81ee8b0be96717190f9e00683244f0616"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82c3f0752547f928e4fcfb00151d6deb9124be7d35e012c567429fe93ec71b71"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:100bbd622433d9d7ca8ee4fa63dfae90f9f38358558955173aed6ed56c573db8"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f919d17581fdf6e71ff3d3fe4b02ed32aaa0429e0b4346798de7a1361e098ef"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f32df1d8d383e1b729674ad1053d8f43f7ed79848496d3cb6ca81a906318317b"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab4d279c480e83c516e4e0b7b1f882f168f614d9c62e18ab779edef0cd13aaa9"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c394e9ed6f9e6f4af3618c34bc15f2af4237f7d1989b7f45588f8e855bc10e08"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bc0a33779fded534ff0b5d8ef766a1c94d3e740877ea8adab65cbf1878ba03b4"}, + {file = "pydantic_core-2.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c1041d8fcd313c68b77dec6a16bf1d690008270b50eec11e96d89e1b4ba756b1"}, + {file = "pydantic_core-2.10.0-cp37-none-win32.whl", hash = "sha256:68992f78507e95ed63ca87b8b177785d9806cde34ca3a9f98382188dd11d8720"}, + {file = "pydantic_core-2.10.0-cp37-none-win_amd64.whl", hash = "sha256:aa45f0846773cb142252ccef66b096d917bb76c6ef9da1aa747e6b44aa318192"}, + {file = "pydantic_core-2.10.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:a04054b91afc41282a0a7426147654849136b37a41da86412d4ff5ba51b9cd2f"}, + {file = "pydantic_core-2.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c1bfa2ca352bf43d34b7099f8ed675deb88113bd36c76880f4ca18fc0d3af50"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba288fa675b2951e7898ebfdd8defa0e958e514d4d1cc7a5f6a8d627378c0c47"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbba90179672707ab69ad19ef7d3c3f0a8e2f0a0579f0eb79649ffcdacf476d0"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c6915a9b3dd16e016dba7e76070e667eca50530f957daa5b78c73abbf281b25"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e72c1e06a20c10b9c5f7a3fe09ec46e0e208c65a69d2efb92a3e1b64443e6c3"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b50848d1a614093c05c97d0fdf841ef547d8c087fbd06f6eafe8ef1d836d6c1"}, + {file = "pydantic_core-2.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ad7b5f4352f3dfcc481b008bce3b3931a485a93112deaa0a25bee2817d3f7b98"}, + {file = "pydantic_core-2.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:66c0169457733a4dfe72be51dd359414eddd0738b15dda07827f18a10e9f6ab7"}, + {file = "pydantic_core-2.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e24d92d3a60d6eb19a1bd0f1f259369f478e0f34412a33e794da6cdaa36218be"}, + {file = "pydantic_core-2.10.0-cp38-none-win32.whl", hash = "sha256:30c5df611afc5a9f2ad48babe2192f9cf0d12ed6c0dd5eb57b3538491c113737"}, + {file = "pydantic_core-2.10.0-cp38-none-win_amd64.whl", hash = "sha256:d72a561d7c0738ae5d05a709c739b2953d05e18151539750ca9622f3438de041"}, + {file = "pydantic_core-2.10.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7cecd7669b1ebee8ae90f5aa7d459770b6e79db7b95983aacc5b7392a050b9ab"}, + {file = "pydantic_core-2.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:923000ea46def1bdded511b5792ec19866909797a05dc8f75342c6a9cacb2d66"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:729a2fc4bc1564d164258eaf138ab4c03baa2080a5e3f91a9b3cb2d758248b8f"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6930eaf3aa2ba660ed3f64206902a534f454f9954e5de06354e20d890bebbd8a"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0b17e8d08a1c94efb91d8d389ec76a32fc3f85ba06626b5ef0c2d6bffcbe066"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c61755149ba534123ae08e6aa814aa34f47c6ba45a622ea98ddd7860b5312767"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b126893f53c789ad2253c9288a59362171a5bafbb865190c43d430dc805edb"}, + {file = "pydantic_core-2.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:776eee60ca8ca3de83add0fb95a0034ac965a12590bb22ec09b05c87870ba401"}, + {file = "pydantic_core-2.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1496f38e49c7960461002768c5f4c9ba9720fe259cd5c8b229cd0b3b0861844"}, + {file = "pydantic_core-2.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cd175beab2ac845a90d31bb4ea8e6c1e8be12efaf14b9918d0ab4828dd3c916b"}, + {file = "pydantic_core-2.10.0-cp39-none-win32.whl", hash = "sha256:391805e8a4ad731e729a22d8e14bad2d724915d28618be6c66dc7ccb421a13a0"}, + {file = "pydantic_core-2.10.0-cp39-none-win_amd64.whl", hash = "sha256:7e2360b86b21e2aab8d4f1ce2551e2b731bc30610b7cc9324ea7517af4375b08"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b40221d1490f2c6e488d2576773a574d42436b5aba1faed91f59a9feb82c384b"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f3b25201efe20d182f3bd6fe8d99685f4ed01cac67b79c017c9cf688b747263"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a45943bb14275e9681fd4abafbe3acae1e7dac7248bebf38ac5bde492e00f7"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5be7a29a6b25a186941e9e2b5f9281c05723628e1fdb244f429f4c1682ff49"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17460ffd8f8e49ca52711b4926fefe2b336d01b63dc27aee432a576c2147c8ce"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c1ab3701d660bd136a22e1ca95292bfed50245eb869adaee2e08f29d4dd5e360"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:09ac18617199704327d99c85893d697b8442c18b8c2db1ea636ba83313223541"}, + {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e3f69d48191103587950981cf47c936064c808b6c18f57e745ed130a305c73a6"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:792af9e4f78d6f1d0aabfb95162c5ed56b5369b25350eaa68b1495e8f675d4d9"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ecd28fb4c98c97836046d092029017bcc35e060ea547484aa1234b8a592de17"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a622a8abf656cc51960766fa4d194504e8a9f85ae48032f87fb42c79462c7b8"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52eb5c61de017bfee422f6aa9a3e76de5aa5a9189ba808bba63b9de67e55c4ca"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:69772dcdcf90b677d0d2ecedafe4c6a610572f1fad15912cde28a6f8eb5654fd"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:12470a4de172aaa1bbadb45744de4a9b0298fa8f974eb508314c3b5da0cb4aed"}, + {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f9f2c70257f03db712658d4138e2b892bdd7c71472783eaebc2813a47fd29ef3"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8a5323d6778931ab1b3b22bac05fb7c961786d3b04a6c84f7c0ffcc331b4b998"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f00e83aa9aebbfd4382695a5ed94e6282ac01455fbb1a37d99d2effa29df30f"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c871820c60fc863c7b3f660612af6ce5bb8f5f69d6364f208e29d2ca7992d154"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1bcb1b9b33573eeef218ffb3a2910c57fedc8831caf3c942e68a2222481d2cc"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d122a46c360c8069f7ac39c6f2c29cf99436baa48ba1e28ea5443336e9bbb838"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ffb2a3462bb7905c4d849b95f536ac1f3948e92f5e0fc7e65bd3f3b0d132cf4"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b5d4eec8aba25b163a4d9dcc6be8354bc8f939040bc15a6400cbd62ba0511a5f"}, + {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5cbfe4cd608cf6d032374961e4e07d0506acfaec7b1a69beade1d5f98dce00fd"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:02b3d546342e7f583bf58f4a4618c7e97f44426db2358789393537dd4e9a921d"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7820faf076216654ae54ad8a8443a296faaac9057a49ff404ce92ab85c9518a3"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f114130c44ae52b3bd2450dac8e1d3e1e92a92baecb24dbcdb6de2d2fc15bdb5"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f6f70680c15876c583a24bd476e49004327e87392be0282aedbc65773519ea8"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f230d70be54447e12fcd0f1c2319dac74341244fafd2350d5675aa194f6c3f4"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:96b3007451863b46e8138f8096ef31aea6f7721a9910843b0554ce4ae17024a2"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b196c4ace34be6c2953c6ec3906d1af88c418b93325d612d7f900ed30bf1e0ac"}, + {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5958b1af7acd7b4a629e9758ce54a31c1910695e85e0ef847ba3daa4f25a0a08"}, + {file = "pydantic_core-2.10.0.tar.gz", hash = "sha256:8fe66506700efdfc699c613ccc4974ac7d8fceed8c74983e55ec380504db2e05"}, ] [[package]] diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 9aa865cb80..1a53853393 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -3,7 +3,6 @@ import pydantic_core from pydantic_core.core_schema import ( FieldSerializationInfo, - FieldValidationInfo, SerializationInfo, SerializerFunctionWrapHandler, ValidationInfo, @@ -57,7 +56,6 @@ 'dataclasses', # functional validators 'ValidationInfo', - 'FieldValidationInfo', 'ValidatorFunctionWrapHandler', 'field_validator', 'model_validator', @@ -198,8 +196,11 @@ 'GenerateSchema', ] -# A mapping of {: } defining dynamic imports -_dynamic_imports = {'RootModel': '.root_model'} +# A mapping of {: (package, )} defining dynamic imports +_dynamic_imports: 'dict[str, tuple[str, str]]' = { + 'RootModel': (__package__, '.root_model'), + 'FieldValidationInfo': ('pydantic_core', '.core_schema'), +} if typing.TYPE_CHECKING: from .root_model import RootModel @@ -211,7 +212,9 @@ def __getattr__(attr_name: str) -> object: if dynamic_attr is None: return _getattr_migration(attr_name) + package, module_name = dynamic_attr + from importlib import import_module - module = import_module(_dynamic_imports[attr_name], package=__package__) + module = import_module(module_name, package=package) return getattr(module, attr_name) diff --git a/pydantic/_internal/_annotated_handlers.py b/pydantic/_internal/_annotated_handlers.py index 68f24cd4d3..7d11928f7d 100644 --- a/pydantic/_internal/_annotated_handlers.py +++ b/pydantic/_internal/_annotated_handlers.py @@ -109,6 +109,11 @@ def resolve_ref_schema(self, __maybe_ref_schema: core_schema.CoreSchema) -> core """ raise NotImplementedError + @property + def field_name(self) -> str | None: + """Get the name of the closest field to this validator.""" + raise NotImplementedError + def _get_types_namespace(self) -> dict[str, Any] | None: """Internal method used during type resolution for serializer annotations.""" raise NotImplementedError diff --git a/pydantic/_internal/_dataclasses.py b/pydantic/_internal/_dataclasses.py index 7efe35d888..01f7adcf6a 100644 --- a/pydantic/_internal/_dataclasses.py +++ b/pydantic/_internal/_dataclasses.py @@ -9,7 +9,14 @@ from inspect import Parameter, Signature, signature from typing import Any, Callable, ClassVar -from pydantic_core import ArgsKwargs, PydanticUndefined, SchemaSerializer, SchemaValidator, core_schema +from pydantic_core import ( + ArgsKwargs, + PydanticUndefined, + SchemaSerializer, + SchemaValidator, + core_schema, + validate_core_schema, +) from typing_extensions import TypeGuard from ..errors import PydanticUndefinedAnnotation @@ -162,7 +169,7 @@ def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) - # __pydantic_decorators__ and __pydantic_fields__ should already be set cls = typing.cast('type[PydanticDataclass]', cls) # debug(schema) - cls.__pydantic_core_schema__ = schema + cls.__pydantic_core_schema__ = schema = validate_core_schema(schema) cls.__pydantic_validator__ = validator = SchemaValidator(schema, core_config) cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) diff --git a/pydantic/_internal/_decorators_v1.py b/pydantic/_internal/_decorators_v1.py index 84730d3ecb..4f81e6d4a3 100644 --- a/pydantic/_internal/_decorators_v1.py +++ b/pydantic/_internal/_decorators_v1.py @@ -55,7 +55,7 @@ def can_be_keyword(param: Parameter) -> bool: return param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) -def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.FieldValidatorFunction: +def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithInfoValidatorFunction: """Wrap a V1 style field validator for V2 compatibility. Args: @@ -96,14 +96,14 @@ def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.Field # (v, **kwargs), (v, values, **kwargs), (v, *, values, **kwargs) or (v, *, values) val1 = cast(V1ValidatorWithValues, validator) - def wrapper1(value: Any, info: core_schema.FieldValidationInfo) -> Any: + def wrapper1(value: Any, info: core_schema.ValidationInfo) -> Any: return val1(value, values=info.data) return wrapper1 else: val2 = cast(V1OnlyValueValidator, validator) - def wrapper2(value: Any, _: core_schema.FieldValidationInfo) -> Any: + def wrapper2(value: Any, _: core_schema.ValidationInfo) -> Any: return val2(value) return wrapper2 diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index c68a3ea9db..6532ddfaae 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -264,6 +264,16 @@ def _add_custom_serialization_from_json_encoders( class GenerateSchema: """Generate core schema for a Pydantic model, dataclass and types like `str`, `datetime`, ... .""" + __slots__ = ( + '_config_wrapper_stack', + '_types_namespace', + '_typevars_map', + '_needs_apply_discriminated_union', + '_has_invalid_schema', + 'field_name_stack', + 'defs', + ) + def __init__( self, config_wrapper: ConfigWrapper, @@ -276,6 +286,7 @@ def __init__( self._typevars_map = typevars_map self._needs_apply_discriminated_union = False self._has_invalid_schema = False + self.field_name_stack = _FieldNameStack() self.defs = _Definitions() @classmethod @@ -292,6 +303,7 @@ def __from_parent( obj._typevars_map = typevars_map obj._needs_apply_discriminated_union = False obj._has_invalid_schema = False + obj.field_name_stack = _FieldNameStack() obj.defs = defs return obj @@ -581,7 +593,7 @@ def _generate_schema_from_property(self, obj: Any, source: Any) -> core_schema.C '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', PydanticDeprecatedSince20, ) - schema = core_schema.chain_schema([core_schema.general_plain_validator_function(v) for v in validators()]) + schema = core_schema.chain_schema([core_schema.with_info_plain_validator_function(v) for v in validators()]) else: if len(inspect.signature(get_schema).parameters) == 1: # (source) -> CoreSchema @@ -908,13 +920,14 @@ def set_discriminator(schema: CoreSchema) -> CoreSchema: schema = self._apply_discriminator_to_union(schema, field_info.discriminator) return schema - if field_info.discriminator is not None: - schema = self._apply_annotations(source_type, annotations, transform_inner_schema=set_discriminator) - else: - schema = self._apply_annotations( - source_type, - annotations, - ) + with self.field_name_stack.push(name): + if field_info.discriminator is not None: + schema = self._apply_annotations(source_type, annotations, transform_inner_schema=set_discriminator) + else: + schema = self._apply_annotations( + source_type, + annotations, + ) # This V1 compatibility shim should eventually be removed # push down any `each_item=True` validators @@ -1173,7 +1186,8 @@ def _generate_parameter_schema( field = FieldInfo.from_annotated_attribute(annotation, default) assert field.annotation is not None, 'field.annotation should not be None when generating a schema' source_type, annotations = field.annotation, field.metadata - schema = self._apply_annotations(source_type, annotations) + with self.field_name_stack.push(name): + schema = self._apply_annotations(source_type, annotations) if not field.is_required(): schema = wrap_default(field, schema) @@ -1769,28 +1783,25 @@ def _apply_model_serializers( _VALIDATOR_F_MATCH: Mapping[ - tuple[FieldValidatorModes, Literal['no-info', 'general', 'field']], - Callable[[Callable[..., Any], None, core_schema.CoreSchema], core_schema.CoreSchema], + tuple[FieldValidatorModes, Literal['no-info', 'with-info']], + Callable[[Callable[..., Any], core_schema.CoreSchema, str | None], core_schema.CoreSchema], ] = { - ('before', 'no-info'): lambda f, _, schema: core_schema.no_info_before_validator_function(f, schema), - ('after', 'no-info'): lambda f, _, schema: core_schema.no_info_after_validator_function(f, schema), + ('before', 'no-info'): lambda f, schema, _: core_schema.no_info_before_validator_function(f, schema), + ('after', 'no-info'): lambda f, schema, _: core_schema.no_info_after_validator_function(f, schema), ('plain', 'no-info'): lambda f, _1, _2: core_schema.no_info_plain_validator_function(f), - ('wrap', 'no-info'): lambda f, _, schema: core_schema.no_info_wrap_validator_function(f, schema), - ('before', 'general'): lambda f, _, schema: core_schema.general_before_validator_function(f, schema), - ('after', 'general'): lambda f, _, schema: core_schema.general_after_validator_function(f, schema), - ('plain', 'general'): lambda f, _1, _2: core_schema.general_plain_validator_function(f), - ('wrap', 'general'): lambda f, _, schema: core_schema.general_wrap_validator_function(f, schema), -} - - -_FIELD_VALIDATOR_F_MATCH: Mapping[ - tuple[FieldValidatorModes, Literal['no-info', 'general', 'field']], - Callable[[Callable[..., Any], str, core_schema.CoreSchema], core_schema.CoreSchema], -] = { - ('before', 'field'): core_schema.field_before_validator_function, - ('after', 'field'): core_schema.field_after_validator_function, - ('plain', 'field'): lambda f, field_name, _: core_schema.field_plain_validator_function(f, field_name), - ('wrap', 'field'): core_schema.field_wrap_validator_function, + ('wrap', 'no-info'): lambda f, schema, _: core_schema.no_info_wrap_validator_function(f, schema), + ('before', 'with-info'): lambda f, schema, field_name: core_schema.with_info_before_validator_function( + f, schema, field_name=field_name + ), + ('after', 'with-info'): lambda f, schema, field_name: core_schema.with_info_after_validator_function( + f, schema, field_name=field_name + ), + ('plain', 'with-info'): lambda f, _, field_name: core_schema.with_info_plain_validator_function( + f, field_name=field_name + ), + ('wrap', 'with-info'): lambda f, schema, field_name: core_schema.with_info_wrap_validator_function( + f, schema, field_name=field_name + ), } @@ -1813,18 +1824,9 @@ def apply_validators( """ for validator in validators: info_arg = inspect_validator(validator.func, validator.info.mode) - if not info_arg: - val_type: Literal['no-info', 'general', 'field'] = 'no-info' - elif isinstance(validator.info, (FieldValidatorDecoratorInfo, ValidatorDecoratorInfo)): - assert field_name is not None, 'field validators must be used within a model field' - val_type = 'field' - else: - val_type = 'general' + val_type = 'with-info' if info_arg else 'no-info' - if field_name is None or val_type != 'field': - schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, None, schema) - else: - schema = _FIELD_VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, field_name, schema) + schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema, field_name) return schema @@ -1872,18 +1874,18 @@ def apply_model_validators( info_arg = inspect_validator(validator.func, validator.info.mode) if validator.info.mode == 'wrap': if info_arg: - schema = core_schema.general_wrap_validator_function(function=validator.func, schema=schema) + schema = core_schema.with_info_wrap_validator_function(function=validator.func, schema=schema) else: schema = core_schema.no_info_wrap_validator_function(function=validator.func, schema=schema) elif validator.info.mode == 'before': if info_arg: - schema = core_schema.general_before_validator_function(function=validator.func, schema=schema) + schema = core_schema.with_info_before_validator_function(function=validator.func, schema=schema) else: schema = core_schema.no_info_before_validator_function(function=validator.func, schema=schema) else: assert validator.info.mode == 'after' if info_arg: - schema = core_schema.general_after_validator_function(function=validator.func, schema=schema) + schema = core_schema.with_info_after_validator_function(function=validator.func, schema=schema) else: schema = core_schema.no_info_after_validator_function(function=validator.func, schema=schema) if ref: @@ -2017,3 +2019,22 @@ def resolve_original_schema(schema: CoreSchema, definitions: dict[str, CoreSchem return schema['schema'] else: return schema + + +class _FieldNameStack: + __slots__ = ('_stack',) + + def __init__(self) -> None: + self._stack: list[str] = [] + + @contextmanager + def push(self, field_name: str) -> Iterator[None]: + self._stack.append(field_name) + yield + self._stack.pop() + + def get(self) -> str | None: + if self._stack: + return self._stack[-1] + else: + return None diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index fe95cacb0c..f8ae7255ab 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -9,7 +9,7 @@ from types import FunctionType from typing import Any, Callable, Generic, Mapping -from pydantic_core import PydanticUndefined, SchemaSerializer, SchemaValidator +from pydantic_core import PydanticUndefined, SchemaSerializer, SchemaValidator, validate_core_schema from typing_extensions import dataclass_transform, deprecated from ..errors import PydanticUndefinedAnnotation, PydanticUserError @@ -494,7 +494,7 @@ def complete_model_class( schema = apply_discriminators(simplify_schema_references(schema)) # debug(schema) - cls.__pydantic_core_schema__ = schema + cls.__pydantic_core_schema__ = schema = validate_core_schema(schema) cls.__pydantic_validator__ = SchemaValidator(schema, core_config) cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) cls.__pydantic_complete__ = True diff --git a/pydantic/_internal/_schema_generation_shared.py b/pydantic/_internal/_schema_generation_shared.py index 9a62671d01..1c619789c6 100644 --- a/pydantic/_internal/_schema_generation_shared.py +++ b/pydantic/_internal/_schema_generation_shared.py @@ -95,6 +95,10 @@ def _get_types_namespace(self) -> dict[str, Any] | None: def generate_schema(self, __source_type: Any) -> core_schema.CoreSchema: return self._generate_schema.generate_schema(__source_type) + @property + def field_name(self) -> str | None: + return self._generate_schema.field_name_stack.get() + def resolve_ref_schema(self, maybe_ref_schema: core_schema.CoreSchema) -> core_schema.CoreSchema: """Resolves reference in the core schema. diff --git a/pydantic/_internal/_validate_call.py b/pydantic/_internal/_validate_call.py index b749b062e7..cfdc95ff57 100644 --- a/pydantic/_internal/_validate_call.py +++ b/pydantic/_internal/_validate_call.py @@ -79,6 +79,7 @@ def __init__(self, function: Callable[..., Any], config: ConfigDict | None, vali schema = _discriminated_union.apply_discriminators(simplify_schema_references(schema)) self.__return_pydantic_core_schema__ = schema core_config = config_wrapper.core_config(self) + schema = pydantic_core.validate_core_schema(schema) validator = pydantic_core.SchemaValidator(schema, core_config) if inspect.iscoroutinefunction(self.raw_function): diff --git a/pydantic/color.py b/pydantic/color.py index 8a1d658d7c..1c8e2dc87f 100644 --- a/pydantic/color.py +++ b/pydantic/color.py @@ -233,7 +233,7 @@ def _alpha_float(self) -> float: def __get_pydantic_core_schema__( cls, source: Type[Any], handler: Callable[[Any], CoreSchema] ) -> core_schema.CoreSchema: - return core_schema.general_plain_validator_function( + return core_schema.with_info_plain_validator_function( cls._validate, serialization=core_schema.to_string_ser_schema() ) diff --git a/pydantic/functional_validators.py b/pydantic/functional_validators.py index 881c9b91ad..974ba12c48 100644 --- a/pydantic/functional_validators.py +++ b/pydantic/functional_validators.py @@ -6,7 +6,7 @@ import sys from functools import partialmethod from types import FunctionType -from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, overload +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 @@ -74,15 +74,17 @@ class Model(BaseModel): ``` ''' - func: core_schema.NoInfoValidatorFunction | core_schema.GeneralValidatorFunction + 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: - return core_schema.general_after_validator_function(self.func, schema=schema) # type: ignore + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_after_validator_function(func, schema=schema, field_name=handler.field_name) else: - return core_schema.no_info_after_validator_function(self.func, schema=schema) # type: ignore + 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) @@ -116,15 +118,17 @@ class Model(BaseModel): ``` """ - func: core_schema.NoInfoValidatorFunction | core_schema.GeneralValidatorFunction + 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: - return core_schema.general_before_validator_function(self.func, schema=schema) # type: ignore + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_before_validator_function(func, schema=schema, field_name=handler.field_name) else: - return core_schema.no_info_before_validator_function(self.func, schema=schema) # type: ignore + 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) @@ -152,14 +156,16 @@ class Model(BaseModel): ``` """ - func: core_schema.NoInfoValidatorFunction | core_schema.GeneralValidatorFunction + 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: - return core_schema.general_plain_validator_function(self.func) # type: ignore + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_plain_validator_function(func, field_name=handler.field_name) else: - return core_schema.no_info_plain_validator_function(self.func) # type: ignore + func = cast(core_schema.NoInfoValidatorFunction, self.func) + return core_schema.no_info_plain_validator_function(func) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) @@ -200,19 +206,17 @@ class Model(BaseModel): ``` """ - func: ( - core_schema.NoInfoWrapValidatorFunction - | core_schema.GeneralWrapValidatorFunction - | core_schema.FieldWrapValidatorFunction - ) + 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: - return core_schema.general_wrap_validator_function(self.func, schema=schema) # type: ignore + func = cast(core_schema.WithInfoWrapValidatorFunction, self.func) + return core_schema.with_info_wrap_validator_function(func, schema=schema, field_name=handler.field_name) else: - return core_schema.no_info_wrap_validator_function(self.func, schema=schema) # type: ignore + func = cast(core_schema.NoInfoWrapValidatorFunction, self.func) + return core_schema.no_info_wrap_validator_function(func, schema=schema) if TYPE_CHECKING: @@ -222,7 +226,7 @@ def __call__(self, __cls: Any, __value: Any) -> Any: ... class _V2ValidatorClsMethod(Protocol): - def __call__(self, __cls: Any, __input_value: Any, __info: _core_schema.FieldValidationInfo) -> Any: + def __call__(self, __cls: Any, __input_value: Any, __info: _core_schema.ValidationInfo) -> Any: ... class _V2WrapValidatorClsMethod(Protocol): @@ -237,15 +241,14 @@ def __call__( _V2Validator = Union[ _V2ValidatorClsMethod, - _core_schema.FieldValidatorFunction, + _core_schema.WithInfoValidatorFunction, _OnlyValueValidatorClsMethod, _core_schema.NoInfoValidatorFunction, ] _V2WrapValidator = Union[ _V2WrapValidatorClsMethod, - _core_schema.GeneralWrapValidatorFunction, - _core_schema.FieldWrapValidatorFunction, + _core_schema.WithInfoWrapValidatorFunction, ] _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] diff --git a/pydantic/networks.py b/pydantic/networks.py index 05f439eced..11341e76e2 100644 --- a/pydantic/networks.py +++ b/pydantic/networks.py @@ -193,11 +193,11 @@ class Model(BaseModel): @classmethod def __get_pydantic_core_schema__( cls, - source: type[Any], - handler: _annotated_handlers.GetCoreSchemaHandler, + _source: type[Any], + _handler: _annotated_handlers.GetCoreSchemaHandler, ) -> core_schema.CoreSchema: import_email_validator() - return core_schema.general_after_validator_function(cls._validate, core_schema.str_schema()) + return core_schema.no_info_after_validator_function(cls._validate, core_schema.str_schema()) @classmethod def __get_pydantic_json_schema__( @@ -208,7 +208,7 @@ def __get_pydantic_json_schema__( return field_schema @classmethod - def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> str: + def _validate(cls, __input_value: str) -> str: return validate_email(__input_value)[1] @@ -268,11 +268,11 @@ def __get_pydantic_json_schema__( @classmethod def __get_pydantic_core_schema__( cls, - source: type[Any], - handler: _annotated_handlers.GetCoreSchemaHandler, + _source: type[Any], + _handler: _annotated_handlers.GetCoreSchemaHandler, ) -> core_schema.CoreSchema: import_email_validator() - return core_schema.general_after_validator_function( + return core_schema.no_info_after_validator_function( cls._validate, core_schema.union_schema( [core_schema.is_instance_schema(cls), core_schema.str_schema()], @@ -283,7 +283,7 @@ def __get_pydantic_core_schema__( ) @classmethod - def _validate(cls, __input_value: NameEmail | str, _: core_schema.ValidationInfo) -> NameEmail: + def _validate(cls, __input_value: NameEmail | str) -> NameEmail: if isinstance(__input_value, cls): return __input_value else: @@ -322,15 +322,15 @@ def __get_pydantic_json_schema__( @classmethod def __get_pydantic_core_schema__( cls, - source: type[Any], - handler: _annotated_handlers.GetCoreSchemaHandler, + _source: type[Any], + _handler: _annotated_handlers.GetCoreSchemaHandler, ) -> core_schema.CoreSchema: - return core_schema.general_plain_validator_function( + return core_schema.no_info_plain_validator_function( cls._validate, serialization=core_schema.to_string_ser_schema() ) @classmethod - def _validate(cls, __input_value: Any, _: core_schema.ValidationInfo) -> IPv4Address | IPv6Address: + def _validate(cls, __input_value: Any) -> IPv4Address | IPv6Address: return cls(__input_value) # type: ignore[return-value] @@ -362,15 +362,15 @@ def __get_pydantic_json_schema__( @classmethod def __get_pydantic_core_schema__( cls, - source: type[Any], - handler: _annotated_handlers.GetCoreSchemaHandler, + _source: type[Any], + _handler: _annotated_handlers.GetCoreSchemaHandler, ) -> core_schema.CoreSchema: - return core_schema.general_plain_validator_function( + return core_schema.no_info_plain_validator_function( cls._validate, serialization=core_schema.to_string_ser_schema() ) @classmethod - def _validate(cls, __input_value: NetworkType, _: core_schema.ValidationInfo) -> IPv4Interface | IPv6Interface: + def _validate(cls, __input_value: NetworkType) -> IPv4Interface | IPv6Interface: return cls(__input_value) # type: ignore[return-value] @@ -404,15 +404,15 @@ def __get_pydantic_json_schema__( @classmethod def __get_pydantic_core_schema__( cls, - source: type[Any], - handler: _annotated_handlers.GetCoreSchemaHandler, + _source: type[Any], + _handler: _annotated_handlers.GetCoreSchemaHandler, ) -> core_schema.CoreSchema: - return core_schema.general_plain_validator_function( + return core_schema.no_info_plain_validator_function( cls._validate, serialization=core_schema.to_string_ser_schema() ) @classmethod - def _validate(cls, __input_value: NetworkType, _: core_schema.ValidationInfo) -> IPv4Network | IPv6Network: + def _validate(cls, __input_value: NetworkType) -> IPv4Network | IPv6Network: return cls(__input_value) # type: ignore[return-value] diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py index 85ee6d028d..dff206cdcb 100644 --- a/pydantic/type_adapter.py +++ b/pydantic/type_adapter.py @@ -5,7 +5,7 @@ from dataclasses import is_dataclass from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Set, TypeVar, Union, overload -from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator, Some +from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator, Some, validate_core_schema from typing_extensions import Literal, is_typeddict from pydantic.errors import PydanticUserError @@ -168,6 +168,8 @@ def __init__(self, type: Any, *, config: ConfigDict | None = None, _parent_depth core_schema = _discriminated_union.apply_discriminators(_core_utils.simplify_schema_references(core_schema)) + core_schema = validate_core_schema(core_schema) + core_config = config_wrapper.core_config(None) validator: SchemaValidator try: diff --git a/pydantic/types.py b/pydantic/types.py index 394c73a070..0630f09c31 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -657,12 +657,12 @@ def __get_pydantic_core_schema__( self, source: Any, handler: _annotated_handlers.GetCoreSchemaHandler ) -> core_schema.CoreSchema: function_lookup = { - 'file': cast(core_schema.GeneralValidatorFunction, self.validate_file), - 'dir': cast(core_schema.GeneralValidatorFunction, self.validate_directory), - 'new': cast(core_schema.GeneralValidatorFunction, self.validate_new), + 'file': cast(core_schema.WithInfoValidatorFunction, self.validate_file), + 'dir': cast(core_schema.WithInfoValidatorFunction, self.validate_directory), + 'new': cast(core_schema.WithInfoValidatorFunction, self.validate_new), } - return core_schema.general_after_validator_function( + return core_schema.with_info_after_validator_function( function_lookup[self.path_type], handler(source), ) @@ -1001,7 +1001,7 @@ def __init__(self, card_number: str): def __get_pydantic_core_schema__( cls, source: type[Any], handler: _annotated_handlers.GetCoreSchemaHandler ) -> core_schema.CoreSchema: - return core_schema.general_after_validator_function( + return core_schema.with_info_after_validator_function( cls.validate, core_schema.str_schema( min_length=cls.min_length, max_length=cls.max_length, strip_whitespace=cls.strip_whitespace @@ -1143,7 +1143,7 @@ class MyModel(BaseModel): def __get_pydantic_core_schema__( cls, source: type[Any], handler: _annotated_handlers.GetCoreSchemaHandler ) -> core_schema.CoreSchema: - return core_schema.general_plain_validator_function(cls._validate) + return core_schema.with_info_plain_validator_function(cls._validate) @classmethod def _validate(cls, __input_value: Any, _: core_schema.ValidationInfo) -> ByteSize: @@ -1568,7 +1568,7 @@ def __get_pydantic_json_schema__( def __get_pydantic_core_schema__( self, source: type[Any], handler: _annotated_handlers.GetCoreSchemaHandler ) -> core_schema.CoreSchema: - return core_schema.general_after_validator_function( + return core_schema.with_info_after_validator_function( function=self.decode, schema=core_schema.bytes_schema(), serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode), @@ -1658,7 +1658,7 @@ class Model(BaseModel): def __get_pydantic_core_schema__( self, source: type[Any], handler: _annotated_handlers.GetCoreSchemaHandler ) -> core_schema.CoreSchema: - return core_schema.general_after_validator_function( + return core_schema.with_info_after_validator_function( function=self.decode_str, schema=super(EncodedStr, self).__get_pydantic_core_schema__(source=source, handler=handler), # noqa: UP008 serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode_str), diff --git a/pyproject.toml b/pyproject.toml index 33b357e5a5..ec68caf695 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ requires-python = '>=3.7' dependencies = [ 'typing-extensions>=4.6.1', 'annotated-types>=0.4.0', - "pydantic-core==2.9.0", + "pydantic-core==2.10.0", ] dynamic = ['version', 'readme'] diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 02a3cb22a6..f377776f68 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -20,13 +20,13 @@ BaseModel, BeforeValidator, ConfigDict, - FieldValidationInfo, GenerateSchema, PydanticDeprecatedSince20, PydanticUndefinedAnnotation, PydanticUserError, TypeAdapter, ValidationError, + ValidationInfo, computed_field, field_serializer, field_validator, @@ -1859,7 +1859,7 @@ class Model: @field_validator('b', mode='before') @classmethod - def check_a(cls, v: Any, info: FieldValidationInfo) -> Any: + def check_a(cls, v: Any, info: ValidationInfo) -> Any: assert v == b'but my barbaz is better' assert info.field_name == 'b' assert info.data == {'a': 'your foobar is good'} diff --git a/tests/test_discriminated_union.py b/tests/test_discriminated_union.py index 529477d671..c22852209d 100644 --- a/tests/test_discriminated_union.py +++ b/tests/test_discriminated_union.py @@ -877,7 +877,7 @@ def test_missing_discriminator_field() -> None: def test_wrap_function_schema() -> None: cat_fields = {'kind': core_schema.typed_dict_field(core_schema.literal_schema(['cat']))} dog_fields = {'kind': core_schema.typed_dict_field(core_schema.literal_schema(['dog']))} - cat = core_schema.general_wrap_validator_function(lambda x, y, z: None, core_schema.typed_dict_schema(cat_fields)) + cat = core_schema.with_info_wrap_validator_function(lambda x, y, z: None, core_schema.typed_dict_schema(cat_fields)) dog = core_schema.typed_dict_schema(dog_fields) schema = core_schema.union_schema([cat, dog]) @@ -885,7 +885,7 @@ def test_wrap_function_schema() -> None: 'choices': { 'cat': { 'function': { - 'type': 'general', + 'type': 'with-info', 'function': HasRepr(IsStr(regex=r'\. at 0x[0-9a-fA-F]+>')), }, 'schema': { @@ -915,7 +915,7 @@ def test_plain_function_schema_is_invalid() -> None: ): apply_discriminator( core_schema.union_schema( - [core_schema.general_plain_validator_function(lambda x, y: None), core_schema.int_schema()] + [core_schema.with_info_plain_validator_function(lambda x, y: None), core_schema.int_schema()] ), 'kind', ) @@ -931,7 +931,7 @@ def test_invalid_str_choice_discriminator_values() -> None: # NOTE: Wrapping the union with a validator results in failure to more thoroughly decompose the tagged # union. I think this would be difficult to avoid in the general case, and I would suggest that we not # attempt to do more than this until presented with scenarios where it is helpful/necessary. - core_schema.general_wrap_validator_function(lambda x, y, z: x, dog), + core_schema.with_info_wrap_validator_function(lambda x, y, z: x, dog), ] ) @@ -1017,7 +1017,7 @@ def test_wrapped_nullable_union() -> None: # NOTE: Wrapping the union with a validator results in failure to more thoroughly decompose the tagged # union. I think this would be difficult to avoid in the general case, and I would suggest that we not # attempt to do more than this until presented with scenarios where it is helpful/necessary. - core_schema.general_wrap_validator_function( + core_schema.with_info_wrap_validator_function( lambda x, y, z: x, core_schema.nullable_schema(core_schema.union_schema([cat, dog])) ), ] @@ -1055,7 +1055,7 @@ def test_wrapped_nullable_union() -> None: 'cat': { 'type': 'function-wrap', 'function': { - 'type': 'general', + 'type': 'with-info', 'function': HasRepr(IsStr(regex=r'\. at 0x[0-9a-fA-F]+>')), }, 'schema': { @@ -1088,7 +1088,7 @@ def test_wrapped_nullable_union() -> None: 'dog': { 'type': 'function-wrap', 'function': { - 'type': 'general', + 'type': 'with-info', 'function': HasRepr(IsStr(regex=r'\. at 0x[0-9a-fA-F]+>')), }, 'schema': { diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 8a046e2b97..2e1eaaa322 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -1924,7 +1924,7 @@ def validate(v, _info): ) from exc return v - return core_schema.general_after_validator_function(validate, schema) + return core_schema.with_info_after_validator_function(validate, schema) class Model(BaseModel): a: str diff --git a/tests/test_exports.py b/tests/test_exports.py index 7f440c69c1..ddaf89de57 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -24,14 +24,17 @@ def test_init_export(): continue exported.add(name) - exported.update(pydantic._dynamic_imports) + # add stuff from `pydantic._dynamic_imports` if `package` is "pydantic" + exported.update({k for k, v in pydantic._dynamic_imports.items() if v[0] == 'pydantic'}) assert pydantic_all == exported, "pydantic.__all__ doesn't match actual exports" -@pytest.mark.parametrize(('attr_name', 'module_name'), list(pydantic._dynamic_imports.items())) -def test_public_api_dynamic_imports(attr_name, module_name): - imported_object = getattr(importlib.import_module(module_name, package='pydantic'), attr_name) +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +@pytest.mark.parametrize(('attr_name', 'value'), list(pydantic._dynamic_imports.items())) +def test_public_api_dynamic_imports(attr_name, value): + package, module_name = value + imported_object = getattr(importlib.import_module(module_name, package=package), attr_name) assert isinstance(imported_object, object) diff --git a/tests/test_fastapi.sh b/tests/test_fastapi.sh index c7b91bb243..c7adf07231 100755 --- a/tests/test_fastapi.sh +++ b/tests/test_fastapi.sh @@ -11,6 +11,9 @@ pip install -r requirements.txt pip uninstall -y pydantic cd .. && pip install . && cd fastapi +# apply the patch designed to get the tests to pass (currently ignores some warnings) +git apply ../tests/test_fastapi_changes.patch + # ./scripts/test.sh accepts arbitrary arguments and passes them to the pytest call. # This may be necessary if we make low-consequence changes to pydantic, such as minor changes the details of a JSON # schema or the contents of a ValidationError diff --git a/tests/test_fastapi_changes.patch b/tests/test_fastapi_changes.patch new file mode 100644 index 0000000000..6858bd287e --- /dev/null +++ b/tests/test_fastapi_changes.patch @@ -0,0 +1,17 @@ +Add filterwarnings rules to the pytest section of pyproject.toml to ignore warnings from changes to +core_schemas types in https://github.com/pydantic/pydantic-core/pull/980 + +This can probably be removed once fastapi requires pydantic>2.4. + +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -108,6 +108,9 @@ filterwarnings = [ + "ignore::trio.TrioDeprecationWarning", + # TODO remove pytest-cov + 'ignore::pytest.PytestDeprecationWarning:pytest_cov', ++ # CHANGE IN PYDANTIC, ignore warnings about pydantic-core validation methods ++ 'ignore:`general_(before|after|wrap|plain)_validator_function` is deprecated.*:DeprecationWarning:', ++ 'ignore:`FieldValidationInfo` is deprecated.*:DeprecationWarning:', + ] + + [tool.coverage.run] diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index eb43558375..6b4ee9b199 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -3074,7 +3074,7 @@ def __get_pydantic_core_schema__( source_args = getattr(source, '__args__', [Any]) param = source_args[0] metadata = build_metadata_dict(js_functions=[lambda _c, h: h(handler.generate_schema(param))]) - return core_schema.general_plain_validator_function( + return core_schema.with_info_plain_validator_function( GenModel, metadata=metadata, ) @@ -3205,7 +3205,7 @@ def js_func(s, h): s = handler.generate_schema(Optional[arg]) return h(s) - return core_schema.general_plain_validator_function( + return core_schema.with_info_plain_validator_function( Gen, metadata={'pydantic_js_annotation_functions': [js_func]}, ) @@ -3244,7 +3244,7 @@ def __get_pydantic_core_schema__( if hasattr(source, '__args__'): # the js_function ignores the schema we were given and gets a new Tuple CoreSchema metadata = build_metadata_dict(js_functions=[lambda _c, h: h(handler(Tuple[source.__args__]))]) - return core_schema.general_plain_validator_function( + return core_schema.with_info_plain_validator_function( GenTwoParams, metadata=metadata, ) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 2931fe5596..1f1cad2f8a 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -205,7 +205,7 @@ class CommaFriendlyIntLogic: @classmethod def __get_pydantic_core_schema__(cls, _source, _handler): # here we ignore the schema argument (which is just `{'type': 'int'}`) and return our own - return core_schema.general_before_validator_function( + return core_schema.with_info_before_validator_function( parse_int, core_schema.int_schema(), serialization=core_schema.format_ser_schema(',', when_used='unless-none'), diff --git a/tests/test_utils.py b/tests/test_utils.py index 34a361d6a2..ccbc956a00 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -637,8 +637,8 @@ def walk(s, recurse): def test_handle_function_schema(): - schema = core_schema.field_before_validator_function( - function=lambda v, _info: v, field_name='field_name', schema=core_schema.float_schema() + schema = core_schema.with_info_before_validator_function( + lambda v, _info: v, core_schema.float_schema(), field_name='field_name' ) def walk(s, recurse): diff --git a/tests/test_validators.py b/tests/test_validators.py index 42a2abbe3b..32a73fba12 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -2,24 +2,27 @@ import re import sys from collections import deque +from dataclasses import dataclass from datetime import date, datetime from enum import Enum from functools import partial, partialmethod from itertools import product -from typing import Any, Callable, Deque, Dict, FrozenSet, List, Optional, Tuple, Union +from typing import Any, Callable, Deque, Dict, FrozenSet, List, NamedTuple, Optional, Tuple, Union from unittest.mock import MagicMock import pytest from dirty_equals import HasRepr -from typing_extensions import Annotated, Literal +from pydantic_core import core_schema +from typing_extensions import Annotated, Literal, TypedDict from pydantic import ( BaseModel, ConfigDict, Field, - FieldValidationInfo, + GetCoreSchemaHandler, PydanticDeprecatedSince20, PydanticUserError, + TypeAdapter, ValidationError, ValidationInfo, ValidatorFunctionWrapHandler, @@ -27,6 +30,7 @@ field_validator, model_validator, root_validator, + validate_call, validator, ) from pydantic.functional_validators import AfterValidator, BeforeValidator, PlainValidator, WrapValidator @@ -455,7 +459,7 @@ class ModelTwo(BaseModel): @field_validator('b') @classmethod - def validate_b(cls, b, info: FieldValidationInfo): + def validate_b(cls, b, info: ValidationInfo): if 'm' in info.data: return b + info.data['m'].a # this fails if info.data['m'] is a dict else: @@ -476,7 +480,7 @@ class Model(BaseModel): @field_validator('a', 'b') @classmethod - def check_a_and_b(cls, v: Any, info: FieldValidationInfo) -> Any: + def check_a_and_b(cls, v: Any, info: ValidationInfo) -> Any: if len(v) < 4: field = cls.model_fields[info.field_name] raise AssertionError(f'{field.alias or info.field_name} is too short') @@ -1687,13 +1691,13 @@ class Model(BaseModel): @field_validator('foo') @classmethod - def validate_foo(cls, v: Any, info: FieldValidationInfo) -> Any: + def validate_foo(cls, v: Any, info: ValidationInfo) -> Any: check_values({**info.data}) return v @field_validator('bar') @classmethod - def validate_bar(cls, v: Any, info: FieldValidationInfo) -> Any: + def validate_bar(cls, v: Any, info: ValidationInfo) -> Any: check_values({**info.data}) return v @@ -1932,7 +1936,7 @@ class Model(BaseModel): @field_validator('b', mode='before') @classmethod - def check_a(cls, v: Any, info: FieldValidationInfo) -> Any: + def check_a(cls, v: Any, info: ValidationInfo) -> Any: assert v == b'but my barbaz is better' assert info.field_name == 'b' assert info.data == {'a': 'your foobar is good'} @@ -2177,7 +2181,7 @@ def partial_values_val_func2( def partial_info_val_func( value: int, - info: FieldValidationInfo, + info: ValidationInfo, *, allowed: int, ) -> int: @@ -2240,7 +2244,7 @@ def partial_cls_values_val_func2( def partial_cls_info_val_func( cls: Any, value: int, - info: FieldValidationInfo, + info: ValidationInfo, *, allowed: int, expected_cls: Any, @@ -2610,3 +2614,125 @@ def check_a1(cls, v: str) -> str: with pytest.raises(ValidationError, match=re.escape(f'Value error, foo [{input_str}]')): Model(x='123') + + +def foobar_validate(value: Any, info: core_schema.ValidationInfo): + data = info.data + if isinstance(data, dict): + data = data.copy() + return {'value': value, 'field_name': info.field_name, 'data': data} + + +class Foobar: + @classmethod + def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + return core_schema.with_info_plain_validator_function(foobar_validate, field_name=handler.field_name) + + +def test_custom_type_field_name_model(): + class MyModel(BaseModel): + foobar: Foobar + + m = MyModel(foobar=1, tuple_nesting=(1, 2)) + # insert_assert(m.foobar) + assert m.foobar == {'value': 1, 'field_name': 'foobar', 'data': {}} + + +def test_custom_type_field_name_model_nested(): + class MyModel(BaseModel): + x: int + tuple_nested: Tuple[int, Foobar] + + m = MyModel(x='123', tuple_nested=(1, 2)) + # insert_assert(m.tuple_nested[1]) + assert m.tuple_nested[1] == {'value': 2, 'field_name': 'tuple_nested', 'data': {'x': 123}} + + +def test_custom_type_field_name_typed_dict(): + class MyDict(TypedDict): + x: int + foobar: Foobar + + ta = TypeAdapter(MyDict) + m = ta.validate_python({'x': '123', 'foobar': 1}) + # insert_assert(m['foobar']) + assert m['foobar'] == {'value': 1, 'field_name': 'foobar', 'data': {'x': 123}} + + +def test_custom_type_field_name_dataclass(): + @dataclass + class MyDc: + x: int + foobar: Foobar + + ta = TypeAdapter(MyDc) + m = ta.validate_python({'x': '123', 'foobar': 1}) + # insert_assert(m.foobar) + assert m.foobar == {'value': 1, 'field_name': 'foobar', 'data': {'x': 123}} + + +def test_custom_type_field_name_named_tuple(): + class MyNamedTuple(NamedTuple): + x: int + foobar: Foobar + + ta = TypeAdapter(MyNamedTuple) + m = ta.validate_python({'x': '123', 'foobar': 1}) + # insert_assert(m.foobar) + assert m.foobar == {'value': 1, 'field_name': 'foobar', 'data': None} + + +def test_custom_type_field_name_validate_call(): + @validate_call + def foobar(x: int, y: Foobar): + return x, y + + # insert_assert(foobar(1, 2)) + assert foobar(1, 2) == (1, {'value': 2, 'field_name': 'y', 'data': None}) + + +def test_after_validator_field_name(): + class MyModel(BaseModel): + x: int + foobar: Annotated[int, AfterValidator(foobar_validate)] + + m = MyModel(x='123', foobar='1') + # insert_assert(m.foobar) + assert m.foobar == {'value': 1, 'field_name': 'foobar', 'data': {'x': 123}} + + +def test_before_validator_field_name(): + class MyModel(BaseModel): + x: int + foobar: Annotated[Dict[Any, Any], BeforeValidator(foobar_validate)] + + m = MyModel(x='123', foobar='1') + # insert_assert(m.foobar) + assert m.foobar == {'value': '1', 'field_name': 'foobar', 'data': {'x': 123}} + + +def test_plain_validator_field_name(): + class MyModel(BaseModel): + x: int + foobar: Annotated[int, PlainValidator(foobar_validate)] + + m = MyModel(x='123', foobar='1') + # insert_assert(m.foobar) + assert m.foobar == {'value': '1', 'field_name': 'foobar', 'data': {'x': 123}} + + +def validate_wrap(value: Any, handler: core_schema.ValidatorFunctionWrapHandler, info: core_schema.ValidationInfo): + data = info.data + if isinstance(data, dict): + data = data.copy() + return {'value': handler(value), 'field_name': info.field_name, 'data': data} + + +def test_wrap_validator_field_name(): + class MyModel(BaseModel): + x: int + foobar: Annotated[int, WrapValidator(validate_wrap)] + + m = MyModel(x='123', foobar='1') + # insert_assert(m.foobar) + assert m.foobar == {'value': 1, 'field_name': 'foobar', 'data': {'x': 123}}