From cb8d3002ab6febd3f2d579acf46006cbb4f6fcfe Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:09:12 +0200 Subject: [PATCH 001/412] Do not check for `str` when using `has_instance_in_type` (#10317) --- pydantic/_internal/_generate_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 40ac2ddbdd9..5750cb1ffff 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1264,7 +1264,7 @@ def _common_field_schema( # C901 # Update FieldInfo annotation if appropriate: FieldInfo = import_cached_field_info() - if has_instance_in_type(field_info.annotation, (ForwardRef, str)): + if has_instance_in_type(field_info.annotation, ForwardRef): types_namespace = self._types_namespace if self._typevars_map: types_namespace = (types_namespace or {}).copy() From 0061aabcfd6a521d9801282af88e5b2f146b2780 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:24:32 -0500 Subject: [PATCH 002/412] Fix `Predicate` issue in `v2.9.0` (#10321) --- pydantic/_internal/_known_annotated_metadata.py | 2 ++ tests/test_annotated.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/pydantic/_internal/_known_annotated_metadata.py b/pydantic/_internal/_known_annotated_metadata.py index efdb8924c7a..b34ece10d96 100644 --- a/pydantic/_internal/_known_annotated_metadata.py +++ b/pydantic/_internal/_known_annotated_metadata.py @@ -319,6 +319,8 @@ def val_func(v: Any) -> Any: f'Not of {predicate_name} failed', # type: ignore # noqa: B023 ) + return v + schema = cs.no_info_after_validator_function(val_func, schema) else: # ignore any other unknown metadata diff --git a/tests/test_annotated.py b/tests/test_annotated.py index e0d3da79968..a4160dc7c81 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -385,6 +385,12 @@ def test_validate_float_inf_nan_python() -> None: ] +def test_predicate_success_python() -> None: + ta = TypeAdapter(Annotated[int, Predicate(lambda x: x > 0)]) + + assert ta.validate_python(1) == 1 + + def test_predicate_error_python() -> None: ta = TypeAdapter(Annotated[int, Predicate(lambda x: x > 0)]) From 6db2a0cd970ed7d12ae85a8f83dfb8b0da047e9c Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:49:11 -0500 Subject: [PATCH 003/412] Don't allow customization of `SchemaGenerator` until interface is more stable (#10303) --- docs/concepts/json_schema.md | 1 - pydantic/__init__.py | 17 ++++--- pydantic/_internal/_config.py | 3 -- pydantic/_internal/_generate_schema.py | 49 ++++--------------- pydantic/config.py | 14 +----- pydantic/type_adapter.py | 4 +- tests/conftest.py | 2 +- tests/test_config.py | 5 -- tests/test_dataclasses.py | 16 +------ tests/test_main.py | 65 ++++---------------------- tests/test_type_adapter.py | 11 +---- tests/test_types_typeddict.py | 17 +------ 12 files changed, 34 insertions(+), 170 deletions(-) diff --git a/docs/concepts/json_schema.md b/docs/concepts/json_schema.md index 0ee44189984..e889ccb0fd6 100644 --- a/docs/concepts/json_schema.md +++ b/docs/concepts/json_schema.md @@ -534,7 +534,6 @@ Specifically, the following config options are relevant: * [`title`][pydantic.config.ConfigDict.title] * [`json_schema_extra`][pydantic.config.ConfigDict.json_schema_extra] -* [`schema_generator`][pydantic.config.ConfigDict.schema_generator] * [`json_schema_mode_override`][pydantic.config.ConfigDict.json_schema_mode_override] * [`field_title_generator`][pydantic.config.ConfigDict.field_title_generator] * [`model_title_generator`][pydantic.config.ConfigDict.model_title_generator] diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 72ee9688473..25484b15384 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -1,5 +1,6 @@ import typing from importlib import import_module +from warnings import warn from ._migration import getattr_migration from .version import VERSION @@ -17,7 +18,6 @@ ) from . import dataclasses - from ._internal._generate_schema import GenerateSchema as GenerateSchema from .aliases import AliasChoices, AliasGenerator, AliasPath from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler from .config import ConfigDict, with_config @@ -217,8 +217,6 @@ # annotated handlers 'GetCoreSchemaHandler', 'GetJsonSchemaHandler', - # generate schema from ._internal - 'GenerateSchema', # pydantic_core 'ValidationError', 'ValidationInfo', @@ -372,8 +370,6 @@ # annotated handlers 'GetCoreSchemaHandler': (__spec__.parent, '.annotated_handlers'), 'GetJsonSchemaHandler': (__spec__.parent, '.annotated_handlers'), - # generate schema from ._internal - 'GenerateSchema': (__spec__.parent, '._internal._generate_schema'), # pydantic_core stuff 'ValidationError': ('pydantic_core', '.'), 'ValidationInfo': ('pydantic_core', '.core_schema'), @@ -389,14 +385,23 @@ 'parse_obj_as': (__spec__.parent, '.deprecated.tools'), 'schema_of': (__spec__.parent, '.deprecated.tools'), 'schema_json_of': (__spec__.parent, '.deprecated.tools'), + # deprecated dynamic imports 'FieldValidationInfo': ('pydantic_core', '.core_schema'), + 'GenerateSchema': (__spec__.parent, '._internal._generate_schema'), } -_deprecated_dynamic_imports = {'FieldValidationInfo'} +_deprecated_dynamic_imports = {'FieldValidationInfo', 'GenerateSchema'} _getattr_migration = getattr_migration(__name__) def __getattr__(attr_name: str) -> object: + if attr_name in _deprecated_dynamic_imports: + warn( + f'Importing {attr_name} from `pydantic` is deprecated. This feature is either no longer supported, or is not public.', + DeprecationWarning, + stacklevel=2, + ) + dynamic_attr = _dynamic_imports.get(attr_name) if dynamic_attr is None: return _getattr_migration(attr_name) diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index 2634fcd8809..20cb415a80c 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -26,7 +26,6 @@ DeprecationWarning = PydanticDeprecatedSince20 if TYPE_CHECKING: - from .._internal._schema_generation_shared import GenerateSchema from ..fields import ComputedFieldInfo, FieldInfo DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.' @@ -81,7 +80,6 @@ class ConfigWrapper: defer_build: bool experimental_defer_build_mode: tuple[Literal['model', 'type_adapter'], ...] plugin_settings: dict[str, object] | None - schema_generator: type[GenerateSchema] | None json_schema_serialization_defaults_required: bool json_schema_mode_override: Literal['validation', 'serialization', None] coerce_numbers_to_str: bool @@ -264,7 +262,6 @@ def push(self, config_wrapper: ConfigWrapper | ConfigDict | None): defer_build=False, experimental_defer_build_mode=('model',), plugin_settings=None, - schema_generator=None, json_schema_serialization_defaults_required=False, json_schema_mode_override=None, coerce_numbers_to_str=False, diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 5750cb1ffff..5530fc130ea 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -372,7 +372,7 @@ def __init__( types_namespace: dict[str, Any] | None, typevars_map: dict[Any, Any] | None = None, ) -> None: - # we need a stack for recursing into child models + # we need a stack for recursing into nested models self._config_wrapper_stack = ConfigWrapperStack(config_wrapper) self._types_namespace_stack = TypesNamespaceStack(types_namespace) self._typevars_map = typevars_map @@ -380,23 +380,13 @@ def __init__( self.model_type_stack = _ModelTypeStack() self.defs = _Definitions() - @classmethod - def __from_parent( - cls, - config_wrapper_stack: ConfigWrapperStack, - types_namespace_stack: TypesNamespaceStack, - model_type_stack: _ModelTypeStack, - typevars_map: dict[Any, Any] | None, - defs: _Definitions, - ) -> GenerateSchema: - obj = cls.__new__(cls) - obj._config_wrapper_stack = config_wrapper_stack - obj._types_namespace_stack = types_namespace_stack - obj.model_type_stack = model_type_stack - obj._typevars_map = typevars_map - obj.field_name_stack = _FieldNameStack() - obj.defs = defs - return obj + def __init_subclass__(cls) -> None: + super().__init_subclass__() + warnings.warn( + 'Subclassing `GenerateSchema` is not supported. The API is highly subject to change in minor versions.', + UserWarning, + stacklevel=2, + ) @property def _config_wrapper(self) -> ConfigWrapper: @@ -406,25 +396,10 @@ def _config_wrapper(self) -> ConfigWrapper: def _types_namespace(self) -> dict[str, Any] | None: return self._types_namespace_stack.tail - @property - def _current_generate_schema(self) -> GenerateSchema: - cls = self._config_wrapper.schema_generator or GenerateSchema - return cls.__from_parent( - self._config_wrapper_stack, - self._types_namespace_stack, - self.model_type_stack, - self._typevars_map, - self.defs, - ) - @property def _arbitrary_types(self) -> bool: return self._config_wrapper.arbitrary_types_allowed - def str_schema(self) -> CoreSchema: - """Generate a CoreSchema for `str`""" - return core_schema.str_schema() - # the following methods can be overridden but should be considered # unstable / private APIs def _list_schema(self, items_type: Any) -> CoreSchema: @@ -689,8 +664,6 @@ def _model_schema(self, cls: type[BaseModel]) -> core_schema.CoreSchema: model_validators = decorators.model_validators.values() with self._config_wrapper_stack.push(config_wrapper), self._types_namespace_stack.push(cls): - self = self._current_generate_schema - extras_schema = None if core_config.get('extra_fields_behavior') == 'allow': assert cls.__mro__[0] is cls @@ -942,7 +915,7 @@ def match_type(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901 as they get requested and we figure out what the right API for them is. """ if obj is str: - return self.str_schema() + return core_schema.str_schema() elif obj is bytes: return core_schema.bytes_schema() elif obj is int: @@ -1471,8 +1444,6 @@ def _typed_dict_schema(self, typed_dict_cls: Any, origin: Any) -> core_schema.Co with self._config_wrapper_stack.push(config), self._types_namespace_stack.push(typed_dict_cls): core_config = self._config_wrapper.core_config(typed_dict_cls) - self = self._current_generate_schema - required_keys: frozenset[str] = typed_dict_cls.__required_keys__ fields: dict[str, core_schema.TypedDictField] = {} @@ -1786,8 +1757,6 @@ def _dataclass_schema( core_config = self._config_wrapper.core_config(dataclass) - self = self._current_generate_schema - from ..dataclasses import is_pydantic_dataclass if is_pydantic_dataclass(dataclass): diff --git a/pydantic/config.py b/pydantic/config.py index 86d2ecd7406..ed18d866253 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -11,7 +11,6 @@ from .errors import PydanticUserError if TYPE_CHECKING: - from ._internal._generate_schema import GenerateSchema as _GenerateSchema from .fields import ComputedFieldInfo, FieldInfo __all__ = ('ConfigDict', 'with_config') @@ -762,18 +761,7 @@ class Model(BaseModel): """ plugin_settings: dict[str, object] | None - """A `dict` of settings for plugins. Defaults to `None`. - """ - - schema_generator: type[_GenerateSchema] | None - """ - A custom core schema generator class to use when generating JSON schemas. - Useful if you want to change the way types are validated across an entire model/schema. Defaults to `None`. - - The `GenerateSchema` interface is subject to change, currently only the `string_schema` method is public. - - See [#6737](https://github.com/pydantic/pydantic/pull/6737) for details. - """ + """A `dict` of settings for plugins. Defaults to `None`.""" json_schema_serialization_defaults_required: bool """ diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py index 3619fdff3c7..75e558f6f12 100644 --- a/pydantic/type_adapter.py +++ b/pydantic/type_adapter.py @@ -98,9 +98,7 @@ def _get_schema(type_: Any, config_wrapper: _config.ConfigWrapper, parent_depth: local_ns = _typing_extra.parent_frame_namespace(parent_depth=parent_depth) global_ns = sys._getframe(max(parent_depth - 1, 1)).f_globals.copy() global_ns.update(local_ns or {}) - gen = (config_wrapper.schema_generator or _generate_schema.GenerateSchema)( - config_wrapper, types_namespace=global_ns, typevars_map={} - ) + gen = _generate_schema.GenerateSchema(config_wrapper, types_namespace=global_ns, typevars_map={}) schema = gen.generate_schema(type_) schema = gen.clean_schema(schema) return schema diff --git a/tests/conftest.py b/tests/conftest.py index 24aef2cfa7b..0f406dbace7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,7 +17,7 @@ from _pytest.assertion.rewrite import AssertionRewritingHook from jsonschema import Draft202012Validator, SchemaError -from pydantic import GenerateSchema +from pydantic._internal._generate_schema import GenerateSchema from pydantic.json_schema import GenerateJsonSchema diff --git a/tests/test_config.py b/tests/test_config.py index a6259c73d6e..7dd3e14fdb8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,7 +13,6 @@ BaseConfig, BaseModel, Field, - GenerateSchema, PrivateAttr, PydanticDeprecatedSince20, PydanticSchemaGenerationError, @@ -519,8 +518,6 @@ class Child(Mixin, Parent): def test_config_wrapper_match(): localns = { - '_GenerateSchema': GenerateSchema, - 'GenerateSchema': GenerateSchema, 'JsonValue': JsonValue, 'FieldInfo': FieldInfo, 'ComputedFieldInfo': ComputedFieldInfo, @@ -568,8 +565,6 @@ def check_foo(cls, v): def test_config_defaults_match(): localns = { - '_GenerateSchema': GenerateSchema, - 'GenerateSchema': GenerateSchema, 'FieldInfo': FieldInfo, 'ComputedFieldInfo': ComputedFieldInfo, } diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 050d94f0f37..9b38c4fe498 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -12,7 +12,7 @@ import pytest from dirty_equals import HasRepr -from pydantic_core import ArgsKwargs, CoreSchema, SchemaValidator, core_schema +from pydantic_core import ArgsKwargs, SchemaValidator from typing_extensions import Annotated, Literal import pydantic @@ -20,7 +20,6 @@ BaseModel, BeforeValidator, ConfigDict, - GenerateSchema, PydanticDeprecatedSince20, PydanticUndefinedAnnotation, PydanticUserError, @@ -2654,19 +2653,6 @@ class B: assert B().b == 1 -def test_schema_generator() -> None: - class LaxStrGenerator(GenerateSchema): - def str_schema(self) -> CoreSchema: - return core_schema.no_info_plain_validator_function(str) - - @pydantic.dataclasses.dataclass - class Model: - x: str - __pydantic_config__ = ConfigDict(schema_generator=LaxStrGenerator) - - assert Model(x=1).x == '1' - - @pytest.mark.parametrize('decorator1', **dataclass_decorators()) def test_annotated_before_validator_called_once(decorator1): count = 0 diff --git a/tests/test_main.py b/tests/test_main.py index bd06dbe79bb..75288892433 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -35,7 +35,6 @@ BaseModel, ConfigDict, Field, - GenerateSchema, GetCoreSchemaHandler, PrivateAttr, PydanticUndefinedAnnotation, @@ -48,6 +47,7 @@ constr, field_validator, ) +from pydantic._internal._generate_schema import GenerateSchema from pydantic._internal._mock_val_ser import MockCoreSchema from pydantic.dataclasses import dataclass as pydantic_dataclass @@ -3131,62 +3131,7 @@ class Model3(BaseModel): ___: int = Field(default=1) -def test_schema_generator_customize_type() -> None: - class LaxStrGenerator(GenerateSchema): - def str_schema(self) -> CoreSchema: - return core_schema.no_info_plain_validator_function(str) - - class Model(BaseModel): - x: str - model_config = ConfigDict(schema_generator=LaxStrGenerator) - - assert Model(x=1).x == '1' - - -def test_schema_generator_customize_type_constraints() -> None: - class LaxStrGenerator(GenerateSchema): - def str_schema(self) -> CoreSchema: - return core_schema.no_info_plain_validator_function(str) - - class Model(BaseModel): - x: Annotated[str, Field(pattern='^\\d+$')] - y: Annotated[float, Field(gt=0)] - z: Annotated[List[int], Field(min_length=1)] - model_config = ConfigDict(schema_generator=LaxStrGenerator) - - # insert_assert(Model(x='123', y=1, z=[-1]).model_dump()) - assert Model(x='123', y=1, z=[-1]).model_dump() == {'x': '123', 'y': 1.0, 'z': [-1]} - - with pytest.raises(ValidationError) as exc_info: - Model(x='abc', y=-1, z=[]) - - # insert_assert(exc_info.value.errors(include_url=False)) - assert exc_info.value.errors(include_url=False) == [ - { - 'type': 'string_pattern_mismatch', - 'loc': ('x',), - 'msg': "String should match pattern '^\\d+$'", - 'input': 'abc', - 'ctx': {'pattern': '^\\d+$'}, - }, - { - 'type': 'greater_than', - 'loc': ('y',), - 'msg': 'Input should be greater than 0', - 'input': -1, - 'ctx': {'gt': 0.0}, - }, - { - 'type': 'too_short', - 'loc': ('z',), - 'msg': 'List should have at least 1 item after validation, not 0', - 'input': [], - 'ctx': {'field_type': 'List', 'min_length': 1, 'actual_length': 0}, - }, - ] - - -def test_schema_generator_customize_type_constraints_order() -> None: +def test_customize_type_constraints_order() -> None: class Model(BaseModel): # whitespace will be stripped first, then max length will be checked, should pass on ' 1 ' x: Annotated[str, AfterValidator(lambda x: x.strip()), StringConstraints(max_length=1)] @@ -3368,3 +3313,9 @@ def model_post_init(self, context: Any) -> None: assert m == copy assert id(m) != id(copy) + + +def test_subclassing_gen_schema_warns() -> None: + with pytest.warns(UserWarning, match='Subclassing `GenerateSchema` is not supported.'): + + class MyGenSchema(GenerateSchema): ... diff --git a/tests/test_type_adapter.py b/tests/test_type_adapter.py index 74bc98a9351..4773d3f779c 100644 --- a/tests/test_type_adapter.py +++ b/tests/test_type_adapter.py @@ -5,11 +5,10 @@ from typing import Any, Dict, ForwardRef, Generic, List, NamedTuple, Optional, Tuple, TypeVar, Union import pytest -from pydantic_core import ValidationError, core_schema +from pydantic_core import ValidationError from typing_extensions import Annotated, TypeAlias, TypedDict from pydantic import BaseModel, Field, TypeAdapter, ValidationInfo, create_model, field_validator -from pydantic._internal._generate_schema import GenerateSchema from pydantic._internal._typing_extra import annotated_type from pydantic.config import ConfigDict from pydantic.dataclasses import dataclass as pydantic_dataclass @@ -568,11 +567,3 @@ def test_core_schema_respects_defer_build(model: Any, config: ConfigDict, method assert type_adapter._core_schema is not None, 'Should be initialized after the usage' assert type_adapter._validator is not None, 'Should be initialized after the usage' assert type_adapter._serializer is not None, 'Should be initialized after the usage' - - -def test_custom_schema_gen_respected() -> None: - class LaxStrGenerator(GenerateSchema): - def str_schema(self) -> core_schema.CoreSchema: - return core_schema.no_info_plain_validator_function(str) - - assert TypeAdapter(str, config=ConfigDict(schema_generator=LaxStrGenerator)).validate_python(1) == '1' diff --git a/tests/test_types_typeddict.py b/tests/test_types_typeddict.py index 8255504d6f8..aeb137ef4bd 100644 --- a/tests/test_types_typeddict.py +++ b/tests/test_types_typeddict.py @@ -9,14 +9,13 @@ import pytest import typing_extensions from annotated_types import Lt -from pydantic_core import CoreSchema, core_schema +from pydantic_core import core_schema from typing_extensions import Annotated, TypedDict from pydantic import ( BaseModel, ConfigDict, Field, - GenerateSchema, GetCoreSchemaHandler, PositiveInt, PydanticUserError, @@ -881,20 +880,6 @@ class Model(Base): assert ta.validate_python({'x': 'ABC'}) == {'x': 'abc'} -def test_schema_generator() -> None: - class LaxStrGenerator(GenerateSchema): - def str_schema(self) -> CoreSchema: - return core_schema.no_info_plain_validator_function(str) - - class Model(TypedDict): - x: str - __pydantic_config__ = ConfigDict(schema_generator=LaxStrGenerator) # type: ignore - - ta = TypeAdapter(Model) - - assert ta.validate_python(dict(x=1))['x'] == '1' - - def test_grandparent_config(): class MyTypedDict(TypedDict): __pydantic_config__ = ConfigDict(str_to_lower=True) From 28af0c2a587ca2ecdc78db413b025c185c57dacc Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:57:02 -0500 Subject: [PATCH 004/412] Support `fractions.Fraction` (#10318) --- docs/api/standard_library_types.md | 10 +++++++++ pydantic/_internal/_generate_schema.py | 22 +++++++++++++++++++ pydantic/_internal/_validators.py | 11 ++++++++++ tests/test_types.py | 30 ++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/docs/api/standard_library_types.md b/docs/api/standard_library_types.md index 40832d5492f..f434e0962d1 100644 --- a/docs/api/standard_library_types.md +++ b/docs/api/standard_library_types.md @@ -212,6 +212,16 @@ print(my_model.model_dump_json()) # (3)! 2. Using [`model_dump`][pydantic.main.BaseModel.model_dump] with `mode='json'`, `x` is serialized as a `string`, and `y` is serialized as a `float` because of the custom serializer applied. 3. Using [`model_dump_json`][pydantic.main.BaseModel.model_dump_json], `x` is serialized as a `string`, and `y` is serialized as a `float` because of the custom serializer applied. +### [`complex`][] + +* Validation: Pydantic supports `complex` types or `str` values that can be converted to a `complex` type. +* Serialization: Pydantic serializes [`complex`][] types as strings. + +### [`fractions.Fraction`][fractions.Fraction] + +* Validation: Pydantic attempts to convert the value to a `Fraction` using `Fraction(v)`. +* Serialization: Pydantic serializes [`Fraction`][fractions.Fraction] types as strings. + ## [`Enum`][enum.Enum] Pydantic uses Python's standard [`enum`][] classes to define choices. diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 5530fc130ea..f9a27873542 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -16,6 +16,7 @@ from copy import copy, deepcopy from decimal import Decimal from enum import Enum +from fractions import Fraction from functools import partial from inspect import Parameter, _ParameterKind, signature from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network @@ -517,6 +518,25 @@ def ser_ip(ip: Any) -> str: ), ) + def _fraction_schema(self) -> CoreSchema: + """Support for [`fractions.Fraction`][fractions.Fraction].""" + from ._validators import fraction_validator + + # TODO: note, this is a fairly common pattern, re lax / strict for attempted type coercion, + # can we use a helper function to reduce boilerplate? + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(fraction_validator), + strict_schema=core_schema.json_or_python_schema( + json_schema=core_schema.no_info_plain_validator_function(fraction_validator), + python_schema=core_schema.is_instance_schema(Fraction), + ), + # use str serialization to guarantee round trip behavior + serialization=core_schema.to_string_ser_schema(when_used='always'), + metadata=build_metadata_dict( + js_functions=[lambda _1, _2: {'type': 'string', 'format': 'fraction'}], + ), + ) + def _arbitrary_type_schema(self, tp: Any) -> CoreSchema: if not isinstance(tp, type): warn( @@ -942,6 +962,8 @@ def match_type(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901 return core_schema.uuid_schema() elif obj is Url: return core_schema.url_schema() + elif obj is Fraction: + return self._fraction_schema() elif obj is MultiHostUrl: return core_schema.multi_host_url_schema() elif obj is None or obj is _typing_extra.NoneType: diff --git a/pydantic/_internal/_validators.py b/pydantic/_internal/_validators.py index e97073740c9..6380fe83968 100644 --- a/pydantic/_internal/_validators.py +++ b/pydantic/_internal/_validators.py @@ -8,6 +8,7 @@ import math import re import typing +from fractions import Fraction from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from typing import Any, Callable @@ -236,6 +237,16 @@ def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') +def fraction_validator(input_value: Any, /) -> Fraction: + if isinstance(input_value, Fraction): + return input_value + + try: + return Fraction(input_value) + except ValueError: + raise PydanticCustomError('fraction_parsing', 'Input is not a valid fraction') + + def forbid_inf_nan_check(x: Any) -> Any: if not math.isfinite(x): raise PydanticKnownError('finite_number') diff --git a/tests/test_types.py b/tests/test_types.py index 0180a3633ca..84e5e6616af 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -14,6 +14,7 @@ from datetime import date, datetime, time, timedelta, timezone from decimal import Decimal from enum import Enum, IntEnum +from fractions import Fraction from numbers import Number from pathlib import Path from typing import ( @@ -6944,3 +6945,32 @@ def test_ser_ip_with_unexpected_value() -> None: with pytest.warns(UserWarning, match='serialized value may not be as expected.'): assert ta.dump_python(123) + + +@pytest.mark.parametrize('input_data', ['1/3', 1.333, Fraction(1, 3), Decimal('1.333')]) +def test_fraction_validation_lax(input_data) -> None: + ta = TypeAdapter(Fraction) + fraction = ta.validate_python(input_data) + assert isinstance(fraction, Fraction) + + +def test_fraction_validation_strict() -> None: + ta = TypeAdapter(Fraction, config=ConfigDict(strict=True)) + + assert ta.validate_python(Fraction(1 / 3)) == Fraction(1 / 3) + + # only fractions accepted in strict mode + for lax_fraction in ['1/3', 1.333, Decimal('1.333')]: + with pytest.raises(ValidationError): + ta.validate_python(lax_fraction) + + +def test_fraction_serialization() -> None: + ta = TypeAdapter(Fraction) + assert ta.dump_python(Fraction(1, 3)) == '1/3' + assert ta.dump_json(Fraction(1, 3)) == b'"1/3"' + + +def test_fraction_json_schema() -> None: + ta = TypeAdapter(Fraction) + assert ta.json_schema() == {'type': 'string', 'format': 'fraction'} From 852736c3a3d1e7289bb48e89dee34e4ea4cdf28c Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:25:48 -0500 Subject: [PATCH 005/412] Fixing `annotated-types` bound (#10327) --- pdm.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pdm.lock b/pdm.lock index 2b73e296be6..f8382c399ca 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:45f8c82d3cadfc9650934637c003cd47f88b5cf9487a09801e06c847690abc01" +content_hash = "sha256:a35cfb3d1aa9400acb86d7b2a4727c0d4dd0998e3a00cd7cccd5d26893211f31" [[metadata.targets]] requires_python = ">=3.8" diff --git a/pyproject.toml b/pyproject.toml index 377a52bcc14..4d0a8f26ac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ requires-python = '>=3.8' dependencies = [ 'typing-extensions>=4.6.1; python_version < "3.13"', 'typing-extensions>=4.12.2; python_version >= "3.13"', - 'annotated-types>=0.4.0', + 'annotated-types>=0.6.0', "pydantic-core==2.23.2", # See: https://docs.python.org/3/library/zoneinfo.html#data-sources 'tzdata; python_version >= "3.9"', From 0fdf06f9d5d5fc1c658d67d338e55a393d03faab Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:36:46 -0500 Subject: [PATCH 006/412] Support `Hashable` for json validation (#10324) --- docs/api/standard_library_types.md | 4 ++ pydantic/_internal/_generate_schema.py | 7 +++- tests/test_edge_cases.py | 53 ++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/docs/api/standard_library_types.md b/docs/api/standard_library_types.md index f434e0962d1..b705434e17f 100644 --- a/docs/api/standard_library_types.md +++ b/docs/api/standard_library_types.md @@ -1067,6 +1067,10 @@ print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__) Allows any value, including `None`. +## [`typing.Hashable`][] + +* From Python, supports any data that passes an `isinstance(v, Hashable)` check. +* From JSON, first loads the data via an `Any` validator, then checks if the data is hashable with `isinstance(v, Hashable)`. ## [`typing.Annotated`][] diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index f9a27873542..f09be5943c4 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1744,7 +1744,12 @@ def _pattern_schema(self, pattern_type: Any) -> core_schema.CoreSchema: def _hashable_schema(self) -> core_schema.CoreSchema: return core_schema.custom_error_schema( - core_schema.is_instance_schema(collections.abc.Hashable), + schema=core_schema.json_or_python_schema( + json_schema=core_schema.chain_schema( + [core_schema.any_schema(), core_schema.is_instance_schema(collections.abc.Hashable)] + ), + python_schema=core_schema.is_instance_schema(collections.abc.Hashable), + ), custom_error_type='is_hashable', custom_error_message='Input should be hashable', ) diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 517f49e0932..933185e3aef 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -32,7 +32,6 @@ ConfigDict, GetCoreSchemaHandler, PydanticDeprecatedSince20, - PydanticInvalidForJsonSchema, PydanticSchemaGenerationError, RootModel, TypeAdapter, @@ -2107,17 +2106,55 @@ def __hash__(self): m.model_dump_json() +def test_hashable_validate_json(): + class Model(BaseModel): + v: Hashable + + ta = TypeAdapter(Model) + + # Test a large nested dict + for validate in (Model.model_validate_json, ta.validate_json): + for testcase in ( + '{"v": "a"}', + '{"v": 1}', + '{"v": 1.0}', + '{"v": true}', + '{"v": null}', + ): + assert hash(validate(testcase).v) == hash(validate(testcase).v) + + +@pytest.mark.parametrize( + 'non_hashable', + [ + '{"v": []}', + '{"v": {"a": 0}}', + ], +) +def test_hashable_invalid_json(non_hashable) -> None: + """This is primarily included in order to document the behavior / limitations of the `Hashable` type's validation logic. + + Specifically, we don't do any coercions to arrays / dicts when loading from JSON, and thus they are not considered hashable. + This would be different if we, for example, coerced arrays to tuples. + """ + + class Model(BaseModel): + v: Hashable + + with pytest.raises(ValidationError): + Model.model_validate_json(non_hashable) + + def test_hashable_json_schema(): class Model(BaseModel): v: Hashable - with pytest.raises( - PydanticInvalidForJsonSchema, - match=re.escape( - "Cannot generate a JsonSchema for core_schema.IsInstanceSchema ()" - ), - ): - Model.model_json_schema() + assert Model.model_json_schema() == { + 'properties': {'v': {'title': 'V'}}, + 'required': ['v'], + 'title': 'Model', + 'type': 'object', + } def test_default_factory_called_once(): From 97d357f50a6e8f2a5129a2061887a9c124f7c9c3 Mon Sep 17 00:00:00 2001 From: Jakob Keller <57402305+jakob-keller@users.noreply.github.com> Date: Fri, 6 Sep 2024 07:39:57 +0200 Subject: [PATCH 007/412] Turn `tzdata` install requirement into optional `timezone` dependency (#10331) --- .github/workflows/ci.yml | 4 ++-- .../styles/config/vocabularies/hyperlint/accept.txt | 1 + docs/install.md | 12 ++++++++---- pdm.lock | 4 ++-- pyproject.toml | 6 ++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3aad3f57ed7..53c4d99e9ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: - name: install deps run: | pdm venv create --with-pip --force $PYTHON - pdm install -G testing -G email + pdm install -G testing -G email -G timezone - run: pdm info && pdm list @@ -156,7 +156,7 @@ jobs: CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-without-deps - name: install extra deps - run: pdm install -G testing-extra -G email + run: pdm install -G testing-extra -G email -G timezone - name: test with deps run: make test diff --git a/.hyperlint/styles/config/vocabularies/hyperlint/accept.txt b/.hyperlint/styles/config/vocabularies/hyperlint/accept.txt index 449bab60657..3e376ae2f3e 100644 --- a/.hyperlint/styles/config/vocabularies/hyperlint/accept.txt +++ b/.hyperlint/styles/config/vocabularies/hyperlint/accept.txt @@ -6,6 +6,7 @@ Hyperlint preprocess tokenization tokenizer +tzdata API APIs SDKs diff --git a/docs/install.md b/docs/install.md index 6ab2d8161fb..a6014781a1e 100644 --- a/docs/install.md +++ b/docs/install.md @@ -23,15 +23,19 @@ conda install pydantic -c conda-forge Pydantic has the following optional dependencies: -* Email validation provided by the [email-validator](https://pypi.org/project/email-validator/) package. +* `email`: Email validation provided by the [email-validator](https://pypi.org/project/email-validator/) package. +* `timezone`: Fallback IANA time zone database provided by the [tzdata](https://pypi.org/project/tzdata/) package. To install optional dependencies along with Pydantic: ```bash +# with the `email` extra: pip install pydantic[email] +# or with `email` and `timezone` extras: +pip install pydantic[email,timezone] ``` -Of course, you can also install requirements manually with `pip install email-validator`. +Of course, you can also install requirements manually with `pip install email-validator tzdata`. ## Install from repository @@ -39,6 +43,6 @@ And if you prefer to install Pydantic directly from the repository: ```bash pip install git+https://github.com/pydantic/pydantic@main#egg=pydantic -# or with the `email` extra: -pip install git+https://github.com/pydantic/pydantic@main#egg=pydantic[email] +# or with `email` and `timezone` extras: +pip install git+https://github.com/pydantic/pydantic@main#egg=pydantic[email,timezone] ``` diff --git a/pdm.lock b/pdm.lock index f8382c399ca..f88c5abf5b1 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra"] +groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra", "timezone"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:a35cfb3d1aa9400acb86d7b2a4727c0d4dd0998e3a00cd7cccd5d26893211f31" +content_hash = "sha256:3a584a7e92099980d8871eb23c5549630f86f8e2284884ab3042dd043ddf50f8" [[metadata.targets]] requires_python = ">=3.8" diff --git a/pyproject.toml b/pyproject.toml index 4d0a8f26ac5..51cb5c9c3da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,13 +51,15 @@ dependencies = [ 'typing-extensions>=4.12.2; python_version >= "3.13"', 'annotated-types>=0.6.0', "pydantic-core==2.23.2", - # See: https://docs.python.org/3/library/zoneinfo.html#data-sources - 'tzdata; python_version >= "3.9"', ] dynamic = ['version', 'readme'] [project.optional-dependencies] email = ['email-validator>=2.0.0'] +timezone = [ + # See: https://docs.python.org/3/library/zoneinfo.html#data-sources + 'tzdata; python_version >= "3.9" and sys_platform == "win32"', +] [project.urls] Homepage = 'https://github.com/pydantic/pydantic' From c452c7e5fe37fae8e712468fd0eb0f4506cbca86 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:04:36 +0200 Subject: [PATCH 008/412] Use correct types namespace when building namedtuple core schemas (#10337) --- pydantic/_internal/_generate_schema.py | 45 ++++++++++++++------------ tests/test_types_namedtuple.py | 18 +++++++++++ 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index f09be5943c4..cff2ad5f32d 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1539,27 +1539,30 @@ def _namedtuple_schema(self, namedtuple_cls: Any, origin: Any) -> core_schema.Co if origin is not None: namedtuple_cls = origin - annotations: dict[str, Any] = get_cls_type_hints_lenient(namedtuple_cls, self._types_namespace) - if not annotations: - # annotations is empty, happens if namedtuple_cls defined via collections.namedtuple(...) - annotations = {k: Any for k in namedtuple_cls._fields} - - if typevars_map: - annotations = { - field_name: replace_types(annotation, typevars_map) - for field_name, annotation in annotations.items() - } - - arguments_schema = core_schema.arguments_schema( - [ - self._generate_parameter_schema( - field_name, annotation, default=namedtuple_cls._field_defaults.get(field_name, Parameter.empty) - ) - for field_name, annotation in annotations.items() - ], - metadata=build_metadata_dict(js_prefer_positional_arguments=True), - ) - return core_schema.call_schema(arguments_schema, namedtuple_cls, ref=namedtuple_ref) + with self._types_namespace_stack.push(namedtuple_cls): + annotations: dict[str, Any] = get_cls_type_hints_lenient(namedtuple_cls, self._types_namespace) + if not annotations: + # annotations is empty, happens if namedtuple_cls defined via collections.namedtuple(...) + annotations = {k: Any for k in namedtuple_cls._fields} + + if typevars_map: + annotations = { + field_name: replace_types(annotation, typevars_map) + for field_name, annotation in annotations.items() + } + + arguments_schema = core_schema.arguments_schema( + [ + self._generate_parameter_schema( + field_name, + annotation, + default=namedtuple_cls._field_defaults.get(field_name, Parameter.empty), + ) + for field_name, annotation in annotations.items() + ], + metadata=build_metadata_dict(js_prefer_positional_arguments=True), + ) + return core_schema.call_schema(arguments_schema, namedtuple_cls, ref=namedtuple_ref) def _generate_parameter_schema( self, diff --git a/tests/test_types_namedtuple.py b/tests/test_types_namedtuple.py index 3774558275c..91829a626fd 100644 --- a/tests/test_types_namedtuple.py +++ b/tests/test_types_namedtuple.py @@ -136,6 +136,24 @@ class Model(BaseModel): Model.model_validate({'t': [-1]}) +def test_namedtuple_different_module(create_module) -> None: + """https://github.com/pydantic/pydantic/issues/10336""" + + @create_module + def other_module(): + from typing import NamedTuple + + TestIntOtherModule = int + + class Tup(NamedTuple): + f: 'TestIntOtherModule' + + class Model(BaseModel): + tup: other_module.Tup + + assert Model(tup={'f': 1}).tup.f == 1 + + def test_namedtuple_arbitrary_type(): class CustomClass: pass From e0604253bd56abf1bf0660fa2c6779a3cff98c46 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:02:11 -0500 Subject: [PATCH 009/412] Cleanly `defer_build` on `TypeAdapters`, removing experimental flag (#10329) Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> --- pydantic/_internal/_config.py | 2 - pydantic/_internal/_model_construction.py | 2 +- pydantic/config.py | 24 -------- pydantic/type_adapter.py | 19 +------ tests/test_config.py | 68 +++++++++-------------- tests/test_type_adapter.py | 26 +++------ 6 files changed, 38 insertions(+), 103 deletions(-) diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index 20cb415a80c..e1e18139f42 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -78,7 +78,6 @@ class ConfigWrapper: protected_namespaces: tuple[str, ...] hide_input_in_errors: bool defer_build: bool - experimental_defer_build_mode: tuple[Literal['model', 'type_adapter'], ...] plugin_settings: dict[str, object] | None json_schema_serialization_defaults_required: bool json_schema_mode_override: Literal['validation', 'serialization', None] @@ -260,7 +259,6 @@ def push(self, config_wrapper: ConfigWrapper | ConfigDict | None): hide_input_in_errors=False, json_encoders=None, defer_build=False, - experimental_defer_build_mode=('model',), plugin_settings=None, json_schema_serialization_defaults_required=False, json_schema_mode_override=None, diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 86c9bf05fe4..418bf731a74 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -560,7 +560,7 @@ def complete_model_class( ref_mode='unpack', ) - if config_wrapper.defer_build and 'model' in config_wrapper.experimental_defer_build_mode: + if config_wrapper.defer_build: set_model_mocks(cls, cls_name) return False diff --git a/pydantic/config.py b/pydantic/config.py index ed18d866253..f0ef8cbc2d7 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -734,30 +734,6 @@ class Model(BaseModel): This can be useful to avoid the overhead of building models which are only used nested within other models, or when you want to manually define type namespace via [`Model.model_rebuild(_types_namespace=...)`][pydantic.BaseModel.model_rebuild]. - - See also [`experimental_defer_build_mode`][pydantic.config.ConfigDict.experimental_defer_build_mode]. - - !!! note - `defer_build` does not work by default with FastAPI Pydantic models. By default, the validator and serializer - for said models is constructed immediately for FastAPI routes. You also need to define - [`experimental_defer_build_mode=('model', 'type_adapter')`][pydantic.config.ConfigDict.experimental_defer_build_mode] with FastAPI - models in order for `defer_build=True` to take effect. This additional (experimental) parameter is required for - the deferred building due to FastAPI relying on `TypeAdapter`s. - """ - - experimental_defer_build_mode: tuple[Literal['model', 'type_adapter'], ...] - """ - Controls when [`defer_build`][pydantic.config.ConfigDict.defer_build] is applicable. Defaults to `('model',)`. - - Due to backwards compatibility reasons [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] does not by default - respect `defer_build`. Meaning when `defer_build` is `True` and `experimental_defer_build_mode` is the default `('model',)` - then `TypeAdapter` immediately constructs its validator and serializer instead of postponing said construction until - the first model validation. Set this to `('model', 'type_adapter')` to make `TypeAdapter` respect the `defer_build` - so it postpones validator and serializer construction until the first validation or serialization. - - !!! note - The `experimental_defer_build_mode` parameter is named with an underscore to suggest this is an experimental feature. It may - be removed or changed in the future in a minor release. """ plugin_settings: dict[str, object] | None diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py index 75e558f6f12..b6d5735709a 100644 --- a/pydantic/type_adapter.py +++ b/pydantic/type_adapter.py @@ -157,13 +157,6 @@ class TypeAdapter(Generic[T]): **Note:** `TypeAdapter` instances are not types, and cannot be used as type annotations for fields. - **Note:** By default, `TypeAdapter` does not respect the - [`defer_build=True`][pydantic.config.ConfigDict.defer_build] setting in the - [`model_config`][pydantic.BaseModel.model_config] or in the `TypeAdapter` constructor `config`. You need to also - explicitly set [`experimental_defer_build_mode=('model', 'type_adapter')`][pydantic.config.ConfigDict.experimental_defer_build_mode] of the - config to defer the model validator and serializer construction. Thus, this feature is opt-in to ensure backwards - compatibility. - Attributes: core_schema: The core schema for the type. validator (SchemaValidator): The schema validator for the type. @@ -329,7 +322,9 @@ def serializer(self) -> SchemaSerializer: def _defer_build(self) -> bool: config = self._config if self._config is not None else self._model_config() - return self._is_defer_build_config(config) if config is not None else False + if config: + return config.get('defer_build') is True + return False def _model_config(self) -> ConfigDict | None: type_: Any = _typing_extra.annotated_type(self._type) or self._type # Eg FastAPI heavily uses Annotated @@ -337,14 +332,6 @@ def _model_config(self) -> ConfigDict | None: return type_.model_config return getattr(type_, '__pydantic_config__', None) - @staticmethod - def _is_defer_build_config(config: ConfigDict) -> bool: - # TODO reevaluate this logic when we have a better understanding of how defer_build should work with TypeAdapter - # Should we drop the special experimental_defer_build_mode check? - return config.get('defer_build', False) is True and 'type_adapter' in config.get( - 'experimental_defer_build_mode', () - ) - @_frame_depth(1) def validate_python( self, diff --git a/tests/test_config.py b/tests/test_config.py index 7dd3e14fdb8..c23dc8e0994 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,7 +4,7 @@ from contextlib import nullcontext as does_not_raise from decimal import Decimal from inspect import signature -from typing import Any, ContextManager, Dict, Iterable, NamedTuple, Optional, Tuple, Type, Union +from typing import Any, ContextManager, Dict, Iterable, NamedTuple, Optional, Type, Union from dirty_equals import HasRepr, IsPartialDict from pydantic_core import SchemaError, SchemaSerializer, SchemaValidator @@ -35,9 +35,9 @@ from .conftest import CallCounter if sys.version_info < (3, 9): - from typing_extensions import Annotated, Literal + from typing_extensions import Annotated else: - from typing import Annotated, Literal + from typing import Annotated import pytest @@ -700,26 +700,22 @@ def test_json_encoders_type_adapter() -> None: assert json.loads(ta.dump_json(1)) == '2' -@pytest.mark.parametrize('defer_build_mode', [None, tuple(), ('model',), ('type_adapter',), ('model', 'type_adapter')]) -def test_config_model_defer_build( - defer_build_mode: Optional[Tuple[Literal['model', 'type_adapter'], ...]], generate_schema_calls: CallCounter -): - config = ConfigDict(defer_build=True) - if defer_build_mode is not None: - config['experimental_defer_build_mode'] = defer_build_mode +@pytest.mark.parametrize('defer_build', [True, False]) +def test_config_model_defer_build(defer_build: bool, generate_schema_calls: CallCounter): + config = ConfigDict(defer_build=defer_build) class MyModel(BaseModel): model_config = config x: int - if defer_build_mode is None or 'model' in defer_build_mode: + if defer_build: assert isinstance(MyModel.__pydantic_validator__, MockValSer) assert isinstance(MyModel.__pydantic_serializer__, MockValSer) - assert generate_schema_calls.count == 0, 'Should respect experimental_defer_build_mode' + assert generate_schema_calls.count == 0, 'Should respect defer_build' else: assert isinstance(MyModel.__pydantic_validator__, SchemaValidator) assert isinstance(MyModel.__pydantic_serializer__, SchemaSerializer) - assert generate_schema_calls.count == 1, 'Should respect experimental_defer_build_mode' + assert generate_schema_calls.count == 1, 'Should respect defer_build' m = MyModel(x=1) assert m.x == 1 @@ -732,20 +728,15 @@ class MyModel(BaseModel): assert generate_schema_calls.count == 1, 'Should not build duplicated core schemas' -@pytest.mark.parametrize('defer_build_mode', [None, tuple(), ('model',), ('type_adapter',), ('model', 'type_adapter')]) -def test_config_model_type_adapter_defer_build( - defer_build_mode: Optional[Tuple[Literal['model', 'type_adapter'], ...]], generate_schema_calls: CallCounter -): - config = ConfigDict(defer_build=True) - if defer_build_mode is not None: - config['experimental_defer_build_mode'] = defer_build_mode +@pytest.mark.parametrize('defer_build', [True, False]) +def test_config_model_type_adapter_defer_build(defer_build: bool, generate_schema_calls: CallCounter): + config = ConfigDict(defer_build=defer_build) class MyModel(BaseModel): model_config = config x: int - is_deferred = defer_build_mode is None or 'model' in defer_build_mode - assert generate_schema_calls.count == (0 if is_deferred else 1) + assert generate_schema_calls.count == (0 if defer_build is True else 1) generate_schema_calls.reset() ta = TypeAdapter(MyModel) @@ -757,21 +748,16 @@ class MyModel(BaseModel): assert ta.dump_python(MyModel.model_construct(x=1))['x'] == 1 assert ta.json_schema()['type'] == 'object' - assert generate_schema_calls.count == (1 if is_deferred else 0), 'Should not build duplicate core schemas' + assert generate_schema_calls.count == (1 if defer_build is True else 0), 'Should not build duplicate core schemas' -@pytest.mark.parametrize('defer_build_mode', [None, tuple(), ('model',), ('type_adapter',), ('model', 'type_adapter')]) -def test_config_plain_type_adapter_defer_build( - defer_build_mode: Optional[Tuple[Literal['model', 'type_adapter'], ...]], generate_schema_calls: CallCounter -): - config = ConfigDict(defer_build=True) - if defer_build_mode is not None: - config['experimental_defer_build_mode'] = defer_build_mode - is_deferred = defer_build_mode is not None and 'type_adapter' in defer_build_mode +@pytest.mark.parametrize('defer_build', [True, False]) +def test_config_plain_type_adapter_defer_build(defer_build: bool, generate_schema_calls: CallCounter): + config = ConfigDict(defer_build=defer_build) ta = TypeAdapter(Dict[str, int], config=config) - assert generate_schema_calls.count == (0 if is_deferred else 1) + assert generate_schema_calls.count == (0 if defer_build else 1) generate_schema_calls.reset() assert ta.validate_python({}) == {} @@ -779,16 +765,12 @@ def test_config_plain_type_adapter_defer_build( assert ta.dump_python({'x': 2}) == {'x': 2} assert ta.json_schema()['type'] == 'object' - assert generate_schema_calls.count == (1 if is_deferred else 0), 'Should not build duplicate core schemas' + assert generate_schema_calls.count == (1 if defer_build else 0), 'Should not build duplicate core schemas' -@pytest.mark.parametrize('defer_build_mode', [None, ('model',), ('type_adapter',), ('model', 'type_adapter')]) -def test_config_model_defer_build_nested( - defer_build_mode: Optional[Tuple[Literal['model', 'type_adapter'], ...]], generate_schema_calls: CallCounter -): - config = ConfigDict(defer_build=True) - if defer_build_mode: - config['experimental_defer_build_mode'] = defer_build_mode +@pytest.mark.parametrize('defer_build', [True, False]) +def test_config_model_defer_build_nested(defer_build: bool, generate_schema_calls: CallCounter): + config = ConfigDict(defer_build=defer_build) assert generate_schema_calls.count == 0 @@ -802,10 +784,10 @@ class MyModel(BaseModel): assert isinstance(MyModel.__pydantic_validator__, SchemaValidator) assert isinstance(MyModel.__pydantic_serializer__, SchemaSerializer) - expected_schema_count = 1 if defer_build_mode is None or 'model' in defer_build_mode else 2 + expected_schema_count = 1 if defer_build is True else 2 assert generate_schema_calls.count == expected_schema_count, 'Should respect experimental_defer_build_mode' - if defer_build_mode is None or 'model' in defer_build_mode: + if defer_build: assert isinstance(MyNestedModel.__pydantic_validator__, MockValSer) assert isinstance(MyNestedModel.__pydantic_serializer__, MockValSer) else: @@ -818,7 +800,7 @@ class MyModel(BaseModel): assert m.model_validate({'y': {'x': 1}}).y.x == 1 assert m.model_json_schema()['type'] == 'object' - if defer_build_mode is None or 'model' in defer_build_mode: + if defer_build: assert isinstance(MyNestedModel.__pydantic_validator__, MockValSer) assert isinstance(MyNestedModel.__pydantic_serializer__, MockValSer) else: diff --git a/tests/test_type_adapter.py b/tests/test_type_adapter.py index 4773d3f779c..942e11ce6cf 100644 --- a/tests/test_type_adapter.py +++ b/tests/test_type_adapter.py @@ -19,8 +19,6 @@ NestedList = List[List[ItemType]] -DEFER_ENABLE_MODE = ('model', 'type_adapter') - class PydanticModel(BaseModel): x: int @@ -73,7 +71,7 @@ def test_types(tp: Any, val: Any, expected: Any): @pytest.mark.parametrize('defer_build', [False, True]) @pytest.mark.parametrize('method', ['validate', 'serialize', 'json_schema', 'json_schemas']) def test_global_namespace_variables(defer_build: bool, method: str, generate_schema_calls): - config = ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None + config = ConfigDict(defer_build=True) if defer_build else None ta = TypeAdapter(OuterDict, config=config) assert generate_schema_calls.count == (0 if defer_build else 1), 'Should be built deferred' @@ -94,9 +92,7 @@ def test_global_namespace_variables(defer_build: bool, method: str, generate_sch @pytest.mark.parametrize('method', ['validate', 'serialize', 'json_schema', 'json_schemas']) def test_model_global_namespace_variables(defer_build: bool, method: str, generate_schema_calls): class MyModel(BaseModel): - model_config = ( - ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None - ) + model_config = ConfigDict(defer_build=defer_build) x: OuterDict ta = TypeAdapter(MyModel) @@ -121,7 +117,7 @@ def test_local_namespace_variables(defer_build: bool, method: str, generate_sche IntList = List[int] # noqa: F841 OuterDict = Dict[str, 'IntList'] - config = ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None + config = ConfigDict(defer_build=True) if defer_build else None ta = TypeAdapter(OuterDict, config=config) assert generate_schema_calls.count == (0 if defer_build else 1), 'Should be built deferred' @@ -144,9 +140,7 @@ def test_model_local_namespace_variables(defer_build: bool, method: str, generat IntList = List[int] # noqa: F841 class MyModel(BaseModel): - model_config = ( - ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None - ) + model_config = ConfigDict(defer_build=defer_build) x: Dict[str, 'IntList'] ta = TypeAdapter(MyModel) @@ -169,7 +163,7 @@ class MyModel(BaseModel): @pytest.mark.parametrize('method', ['validate', 'serialize', 'json_schema', 'json_schemas']) @pytest.mark.skipif(sys.version_info < (3, 9), reason="ForwardRef doesn't accept module as a parameter in Python < 3.9") def test_top_level_fwd_ref(defer_build: bool, method: str, generate_schema_calls): - config = ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None + config = ConfigDict(defer_build=True) if defer_build else None FwdRef = ForwardRef('OuterDict', module=__name__) ta = TypeAdapter(FwdRef, config=config) @@ -397,7 +391,7 @@ class UnrelatedClass: def test_validate_strings( field_type, input_value, expected, raises_match, strict, defer_build: bool, generate_schema_calls ): - config = ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE) if defer_build else None + config = ConfigDict(defer_build=True) if defer_build else None ta = TypeAdapter(field_type, config=config) assert generate_schema_calls.count == (0 if defer_build else 1), 'Should be built deferred' @@ -518,10 +512,8 @@ class TypedDictModel(TypedDict): CONFIGS = [ - ConfigDict(defer_build=False, experimental_defer_build_mode=('model',)), - ConfigDict(defer_build=False, experimental_defer_build_mode=DEFER_ENABLE_MODE), - ConfigDict(defer_build=True, experimental_defer_build_mode=('model',)), - ConfigDict(defer_build=True, experimental_defer_build_mode=DEFER_ENABLE_MODE), + ConfigDict(defer_build=False), + ConfigDict(defer_build=True), ] MODELS_CONFIGS: List[Tuple[Any, ConfigDict]] = [ (model, config) for config in CONFIGS for model in defer_build_test_models(config) @@ -537,7 +529,7 @@ def test_core_schema_respects_defer_build(model: Any, config: ConfigDict, method type_adapter = TypeAdapter(model) if _type_has_config(model) else TypeAdapter(model, config=config) - if config['defer_build'] and 'type_adapter' in config['experimental_defer_build_mode']: + if config.get('defer_build'): assert generate_schema_calls.count == 0, 'Should be built deferred' assert type_adapter._core_schema is None, 'Should be initialized deferred' assert type_adapter._validator is None, 'Should be initialized deferred' From c19e3bd647e1c5a0de241a419b510faba32e647d Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Sat, 7 Sep 2024 17:45:27 +0200 Subject: [PATCH 010/412] Fix evaluation of stringified annotations during namespace inspection (#10347) --- pydantic/_internal/_model_construction.py | 9 +++++++-- tests/test_forward_ref.py | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 418bf731a74..0cea5dbc095 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -11,7 +11,7 @@ from abc import ABCMeta from functools import lru_cache, partial from types import FunctionType -from typing import Any, Callable, ForwardRef, Generic, Literal, NoReturn +from typing import Any, Callable, Generic, Literal, NoReturn import typing_extensions from pydantic_core import PydanticUndefined, SchemaSerializer @@ -30,6 +30,7 @@ from ._schema_generation_shared import CallbackGetCoreSchemaHandler from ._signature import generate_pydantic_signature from ._typing_extra import ( + _make_forward_ref, eval_type_backport, is_annotated, is_classvar, @@ -436,6 +437,8 @@ def inspect_namespace( # noqa C901 is_valid_privateattr_name(ann_name) and ann_name not in private_attributes and ann_name not in ignored_names + # This condition is a false negative when `ann_type` is stringified, + # but it is handled in `set_model_fields`: and not is_classvar(ann_type) and ann_type not in all_ignored_types and getattr(ann_type, '__module__', None) != 'functools' @@ -446,7 +449,9 @@ def inspect_namespace( # noqa C901 frame = sys._getframe(2) if frame is not None: ann_type = eval_type_backport( - ForwardRef(ann_type), globalns=frame.f_globals, localns=frame.f_locals + _make_forward_ref(ann_type, is_argument=False, is_class=True), + globalns=frame.f_globals, + localns=frame.f_locals, ) if is_annotated(ann_type): _, *metadata = typing_extensions.get_args(ann_type) diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py index d9280ff6be7..4d0e1e2b2da 100644 --- a/tests/test_forward_ref.py +++ b/tests/test_forward_ref.py @@ -581,10 +581,12 @@ def test_class_var_as_string(create_module): class Model(BaseModel): a: ClassVar[int] + _b: ClassVar[int] """ ) - assert module.Model.__class_vars__ == {'a'} + assert module.Model.__class_vars__ == {'a', '_b'} + assert module.Model.__private_attributes__ == {} def test_json_encoder_str(create_module): From cdca7aebba33770416c260335e914c1755c48f13 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Sat, 7 Sep 2024 18:01:59 +0200 Subject: [PATCH 011/412] Fix `IncEx` type alias definition (#10339) Co-authored-by: sydney-runkle --- pdm.lock | 184 +++++++++++++++++++-------------------- pydantic/main.py | 31 +++---- pydantic/type_adapter.py | 11 +-- pyproject.toml | 2 +- 4 files changed, 110 insertions(+), 118 deletions(-) diff --git a/pdm.lock b/pdm.lock index f88c5abf5b1..e0fbb38d96a 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra", "timezone"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:3a584a7e92099980d8871eb23c5549630f86f8e2284884ab3042dd043ddf50f8" +content_hash = "sha256:353e0a512023413e67aad3b741688cdb4c38fe003b8ce6cc28f9f0271d9d7d0a" [[metadata.targets]] requires_python = ">=3.8" @@ -1267,102 +1267,102 @@ files = [ [[package]] name = "pydantic-core" -version = "2.23.2" +version = "2.23.3" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, - {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, - {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, - {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, - {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, - {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, - {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, - {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, - {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, - {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, - {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, - {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, - {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, - {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, + {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, + {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, + {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, + {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, + {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, + {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, + {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, + {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, + {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, + {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, + {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, + {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, + {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, + {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, + {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, + {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, + {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, + {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, + {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, + {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, + {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, + {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, + {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, + {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, + {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, + {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, + {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, + {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, + {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, + {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"}, + {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, + {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, + {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, + {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, + {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, + {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, + {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"}, + {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, + {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, + {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, + {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, + {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, + {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, + {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, + {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, ] [[package]] @@ -1371,7 +1371,7 @@ version = "2.9.0" requires_python = ">=3.8" git = "https://github.com/pydantic/pydantic-extra-types.git" ref = "main" -revision = "c7db9d77c70ae8024edd4309c33e7f9548803b74" +revision = "55a01b2d619d371de5bc578db0fa359f5fddec03" summary = "Extra Pydantic types." dependencies = [ "pydantic>=2.5.2", diff --git a/pydantic/main.py b/pydantic/main.py index 4b3d8eff730..e8c21dcd4d1 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -51,13 +51,6 @@ from .plugin._schema_validator import PluggableSchemaValidator from .warnings import PydanticDeprecatedSince20 -# Always define certain types that are needed to resolve method type hints/annotations -# (even when not type checking) via typing.get_type_hints. -ModelT = TypeVar('ModelT', bound='BaseModel') -TupleGenerator = Generator[Tuple[str, Any], None, None] -IncEx: TypeAlias = Union[Set[int], Set[str], Dict[int, 'IncEx'], Dict[str, 'IncEx'], None] - - if TYPE_CHECKING: from inspect import Signature from pathlib import Path @@ -74,6 +67,11 @@ __all__ = 'BaseModel', 'create_model' +# Keep these type aliases available at runtime: +TupleGenerator: TypeAlias = Generator[Tuple[str, Any], None, None] +# Keep this type alias in sync with the stub definition in `pydantic-core`: +IncEx: TypeAlias = Union[Set[int], Set[str], Dict[int, Union['IncEx', bool]], Dict[str, Union['IncEx', bool]]] + _object_setattr = _model_construction.object_setattr @@ -352,8 +350,8 @@ def model_dump( self, *, mode: Literal['json', 'python'] | str = 'python', - include: IncEx = None, - exclude: IncEx = None, + include: IncEx | None = None, + exclude: IncEx | None = None, context: Any | None = None, by_alias: bool = False, exclude_unset: bool = False, @@ -405,8 +403,8 @@ def model_dump_json( self, *, indent: int | None = None, - include: IncEx = None, - exclude: IncEx = None, + include: IncEx | None = None, + exclude: IncEx | None = None, context: Any | None = None, by_alias: bool = False, exclude_unset: bool = False, @@ -1103,8 +1101,8 @@ def __fields_set__(self) -> set[str]: def dict( # noqa: D102 self, *, - include: IncEx = None, - exclude: IncEx = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, @@ -1124,8 +1122,8 @@ def dict( # noqa: D102 def json( # noqa: D102 self, *, - include: IncEx = None, - exclude: IncEx = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, @@ -1453,6 +1451,9 @@ def _calculate_keys(self, *args: Any, **kwargs: Any) -> Any: return copy_internals._calculate_keys(self, *args, **kwargs) +ModelT = TypeVar('ModelT', bound=BaseModel) + + @overload def create_model( model_name: str, diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py index b6d5735709a..5ce06d8485c 100644 --- a/pydantic/type_adapter.py +++ b/pydantic/type_adapter.py @@ -7,17 +7,13 @@ from dataclasses import is_dataclass from functools import cached_property, wraps from typing import ( - TYPE_CHECKING, Any, Callable, - Dict, Generic, Iterable, Iterator, Literal, - Set, TypeVar, - Union, cast, final, overload, @@ -27,7 +23,7 @@ from typing_extensions import Concatenate, ParamSpec, is_typeddict from pydantic.errors import PydanticUserError -from pydantic.main import BaseModel +from pydantic.main import BaseModel, IncEx from ._internal import _config, _generate_schema, _mock_val_ser, _typing_extra, _utils from .config import ConfigDict @@ -46,11 +42,6 @@ TypeAdapterT = TypeVar('TypeAdapterT', bound='TypeAdapter') -if TYPE_CHECKING: - # should be `set[int] | set[str] | dict[int, IncEx] | dict[str, IncEx] | None`, but mypy can't cope - IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] - - def _get_schema(type_: Any, config_wrapper: _config.ConfigWrapper, parent_depth: int) -> CoreSchema: """`BaseModel` uses its own `__module__` to find out where it was defined and then looks for symbols to resolve forward references in those globals. diff --git a/pyproject.toml b/pyproject.toml index 51cb5c9c3da..c065ed367fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dependencies = [ 'typing-extensions>=4.6.1; python_version < "3.13"', 'typing-extensions>=4.12.2; python_version >= "3.13"', 'annotated-types>=0.6.0', - "pydantic-core==2.23.2", + "pydantic-core==2.23.3", ] dynamic = ['version', 'readme'] From fa79d935b45afac09baf52ffde223cabe3254596 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Mon, 9 Sep 2024 03:41:24 -0500 Subject: [PATCH 012/412] Bump version to alpha on `main` (#10356) --- pydantic/aliases.py | 6 +++--- pydantic/config.py | 2 +- pydantic/dataclasses.py | 2 +- pydantic/fields.py | 6 +++--- pydantic/functional_validators.py | 12 ++++++------ pydantic/json_schema.py | 6 +++--- pydantic/main.py | 12 ++++++------ pydantic/plugin/__init__.py | 2 +- pydantic/root_model.py | 2 +- pydantic/type_adapter.py | 6 +++--- pydantic/types.py | 8 ++++---- pydantic/validate_call_decorator.py | 2 +- pydantic/version.py | 2 +- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/pydantic/aliases.py b/pydantic/aliases.py index 0325bedacbe..a9b806da05e 100644 --- a/pydantic/aliases.py +++ b/pydantic/aliases.py @@ -14,7 +14,7 @@ @dataclasses.dataclass(**_internal_dataclass.slots_true) class AliasPath: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/alias#aliaspath-and-aliaschoices + """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#aliaspath-and-aliaschoices A data class used by `validation_alias` as a convenience to create aliases. @@ -55,7 +55,7 @@ def search_dict_for_path(self, d: dict) -> Any: @dataclasses.dataclass(**_internal_dataclass.slots_true) class AliasChoices: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/alias#aliaspath-and-aliaschoices + """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#aliaspath-and-aliaschoices A data class used by `validation_alias` as a convenience to create aliases. @@ -85,7 +85,7 @@ def convert_to_aliases(self) -> list[list[str | int]]: @dataclasses.dataclass(**_internal_dataclass.slots_true) class AliasGenerator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/alias#using-an-aliasgenerator + """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#using-an-aliasgenerator A data class used by `alias_generator` as a convenience to create various aliases. diff --git a/pydantic/config.py b/pydantic/config.py index f0ef8cbc2d7..634cac0853f 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -981,7 +981,7 @@ class Model(BaseModel): def with_config(config: ConfigDict) -> Callable[[_TypeT], _TypeT]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/config/#configuration-with-dataclass-from-the-standard-library-or-typeddict + """Usage docs: https://docs.pydantic.dev/2.10/concepts/config/#configuration-with-dataclass-from-the-standard-library-or-typeddict A convenience decorator to set a [Pydantic configuration](config.md) on a `TypedDict` or a `dataclass` from the standard library. diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 1d30991f155..91946fbe98b 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -108,7 +108,7 @@ def dataclass( kw_only: bool = False, slots: bool = False, ) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/dataclasses/ + """Usage docs: https://docs.pydantic.dev/2.10/concepts/dataclasses/ A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclass`, but with added validation. diff --git a/pydantic/fields.py b/pydantic/fields.py index fb26f70b19d..1bd50ddeac5 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -721,7 +721,7 @@ def Field( # noqa: C901 fail_fast: bool | None = _Unset, **extra: Unpack[_EmptyKwargs], ) -> Any: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/fields + """Usage docs: https://docs.pydantic.dev/2.10/concepts/fields Create a field for objects that can be configured. @@ -959,7 +959,7 @@ def PrivateAttr( default_factory: typing.Callable[[], Any] | None = None, init: Literal[False] = False, ) -> Any: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/models/#private-model-attributes + """Usage docs: https://docs.pydantic.dev/2.10/concepts/models/#private-model-attributes Indicates that an attribute is intended for private use and not handled during normal validation/serialization. @@ -1084,7 +1084,7 @@ def computed_field( repr: bool | None = None, return_type: Any = PydanticUndefined, ) -> PropertyT | typing.Callable[[PropertyT], PropertyT]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/fields#the-computed_field-decorator + """Usage docs: https://docs.pydantic.dev/2.10/concepts/fields#the-computed_field-decorator Decorator to include `property` and `cached_property` when serializing models or dataclasses. diff --git a/pydantic/functional_validators.py b/pydantic/functional_validators.py index 8779f72b74b..674f3817bba 100644 --- a/pydantic/functional_validators.py +++ b/pydantic/functional_validators.py @@ -26,7 +26,7 @@ @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class AfterValidator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#annotated-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **after** the inner validation logic. @@ -86,7 +86,7 @@ def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValid @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class BeforeValidator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#annotated-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **before** the inner validation logic. @@ -152,7 +152,7 @@ def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValid @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class PlainValidator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#annotated-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **instead** of the inner validation logic. @@ -224,7 +224,7 @@ def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValid @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class WrapValidator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#annotated-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **around** the inner validation logic. @@ -385,7 +385,7 @@ def field_validator( check_fields: bool | None = None, json_schema_input_type: Any = PydanticUndefined, ) -> Callable[[Any], Any]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#field-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#field-validators Decorate methods on the class indicating that they should be used to validate fields. @@ -642,7 +642,7 @@ def model_validator( *, mode: Literal['wrap', 'before', 'after'], ) -> Any: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validators/#model-validators + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validators/#model-validators Decorate model methods for validation purposes. diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py index 64c659e8d22..e7167a5e082 100644 --- a/pydantic/json_schema.py +++ b/pydantic/json_schema.py @@ -231,7 +231,7 @@ def remap_json_schema(self, schema: Any) -> Any: class GenerateJsonSchema: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json_schema/#customizing-the-json-schema-generation-process + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json_schema/#customizing-the-json-schema-generation-process A class for generating JSON schemas. @@ -2369,7 +2369,7 @@ def _sort_json_schema(value: JsonSchemaValue, parent_key: str | None = None) -> @dataclasses.dataclass(**_internal_dataclass.slots_true) class WithJsonSchema: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json_schema/#withjsonschema-annotation + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json_schema/#withjsonschema-annotation Add this as an annotation on a field to override the (base) JSON schema that would be generated for that field. This provides a way to set a JSON schema for types that would otherwise raise errors when producing a JSON schema, @@ -2504,7 +2504,7 @@ def _get_all_json_refs(item: Any) -> set[JsonRef]: @dataclasses.dataclass(**_internal_dataclass.slots_true) class SkipJsonSchema: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json_schema/#skipjsonschema-annotation + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json_schema/#skipjsonschema-annotation Add this as an annotation on a field to skip generating a JSON schema for that field. diff --git a/pydantic/main.py b/pydantic/main.py index e8c21dcd4d1..89d19074b12 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -76,7 +76,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): - """Usage docs: https://docs.pydantic.dev/2.9/concepts/models/ + """Usage docs: https://docs.pydantic.dev/2.10/concepts/models/ A base class for creating Pydantic models. @@ -319,7 +319,7 @@ def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> return m def model_copy(self, *, update: dict[str, Any] | None = None, deep: bool = False) -> Self: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/serialization/#model_copy + """Usage docs: https://docs.pydantic.dev/2.10/concepts/serialization/#model_copy Returns a copy of the model. @@ -361,7 +361,7 @@ def model_dump( warnings: bool | Literal['none', 'warn', 'error'] = True, serialize_as_any: bool = False, ) -> dict[str, Any]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/serialization/#modelmodel_dump + """Usage docs: https://docs.pydantic.dev/2.10/concepts/serialization/#modelmodel_dump Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. @@ -414,7 +414,7 @@ def model_dump_json( warnings: bool | Literal['none', 'warn', 'error'] = True, serialize_as_any: bool = False, ) -> str: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/serialization/#modelmodel_dump_json + """Usage docs: https://docs.pydantic.dev/2.10/concepts/serialization/#modelmodel_dump_json Generates a JSON representation of the model using Pydantic's `to_json` method. @@ -602,7 +602,7 @@ def model_validate_json( strict: bool | None = None, context: Any | None = None, ) -> Self: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json/#json-parsing + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json/#json-parsing Validate the given JSON data against the Pydantic model. @@ -1497,7 +1497,7 @@ def create_model( # noqa: C901 __slots__: tuple[str, ...] | None = None, **field_definitions: Any, ) -> type[ModelT]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/models/#dynamic-model-creation + """Usage docs: https://docs.pydantic.dev/2.10/concepts/models/#dynamic-model-creation Dynamically creates and returns a new Pydantic model, in other words, `create_model` dynamically creates a subclass of [`BaseModel`][pydantic.BaseModel]. diff --git a/pydantic/plugin/__init__.py b/pydantic/plugin/__init__.py index 952f6031923..d821566012e 100644 --- a/pydantic/plugin/__init__.py +++ b/pydantic/plugin/__init__.py @@ -1,4 +1,4 @@ -"""Usage docs: https://docs.pydantic.dev/2.9/concepts/plugins#build-a-plugin +"""Usage docs: https://docs.pydantic.dev/2.10/concepts/plugins#build-a-plugin Plugin interface for Pydantic plugins, and related types. """ diff --git a/pydantic/root_model.py b/pydantic/root_model.py index b408c5c8fa8..e050ce02fb7 100644 --- a/pydantic/root_model.py +++ b/pydantic/root_model.py @@ -33,7 +33,7 @@ class _RootModelMetaclass(_model_construction.ModelMetaclass): ... class RootModel(BaseModel, typing.Generic[RootModelRootType], metaclass=_RootModelMetaclass): - """Usage docs: https://docs.pydantic.dev/2.9/concepts/models/#rootmodel-and-custom-root-types + """Usage docs: https://docs.pydantic.dev/2.10/concepts/models/#rootmodel-and-custom-root-types A Pydantic `BaseModel` for the root object of the model. diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py index 5ce06d8485c..461fad8bfa0 100644 --- a/pydantic/type_adapter.py +++ b/pydantic/type_adapter.py @@ -139,7 +139,7 @@ def wrapped(self: TypeAdapterT, *args: P.args, **kwargs: P.kwargs) -> R: @final class TypeAdapter(Generic[T]): - """Usage docs: https://docs.pydantic.dev/2.9/concepts/type_adapter/ + """Usage docs: https://docs.pydantic.dev/2.10/concepts/type_adapter/ Type adapters provide a flexible way to perform validation and serialization based on a Python type. @@ -354,7 +354,7 @@ def validate_python( def validate_json( self, data: str | bytes, /, *, strict: bool | None = None, context: dict[str, Any] | None = None ) -> T: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json/#json-parsing + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json/#json-parsing Validate a JSON string or bytes against the model. @@ -466,7 +466,7 @@ def dump_json( serialize_as_any: bool = False, context: dict[str, Any] | None = None, ) -> bytes: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/json/#json-serialization + """Usage docs: https://docs.pydantic.dev/2.10/concepts/json/#json-serialization Serialize an instance of the adapted type to JSON. diff --git a/pydantic/types.py b/pydantic/types.py index 58b7899645f..ac5958825d7 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -112,7 +112,7 @@ @_dataclasses.dataclass class Strict(_fields.PydanticMetadata, BaseMetadata): - """Usage docs: https://docs.pydantic.dev/2.9/concepts/strict_mode/#strict-mode-with-annotated-strict + """Usage docs: https://docs.pydantic.dev/2.10/concepts/strict_mode/#strict-mode-with-annotated-strict A field metadata class to indicate that a field should be validated in strict mode. Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. @@ -687,7 +687,7 @@ def conbytes( @_dataclasses.dataclass(frozen=True) class StringConstraints(annotated_types.GroupedMetadata): - """Usage docs: https://docs.pydantic.dev/2.9/concepts/fields/#string-constraints + """Usage docs: https://docs.pydantic.dev/2.10/concepts/fields/#string-constraints A field metadata class to apply constraints to `str` types. Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. @@ -2627,7 +2627,7 @@ class Model(BaseModel): @_dataclasses.dataclass(**_internal_dataclass.slots_true) class GetPydanticSchema: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/types/#using-getpydanticschema-to-reduce-boilerplate + """Usage docs: https://docs.pydantic.dev/2.10/concepts/types/#using-getpydanticschema-to-reduce-boilerplate A convenience class for creating an annotation that provides pydantic custom type hooks. @@ -2763,7 +2763,7 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH @_dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) class Discriminator: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/unions/#discriminated-unions-with-callable-discriminator + """Usage docs: https://docs.pydantic.dev/2.10/concepts/unions/#discriminated-unions-with-callable-discriminator Provides a way to use a custom callable as the way to extract the value of a union discriminator. diff --git a/pydantic/validate_call_decorator.py b/pydantic/validate_call_decorator.py index 1151694515d..fef84af912c 100644 --- a/pydantic/validate_call_decorator.py +++ b/pydantic/validate_call_decorator.py @@ -32,7 +32,7 @@ def validate_call( config: ConfigDict | None = None, validate_return: bool = False, ) -> AnyCallableT | Callable[[AnyCallableT], AnyCallableT]: - """Usage docs: https://docs.pydantic.dev/2.9/concepts/validation_decorator/ + """Usage docs: https://docs.pydantic.dev/2.10/concepts/validation_decorator/ Returns a decorated wrapper around the function that validates the arguments and, optionally, the return value. diff --git a/pydantic/version.py b/pydantic/version.py index 42e19011e41..8f26eaa3791 100644 --- a/pydantic/version.py +++ b/pydantic/version.py @@ -4,7 +4,7 @@ __all__ = 'VERSION', 'version_info' -VERSION = '2.9.0' +VERSION = '2.10.0a1' """The version of Pydantic.""" From 3bd74349ecd64217437bdc96e23807d1744a400d Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:04:47 +0200 Subject: [PATCH 013/412] Do not error when trying to evaluate annotations of private attributes (#10358) --- pydantic/_internal/_model_construction.py | 14 +++++++++----- tests/test_forward_ref.py | 5 ++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 0cea5dbc095..c904daa2da9 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -448,11 +448,15 @@ def inspect_namespace( # noqa C901 # (as the model class wasn't created yet, we unfortunately can't use `cls.__module__`): frame = sys._getframe(2) if frame is not None: - ann_type = eval_type_backport( - _make_forward_ref(ann_type, is_argument=False, is_class=True), - globalns=frame.f_globals, - localns=frame.f_locals, - ) + try: + ann_type = eval_type_backport( + _make_forward_ref(ann_type, is_argument=False, is_class=True), + globalns=frame.f_globals, + localns=frame.f_locals, + ) + except (NameError, TypeError): + pass + if is_annotated(ann_type): _, *metadata = typing_extensions.get_args(ann_type) private_attr = next((v for v in metadata if isinstance(v, ModelPrivateAttr)), None) diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py index 4d0e1e2b2da..688b7e56b17 100644 --- a/tests/test_forward_ref.py +++ b/tests/test_forward_ref.py @@ -582,10 +582,13 @@ def test_class_var_as_string(create_module): class Model(BaseModel): a: ClassVar[int] _b: ClassVar[int] + _c: ClassVar[Forward] + +Forward = int """ ) - assert module.Model.__class_vars__ == {'a', '_b'} + assert module.Model.__class_vars__ == {'a', '_b', '_c'} assert module.Model.__private_attributes__ == {} From 8138311e4c779519f8e981aaef2be1cef328fee1 Mon Sep 17 00:00:00 2001 From: kc0506 <89458301+kc0506@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:50:48 +0800 Subject: [PATCH 014/412] Fix nested type statement (#10369) --- pydantic/_internal/_model_construction.py | 11 +++++++++-- tests/test_edge_cases.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index c904daa2da9..015bcdf56e4 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -741,7 +741,7 @@ def unpack_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | N def default_ignored_types() -> tuple[type[Any], ...]: from ..fields import ComputedFieldInfo - return ( + ignored_types = [ FunctionType, property, classmethod, @@ -749,4 +749,11 @@ def default_ignored_types() -> tuple[type[Any], ...]: PydanticDescriptorProxy, ComputedFieldInfo, ValidateCallWrapper, - ) + ] + + if sys.version_info >= (3, 12): + from typing import TypeAliasType + + ignored_types.append(TypeAliasType) + + return tuple(ignored_types) diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 933185e3aef..01d28896289 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -2808,3 +2808,21 @@ def test_model_metaclass_on_other_class() -> None: class OtherClass(metaclass=ModelMetaclass): pass + + +@pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+') +def test_nested_type_statement(): + # https://docs.python.org/3/reference/simple_stmts.html#type + + globs = {} + exec( + """ +from pydantic import BaseModel +class A(BaseModel): + type Int = int + a: Int +""", + globs, + ) + A = globs['A'] + assert A(a=1).a == 1 From 287c266cc39a5a9f1cafa6b7f5e37d8050bb493a Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:38:45 +0200 Subject: [PATCH 015/412] Simplify unions involving `Any` or `Never` when replacing type variables (#10338) --- pydantic/_internal/_generics.py | 12 +++++++++++- tests/test_generics.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py index fad5f9479b2..69d68a82e32 100644 --- a/pydantic/_internal/_generics.py +++ b/pydantic/_internal/_generics.py @@ -275,7 +275,6 @@ def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any: type_args = get_args(type_) origin_type = get_origin(type_) - if origin_type is typing_extensions.Annotated: annotated_type, *annotations = type_args annotated = replace_types(annotated_type, type_map) @@ -291,6 +290,7 @@ def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any: # If all arguments are the same, there is no need to modify the # type or create a new object at all return type_ + if ( origin_type is not None and isinstance(type_, typing_base) @@ -302,6 +302,16 @@ def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any: # See: https://www.python.org/dev/peps/pep-0585 origin_type = getattr(typing, type_._name) assert origin_type is not None + + if origin_type is typing.Union or sys.version_info >= (3, 10) and origin_type is types.UnionType: + if any(arg is Any for arg in resolved_type_args): + # `Any | T` ~ `Any`: + resolved_type_args = (Any,) + # `Never | T` ~ `T`: + resolved_type_args = tuple( + arg for arg in resolved_type_args if arg not in (typing.NoReturn, typing_extensions.Never) + ) + # PEP-604 syntax (Ex.: list | str) is represented with a types.UnionType object that does not have __getitem__. # We also cannot use isinstance() since we have to compare types. if sys.version_info >= (3, 10) and origin_type is types.UnionType: diff --git a/tests/test_generics.py b/tests/test_generics.py index 2980af5d6c3..3f5102733d1 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -36,6 +36,7 @@ from typing_extensions import ( Annotated, Literal, + Never, NotRequired, ParamSpec, TypedDict, @@ -2875,3 +2876,16 @@ def test_generic_with_allow_extra(): # This used to raise an error related to accessing the __annotations__ attribute of the Generic class class AllowExtraGeneric(BaseModel, Generic[T], extra='allow'): data: T + + +def test_generic_any_or_never() -> None: + T = TypeVar('T') + + class GenericModel(BaseModel, Generic[T]): + f: Union[T, int] + + any_json_schema = GenericModel[Any].model_json_schema() + assert any_json_schema['properties']['f'] == {'title': 'F'} # any type + + never_json_schema = GenericModel[Never].model_json_schema() + assert never_json_schema['properties']['f'] == {'type': 'integer', 'title': 'F'} From cb57c131aef2b1988be8d93a6e7874ec9e12784c Mon Sep 17 00:00:00 2001 From: kc0506 <89458301+kc0506@users.noreply.github.com> Date: Wed, 11 Sep 2024 01:15:18 +0800 Subject: [PATCH 016/412] Fix `mro` of generic subclass (#10100) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- pydantic/_internal/_model_construction.py | 56 ++++++++ pydantic/main.py | 7 +- tests/test_generics.py | 156 +++++++++++++++++++++- 3 files changed, 215 insertions(+), 4 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 015bcdf56e4..92bd93ae394 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -133,6 +133,8 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: namespace['__class_vars__'] = class_vars namespace['__private_attributes__'] = {**base_private_attributes, **private_attributes} + if __pydantic_generic_metadata__: + namespace['__pydantic_generic_metadata__'] = __pydantic_generic_metadata__ cls: type[BaseModel] = super().__new__(mcs, cls_name, bases, namespace, **kwargs) # type: ignore @@ -251,6 +253,60 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: namespace.get('__annotations__', {}).clear() return super().__new__(mcs, cls_name, bases, namespace, **kwargs) + def mro(cls): + original_mro = super().mro() + + if cls.__bases__ == (object,): + return original_mro + + generic_metadata = cls.__dict__.get('__pydantic_generic_metadata__', None) + if not generic_metadata: + return original_mro + + assert_err_msg = 'Unexpected error occurred when generating MRO of generic subclass. Please report this issue on github: https://github.com/pydantic/pydantic/issues.' + + origin: type[BaseModel] | None + origin, args = ( + generic_metadata['origin'], + generic_metadata['args'], + ) + if not origin: + return original_mro + + target_params = origin.__pydantic_generic_metadata__['parameters'] + param_dict = dict(zip(target_params, args)) + + indexed_origins = {origin} + + new_mro: list[type] = [cls] + for base in original_mro[1:]: + base_origin = getattr(base, '__pydantic_generic_metadata__', {}).get('origin', None) + base_params = getattr(base, '__pydantic_generic_metadata__', {}).get('parameters', ()) + + if base_origin in indexed_origins: + continue + elif base not in indexed_origins and base_params: + assert set(base_params) <= param_dict.keys(), assert_err_msg + new_base_args = tuple(param_dict[param] for param in base_params) + new_base = base[new_base_args] # type: ignore + new_mro.append(new_base) + + indexed_origins.add(base_origin or base) + + if base_origin is not None: + # dropped previous indexed origins + continue + else: + indexed_origins.add(base_origin or base) + + # Avoid redundunt case such as + # class A(BaseModel, Generic[T]): ... + # A[T] is A # True + if base is not new_mro[-1]: + new_mro.append(base) + + return new_mro + if not typing.TYPE_CHECKING: # pragma: no branch # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access diff --git a/pydantic/main.py b/pydantic/main.py index 89d19074b12..5a9fba65979 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -752,13 +752,14 @@ def __class_getitem__( ) # use dict as ordered set with _generics.generic_recursion_self_type(origin, args) as maybe_self_type: - if maybe_self_type is not None: - return maybe_self_type - + # Check cached first otherwise `mro` may return `PydanticRecursiveRef` cached = _generics.get_cached_generic_type_late(cls, typevar_values, origin, args) if cached is not None: return cached + if maybe_self_type is not None: + return maybe_self_type + # Attempt to rebuild the origin in case new types have been defined try: # depth 3 gets you above this __class_getitem__ call diff --git a/tests/test_generics.py b/tests/test_generics.py index 3f5102733d1..5e067a62abc 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -476,7 +476,10 @@ class M(BaseModel): Working = B[m] generics.append(Working) - target_size = cache_size + count_create_models * 3 + 2 + # A[int, C] -> 2 caches + # A[int, C][M] -> 3 caches // this is generated by `mro` + # B[M] -> 3 caches + target_size = cache_size + count_create_models * 3 * 2 + 2 assert len(_GENERIC_TYPES_CACHE) < target_size + _LIMITED_DICT_SIZE del models del generics @@ -2878,6 +2881,157 @@ class AllowExtraGeneric(BaseModel, Generic[T], extra='allow'): data: T +def test_generic_mro_single(): + T = TypeVar('T') + + # 1. + class A(BaseModel, Generic[T]): ... + + class B(A, Generic[T]): ... + + assert B[int].__mro__ == (B[int], B, A[int], A, BaseModel, Generic, object) + + # 2. + class A(BaseModel, Generic[T]): ... + + class B(A[T]): ... + + class C(B[int]): ... + + assert B.__mro__ == (B, A, BaseModel, Generic, object) + assert C.__mro__ == (C, B[int], B, A[int], A, BaseModel, Generic, object) + + # 3. + class A(BaseModel, Generic[T]): ... + + class B(A[T], Generic[T]): ... + + class C(B[T], Generic[T]): ... + + assert B.__mro__ == (B, A, BaseModel, Generic, object) + assert B[int].__mro__ == (B[int], B, A[int], A, BaseModel, Generic, object) + assert C[int].__mro__ == (C[int], C, B[int], B, A[int], A, BaseModel, Generic, object) + + # 4. + class A(BaseModel, Generic[T]): ... + + class B(A[int], Generic[T]): ... + + class C(B[str], Generic[T]): ... + + assert B.__mro__ == (B, A[int], A, BaseModel, Generic, object) + assert C.__mro__ == (C, B[str], B, A[int], A, BaseModel, Generic, object) + + +def test_generic_mro_multi(): + T1 = TypeVar('T1') + T2 = TypeVar('T2') + T3 = TypeVar('T3') + + # 1. + class A(BaseModel, Generic[T1, T2]): ... + + class B(A[int, T2], BaseModel, Generic[T2]): ... + + assert A[int, T2][str].__mro__ == (A[int, T2][str], A, BaseModel, Generic, object) + assert B[str].__mro__ == (B[str], B, A[int, T2][str], A, BaseModel, Generic, object) + + # 2. + class A(BaseModel, Generic[T1, T2]): ... + + B = A[int, T2] + C = B[str] + + assert B.__mro__ == (A[int, T2], A, BaseModel, Generic, object) + assert C.__mro__ == (B[str], A, BaseModel, Generic, object) + + # 3. + + class A(BaseModel, Generic[T1]): ... + + class B1(A[T1], Generic[T1, T2]): ... + + class B2(A[T2], Generic[T1, T2]): ... + + assert B1[T1, str].__mro__ == (B1[T1, str], B1, A, BaseModel, Generic, object) + assert B2[T1, str].__mro__ == (B2[T1, str], B2, A[str], A, BaseModel, Generic, object) + + assert B1[T1, str][int].__mro__ == (B1[T1, str][int], B1, A[int], A, BaseModel, Generic, object) + assert B2[T1, str][int].__mro__ == (B2[T1, str][int], B2, A[str], A, BaseModel, Generic, object) + + assert B1[int, str].__mro__ == (B1[int, str], B1, A[int], A, BaseModel, Generic, object) + assert B2[int, str].__mro__ == (B2[int, str], B2, A[str], A, BaseModel, Generic, object) + + # 4. + + class A(BaseModel, Generic[T1]): ... + + class B(A[T2], Generic[T2]): ... + + class C1(B[T3], Generic[T3, T1]): ... + + class C2(B[T3], Generic[T3, T2]): ... + + assert C1[int, str].__mro__ == (C1[int, str], C1, B[int], B, A[int], A, BaseModel, Generic, object) + assert C2[int, str].__mro__ == (C2[int, str], C2, B[int], B, A[int], A, BaseModel, Generic, object) + + +def test_generic_mro_circular_typevar(): + T1 = TypeVar('T1') + T2 = TypeVar('T2') + T3 = TypeVar('T3') + + class A(BaseModel, Generic[T1]): ... + + class B(A[T2], Generic[T2]): ... + + class C(B[T3], Generic[T3]): ... + + class D(C[T1], Generic[T1]): ... + + class E(D[T2], Generic[T2]): ... + + assert E[int].__mro__ == (E[int], E, D[int], D, C[int], C, B[int], B, A[int], A, BaseModel, Generic, object) + + +def test_generic_mro_swap(): + T1 = TypeVar('T1') + T2 = TypeVar('T2') + + class A(BaseModel, Generic[T1]): ... + + class B(A[T1], Generic[T1, T2]): ... + + class BSwap(B[T2, T1], Generic[T1, T2]): ... + + C = BSwap[T1, str] + + assert C.__mro__ == (C, BSwap, B[str, T1], B, A[str], A, BaseModel, Generic, object) + assert C[int].__mro__ == (C[int], BSwap, B[str, int], B, A[str], A, BaseModel, Generic, object) + + assert BSwap[int, str].__mro__[1:] == C[int].__mro__[1:] + + +def test_generic_field(): + # https://github.com/pydantic/pydantic/issues/10039 + + T = TypeVar('T') + + class A(BaseModel, Generic[T]): ... + + class B(A[T]): ... + + class C(B[bool]): ... + + class Model(BaseModel): + input_bool: A[bool] + + Model(input_bool=C()) + + assert C.__mro__ == (C, B[bool], B, A[bool], A, BaseModel, Generic, object) + assert issubclass(C, A[bool]) and issubclass(C, A[int]) is False + + def test_generic_any_or_never() -> None: T = TypeVar('T') From 204e109691c69583e656c6e16a62ad79da2f59b9 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:43:50 +0200 Subject: [PATCH 017/412] Improve typing of `ModelMetaclass.mro` (#10372) --- pydantic/_internal/_model_construction.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 92bd93ae394..cfae217454a 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -253,17 +253,17 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: namespace.get('__annotations__', {}).clear() return super().__new__(mcs, cls_name, bases, namespace, **kwargs) - def mro(cls): + def mro(cls) -> list[type[Any]]: original_mro = super().mro() if cls.__bases__ == (object,): return original_mro - generic_metadata = cls.__dict__.get('__pydantic_generic_metadata__', None) + generic_metadata: PydanticGenericMetadata | None = cls.__dict__.get('__pydantic_generic_metadata__') if not generic_metadata: return original_mro - assert_err_msg = 'Unexpected error occurred when generating MRO of generic subclass. Please report this issue on github: https://github.com/pydantic/pydantic/issues.' + assert_err_msg = 'Unexpected error occurred when generating MRO of generic subclass. Please report this issue on GitHub: https://github.com/pydantic/pydantic/issues.' origin: type[BaseModel] | None origin, args = ( @@ -278,10 +278,12 @@ def mro(cls): indexed_origins = {origin} - new_mro: list[type] = [cls] + new_mro: list[type[Any]] = [cls] for base in original_mro[1:]: - base_origin = getattr(base, '__pydantic_generic_metadata__', {}).get('origin', None) - base_params = getattr(base, '__pydantic_generic_metadata__', {}).get('parameters', ()) + base_origin: type[BaseModel] | None = getattr(base, '__pydantic_generic_metadata__', {}).get('origin') + base_params: tuple[type[Any], ...] = getattr(base, '__pydantic_generic_metadata__', {}).get( + 'parameters', () + ) if base_origin in indexed_origins: continue From a031acd450da71fae356711f7bd58774d95c6569 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:03:48 +0200 Subject: [PATCH 018/412] Add Victorien to the list of authors (#10393) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c065ed367fa..66b1470426a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ authors = [ {name = 'Sydney Runkle', email = 'sydneymarierunkle@gmail.com'}, {name = 'David Hewitt', email = 'mail@davidhewitt.io'}, {name = 'Alex Hall', email='alex.mojaki@gmail.com'}, + {name = 'Victorien Plot', email='contact@vctrn.dev'}, ] license = 'MIT' classifiers = [ From 8b6d5fc48541b7411b5ebfe74c8e5f6951f74203 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:00:31 +0200 Subject: [PATCH 019/412] Fix class access of deprecated computed fields (#10391) --- pydantic/_internal/_model_construction.py | 6 ++++-- tests/test_deprecated_fields.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index cfae217454a..11ee41aa061 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -690,7 +690,7 @@ def set_deprecated_descriptors(cls: type[BaseModel]) -> None: class _DeprecatedFieldDescriptor: - """Data descriptor used to emit a runtime deprecation warning before accessing a deprecated field. + """Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field. Attributes: msg: The deprecation message to be emitted. @@ -709,6 +709,8 @@ def __set_name__(self, cls: type[BaseModel], name: str) -> None: def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: + if self.wrapped_property is not None: + return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, builtins.DeprecationWarning, stacklevel=2) @@ -717,7 +719,7 @@ def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name] - # Defined to take precedence over the instance's dictionary + # Defined to make it a data descriptor and take precedence over the instance's dictionary. # Note that it will not be called when setting a value on a model instance # as `BaseModel.__setattr__` is defined and takes priority. def __set__(self, obj: Any, value: Any) -> NoReturn: diff --git a/tests/test_deprecated_fields.py b/tests/test_deprecated_fields.py index 43559a9e54a..7835127b913 100644 --- a/tests/test_deprecated_fields.py +++ b/tests/test_deprecated_fields.py @@ -229,3 +229,24 @@ class Model(BaseModel): instance = Model(a=1, b=1) pytest.warns(DeprecationWarning, lambda: instance.a, match='deprecated') + + +def test_computed_field_deprecated_class_access() -> None: + class Model(BaseModel): + @computed_field(deprecated=True) + def prop(self) -> int: + return 1 + + assert isinstance(Model.prop, property) + + +def test_computed_field_deprecated_subclass() -> None: + """https://github.com/pydantic/pydantic/issues/10384""" + + class Base(BaseModel): + @computed_field(deprecated=True) + def prop(self) -> int: + return 1 + + class Sub(Base): + pass From 1cb0a8b19fa5742b1bc0d0747f95dda1cc8c8cd4 Mon Sep 17 00:00:00 2001 From: Movis Li <37662018+MovisLi@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:20:11 +0800 Subject: [PATCH 020/412] Make sure `inspect.iscoroutinefunction` works on coroutines decorated with `@validate_call` (#10374) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- pydantic/validate_call_decorator.py | 15 +++++++++++---- tests/test_validate_call.py | 3 +++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pydantic/validate_call_decorator.py b/pydantic/validate_call_decorator.py index fef84af912c..509a7463c5e 100644 --- a/pydantic/validate_call_decorator.py +++ b/pydantic/validate_call_decorator.py @@ -3,6 +3,7 @@ from __future__ import annotations as _annotations import functools +import inspect from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload from ._internal import _typing_extra, _validate_call @@ -55,12 +56,18 @@ def validate(function: AnyCallableT) -> AnyCallableT: validate_call_wrapper = _validate_call.ValidateCallWrapper(function, config, validate_return, local_ns) - @functools.wraps(function) - def wrapper_function(*args, **kwargs): - return validate_call_wrapper(*args, **kwargs) + if inspect.iscoroutinefunction(function): - wrapper_function.raw_function = function # type: ignore + @functools.wraps(function) + async def wrapper_function(*args, **kwargs): # type: ignore + return await validate_call_wrapper(*args, **kwargs) + else: + + @functools.wraps(function) + def wrapper_function(*args, **kwargs): + return validate_call_wrapper(*args, **kwargs) + wrapper_function.raw_function = function # type: ignore return wrapper_function # type: ignore if func: diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py index 12547f5a4f7..e0a10504c99 100644 --- a/tests/test_validate_call.py +++ b/tests/test_validate_call.py @@ -292,6 +292,9 @@ async def run(): v = await foo(1, 2) assert v == 'a=1 b=2' + # insert_assert(inspect.iscoroutinefunction(foo) is True) + assert inspect.iscoroutinefunction(foo) is True + asyncio.run(run()) with pytest.raises(ValidationError) as exc_info: asyncio.run(foo('x')) From 07beaffb154b376976f800c374eeccacd0dbc0f2 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:56:58 -0500 Subject: [PATCH 021/412] Updating `FieldInfo` note in `create_model` documentation (#10399) --- pydantic/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydantic/main.py b/pydantic/main.py index 5a9fba65979..5a1fff123e9 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -1518,6 +1518,8 @@ def create_model( # noqa: C901 **field_definitions: Attributes of the new model. They should be passed in the format: `=(, )`, `=(, )`, or `typing.Annotated[, ]`. Any additional metadata in `typing.Annotated[, , ...]` will be ignored. + Note, `FieldInfo` instances should be created via `pydantic.Field(...)`. + Initializing `FieldInfo` instances directly is not supported. Returns: The new [model][pydantic.BaseModel]. From 899ff2ca81ead76b66f1bd4ede7639343ce6e22c Mon Sep 17 00:00:00 2001 From: kc0506 <89458301+kc0506@users.noreply.github.com> Date: Fri, 13 Sep 2024 02:58:15 +0800 Subject: [PATCH 022/412] Fix `NameError` when using `validate_call` with PEP 695 on a class (#10380) --- pydantic/_internal/_fields.py | 1 - pydantic/_internal/_validate_call.py | 2 +- tests/test_validate_call.py | 77 ++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 170464016f7..216b2a5efaa 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -220,7 +220,6 @@ def collect_model_fields( # noqa: C901 field.apply_typevars_map(typevars_map, types_namespace) _update_fields_from_docstrings(cls, fields, config_wrapper) - return fields, class_vars diff --git a/pydantic/_internal/_validate_call.py b/pydantic/_internal/_validate_call.py index 52f6593e70b..778d61688c7 100644 --- a/pydantic/_internal/_validate_call.py +++ b/pydantic/_internal/_validate_call.py @@ -46,7 +46,7 @@ def __init__( # TODO: this is a bit of a hack, we should probably have a better way to handle this # specifically, we shouldn't be pumping the namespace full of type_params # when we take namespace and type_params arguments in eval_type_backport - type_params = getattr(schema_type, '__type_params__', ()) + type_params = (namespace or {}).get('__type_params__', ()) + getattr(schema_type, '__type_params__', ()) namespace = { **{param.__name__: param for param in type_params}, **(global_ns or {}), diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py index e0a10504c99..f9be931507a 100644 --- a/tests/test_validate_call.py +++ b/tests/test_validate_call.py @@ -851,6 +851,83 @@ def find_max_validate_return[T](args: Iterable[T]) -> T: find_max(1) +@pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+ for PEP 695 syntax with generics') +def test_pep695_with_class(): + """Primarily to ensure that the syntax is accepted and doesn't raise a `NameError` with `T`. + The validation is not expected to work properly when parameterized at this point.""" + + for import_annotations in ('from __future__ import annotations', ''): + globs = {} + exec( + f""" +{import_annotations} +from pydantic import validate_call + +class A[T]: + @validate_call(validate_return=True) + def f(self, a: T) -> T: + return str(a) + + """, + globs, + ) + + A = globs['A'] + a = A[int]() + # these two are undesired behavior, but it's what happens now + assert a.f(1) == '1' + assert a.f('1') == '1' + + +@pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+ for PEP 695 syntax with generics') +def test_pep695_with_nested_scopes(): + """Nested scopes generally cannot be caught by `parent_frame_namespace`, + so currently this test is expected to fail. + """ + + globs = {} + exec( + """ +from __future__ import annotations +from pydantic import validate_call + +class A[T]: + def g(self): + @validate_call(validate_return=True) + def inner(a: T) -> T: ... + + def h[S](self): + @validate_call(validate_return=True) + def inner(a: T) -> S: ... + +""", + globs, + ) + A = globs['A'] + a = A[int]() + with pytest.raises(NameError): + a.g() + with pytest.raises(NameError): + a.h() + + with pytest.raises(NameError): + exec( + """ +from __future__ import annotations +from pydantic import validate_call + +class A[T]: + class B: + @validate_call(validate_return=True) + def f(a: T) -> T: ... + + class C[S]: + @validate_call(validate_return=True) + def f(a: T) -> S: ... + """, + ) + + class M0(BaseModel): z: int From a6dc87285f93f90c2d5c298ee7c52f5d7e878194 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:24:23 -0500 Subject: [PATCH 023/412] Adding notes on designing callable discriminators (#10400) --- docs/concepts/unions.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/concepts/unions.md b/docs/concepts/unions.md index aa8f8bf291e..ca77e9905ba 100644 --- a/docs/concepts/unions.md +++ b/docs/concepts/unions.md @@ -248,6 +248,19 @@ In the case of a `Union` with multiple models, sometimes there isn't a single un across all models that you can use as a discriminator. This is the perfect use case for a callable `Discriminator`. +!!! tip + When you're designing callable discriminators, remember that you might have to account + for both `dict` and model type inputs. This pattern is similar to that of `mode='before'` validators, + where you have to anticipate various forms of input. + + But wait! You ask, I only anticipate passing in `dict` types, why do I need to account for models? + Pydantic uses callable discriminators for serialization as well, at which point the input to your callable is + very likely to be a model instance. + + In the following examples, you'll see that the callable discriminators are designed to handle both `dict` and model inputs. + If you don't follow this practice, it's likely that you'll, in the best case, get warnings during serialization, + and in the worst case, get runtime errors during validation. + ```py from typing import Any, Literal, Union From 07bcf00ef6dbab8f937b8b2b9f8f5059620baf4f Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:27:58 +0200 Subject: [PATCH 024/412] Use `create_module` instead of `exec` in tests (#10401) --- tests/test_validate_call.py | 40 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py index f9be931507a..b13c121a7db 100644 --- a/tests/test_validate_call.py +++ b/tests/test_validate_call.py @@ -822,12 +822,12 @@ def foo(bar: 'list[int | str]') -> 'list[int | str]': @pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+ for PEP 695 syntax with generics') -def test_validate_call_with_pep_695_syntax() -> None: +def test_validate_call_with_pep_695_syntax(create_module) -> None: """Note: validate_call still doesn't work properly with generics, see https://github.com/pydantic/pydantic/issues/7796. This test is just to ensure that the syntax is accepted and doesn't raise a NameError.""" - globs = {} - exec( + + module = create_module( """ from typing import Iterable from pydantic import validate_call @@ -839,10 +839,10 @@ def find_max_no_validate_return[T](args: Iterable[T]) -> T: @validate_call(validate_return=True) def find_max_validate_return[T](args: Iterable[T]) -> T: return sorted(args, reverse=True)[0] - """, - globs, + """ ) - functions = [globs['find_max_no_validate_return'], globs['find_max_validate_return']] + + functions = [module.find_max_no_validate_return, module.find_max_validate_return] for find_max in functions: assert len(find_max.__type_params__) == 1 assert find_max([1, 2, 10, 5]) == 10 @@ -852,13 +852,12 @@ def find_max_validate_return[T](args: Iterable[T]) -> T: @pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+ for PEP 695 syntax with generics') -def test_pep695_with_class(): +def test_pep695_with_class(create_module): """Primarily to ensure that the syntax is accepted and doesn't raise a `NameError` with `T`. The validation is not expected to work properly when parameterized at this point.""" for import_annotations in ('from __future__ import annotations', ''): - globs = {} - exec( + module = create_module( f""" {import_annotations} from pydantic import validate_call @@ -867,12 +866,9 @@ class A[T]: @validate_call(validate_return=True) def f(self, a: T) -> T: return str(a) - - """, - globs, + """ ) - - A = globs['A'] + A = module.A a = A[int]() # these two are undesired behavior, but it's what happens now assert a.f(1) == '1' @@ -880,13 +876,12 @@ def f(self, a: T) -> T: @pytest.mark.skipif(sys.version_info < (3, 12), reason='requires Python 3.12+ for PEP 695 syntax with generics') -def test_pep695_with_nested_scopes(): +def test_pep695_with_nested_scopes(create_module): """Nested scopes generally cannot be caught by `parent_frame_namespace`, so currently this test is expected to fail. """ - globs = {} - exec( + module = create_module( """ from __future__ import annotations from pydantic import validate_call @@ -899,11 +894,10 @@ def inner(a: T) -> T: ... def h[S](self): @validate_call(validate_return=True) def inner(a: T) -> S: ... - -""", - globs, + """ ) - A = globs['A'] + + A = module.A a = A[int]() with pytest.raises(NameError): a.g() @@ -911,7 +905,7 @@ def inner(a: T) -> S: ... a.h() with pytest.raises(NameError): - exec( + create_module( """ from __future__ import annotations from pydantic import validate_call @@ -924,7 +918,7 @@ def f(a: T) -> T: ... class C[S]: @validate_call(validate_return=True) def f(a: T) -> S: ... - """, + """ ) From d08eddf4dd8f8451c17fb1e977cd4a0605e25fa6 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 13 Sep 2024 08:59:58 -0500 Subject: [PATCH 025/412] Strip whitespaces on JSON Schema title generation (#10404) --- pydantic/json_schema.py | 2 +- tests/test_json_schema.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py index e7167a5e082..c48e781ba3f 100644 --- a/pydantic/json_schema.py +++ b/pydantic/json_schema.py @@ -1899,7 +1899,7 @@ def get_title_from_name(self, name: str) -> str: Returns: The title. """ - return name.title().replace('_', ' ') + return name.title().replace('_', ' ').strip() def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool: """Returns true if a field with the given schema should have a title set based on the field name. diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index 089b9c51cd5..7ec70a20499 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -6547,3 +6547,10 @@ def validate_f(cls, value: Any) -> int: ... 'e': {'title': 'E', 'type': 'integer'}, 'f': {'title': 'F', 'type': 'integer'}, } + + +def test_title_strip() -> None: + class Model(BaseModel): + some_field: str = Field(alias='_some_field') + + assert Model.model_json_schema()['properties']['_some_field']['title'] == 'Some Field' From 661490066c4598b8cc9396e9e45c151622885f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palmeiro?= Date: Fri, 13 Sep 2024 15:37:16 +0100 Subject: [PATCH 026/412] Fix indentation of first note on mypy documentation page (#10405) --- docs/integrations/mypy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/mypy.md b/docs/integrations/mypy.md index 1d7ef0e6f13..92ea8185562 100644 --- a/docs/integrations/mypy.md +++ b/docs/integrations/mypy.md @@ -127,7 +127,7 @@ plugins = pydantic.mypy ``` !!! note - If you're using `pydantic.v1` models, you'll need to add `pydantic.v1.mypy` to your list of plugins. + If you're using `pydantic.v1` models, you'll need to add `pydantic.v1.mypy` to your list of plugins. The plugin is compatible with mypy versions `>=0.930`. From 656481f377536d5a2641edb8aee9e2b1ef226506 Mon Sep 17 00:00:00 2001 From: Nix Date: Fri, 13 Sep 2024 21:40:33 +0700 Subject: [PATCH 027/412] Remove guarding check on computed field with field_serializer (#10390) --- pydantic/_internal/_decorators.py | 13 ++----------- pydantic/_internal/_generate_schema.py | 6 +----- tests/test_computed_fields.py | 17 +++++++++++++++++ tests/test_serialize.py | 19 ------------------- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/pydantic/_internal/_decorators.py b/pydantic/_internal/_decorators.py index 52a8ec17a6c..51b52aa2e5f 100644 --- a/pydantic/_internal/_decorators.py +++ b/pydantic/_internal/_decorators.py @@ -548,9 +548,7 @@ def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes) ) -def inspect_field_serializer( - serializer: Callable[..., Any], mode: Literal['plain', 'wrap'], computed_field: bool = False -) -> tuple[bool, bool]: +def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> tuple[bool, bool]: """Look at a field serializer function and determine if it is a field serializer, and whether it takes an info argument. @@ -559,8 +557,6 @@ def inspect_field_serializer( Args: serializer: The serializer function to inspect. mode: The serializer mode, either 'plain' or 'wrap'. - computed_field: When serializer is applied on computed_field. It doesn't require - info signature. Returns: Tuple of (is_field_serializer, info_arg). @@ -587,13 +583,8 @@ def inspect_field_serializer( f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', code='field-serializer-signature', ) - if info_arg and computed_field: - raise PydanticUserError( - 'field_serializer on computed_field does not use info signature', code='field-serializer-signature' - ) - else: - return is_field_serializer, info_arg + return is_field_serializer, info_arg def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index cff2ad5f32d..e3afcbae78d 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1972,7 +1972,6 @@ def _computed_field_schema( return_type_schema = self._apply_field_serializers( return_type_schema, filter_field_decorator_info_by_field(field_serializers.values(), d.cls_var_name), - computed_field=True, ) alias_generator = self._config_wrapper.alias_generator @@ -2197,7 +2196,6 @@ def _apply_field_serializers( self, schema: core_schema.CoreSchema, serializers: list[Decorator[FieldSerializerDecoratorInfo]], - computed_field: bool = False, ) -> core_schema.CoreSchema: """Apply field serializers to a schema.""" if serializers: @@ -2214,9 +2212,7 @@ def _apply_field_serializers( # use the last serializer to make it easy to override a serializer set on a parent model serializer = serializers[-1] - is_field_serializer, info_arg = inspect_field_serializer( - serializer.func, serializer.info.mode, computed_field=computed_field - ) + is_field_serializer, info_arg = inspect_field_serializer(serializer.func, serializer.info.mode) try: return_type = _decorators.get_function_return_type( diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index 99182f8de5f..e0a29b40b92 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -10,6 +10,7 @@ from pydantic import ( BaseModel, Field, + FieldSerializationInfo, GetCoreSchemaHandler, PrivateAttr, TypeAdapter, @@ -798,3 +799,19 @@ def id(self) -> str: m = Model(bar=42) assert m.model_dump() == {'bar': 42, 'id': 'id: {"bar":42}'} + + +def test_computed_field_with_field_serializer(): + class MyModel(BaseModel): + other_field: int = 42 + + @computed_field + @property + def my_field(self) -> str: + return 'foo' + + @field_serializer('*') + def my_field_serializer(self, value: Any, info: FieldSerializationInfo) -> Any: + return f'{info.field_name} = {value}' + + assert MyModel().model_dump() == {'my_field': 'my_field = foo', 'other_field': 'other_field = 42'} diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 505396e7a36..68e1c3dfbfb 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -17,7 +17,6 @@ BaseModel, Field, FieldSerializationInfo, - PydanticUserError, SerializationInfo, SerializerFunctionWrapHandler, TypeAdapter, @@ -1073,24 +1072,6 @@ def quadruple_x_plus_one(self) -> Annotated[int, PlainSerializer(lambda v: v + 1 } -def test_computed_field_custom_serializer_bad_signature(): - error_msg = 'field_serializer on computed_field does not use info signature' - - with pytest.raises(PydanticUserError, match=error_msg): - - class Model(BaseModel): - x: int - - @computed_field - @property - def two_x(self) -> int: - return self.x * 2 - - @field_serializer('two_x') - def ser_two_x_bad_signature(self, v, _info): - return f'The double of x is {v}' - - @pytest.mark.skipif( sys.version_info < (3, 9) or sys.version_info >= (3, 13), reason='@computed_field @classmethod @property only works in 3.9-3.12', From ee09d11c3aefbf08a35b8152a4bec4991f374200 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:31:30 -0500 Subject: [PATCH 028/412] Warn if configuration is specified on the `@dataclass` decorator and with the `__pydantic_config__` attribute (#10406) Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> --- pydantic/dataclasses.py | 11 ++++++++++ tests/test_dataclasses.py | 42 ++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 91946fbe98b..50c0d05386e 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -201,6 +201,17 @@ def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]: original_cls = cls + # we warn on conflicting config specifications, but only if the class doesn't have a dataclass base + # because a dataclass base might provide a __pydantic_config__ attribute that we don't want to warn about + has_dataclass_base = any(_typing_extra.is_dataclass(base) for base in cls.__bases__) + if not has_dataclass_base and config is not None and hasattr(cls, '__pydantic_config__'): + warn( + f'`config` is set via both the `dataclass` decorator and `__pydantic_config__` for dataclass {cls.__name__}. ' + f'The `config` specification from `dataclass` decorator will take priority.', + category=UserWarning, + stacklevel=2, + ) + # if config is not explicitly provided, try to read it from the type config_dict = config if config is not None else getattr(cls, '__pydantic_config__', None) config_wrapper = _config.ConfigWrapper(config_dict) diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 9b38c4fe498..49513f0bf2d 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -2487,23 +2487,31 @@ class Model: def test_model_config_override_in_decorator() -> None: - @pydantic.dataclasses.dataclass(config=ConfigDict(str_to_lower=False, str_strip_whitespace=True)) - class Model: - x: str - __pydantic_config__ = ConfigDict(str_to_lower=True) + with pytest.warns( + UserWarning, match='`config` is set via both the `dataclass` decorator and `__pydantic_config__`' + ): - ta = TypeAdapter(Model) - assert ta.validate_python({'x': 'ABC '}).x == 'ABC' + @pydantic.dataclasses.dataclass(config=ConfigDict(str_to_lower=False, str_strip_whitespace=True)) + class Model: + x: str + __pydantic_config__ = ConfigDict(str_to_lower=True) + + ta = TypeAdapter(Model) + assert ta.validate_python({'x': 'ABC '}).x == 'ABC' def test_model_config_override_in_decorator_empty_config() -> None: - @pydantic.dataclasses.dataclass(config=ConfigDict()) - class Model: - x: str - __pydantic_config__ = ConfigDict(str_to_lower=True) + with pytest.warns( + UserWarning, match='`config` is set via both the `dataclass` decorator and `__pydantic_config__`' + ): - ta = TypeAdapter(Model) - assert ta.validate_python({'x': 'ABC '}).x == 'ABC ' + @pydantic.dataclasses.dataclass(config=ConfigDict()) + class Model: + x: str + __pydantic_config__ = ConfigDict(str_to_lower=True) + + ta = TypeAdapter(Model) + assert ta.validate_python({'x': 'ABC '}).x == 'ABC ' def test_dataclasses_with_config_decorator(): @@ -3031,3 +3039,13 @@ def test_warns_on_double_frozen() -> None: @pydantic.dataclasses.dataclass(frozen=True, config=ConfigDict(frozen=True)) class DC: x: int + + +def test_warns_on_double_config() -> None: + with pytest.warns( + UserWarning, match='`config` is set via both the `dataclass` decorator and `__pydantic_config__`' + ): + + @pydantic.dataclasses.dataclass(config=ConfigDict(title='from decorator')) + class Foo: + __pydantic_config__ = ConfigDict(title='from __pydantic_config__') From 7daa2cd3fd24ac51cc98cb202d28faa9a4c94dd9 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:27:58 -0500 Subject: [PATCH 029/412] Fix `ZoneInfo` with various invalid types (#10408) --- pydantic/_internal/_generate_schema.py | 2 +- tests/test_types_zoneinfo.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index e3afcbae78d..5eddf00a95e 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1650,7 +1650,7 @@ def validate_str_is_valid_iana_tz(value: Any, /) -> ZoneInfo: return value try: return ZoneInfo(value) - except (ZoneInfoNotFoundError, ValueError): + except (ZoneInfoNotFoundError, ValueError, TypeError): raise PydanticCustomError('zoneinfo_str', 'invalid timezone: {value}', {'value': value}) metadata = build_metadata_dict(js_functions=[lambda _1, _2: {'type': 'string', 'format': 'zoneinfo'}]) diff --git a/tests/test_types_zoneinfo.py b/tests/test_types_zoneinfo.py index 0c8becf2d70..f633c7eb3af 100644 --- a/tests/test_types_zoneinfo.py +++ b/tests/test_types_zoneinfo.py @@ -1,6 +1,9 @@ +from datetime import timezone +from typing import Union + import pytest -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, ConfigDict, TypeAdapter, ValidationError zoneinfo = pytest.importorskip('zoneinfo', reason='zoneinfo requires >=3.9') @@ -48,3 +51,8 @@ def test_zoneinfo_json_schema(): 'properties': {'tz': {'type': 'string', 'format': 'zoneinfo', 'title': 'Tz'}}, 'required': ['tz'], } + + +def test_zoneinfo_union() -> None: + ta = TypeAdapter(Union[zoneinfo.ZoneInfo, timezone], config=ConfigDict(arbitrary_types_allowed=True)) + assert ta.validate_python(timezone.utc) is timezone.utc From 5d5b8af26ba92fdaacfe22b8bb508c2e002d7809 Mon Sep 17 00:00:00 2001 From: Joshua Salzedo Date: Sat, 14 Sep 2024 03:34:01 -0700 Subject: [PATCH 030/412] Add a `SocketPath` type for `linux` systems (#10378) --- pydantic/__init__.py | 2 ++ pydantic/types.py | 13 ++++++++++++- tests/test_types.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 25484b15384..3c3c130f0fb 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -177,6 +177,7 @@ 'Secret', 'SecretStr', 'SecretBytes', + 'SocketPath', 'StrictBool', 'StrictBytes', 'StrictInt', @@ -339,6 +340,7 @@ 'PaymentCardNumber': (__spec__.parent, '.types'), 'ByteSize': (__spec__.parent, '.types'), 'PastDate': (__spec__.parent, '.types'), + 'SocketPath': (__spec__.parent, '.types'), 'FutureDate': (__spec__.parent, '.types'), 'PastDatetime': (__spec__.parent, '.types'), 'FutureDatetime': (__spec__.parent, '.types'), diff --git a/pydantic/types.py b/pydantic/types.py index ac5958825d7..49c1c203079 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -46,6 +46,7 @@ __all__ = ( 'Strict', 'StrictStr', + 'SocketPath', 'conbytes', 'conlist', 'conset', @@ -1238,7 +1239,7 @@ class Model(BaseModel): @_dataclasses.dataclass class PathType: - path_type: Literal['file', 'dir', 'new'] + path_type: Literal['file', 'dir', 'new', 'socket'] def __get_pydantic_json_schema__( self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler @@ -1253,6 +1254,7 @@ def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandle 'file': cast(core_schema.WithInfoValidatorFunction, self.validate_file), 'dir': cast(core_schema.WithInfoValidatorFunction, self.validate_directory), 'new': cast(core_schema.WithInfoValidatorFunction, self.validate_new), + 'socket': cast(core_schema.WithInfoValidatorFunction, self.validate_socket), } return core_schema.with_info_after_validator_function( @@ -1267,6 +1269,13 @@ def validate_file(path: Path, _: core_schema.ValidationInfo) -> Path: else: raise PydanticCustomError('path_not_file', 'Path does not point to a file') + @staticmethod + def validate_socket(path: Path, _: core_schema.ValidationInfo) -> Path: + if path.is_socket(): + return path + else: + raise PydanticCustomError('path_not_socket', 'Path does not point to a socket') + @staticmethod def validate_directory(path: Path, _: core_schema.ValidationInfo) -> Path: if path.is_dir(): @@ -1374,6 +1383,8 @@ class Model(BaseModel): NewPath = Annotated[Path, PathType('new')] """A path for a new file or directory that must not already exist. The parent directory must already exist.""" +SocketPath = Annotated[Path, PathType('socket')] +"""A path to an existing socket file""" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/test_types.py b/tests/test_types.py index 84e5e6616af..1514e055334 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -101,6 +101,7 @@ SecretStr, SerializeAsAny, SkipValidation, + SocketPath, Strict, StrictBool, StrictBytes, @@ -3699,6 +3700,24 @@ class Model(BaseModel): ] +@pytest.mark.skipif( + not sys.platform.startswith('linux'), + reason='Test only works for linux systems. Windows excluded (unsupported). Mac excluded due to CI issues.', +) +def test_socket_exists(tmp_path): + import socket + + # Working around path length limits by reducing character count where possible. + target = tmp_path / 's' + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + sock.bind(str(target)) + + class Model(BaseModel): + path: SocketPath + + assert Model(path=target).path == target + + @pytest.mark.parametrize('value', ('/nonexistentdir/foo.py', Path('/nonexistentdir/foo.py'))) def test_new_path_validation_parent_does_not_exist(value): class Model(BaseModel): From a764871df98c8932e9b7bc10d861053d110a99e4 Mon Sep 17 00:00:00 2001 From: Chris Wilson <14631+cdwilson@users.noreply.github.com> Date: Sat, 14 Sep 2024 23:32:57 -0700 Subject: [PATCH 031/412] Don't raise an error when an empty `model_config` is annotated (#10412) --- pydantic/_internal/_config.py | 2 +- tests/test_config.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index e1e18139f42..f4c4000d9bf 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -120,7 +120,7 @@ def for_model(cls, bases: tuple[type[Any], ...], namespace: dict[str, Any], kwar config_dict_from_namespace = namespace.get('model_config') raw_annotations = namespace.get('__annotations__', {}) - if raw_annotations.get('model_config') and not config_dict_from_namespace: + if raw_annotations.get('model_config') and config_dict_from_namespace is None: raise PydanticUserError( '`model_config` cannot be used as a model field name. Use `model_config` for model configuration.', code='model-config-invalid-field-name', diff --git a/tests/test_config.py b/tests/test_config.py index c23dc8e0994..1b98c83ffb3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -888,3 +888,10 @@ def test_with_config_disallowed_with_model(): @with_config({'coerce_numbers_to_str': True}) class Model(BaseModel): pass + + +def test_empty_config_with_annotations(): + class Model(BaseModel): + model_config: ConfigDict = {} + + assert Model.model_config == {} From 2e38ec444b4db0a3da5498fe6c39c4e58d7af142 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:57:23 +0200 Subject: [PATCH 032/412] Fix variance issue in `_IncEx` type alias, only allow `True` (#10414) Co-authored-by: sydney-runkle --- docs/concepts/serialization.md | 2 +- pdm.lock | 309 +++++++++++++++++---------------- pydantic/main.py | 5 +- pyproject.toml | 2 +- tests/pyright/misc.py | 30 ++++ 5 files changed, 195 insertions(+), 153 deletions(-) create mode 100644 tests/pyright/misc.py diff --git a/docs/concepts/serialization.md b/docs/concepts/serialization.md index 9e61dad8096..a71ded16e70 100644 --- a/docs/concepts/serialization.md +++ b/docs/concepts/serialization.md @@ -700,7 +700,7 @@ print(t.model_dump(include={'id': True, 'user': {'id'}})) #> {'id': '1234567890', 'user': {'id': 42}} ``` -The `True` indicates that we want to exclude or include an entire key, just as if we included it in a set. +Using `True` indicates that we want to exclude or include an entire key, just as if we included it in a set (note that using `False` isn't supported). This can be done at any depth level. Special care must be taken when including or excluding fields from a list or tuple of submodels or dictionaries. diff --git a/pdm.lock b/pdm.lock index e0fbb38d96a..e73840f3bc2 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra", "timezone"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:353e0a512023413e67aad3b741688cdb4c38fe003b8ce6cc28f9f0271d9d7d0a" +content_hash = "sha256:c1127f3971d4a0c6271ab715e0bd62f40e67b070ea9cc71c78fa9dce52fc993d" [[metadata.targets]] requires_python = ">=3.8" @@ -618,58 +618,67 @@ files = [ [[package]] name = "greenlet" -version = "3.0.3" +version = "3.1.0" requires_python = ">=3.7" summary = "Lightweight in-process concurrent programming" files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, + {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, + {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, + {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, + {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, + {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, + {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, + {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, + {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, + {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, + {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, + {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, + {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, + {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, + {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, + {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, + {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, + {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, + {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, + {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, + {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, + {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, + {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, + {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, + {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, + {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, + {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, ] [[package]] @@ -1267,102 +1276,102 @@ files = [ [[package]] name = "pydantic-core" -version = "2.23.3" +version = "2.23.4" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, - {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, - {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, - {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, - {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, - {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, - {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, - {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, - {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, - {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, - {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, - {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, - {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, - {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [[package]] @@ -1371,7 +1380,7 @@ version = "2.9.0" requires_python = ">=3.8" git = "https://github.com/pydantic/pydantic-extra-types.git" ref = "main" -revision = "55a01b2d619d371de5bc578db0fa359f5fddec03" +revision = "58db4b096d7c90566d3d48d51b4665c01a591df6" summary = "Extra Pydantic types." dependencies = [ "pydantic>=2.5.2", @@ -1380,7 +1389,7 @@ dependencies = [ [[package]] name = "pydantic-settings" -version = "2.4.0" +version = "2.5.2" requires_python = ">=3.8" summary = "Settings management using Pydantic" dependencies = [ @@ -1388,8 +1397,8 @@ dependencies = [ "python-dotenv>=0.21.0", ] files = [ - {file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"}, - {file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"}, + {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, + {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, ] [[package]] @@ -1438,7 +1447,7 @@ files = [ [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" requires_python = ">=3.8" summary = "pytest: simple powerful testing with Python" dependencies = [ @@ -1450,8 +1459,8 @@ dependencies = [ "tomli>=1; python_version < \"3.11\"", ] files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [[package]] @@ -1567,11 +1576,11 @@ files = [ [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" summary = "World timezone definitions, modern and historical" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] diff --git a/pydantic/main.py b/pydantic/main.py index 5a1fff123e9..74643f15561 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -16,6 +16,7 @@ Dict, Generator, Literal, + Mapping, Set, Tuple, TypeVar, @@ -70,7 +71,9 @@ # Keep these type aliases available at runtime: TupleGenerator: TypeAlias = Generator[Tuple[str, Any], None, None] # Keep this type alias in sync with the stub definition in `pydantic-core`: -IncEx: TypeAlias = Union[Set[int], Set[str], Dict[int, Union['IncEx', bool]], Dict[str, Union['IncEx', bool]]] +IncEx: TypeAlias = Union[ + Set[int], Set[str], Mapping[int, Union['IncEx', Literal[True]]], Mapping[str, Union['IncEx', Literal[True]]] +] _object_setattr = _model_construction.object_setattr diff --git a/pyproject.toml b/pyproject.toml index 66b1470426a..ed710c638d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ dependencies = [ 'typing-extensions>=4.6.1; python_version < "3.13"', 'typing-extensions>=4.12.2; python_version >= "3.13"', 'annotated-types>=0.6.0', - "pydantic-core==2.23.3", + "pydantic-core==2.23.4", ] dynamic = ['version', 'readme'] diff --git a/tests/pyright/misc.py b/tests/pyright/misc.py new file mode 100644 index 00000000000..a199b2c8d56 --- /dev/null +++ b/tests/pyright/misc.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel + + +class Sub(BaseModel): + a: int + b: int + + +class Model(BaseModel): + subs: list[Sub] + + +def func(model: Model) -> None: + model.model_dump( + include={'a': {1: True}}, + ) + model.model_dump( + include={'a': {'__all__': True}}, + ) + model.model_dump( + include={'a': {1: {'a'}}}, + ) + model.model_dump( + include={'a': {1, 2}}, + ) + + # Invalid cases: + model.model_dump( + include={'a': {1: False}}, # pyright: ignore[reportArgumentType] + ) From 2f4723f64b8e034fcc9c35eb756c8b4c724e3408 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:24:36 -0500 Subject: [PATCH 033/412] Allow arbitrary refs in JSON schema `examples` (#10417) --- pydantic/json_schema.py | 6 +++++- tests/test_json_schema.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py index c48e781ba3f..b0653e0318b 100644 --- a/pydantic/json_schema.py +++ b/pydantic/json_schema.py @@ -2172,7 +2172,9 @@ def _add_json_refs(schema: Any) -> None: if not json_ref.startswith(('http://', 'https://')): raise - for v in schema.values(): + for k, v in schema.items(): + if k == 'examples': + continue # skip refs processing for examples, allow arbitrary values / refs _add_json_refs(v) elif isinstance(schema, list): for v in schema: @@ -2484,6 +2486,8 @@ def _get_all_json_refs(item: Any) -> set[JsonRef]: current = stack.pop() if isinstance(current, dict): for key, value in current.items(): + if key == 'examples': + continue # skip examples, allow arbitrary values / refs if key == '$ref' and isinstance(value, str): refs.add(JsonRef(value)) elif isinstance(value, dict): diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index 7ec70a20499..c1649c105ae 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -6554,3 +6554,19 @@ class Model(BaseModel): some_field: str = Field(alias='_some_field') assert Model.model_json_schema()['properties']['_some_field']['title'] == 'Some Field' + + +def test_arbitrary_ref_in_json_schema() -> None: + """See https://github.com/pydantic/pydantic/issues/9981.""" + + class Test(BaseModel): + x: dict = Field(examples=[{'$ref': '#/components/schemas/Pet'}]) + + assert Test.model_json_schema() == { + 'properties': {'x': {'examples': [{'$ref': '#/components/schemas/Pet'}], 'title': 'X', 'type': 'object'}}, + 'required': ['x'], + 'title': 'Test', + 'type': 'object', + } + + # raises KeyError: '#/components/schemas/Pet' From cf671c808fba1a78bc4995e8276dd9e48876f2d9 Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Tue, 17 Sep 2024 10:27:49 +0200 Subject: [PATCH 034/412] Fix rendering of `StringContraints` documentation example (#10421) --- pydantic/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydantic/types.py b/pydantic/types.py index 49c1c203079..2beba262993 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -709,6 +709,7 @@ class StringConstraints(annotated_types.GroupedMetadata): from pydantic.types import StringConstraints ConstrainedStr = Annotated[str, StringConstraints(min_length=1, max_length=10)] + ``` """ strip_whitespace: bool | None = None From 00b376218a7574ab2a4e9df80f5003fcaeda0ce9 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:24:49 +0200 Subject: [PATCH 035/412] Fix serialization schema generation when using `PlainValidator` (#10427) --- pydantic/functional_validators.py | 21 ++++++++++++----- tests/test_validators.py | 39 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/pydantic/functional_validators.py b/pydantic/functional_validators.py index 674f3817bba..0f8260bab01 100644 --- a/pydantic/functional_validators.py +++ b/pydantic/functional_validators.py @@ -190,10 +190,15 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH try: schema = handler(source_type) - serialization = core_schema.wrap_serializer_function_ser_schema( - function=lambda v, h: h(v), - schema=schema, - return_schema=schema, + # TODO if `schema['serialization']` is one of `'include-exclude-dict/sequence', + # schema validation will fail. That's why we use 'type ignore' comments below. + serialization = schema.get( + 'serialization', + core_schema.wrap_serializer_function_ser_schema( + function=lambda v, h: h(v), + schema=schema, + return_schema=handler.generate_schema(source_type), + ), ) except PydanticSchemaGenerationError: serialization = None @@ -207,12 +212,16 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH return core_schema.with_info_plain_validator_function( func, field_name=handler.field_name, - serialization=serialization, + serialization=serialization, # pyright: ignore[reportArgumentType] metadata=metadata, ) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) - return core_schema.no_info_plain_validator_function(func, serialization=serialization, metadata=metadata) + return core_schema.no_info_plain_validator_function( + func, + serialization=serialization, # pyright: ignore[reportArgumentType] + metadata=metadata, + ) @classmethod def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: diff --git a/tests/test_validators.py b/tests/test_validators.py index 51dd636ea60..9ce4972d6ce 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -2869,6 +2869,45 @@ class Blah(BaseModel): assert isinstance(data['bar'], ser_type) +def test_plain_validator_plain_serializer_single_ser_call() -> None: + """https://github.com/pydantic/pydantic/issues/10385""" + + ser_count = 0 + + def ser(v): + nonlocal ser_count + ser_count += 1 + return v + + class Model(BaseModel): + foo: Annotated[bool, PlainSerializer(ser), PlainValidator(lambda v: v)] + + model = Model(foo=True) + data = model.model_dump() + + assert data == {'foo': True} + assert ser_count == 1 + + +@pytest.mark.xfail(reason='https://github.com/pydantic/pydantic/issues/10428') +def test_plain_validator_with_filter_dict_schema() -> None: + class MyDict: + @classmethod + def __get_pydantic_core_schema__(cls, source, handler): + return core_schema.dict_schema( + keys_schema=handler.generate_schema(str), + values_schema=handler.generate_schema(int), + serialization=core_schema.filter_dict_schema( + include={'a'}, + ), + ) + + class Model(BaseModel): + f: Annotated[MyDict, PlainValidator(lambda v: v)] + + assert Model(f={'a': 1, 'b': 1}).model_dump() == {'f': {'a': 1}} + + def test_plain_validator_with_unsupported_type() -> None: class UnsupportedClass: pass From 59a3de71c6592a0e088ce9c7c4c7dd1950a05b7b Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:03:39 -0500 Subject: [PATCH 036/412] Add history updates to `main` `HISTORY.md` (#10429) --- HISTORY.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 7450645e6a5..af0791edafd 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,32 @@ +## v2.9.2 (2024-09-17) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.9.2) + +### What's Changed + +#### Fixes +* Do not error when trying to evaluate annotations of private attributes by @Viicos in [#10358](https://github.com/pydantic/pydantic/pull/10358) +* Adding notes on designing sound `Callable` discriminators by @sydney-runkle in [#10400](https://github.com/pydantic/pydantic/pull/10400) +* Fix serialization schema generation when using `PlainValidator` by @Viicos in [#10427](https://github.com/pydantic/pydantic/pull/10427) +* Fix `Union` serialization warnings by @sydney-runkle in [pydantic/pydantic-core#1449](https://github.com/pydantic/pydantic-core/pull/1449) +* Fix variance issue in `_IncEx` type alias, only allow `True` by @Viicos in [#10414](https://github.com/pydantic/pydantic/pull/10414) +* Fix `ZoneInfo` validation with various invalid types by @sydney-runkle in [#10408](https://github.com/pydantic/pydantic/pull/10408) + +## v2.9.1 (2024-09-09) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.9.1) + +### What's Changed + +#### Fixes +* Fix Predicate issue in v2.9.0 by @sydney-runkle in [#10321](https://github.com/pydantic/pydantic/pull/10321) +* Fixing `annotated-types` bound to `>=0.6.0` by @sydney-runkle in [#10327](https://github.com/pydantic/pydantic/pull/10327) +* Turn `tzdata` install requirement into optional `timezone` dependency by @jakob-keller in [#10331](https://github.com/pydantic/pydantic/pull/10331) +* Fix `IncExc` type alias definition by @Viicos in [#10339](https://github.com/pydantic/pydantic/pull/10339) +* Use correct types namespace when building namedtuple core schemas by @Viicos in [#10337](https://github.com/pydantic/pydantic/pull/10337) +* Fix evaluation of stringified annotations during namespace inspection by @Viicos in [#10347](https://github.com/pydantic/pydantic/pull/10347) +* Fix tagged union serialization with alias generators by @sydney-runkle in [pydantic/pydantic-core#1442](https://github.com/pydantic/pydantic-core/pull/1442) + ## v2.9.0 (2024-09-05) [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.9.0) From 7ac0eb98d2329021b3906d0a2283ea4fbf414ceb Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:56:28 +0200 Subject: [PATCH 037/412] Support `defer_build` for Pydantic dataclasses (#10313) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Co-authored-by: sydney-runkle --- pydantic/_internal/_dataclasses.py | 33 ++++++++++------ pydantic/_internal/_model_construction.py | 8 ++-- pydantic/dataclasses.py | 8 ++++ tests/test_config.py | 47 ++++++++++++++++++++++- 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/pydantic/_internal/_dataclasses.py b/pydantic/_internal/_dataclasses.py index a537cd18b32..d4d585b45a1 100644 --- a/pydantic/_internal/_dataclasses.py +++ b/pydantic/_internal/_dataclasses.py @@ -90,6 +90,7 @@ def complete_dataclass( *, raise_errors: bool = True, types_namespace: dict[str, Any] | None, + _force_build: bool = False, ) -> bool: """Finish building a pydantic dataclass. @@ -102,6 +103,8 @@ def complete_dataclass( config_wrapper: The config wrapper instance. raise_errors: Whether to raise errors, defaults to `True`. types_namespace: The types namespace. + _force_build: Whether to force building the dataclass, no matter if + [`defer_build`][pydantic.config.ConfigDict.defer_build] is set. Returns: `True` if building a pydantic dataclass is successfully completed, `False` otherwise. @@ -109,6 +112,24 @@ def complete_dataclass( Raises: PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations. """ + original_init = cls.__init__ + + # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied, + # and so that the mock validator is used if building was deferred: + def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: + __tracebackhide__ = True + s = __dataclass_self__ + s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) + + __init__.__qualname__ = f'{cls.__qualname__}.__init__' + + cls.__init__ = __init__ # type: ignore + cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore + + if not _force_build and config_wrapper.defer_build: + set_dataclass_mocks(cls, cls.__name__) + return False + if hasattr(cls, '__post_init_post_parse__'): warnings.warn( 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning @@ -128,22 +149,12 @@ def complete_dataclass( # This needs to be called before we change the __init__ sig = generate_pydantic_signature( - init=cls.__init__, + init=original_init, fields=cls.__pydantic_fields__, # type: ignore config_wrapper=config_wrapper, is_dataclass=True, ) - # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied. - def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: - __tracebackhide__ = True - s = __dataclass_self__ - s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) - - __init__.__qualname__ = f'{cls.__qualname__}.__init__' - - cls.__init__ = __init__ # type: ignore - cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore cls.__signature__ = sig # type: ignore get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None) try: diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 11ee41aa061..0946f85041b 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -614,6 +614,10 @@ def complete_model_class( PydanticUndefinedAnnotation: If `PydanticUndefinedAnnotation` occurs in`__get_pydantic_core_schema__` and `raise_errors=True`. """ + if config_wrapper.defer_build: + set_model_mocks(cls, cls_name) + return False + typevars_map = get_model_typevars_map(cls) gen_schema = GenerateSchema( config_wrapper, @@ -627,10 +631,6 @@ def complete_model_class( ref_mode='unpack', ) - if config_wrapper.defer_build: - set_model_mocks(cls, cls_name) - return False - try: schema = cls.__get_pydantic_core_schema__(cls, handler) except PydanticUndefinedAnnotation as e: diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 50c0d05386e..df69a5e1d18 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -320,6 +320,8 @@ def rebuild_dataclass( if not force and cls.__pydantic_complete__: return None else: + if '__pydantic_core_schema__' in cls.__dict__: + delattr(cls, '__pydantic_core_schema__') # delete cached value to ensure full rebuild happens if _types_namespace is not None: types_namespace: dict[str, Any] | None = _types_namespace.copy() else: @@ -332,11 +334,17 @@ def rebuild_dataclass( types_namespace = {} types_namespace = _typing_extra.merge_cls_and_parent_ns(cls, types_namespace) + return _pydantic_dataclasses.complete_dataclass( cls, _config.ConfigWrapper(cls.__pydantic_config__, check=False), raise_errors=raise_errors, types_namespace=types_namespace, + # We could provide a different config instead (with `'defer_build'` set to `True`) + # of this explicit `_force_build` argument, but because config can come from the + # decorator parameter or the `__pydantic_config__` attribute, `complete_dataclass` + # will overwrite `__pydantic_config__` with the provided config above: + _force_build=True, ) diff --git a/tests/test_config.py b/tests/test_config.py index 1b98c83ffb3..5495dac85ed 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -27,6 +27,7 @@ from pydantic._internal._typing_extra import get_type_hints from pydantic.config import ConfigDict, JsonValue from pydantic.dataclasses import dataclass as pydantic_dataclass +from pydantic.dataclasses import rebuild_dataclass from pydantic.errors import PydanticUserError from pydantic.fields import ComputedFieldInfo, FieldInfo from pydantic.type_adapter import TypeAdapter @@ -728,6 +729,48 @@ class MyModel(BaseModel): assert generate_schema_calls.count == 1, 'Should not build duplicated core schemas' +@pytest.mark.parametrize('defer_build', [True, False]) +def test_config_dataclass_defer_build(defer_build: bool, generate_schema_calls: CallCounter) -> None: + config = ConfigDict(defer_build=defer_build) + + @pydantic_dataclass(config=config) + class MyDataclass: + x: int + + if defer_build: + assert isinstance(MyDataclass.__pydantic_validator__, MockValSer) + assert isinstance(MyDataclass.__pydantic_serializer__, MockValSer) + assert generate_schema_calls.count == 0, 'Should respect defer_build' + else: + assert isinstance(MyDataclass.__pydantic_validator__, SchemaValidator) + assert isinstance(MyDataclass.__pydantic_serializer__, SchemaSerializer) + assert generate_schema_calls.count == 1, 'Should respect defer_build' + + m = MyDataclass(x=1) + assert m.x == 1 + + assert isinstance(MyDataclass.__pydantic_validator__, SchemaValidator) + assert isinstance(MyDataclass.__pydantic_serializer__, SchemaSerializer) + assert generate_schema_calls.count == 1, 'Should not build duplicated core schemas' + + +def test_dataclass_defer_build_override_on_rebuild_dataclass(generate_schema_calls: CallCounter) -> None: + config = ConfigDict(defer_build=True) + + @pydantic_dataclass(config=config) + class MyDataclass: + x: int + + assert isinstance(MyDataclass.__pydantic_validator__, MockValSer) + assert isinstance(MyDataclass.__pydantic_serializer__, MockValSer) + assert generate_schema_calls.count == 0, 'Should respect defer_build' + + rebuild_dataclass(MyDataclass, force=True) + assert isinstance(MyDataclass.__pydantic_validator__, SchemaValidator) + assert isinstance(MyDataclass.__pydantic_serializer__, SchemaSerializer) + assert generate_schema_calls.count == 1, 'Should have called generate_schema once' + + @pytest.mark.parametrize('defer_build', [True, False]) def test_config_model_type_adapter_defer_build(defer_build: bool, generate_schema_calls: CallCounter): config = ConfigDict(defer_build=defer_build) @@ -785,7 +828,7 @@ class MyModel(BaseModel): assert isinstance(MyModel.__pydantic_serializer__, SchemaSerializer) expected_schema_count = 1 if defer_build is True else 2 - assert generate_schema_calls.count == expected_schema_count, 'Should respect experimental_defer_build_mode' + assert generate_schema_calls.count == expected_schema_count, 'Should respect defer_build' if defer_build: assert isinstance(MyNestedModel.__pydantic_validator__, MockValSer) @@ -866,7 +909,7 @@ class MyModel(BaseModel): assert exc_info.value.code == 'model-config-invalid-field-name' -def test_dataclass_allowes_model_config_as_model_field(): +def test_dataclass_allows_model_config_as_model_field(): config_title = 'from_config' field_title = 'from_field' From fbfcc60480ad3e115bef125f781b81f0bce393d9 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:08:27 -0500 Subject: [PATCH 038/412] Adding v1 / v2 incompatibility warning for nested v1 model (#10431) --- pydantic/_internal/_generate_schema.py | 16 ++++++++++++---- tests/test_main.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 5eddf00a95e..ef218ae991f 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -821,10 +821,18 @@ def _generate_schema_from_property(self, obj: Any, source: Any) -> core_schema.C ): schema = existing_schema elif (validators := getattr(obj, '__get_validators__', None)) is not None: - warn( - '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', - PydanticDeprecatedSince20, - ) + from pydantic.v1 import BaseModel as BaseModelV1 + + if issubclass(obj, BaseModelV1): + warn( + f'Mixing V1 models and V2 models (or constructs, like `TypeAdapter`) is not supported. Please upgrade `{obj.__name__}` to V2.', + UserWarning, + ) + else: + warn( + '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', + PydanticDeprecatedSince20, + ) schema = core_schema.chain_schema([core_schema.with_info_plain_validator_function(v) for v in validators()]) else: # we have no existing schema information on the property, exit early so that we can go generate a schema diff --git a/tests/test_main.py b/tests/test_main.py index 75288892433..6a30526c210 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -50,6 +50,7 @@ from pydantic._internal._generate_schema import GenerateSchema from pydantic._internal._mock_val_ser import MockCoreSchema from pydantic.dataclasses import dataclass as pydantic_dataclass +from pydantic.v1 import BaseModel as BaseModelV1 def test_success(): @@ -3319,3 +3320,16 @@ def test_subclassing_gen_schema_warns() -> None: with pytest.warns(UserWarning, match='Subclassing `GenerateSchema` is not supported.'): class MyGenSchema(GenerateSchema): ... + + +def test_nested_v1_model_warns() -> None: + with pytest.warns( + UserWarning, + match=r'Mixing V1 models and V2 models \(or constructs, like `TypeAdapter`\) is not supported. Please upgrade `V1Model` to V2.', + ): + + class V1Model(BaseModelV1): + a: int + + class V2Model(BaseModel): + inner: V1Model From 01daafaab0aae71d2fb57c42461b3d021a3c56d4 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:16:01 +0200 Subject: [PATCH 039/412] Modify schema creation benchmarks to focus on schema building (#10316) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- pyproject.toml | 3 +- .../test_model_schema_generation.py | 287 ++++++------------ 2 files changed, 89 insertions(+), 201 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ed710c638d1..7d3659497f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -220,8 +220,7 @@ pydocstyle = { convention = 'google' } [tool.ruff.lint.extend-per-file-ignores] "docs/**/*.py" = ['T'] "tests/**/*.py" = ['T', 'E721', 'F811'] -"tests/benchmarks/test_fastapi_startup_generics.py" = ['UP006', 'UP007'] -"tests/benchmarks/test_fastapi_startup_simple.py" = ['UP006', 'UP007'] +"tests/benchmarks/**/*.py" = ['UP006', 'UP007'] [tool.ruff.format] quote-style = 'single' diff --git a/tests/benchmarks/test_model_schema_generation.py b/tests/benchmarks/test_model_schema_generation.py index 28c05fe7b6c..d491218f093 100644 --- a/tests/benchmarks/test_model_schema_generation.py +++ b/tests/benchmarks/test_model_schema_generation.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Generic, List, Literal, Optional, TypeVar, Union, get_origin +from typing import Any, Dict, Generic, List, Literal, Optional, Type, TypeVar, Union, get_origin import pytest from typing_extensions import Annotated, Self @@ -11,96 +11,102 @@ Field, PlainSerializer, PlainValidator, - SerializerFunctionWrapHandler, - ValidatorFunctionWrapHandler, WrapSerializer, WrapValidator, create_model, - field_serializer, - field_validator, model_serializer, model_validator, ) -from pydantic.dataclasses import dataclass -from pydantic.functional_validators import ModelWrapValidatorHandler +from pydantic.dataclasses import dataclass, rebuild_dataclass + + +class DeferredModel(BaseModel): + model_config = {'defer_build': True} + + +def rebuild_model(model: Type[BaseModel]) -> None: + model.model_rebuild(force=True, _types_namespace={}) @pytest.mark.benchmark(group='model_schema_generation') def test_simple_model_schema_generation(benchmark) -> None: - def generate_schema(): - class SimpleModel(BaseModel): - field1: str - field2: int - field3: float + class SimpleModel(DeferredModel): + field1: str + field2: int + field3: float - benchmark(generate_schema) + benchmark(rebuild_model, SimpleModel) @pytest.mark.benchmark(group='model_schema_generation') -def test_nested_model_schema_generation(benchmark) -> None: - def generate_schema(): - class NestedModel(BaseModel): - field1: str - field2: List[int] - field3: Dict[str, float] +def test_simple_model_schema_lots_of_fields_generation(benchmark) -> None: + IntStr = Union[int, str] - class OuterModel(BaseModel): - nested: NestedModel - optional_nested: Optional[NestedModel] + Model = create_model('Model', __config__={'defer_build': True}, **{f'f{i}': (IntStr, ...) for i in range(100)}) - benchmark(generate_schema) + benchmark(rebuild_model, Model) @pytest.mark.benchmark(group='model_schema_generation') -def test_complex_model_schema_generation(benchmark) -> None: - def generate_schema(): - class ComplexModel(BaseModel): - field1: Union[str, int, float] - field2: List[Dict[str, Union[int, float]]] - field3: Optional[List[Union[str, int]]] +def test_nested_model_schema_generation(benchmark) -> None: + class NestedModel(BaseModel): + field1: str + field2: List[int] + field3: Dict[str, float] - benchmark(generate_schema) + class OuterModel(DeferredModel): + nested: NestedModel + optional_nested: Optional[NestedModel] + benchmark(rebuild_model, OuterModel) -@pytest.mark.benchmark(group='model_schema_generation') -def test_recursive_model_schema_generation(benchmark) -> None: - def generate_schema(): - class RecursiveModel(BaseModel): - name: str - children: Optional[List['RecursiveModel']] = None - benchmark(generate_schema) +@pytest.mark.benchmark(group='model_schema_generation') +def test_complex_model_schema_generation(benchmark) -> None: + class ComplexModel(DeferredModel): + field1: Union[str, int, float] + field2: List[Dict[str, Union[int, float]]] + field3: Optional[List[Union[str, int]]] + benchmark(rebuild_model, ComplexModel) -@dataclass(frozen=True, kw_only=True) -class Cat: - type: Literal['cat'] = 'cat' +@pytest.mark.benchmark(group='model_schema_generation') +def test_recursive_model_schema_generation(benchmark) -> None: + class RecursiveModel(DeferredModel): + name: str + children: Optional[List['RecursiveModel']] = None -@dataclass(frozen=True, kw_only=True) -class Dog: - type: Literal['dog'] = 'dog' + benchmark(rebuild_model, RecursiveModel) -@dataclass(frozen=True, kw_only=True) -class NestedDataClass: - animal: Annotated[Union[Cat, Dog], Discriminator('type')] +@pytest.mark.benchmark(group='model_schema_generation') +def test_construct_dataclass_schema(benchmark): + @dataclass(frozen=True, kw_only=True) + class Cat: + type: Literal['cat'] = 'cat' + @dataclass(frozen=True, kw_only=True) + class Dog: + type: Literal['dog'] = 'dog' -class NestedModel(BaseModel): - animal: Annotated[Union[Cat, Dog], Discriminator('type')] + @dataclass(frozen=True, kw_only=True) + class NestedDataClass: + animal: Annotated[Union[Cat, Dog], Discriminator('type')] + class NestedModel(BaseModel): + animal: Annotated[Union[Cat, Dog], Discriminator('type')] -@pytest.mark.benchmark(group='model_schema_generation') -def test_construct_schema(): - @dataclass(frozen=True, kw_only=True) + @dataclass(frozen=True, kw_only=True, config={'defer_build': True}) class Root: data_class: NestedDataClass model: NestedModel + benchmark(lambda: rebuild_dataclass(Root, force=True, _types_namespace={})) + @pytest.mark.benchmark(group='model_schema_generation') -def test_lots_of_models_with_lots_of_fields(): +def test_lots_of_models_with_lots_of_fields(benchmark): T = TypeVar('T') class GenericModel(BaseModel, Generic[T]): @@ -131,6 +137,8 @@ class Product(BaseModel): metadata: Dict[str, str] # Repeat the pattern for other models up to Model_99 + models: list[type[BaseModel]] = [] + for i in range(100): model_fields = {} @@ -170,163 +178,44 @@ class Product(BaseModel): model_fields[f'field_{j}'] = (field_type, ...) model_name = f'Model_{i}' - model = create_model(model_name, **model_fields) - globals()[model_name] = model - - -@pytest.mark.parametrize('validator_mode', ['before', 'after', 'plain']) -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_field_validator_via_decorator(benchmark, validator_mode) -> None: - def schema_gen() -> None: - class ModelWithFieldValidator(BaseModel): - field: Any - - @field_validator('field', mode=validator_mode) - @classmethod - def validate_field(cls, v: Any): - return v - - benchmark(schema_gen) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_wrap_field_validator_via_decorator(benchmark) -> None: - def schema_gen() -> None: - class ModelWithWrapFieldValidator(BaseModel): - field: Any - - @field_validator('field', mode='wrap') - @classmethod - def validate_field(cls, v: Any, handler: ValidatorFunctionWrapHandler) -> Any: - return handler(v) - - benchmark(schema_gen) - - -@pytest.mark.parametrize('validator_constructor', [BeforeValidator, AfterValidator, PlainValidator]) -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_field_validator_via_annotation(benchmark, validator_constructor) -> None: - def validate_field(v: Any) -> Any: - return v - - def schema_gen(validation_func) -> None: - class ModelWithFieldValidator(BaseModel): - field: Annotated[Any, validator_constructor(validation_func)] - - benchmark(schema_gen, validate_field) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_wrap_field_validator_via_annotation(benchmark) -> None: - def validate_field(v: Any, handler: ValidatorFunctionWrapHandler) -> Any: - return handler(v) - - def schema_gen(validator_func: Callable) -> None: - class ModelWithWrapFieldValidator(BaseModel): - field: Annotated[Any, WrapValidator(validator_func)] - - benchmark(schema_gen, validate_field) - + models.append(create_model(model_name, __config__={'defer_build': True}, **model_fields)) -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_model_validator_before(benchmark): - def schema_gen() -> None: - class ModelWithBeforeValidator(BaseModel): - field: Any - - @model_validator(mode='before') - @classmethod - def validate_model_before(cls, data: Any) -> Any: - return data + def rebuild_models(models: List[Type[BaseModel]]) -> None: + for model in models: + rebuild_model(model) - benchmark(schema_gen) + benchmark(rebuild_models, models) @pytest.mark.benchmark(group='model_schema_generation') -def test_custom_model_validator_after(benchmark) -> None: - def schema_gen() -> None: - class ModelWithAfterValidator(BaseModel): - field: Any - - @model_validator(mode='after') - def validate_model_after(self: 'ModelWithAfterValidator') -> 'ModelWithAfterValidator': - return self +def test_field_validators_serializers(benchmark) -> None: + class ModelWithFieldValidatorsSerializers(DeferredModel): + field1: Annotated[Any, BeforeValidator(lambda v: v)] + field2: Annotated[Any, AfterValidator(lambda v: v)] + field3: Annotated[Any, PlainValidator(lambda v: v)] + field4: Annotated[Any, WrapValidator(lambda v, h: h(v))] + field5: Annotated[Any, PlainSerializer(lambda x: x, return_type=Any)] + field6: Annotated[Any, WrapSerializer(lambda x, nxt: nxt(x), when_used='json')] - benchmark(schema_gen) + benchmark(rebuild_model, ModelWithFieldValidatorsSerializers) @pytest.mark.benchmark(group='model_schema_generation') -def test_custom_model_validator_wrap(benchmark) -> None: - def schema_gen() -> None: - class ModelWithWrapValidator(BaseModel): - field: Any - - @model_validator(mode='wrap') - @classmethod - def validate_model_wrap(cls, values: Any, handler: ModelWrapValidatorHandler[Self]) -> Any: - return handler(values) - - benchmark(schema_gen) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_field_serializer_plain(benchmark) -> None: - def schema_gen() -> None: - class ModelWithFieldSerializer(BaseModel): - field1: int - - @field_serializer('field1', mode='plain') - def serialize_field(cls, v: int) -> str: - return str(v) +def test_model_validators_serializers(benchmark): + class ModelWithValidator(DeferredModel): + field: Any - benchmark(schema_gen) + @model_validator(mode='before') + @classmethod + def validate_model_before(cls, data: Any) -> Any: + return data + @model_validator(mode='after') + def validate_model_after(self) -> Self: + return self -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_field_serializer_wrap(benchmark) -> None: - def schema_gen() -> None: - class ModelWithFieldSerializer(BaseModel): - field1: int - - @field_serializer('field1', mode='wrap') - def serialize_field(cls, v: int, nxt: SerializerFunctionWrapHandler) -> str: - return nxt(v) - - benchmark(schema_gen) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_model_serializer_decorator(benchmark) -> None: - def schema_gen() -> None: - class ModelWithModelSerializer(BaseModel): - field1: Any - - @model_serializer - def serialize_model(self) -> Any: - return self.field1 - - benchmark(schema_gen) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_serializer_plain_annotated(benchmark) -> None: - def schema_gen() -> None: - def serialize_idempotent(x: Any) -> Any: - return x - - class ModelWithAnnotatedSerializer(BaseModel): - field: Annotated[List, PlainSerializer(serialize_idempotent, return_type=Any)] - - benchmark(schema_gen) - - -@pytest.mark.benchmark(group='model_schema_generation') -def test_custom_serializer_wrap_annotated(benchmark) -> None: - def schema_gen() -> None: - def serialize_idempotent(x: Any, nxt: SerializerFunctionWrapHandler) -> Any: - return nxt(x) - - class ModelWithAnnotatedSerializer(BaseModel): - field: Annotated[List, WrapSerializer(serialize_idempotent, when_used='json')] + @model_serializer + def serialize_model(self) -> Any: + return self.field - benchmark(schema_gen) + benchmark(rebuild_model, ModelWithValidator) From 61818e281d3f4f1143ec6bb449615d4301c2894c Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:42:40 +0200 Subject: [PATCH 040/412] Fix schema generation error when serialization schema holds references (#10444) --- pydantic/_internal/_core_utils.py | 4 ++++ tests/test_serialize.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/pydantic/_internal/_core_utils.py b/pydantic/_internal/_core_utils.py index 493954c789c..dea6bbcd64f 100644 --- a/pydantic/_internal/_core_utils.py +++ b/pydantic/_internal/_core_utils.py @@ -458,6 +458,10 @@ def count_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreS return s current_recursion_ref_count[ref] += 1 + if 'serialization' in s: + # Even though this is a `'definition-ref'` schema, there might + # be more references inside the serialization schema: + recurse(s, count_refs) recurse(definitions[ref], count_refs) current_recursion_ref_count[ref] -= 1 return s diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 68e1c3dfbfb..95271f8af0e 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -892,6 +892,21 @@ def ser_model(self, v) -> int: assert return_serializer == 'return_serializer: Any' +def test_serializer_return_type_model() -> None: + """https://github.com/pydantic/pydantic/issues/10443""" + + class Sub(BaseModel): + pass + + class Model(BaseModel): + sub: Annotated[ + Sub, + PlainSerializer(lambda v: v, return_type=Sub), + ] + + assert Model(sub=Sub()).model_dump() == {'sub': {}} + + def test_type_adapter_dump_json(): class Model(TypedDict): x: int From 3a821f121c6b2dbf87f110944321f2ecf2ca4fbc Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:15:31 +0200 Subject: [PATCH 041/412] Inline references if possible when generating schema for `json_schema_input_type` (#10439) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- pydantic/functional_validators.py | 21 ++++++++++++ tests/test_json_schema.py | 54 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/pydantic/functional_validators.py b/pydantic/functional_validators.py index 0f8260bab01..603400c7978 100644 --- a/pydantic/functional_validators.py +++ b/pydantic/functional_validators.py @@ -127,6 +127,13 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH if self.json_schema_input_type is PydanticUndefined else handler.generate_schema(self.json_schema_input_type) ) + # Try to resolve the original schema if required, because schema cleaning + # won't inline references in metadata: + if input_schema is not None: + try: + input_schema = handler.resolve_ref_schema(input_schema) + except LookupError: + pass metadata = _core_metadata.build_metadata_dict(js_input_core_schema=input_schema) info_arg = _inspect_validator(self.func, 'before') @@ -204,6 +211,13 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH serialization = None input_schema = handler.generate_schema(self.json_schema_input_type) + # Try to resolve the original schema if required, because schema cleaning + # won't inline references in metadata: + try: + input_schema = handler.resolve_ref_schema(input_schema) + except LookupError: + pass + metadata = _core_metadata.build_metadata_dict(js_input_core_schema=input_schema) info_arg = _inspect_validator(self.func, 'plain') @@ -281,6 +295,13 @@ def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaH if self.json_schema_input_type is PydanticUndefined else handler.generate_schema(self.json_schema_input_type) ) + # Try to resolve the original schema if required, because schema cleaning + # won't inline references in metadata: + if input_schema is not None: + try: + input_schema = handler.resolve_ref_schema(input_schema) + except LookupError: + pass metadata = _core_metadata.build_metadata_dict(js_input_core_schema=input_schema) info_arg = _inspect_validator(self.func, 'wrap') diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index c1649c105ae..dbe2bc0b49a 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -6549,6 +6549,60 @@ def validate_f(cls, value: Any) -> int: ... } +@pytest.mark.parametrize( + 'validator', + [ + PlainValidator(lambda v: v, json_schema_input_type='Sub'), + BeforeValidator(lambda v: v, json_schema_input_type='Sub'), + WrapValidator(lambda v, h: h(v), json_schema_input_type='Sub'), + ], +) +def test_json_schema_input_type_with_refs(validator) -> None: + """Test that `'definition-ref` schemas for `json_schema_input_type` are inlined. + + See: https://github.com/pydantic/pydantic/issues/10434. + """ + + class Sub(BaseModel): + pass + + class Model(BaseModel): + sub: Annotated[ + Sub, + PlainSerializer(lambda v: v, return_type=Sub), + validator, + ] + + json_schema = Model.model_json_schema() + + assert 'Sub' in json_schema['$defs'] + assert json_schema['properties']['sub']['$ref'] == '#/$defs/Sub' + + +@pytest.mark.parametrize( + 'validator', + [ + PlainValidator(lambda v: v, json_schema_input_type='Model'), + BeforeValidator(lambda v: v, json_schema_input_type='Model'), + WrapValidator(lambda v, h: h(v), json_schema_input_type='Model'), + ], +) +def test_json_schema_input_type_with_recursive_refs(validator) -> None: + """Test that recursive `'definition-ref` schemas for `json_schema_input_type` are not inlined.""" + + class Model(BaseModel): + model: Annotated[ + 'Model', + PlainSerializer(lambda v: v, return_type='Model'), + validator, + ] + + json_schema = Model.model_json_schema() + + assert 'Model' in json_schema['$defs'] + assert json_schema['$ref'] == '#/$defs/Model' + + def test_title_strip() -> None: class Model(BaseModel): some_field: str = Field(alias='_some_field') From bb4e26c2151713662eaf507887305b5864759658 Mon Sep 17 00:00:00 2001 From: Adolfo Villalobos Date: Fri, 20 Sep 2024 23:51:10 +1000 Subject: [PATCH 042/412] benchmark tagged/untagged unions and stdlib types in schema generation (#10362) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- tests/benchmarks/shared.py | 131 +++++++++++++++++- .../test_model_schema_generation.py | 82 ++++++++++- 2 files changed, 210 insertions(+), 3 deletions(-) diff --git a/tests/benchmarks/shared.py b/tests/benchmarks/shared.py index 7a1e5d5c980..6ce6fd4639c 100644 --- a/tests/benchmarks/shared.py +++ b/tests/benchmarks/shared.py @@ -1,4 +1,36 @@ -from typing import Dict, List, Optional, Union +from collections import deque +from datetime import date, datetime, time, timedelta +from decimal import Decimal +from enum import Enum, IntEnum +from ipaddress import ( + IPv4Address, + IPv4Interface, + IPv4Network, + IPv6Address, + IPv6Interface, + IPv6Network, +) +from pathlib import Path +from re import Pattern +from typing import ( + Any, + Callable, + Deque, + Dict, + FrozenSet, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, +) +from uuid import UUID, uuid4, uuid5 + +from typing_extensions import Literal, TypedDict from pydantic import BaseModel @@ -24,3 +56,100 @@ class ComplexModel(BaseModel): field1: Union[str, int, float] field2: List[Dict[str, Union[int, float]]] field3: Optional[List[Union[str, int]]] + + +class Color(Enum): + RED = 'red' + GREEN = 'green' + BLUE = 'blue' + + +class ToolEnum(IntEnum): + spanner = 1 + wrench = 2 + screwdriver = 3 + + +class Point(NamedTuple): + x: int + y: int + + +class User(TypedDict): + name: str + id: int + + +class Foo: + pass + + +StdLibTypes = [ + deque, # collections.deque + Deque[str], # typing.Deque + Deque[int], # typing.Deque + Deque[float], # typing.Deque + Deque[bytes], # typing.Deque + str, # str + int, # int + float, # float + complex, # complex + bool, # bool + bytes, # bytes + date, # datetime.date + datetime, # datetime.datetime + time, # datetime.time + timedelta, # datetime.timedelta + Decimal, # decimal.Decimal + Color, # enum + ToolEnum, # int enum + IPv4Address, # ipaddress.IPv4Address + IPv6Address, # ipaddress.IPv6Address + IPv4Interface, # ipaddress.IPv4Interface + IPv6Interface, # ipaddress.IPv6Interface + IPv4Network, # ipaddress.IPv4Network + IPv6Network, # ipaddress.IPv6Network + Path, # pathlib.Path + Pattern, # typing.Pattern + UUID, # uuid.UUID + uuid4, # uuid.uuid4 + uuid5, # uuid.uuid5 + Point, # named tuple + list, # built-in list + List[int], # typing.List + List[str], # typing.List + List[bytes], # typing.List + List[float], # typing.List + dict, # built-in dict + Dict[str, float], # typing.Dict + Dict[str, bytes], # typing.Dict + Dict[str, int], # typing.Dict + Dict[str, str], # typing.Dict + User, # TypedDict + tuple, # tuple + Tuple[int, str, float], # typing.Tuple + set, # built-in set + Set[int], # typing.Set + Set[str], # typing.Set + frozenset, # built-in frozenset + FrozenSet[int], # typing.FrozenSet + FrozenSet[str], # typing.FrozenSet + Optional[int], # typing.Optional + Optional[str], # typing.Optional + Optional[float], # typing.Optional + Optional[bytes], # typing.Optional + Optional[bool], # typing.Optional + Sequence[int], # typing.Sequence + Sequence[str], # typing.Sequence + Sequence[bytes], # typing.Sequence + Sequence[float], # typing.Sequence + Iterable[int], # typing.Iterable + Iterable[str], # typing.Iterable + Iterable[bytes], # typing.Iterable + Iterable[float], # typing.Iterable + Callable[[int], int], # typing.Callable + Callable[[str], str], # typing.Callable + Literal['apple', 'pumpkin'], # + Type[Foo], # typing.Type + Any, # typing.Any +] diff --git a/tests/benchmarks/test_model_schema_generation.py b/tests/benchmarks/test_model_schema_generation.py index d491218f093..d786a79c403 100644 --- a/tests/benchmarks/test_model_schema_generation.py +++ b/tests/benchmarks/test_model_schema_generation.py @@ -1,4 +1,15 @@ -from typing import Any, Dict, Generic, List, Literal, Optional, Type, TypeVar, Union, get_origin +from typing import ( + Any, + Dict, + Generic, + List, + Literal, + Optional, + Type, + TypeVar, + Union, + get_origin, +) import pytest from typing_extensions import Annotated, Self @@ -11,6 +22,7 @@ Field, PlainSerializer, PlainValidator, + Tag, WrapSerializer, WrapValidator, create_model, @@ -19,6 +31,8 @@ ) from pydantic.dataclasses import dataclass, rebuild_dataclass +from .shared import StdLibTypes + class DeferredModel(BaseModel): model_config = {'defer_build': True} @@ -42,7 +56,11 @@ class SimpleModel(DeferredModel): def test_simple_model_schema_lots_of_fields_generation(benchmark) -> None: IntStr = Union[int, str] - Model = create_model('Model', __config__={'defer_build': True}, **{f'f{i}': (IntStr, ...) for i in range(100)}) + Model = create_model( + 'Model', + __config__={'defer_build': True}, + **{f'f{i}': (IntStr, ...) for i in range(100)}, + ) benchmark(rebuild_model, Model) @@ -219,3 +237,63 @@ def serialize_model(self) -> Any: return self.field benchmark(rebuild_model, ModelWithValidator) + + +@pytest.mark.benchmark(group='model_schema_generation') +def test_tagged_union_with_str_discriminator_schema_generation(benchmark): + class Cat(BaseModel): + pet_type: Literal['cat'] + meows: int + + class Dog(BaseModel): + pet_type: Literal['dog'] + barks: float + + class Lizard(BaseModel): + pet_type: Literal['reptile', 'lizard'] + scales: bool + + class Model(DeferredModel): + pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type') + n: int + + benchmark(rebuild_model, Model) + + +@pytest.mark.benchmark(group='model_schema_generation') +def test_tagged_union_with_callable_discriminator_schema_generation(benchmark): + class Pie(BaseModel): + time_to_cook: int + num_ingredients: int + + class ApplePie(Pie): + fruit: Literal['apple'] = 'apple' + + class PumpkinPie(Pie): + filling: Literal['pumpkin'] = 'pumpkin' + + def get_discriminator_value(v: Any) -> str: + if isinstance(v, dict): + return v.get('fruit', v.get('filling')) + return getattr(v, 'fruit', getattr(v, 'filling', None)) + + class ThanksgivingDinner(DeferredModel): + dessert: Annotated[ + Union[ + Annotated[ApplePie, Tag('apple')], + Annotated[PumpkinPie, Tag('pumpkin')], + ], + Discriminator(get_discriminator_value), + ] + + benchmark(rebuild_model, ThanksgivingDinner) + + +@pytest.mark.parametrize('field_type', StdLibTypes) +@pytest.mark.benchmark(group='stdlib_schema_generation') +@pytest.mark.skip('Clutters codspeed CI, but should be enabled on branches where we modify schema building.') +def test_stdlib_type_schema_generation(benchmark, field_type): + class StdlibTypeModel(DeferredModel): + field: field_type + + benchmark(rebuild_model, StdlibTypeModel) From 1eb73033f6951b0e6c5e2441d2b0198d7bb3a36e Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:38:38 +0200 Subject: [PATCH 043/412] Remove unused `mro` argument to `get_cls_type_hints_lenient` (#10451) --- pydantic/_internal/_typing_extra.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pydantic/_internal/_typing_extra.py b/pydantic/_internal/_typing_extra.py index 50566e5a24a..f199b8eee9b 100644 --- a/pydantic/_internal/_typing_extra.py +++ b/pydantic/_internal/_typing_extra.py @@ -11,7 +11,7 @@ from collections.abc import Callable from functools import partial from types import GetSetDescriptorType -from typing import TYPE_CHECKING, Any, Final, Iterable +from typing import TYPE_CHECKING, Any, Final from typing_extensions import Annotated, Literal, TypeAliasType, TypeGuard, deprecated, get_args, get_origin @@ -227,17 +227,13 @@ def merge_cls_and_parent_ns(cls: type[Any], parent_namespace: dict[str, Any] | N return ns -def get_cls_type_hints_lenient( - obj: Any, globalns: dict[str, Any] | None = None, mro: Iterable[type[Any]] | None = None -) -> dict[str, Any]: +def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]: """Collect annotations from a class, including those from parent classes. Unlike `typing.get_type_hints`, this function will not error if a forward reference is not resolvable. """ hints = {} - if mro is None: - mro = reversed(obj.__mro__) - for base in mro: + for base in reversed(obj.__mro__): ann = base.__dict__.get('__annotations__') localns = dict(vars(base)) if ann is not None and ann is not GetSetDescriptorType: From 9b69920888054df4ef544ecbdc03e09b90646ac2 Mon Sep 17 00:00:00 2001 From: Oliver Parker <46482091+ollz272@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:24:57 +0100 Subject: [PATCH 044/412] Add millisecond option to `ConfigDict.ser_json_timedelta` (#10293) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Co-authored-by: sydney-runkle --- pdm.lock | 280 +++++++++++++++++----------------- pydantic/_internal/_config.py | 12 +- pydantic/config.py | 10 +- pydantic/json_schema.py | 2 +- pydantic/warnings.py | 7 + pyproject.toml | 2 +- tests/test_json_schema.py | 86 +++++++++-- 7 files changed, 239 insertions(+), 160 deletions(-) diff --git a/pdm.lock b/pdm.lock index e73840f3bc2..ef7dd96f73e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "email", "linting", "memray", "mypy", "testing", "testing-extra", "timezone"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:c1127f3971d4a0c6271ab715e0bd62f40e67b070ea9cc71c78fa9dce52fc993d" +content_hash = "sha256:f81bce4df16a23258371beb49db59afcdf67484fa7c149159d041762dead7eb5" [[metadata.targets]] requires_python = ">=3.8" @@ -583,15 +583,15 @@ files = [ [[package]] name = "faker" -version = "28.4.1" +version = "29.0.0" requires_python = ">=3.8" summary = "Faker is a Python package that generates fake data for you." dependencies = [ "python-dateutil>=2.4", ] files = [ - {file = "Faker-28.4.1-py3-none-any.whl", hash = "sha256:e59c01d1e8b8e20a83255ab8232c143cb2af3b4f5ab6a3f5ce495f385ad8ab4c"}, - {file = "faker-28.4.1.tar.gz", hash = "sha256:4294d169255a045990720d6f3fa4134b764a4cdf46ef0d3c7553d2506f1adaa1"}, + {file = "Faker-29.0.0-py3-none-any.whl", hash = "sha256:32d0ee7d42925ff06e4a7d906ee7efbf34f5052a41a2a1eb8bb174a422a5498f"}, + {file = "faker-29.0.0.tar.gz", hash = "sha256:34e89aec594cad9773431ca479ee95c7ce03dd9f22fda2524e2373b880a2fa77"}, ] [[package]] @@ -1070,7 +1070,7 @@ files = [ [[package]] name = "mkdocs-material" -version = "9.5.34" +version = "9.5.35" requires_python = ">=3.8" summary = "Documentation that simply works" dependencies = [ @@ -1087,8 +1087,8 @@ dependencies = [ "requests~=2.26", ] files = [ - {file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"}, - {file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"}, + {file = "mkdocs_material-9.5.35-py3-none-any.whl", hash = "sha256:44e069d87732d29f4a2533ae0748fa0e67e270043270c71f04d0fba11a357b24"}, + {file = "mkdocs_material-9.5.35.tar.gz", hash = "sha256:0d233d7db067ac896bf22ee7950eebf2b1eaf26c155bb27382bf4174021cc117"}, ] [[package]] @@ -1276,102 +1276,102 @@ files = [ [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.24.0" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:607dcbedeace540deec3f7da7a1d82fe23accd2eee5c410a4753d1d0d03e3b46"}, + {file = "pydantic_core-2.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59a2a1b20dcde876c7719d095d3ab8fe040253c0ed1cd76df06a78b3e8680ca5"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:797d4737142796e9de0666deea3ac5ca54f7abb3dd8a2a173095ee375ed70c39"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1f3f9607e4639f54177ffec2a67d519afb67df852367aaac7e171ba8c66bc1d4"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1abd4ec106aff5959d34765ec67a8bb7efd12b9d6655a96849a4fafc093744f0"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cde58d1746195ebf6f2b046958eb2088748009e48447f216bcfffc37382d429"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fe50b2e6a8af01f3adcd5f45fbc8eecca4f5d50fb84dc1c723655a3906c510"}, + {file = "pydantic_core-2.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1876c3234e8719193b2e6efb5f774db8f957585c4d6fe72c85f906c1f87a0d31"}, + {file = "pydantic_core-2.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f414e3b5edd16d6b13677240ebdec9e31737c26f24c68f73e50b2e4a9d13def5"}, + {file = "pydantic_core-2.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f802b83155fc5bcbb6e753802230e1db5a942a42110b05c997f114c8266dc79f"}, + {file = "pydantic_core-2.24.0-cp310-none-win32.whl", hash = "sha256:e845533e6279dac0a74c1cec95aba24e908cd64eb045b687b151f3dbb45e00f8"}, + {file = "pydantic_core-2.24.0-cp310-none-win_amd64.whl", hash = "sha256:793e670f32e52ceb5fb2396919b19312291f09a4c10752eab1f69871dd265a73"}, + {file = "pydantic_core-2.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:51fac2c6c5009851dac7150a2a052fcb54d2e89e481d747cc0c2100c2927e8bb"}, + {file = "pydantic_core-2.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f92c5356f7aee92c693a2dee9d69e4c90149d1b604f6bd2255bfb39977f8e8f"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6d8ecc8f8418ba2779a2dd357cb7dc3d30b98b9841cf6b22a54b44747e4bd2c"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93934fcf839fe6150a63f877f0dc99967a191d304ac1e30f5364e175212f67df"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eed81a5a673258f1aae6e08c1b3f0ff74e232bce5e56ffb942ace43376019d7"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84c4a4f3f81d23c1cdd001e6a1db5c19c5bfb0af42a89482ddc269b7b7429f75"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c92719d5662d91813a12cf5422c5374a7099434dd50e17f6d630243eb105ce2"}, + {file = "pydantic_core-2.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f07d3c9a49dfec0d5b22fdff7a5f7d8fe8cfd6ffb75c9a8b57cd21d6c516205"}, + {file = "pydantic_core-2.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9a91a2ee612a4a49596a1529cd60471657db3b7192d505472d9e803ec0babcfc"}, + {file = "pydantic_core-2.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d7444ee5415a7a21932ea614997a0f422bd82ae4a04991d8c9fc3aa1d10a5a33"}, + {file = "pydantic_core-2.24.0-cp311-none-win32.whl", hash = "sha256:512972b3aae076afb10bb80d3bd49c19ee8ff43e2ab040fb166fae18ebdbb7fe"}, + {file = "pydantic_core-2.24.0-cp311-none-win_amd64.whl", hash = "sha256:e161760fe70ecf4a9505e069409bfc001a1a739f350043615a6bde42bdd50f7b"}, + {file = "pydantic_core-2.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:cfbee1fc1875c16fe9f7fbf71898bfcfb97945f3bb5a6d9a4ef04c91d8ada958"}, + {file = "pydantic_core-2.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6a18e6b6379431578fb5fbf30d6c2d8e2a0ce2576ea992070049444fb7b1902"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830a286b7078fb544797d440a64f5692c6baf4a759750d51afe41cdc0760f785"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:270aec28844a0cfa08cfd2abdac805b99013c5260ecbccedf8c7e40bb7dde9b5"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2facc30616a867f639e9600d207c3e2bef637f009239770a3be8cf10dbb2af4b"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f57db5b7c1348f84ed9bd1e9e7a6d3a1d99965ba8c159ecc9fe2b024e36e038"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72b5683ae8609297bd31022ca87e097cd1719494542495f2b51522616498bea"}, + {file = "pydantic_core-2.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20dd8a5c841bebab9dddb2e92e06b6d21237b7640d540a0d0639835c168b99f7"}, + {file = "pydantic_core-2.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aff822465415f150a7bc578bdf9bafcf4c15552e989d64f7085752a41ee0c85a"}, + {file = "pydantic_core-2.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71594f9a946c4dab8afa80c48fbe2318452a501426969449bce4a99d6795bebc"}, + {file = "pydantic_core-2.24.0-cp312-none-win32.whl", hash = "sha256:6e7f150629a8baa4ea9c4a84e9222753470e79f3095ce7b84a7d5d1cf9e3ca7e"}, + {file = "pydantic_core-2.24.0-cp312-none-win_amd64.whl", hash = "sha256:1061dfd544be92fbdd2b96e86ad4291d33a98655bac8ccbcd22c660cdb043565"}, + {file = "pydantic_core-2.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:11c132961c2f385e364827e1795279f90eddd00729ed3f3c5cbbe89c71ea6c0e"}, + {file = "pydantic_core-2.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c2c4d2634963c236c6f00eb0d370d9fb6b38b187f2c0a36c1b5b9a53f0e53b5"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a25a9994c690142c8492419a14bf2e89cd5e7788203f32957b427fac9b1e534f"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:594a7776c9fd89037e93e155a7fb5e4db3d27cb59fdccf21f3121cbea9cc7e0b"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74beb5b41a66a9e8e68d9ad6212434428a007e4980dcabecb19308bd94f0b8a7"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6315bae7717ac7f6e0d190b29a7df794db5978b871dcd60de3e9286f9711415f"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cad385a4aa38174ee134a607bb1bcc88698353ede71b725e0ff61cb417a0697"}, + {file = "pydantic_core-2.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffc98c29e607aaf5abb64e757a2447309362ff23a6a0b6d7f6dbc1b6a62c9046"}, + {file = "pydantic_core-2.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:35906dc79898e7b217714756bae91dc235e936166af146abb5e95f4957296378"}, + {file = "pydantic_core-2.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:55eedff3a489e7834c9a8c91363aa8e96e6ce3a75597919417fabafca23dc89e"}, + {file = "pydantic_core-2.24.0-cp313-none-win32.whl", hash = "sha256:31ec26f765c792e454aa29af01f8014cec0a9417be09d572107c7f66857d3a69"}, + {file = "pydantic_core-2.24.0-cp313-none-win_amd64.whl", hash = "sha256:b31024b34d9e83d51e741cd67094c401097e254c3a322a6572118847960cfdbd"}, + {file = "pydantic_core-2.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:adda23d459ae85f6e5191fd7936e65197bbf93106825db49340ee4e5a29c2ed7"}, + {file = "pydantic_core-2.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:758c22dbd1f85c9176256c82ed7d459af4c838dd98b02e23b8305a76230b4b56"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85772cb2110bcfb37e43a9d4f95815aca3f2fb80076376a65b8eb432ad9502dc"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10e0de04b812a71efaeff81c549954674980fe638ddff86aa645334c493ccb6"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70f5ab351c091c80194ac9580db0d0ca0d1709c8f002cbaff66e49e749336a31"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94f74148886ce3e9bb67a6c2f9d4aebf2523eeb5d6d15abc38e53462a56f452"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7f9c4da7eeaaa76417ddb33c4dca0d527015b369e033e57e15e07aaf53dcee"}, + {file = "pydantic_core-2.24.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:06053dfa8e0cbf392c51c2bb5b8a8556f65e6505114e088b4a833e38e03e2921"}, + {file = "pydantic_core-2.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f18892b572930e2a6f0174ffe0adb21a75aa5c985c86777b986f903345ed96a"}, + {file = "pydantic_core-2.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:740d76fe44f276ffa44209f821df79537bcb2aef6f35ca3561e4284ce1d13a1a"}, + {file = "pydantic_core-2.24.0-cp38-none-win32.whl", hash = "sha256:886ca9af28c76e58373683bc585986586bdb57dedeb6aa51e85a5c6bbce25100"}, + {file = "pydantic_core-2.24.0-cp38-none-win_amd64.whl", hash = "sha256:1409c25a9e7aed446be84fa075d8f756b48ab3362eb9d0bbf8cf01155e224e71"}, + {file = "pydantic_core-2.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c500365641f0a10ba505bc90b31179ef7a0f5a57071bc7880fc2b3e94967718c"}, + {file = "pydantic_core-2.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c992be170ae059fb8464c1024c76dfa3a4cd5c7aca3cf3bf37b83193acd78761"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86b1d55b6832e39231ab8675530767cefd2b01178b474bf5af2d47f4a6910236"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57bbb50d6ed6f89e9231da28ecb00091fa33abf5c642d4eafd0dc04b950ddac5"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6802b87b186fac1d6c5adc2a79002cbc9e28d75576b4006ed49500101b1f1c9"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7ce94fcd44e1ea6381e94295dff4f5af08dd2706321b87126fcf78e6d17533"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a597b3f3b20f0b56e3bde46c5ade623f4b411941c4d0111c696eed1d49712b22"}, + {file = "pydantic_core-2.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ea78d40c4942ea4790ae4ea8febae6eaeb500ae1d55af951126834d203538683"}, + {file = "pydantic_core-2.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:867ec3c429934d0a45798a4f104469a9063ea3256de2f0a54134a977f71da3c2"}, + {file = "pydantic_core-2.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3a453150a95a83273a8720df7728d6cb7a2fbb6c04b80d6ad79d9daf4d1aa80a"}, + {file = "pydantic_core-2.24.0-cp39-none-win32.whl", hash = "sha256:e43908e61795f6d8853d38be973ac6d4207012cb413f192e9d1239ef1cabf3e3"}, + {file = "pydantic_core-2.24.0-cp39-none-win_amd64.whl", hash = "sha256:c492c35d36a75d235a121959958e8a9812108c4fdeddf45fb8945929a58500f6"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fb13546dccdffa103b1fc39b0575b4d14ac4f4120e9085848853d9a14f6c2063"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f5617bd807f84ca48b5a112ff6dbf5f3f25ecffae4ffa1a01333462301922d9"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44229a4e94a9bbb475bf7604f5a57f976f68d7e8aba51cc436f1ac3e31bc59f2"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70bc858c30b1988a873d5dee508d386aa180d97b5de7212382723702acdd840"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:da0c4ebb7089cf634d3fdf2fd747e969a53f258dcc263b48421fb2c9962e1e5d"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0f3e16f5b7a2180616bb73d9f50572dcff1246a1edbfbadaa39ca8f5687e359a"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e80c4ecbd86292597cadd05db22a1f94005348646c1c141ee2fb1970967a4cc0"}, + {file = "pydantic_core-2.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:23b40a08e30fb55d673d93d120026b5386ca96fc2550eb1800fa445d11d03e1a"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d393fe3173bf87d20a8df7ce65a315a4b795965a3d64abeca8c4ddbcdd8173a4"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:128754d5385f90e178fbf1293b457b468e290903dbb0fb0826f97da8472fe84c"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24c28c93cbae4131af837dcdeedf5bac25d13cf92a18d611f464564dd766fc36"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3fbed015dc8f1ab4bad7444bc0204c68781082e64ab7092bf451ee6e793eebb"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73450c90cfb4ec4d206c6d375ece549da4e7c58432e98a42ecd5501cef0f530b"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5fba3255b3733845ce501bdca9e5a30a74997c9b6e23991a84987895e5e54cb6"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b0bd9ed888f1607918e8fc405142116170c4fbfba30e7a94fd60853067bd626f"}, + {file = "pydantic_core-2.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:581349fea1a9e2f8c7460f234dccd3f4b4dc00a00ade46eab23abc47f23ebd83"}, + {file = "pydantic_core-2.24.0.tar.gz", hash = "sha256:2bdfe0cd0b977163c3205e89b8585203563a86822e8b20c8e107765b655dcfc4"}, ] [[package]] @@ -1947,7 +1947,7 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.34" +version = "2.0.35" requires_python = ">=3.7" summary = "Database Abstraction Library" dependencies = [ @@ -1956,48 +1956,48 @@ dependencies = [ "typing-extensions>=4.6.0", ] files = [ - {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea54f7300553af0a2a7235e9b85f4204e1fc21848f917a3213b0e0818de9a24"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:196958cde924a00488e3e83ff917be3b73cd4ed8352bbc0f2989333176d1c54d"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-win32.whl", hash = "sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-win_amd64.whl", hash = "sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7db3db284a0edaebe87f8f6642c2b2c27ed85c3e70064b84d1c9e4ec06d5d84"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:430093fce0efc7941d911d34f75a70084f12f6ca5c15d19595c18753edb7c33b"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79cb400c360c7c210097b147c16a9e4c14688a6402445ac848f296ade6283bbc"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fddde2368e777ea2a4891a3fb4341e910a056be0bb15303bf1b92f073b80c02"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-win32.whl", hash = "sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-win_amd64.whl", hash = "sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43f28005141165edd11fbbf1541c920bd29e167b8bbc1fb410d4fe2269c1667a"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b68094b165a9e930aedef90725a8fcfafe9ef95370cbb54abc0464062dbf808f"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1e03db964e9d32f112bae36f0cc1dcd1988d096cfd75d6a588a3c3def9ab2b"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ae92bebca3b1e6bd203494e5ef919a60fb6dfe4d9a47ed2453211d3bd451b9f5"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-win32.whl", hash = "sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-win_amd64.whl", hash = "sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dbcdf987f3aceef9763b6d7b1fd3e4ee210ddd26cac421d78b3c206d07b2700b"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce119fc4ce0d64124d37f66a6f2a584fddc3c5001755f8a49f1ca0a177ef9796"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a17d8fac6df9835d8e2b4c5523666e7051d0897a93756518a1fe101c7f47f2f0"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e6965346fc1491a566e019a4a1d3dfc081ce7ac1a736536367ca305da6472a8"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-win32.whl", hash = "sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-win_amd64.whl", hash = "sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8"}, - {file = "SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f"}, - {file = "sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-win32.whl", hash = "sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, + {file = "SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1"}, + {file = "sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f"}, ] [[package]] diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index f4c4000d9bf..65da2e7226e 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -18,7 +18,7 @@ from ..aliases import AliasGenerator from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable from ..errors import PydanticUserError -from ..warnings import PydanticDeprecatedSince20 +from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210 if not TYPE_CHECKING: # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 @@ -68,7 +68,7 @@ class ConfigWrapper: strict: bool # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' revalidate_instances: Literal['always', 'never', 'subclass-instances'] - ser_json_timedelta: Literal['iso8601', 'float'] + ser_json_timedelta: Literal['iso8601', 'seconds_float', 'milliseconds_float'] ser_json_bytes: Literal['utf8', 'base64', 'hex'] val_json_bytes: Literal['utf8', 'base64', 'hex'] ser_json_inf_nan: Literal['null', 'constants', 'strings'] @@ -166,6 +166,14 @@ def core_config(self, obj: Any) -> core_schema.CoreConfig: """ config = self.config_dict + if config.get('ser_json_timedelta') == 'float': + warnings.warn( + 'The `float` option for `ser_json_timedelta` has been deprecated in favor of `seconds_float`. Please use this setting instead.', + PydanticDeprecatedSince210, + stacklevel=2, + ) + config['ser_json_timedelta'] = 'seconds_float' + core_config_values = { 'title': config.get('title') or (obj and obj.__name__), 'extra_fields_behavior': config.get('extra'), diff --git a/pydantic/config.py b/pydantic/config.py index 634cac0853f..260dc5548b3 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -562,13 +562,15 @@ class Transaction(BaseModel): 3. Using `'never'` we would have gotten `user=SubUser(hobbies=['scuba diving'], sins=['lying'])`. """ - ser_json_timedelta: Literal['iso8601', 'float'] + ser_json_timedelta: Literal['iso8601', 'seconds_float', 'milliseconds_float'] """ - The format of JSON serialized timedeltas. Accepts the string values of `'iso8601'` and - `'float'`. Defaults to `'iso8601'`. + The format of JSON serialized timedeltas. Accepts the string values of `'iso8601'`, + `'seconds_float'`, and `'milliseconds_float'`. Defaults to `'iso8601'`. - `'iso8601'` will serialize timedeltas to ISO 8601 durations. - - `'float'` will serialize timedeltas to the total number of seconds. + - `'seconds_float'` will serialize timedeltas to the total number of seconds. + - `'milliseconds_float'` will serialize timedeltas to the total number of milliseconds. + NOTE: `'float' is deprecated in v2.10 in favour of `'milliseconds_float'` """ ser_json_bytes: Literal['utf8', 'base64', 'hex'] diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py index b0653e0318b..5ee3e767ce9 100644 --- a/pydantic/json_schema.py +++ b/pydantic/json_schema.py @@ -720,7 +720,7 @@ def timedelta_schema(self, schema: core_schema.TimedeltaSchema) -> JsonSchemaVal Returns: The generated JSON schema. """ - if self._config.ser_json_timedelta == 'float': + if self._config.ser_json_timedelta in {'milliseconds_float', 'seconds_float'}: return {'type': 'number'} return {'type': 'string', 'format': 'duration'} diff --git a/pydantic/warnings.py b/pydantic/warnings.py index 9a0736a525b..4cbd52da519 100644 --- a/pydantic/warnings.py +++ b/pydantic/warnings.py @@ -67,6 +67,13 @@ def __init__(self, message: str, *args: object) -> None: super().__init__(message, *args, since=(2, 9), expected_removal=(3, 0)) +class PydanticDeprecatedSince210(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.10.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 10), expected_removal=(3, 0)) + + class GenericBeforeBaseModelWarning(Warning): pass diff --git a/pyproject.toml b/pyproject.toml index 7d3659497f9..d40dad70bde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ dependencies = [ 'typing-extensions>=4.6.1; python_version < "3.13"', 'typing-extensions>=4.12.2; python_version >= "3.13"', 'annotated-types>=0.6.0', - "pydantic-core==2.23.4", + "pydantic-core==2.24.0", ] dynamic = ['version', 'readme'] diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index dbe2bc0b49a..c1a898f23fd 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -108,6 +108,7 @@ conint, constr, ) +from pydantic.warnings import PydanticDeprecatedSince210 try: import email_validator @@ -1777,11 +1778,15 @@ class Outer(BaseModel): @pytest.mark.parametrize( 'ser_json_timedelta,properties', [ - ('float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('seconds_float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('milliseconds_float', {'duration': {'default': 300000.0, 'title': 'Duration', 'type': 'number'}}), ('iso8601', {'duration': {'default': 'PT5M', 'format': 'duration', 'title': 'Duration', 'type': 'string'}}), ], ) -def test_model_default_timedelta(ser_json_timedelta: Literal['float', 'iso8601'], properties: typing.Dict[str, Any]): +def test_model_default_timedelta( + ser_json_timedelta: Literal['iso8601', 'seconds_float', 'milliseconds_float'], + properties: typing.Dict[str, Any], +): class Model(BaseModel): model_config = ConfigDict(ser_json_timedelta=ser_json_timedelta) @@ -1795,6 +1800,22 @@ class Model(BaseModel): } +def test_model_default_timedelta(): + with pytest.warns(PydanticDeprecatedSince210): + + class Model(BaseModel): + model_config = ConfigDict(ser_json_timedelta='float') + + duration: timedelta = timedelta(minutes=5) + + # insert_assert(Model.model_json_schema(mode='serialization')) + assert Model.model_json_schema(mode='serialization') == { + 'properties': {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}, + 'title': 'Model', + 'type': 'object', + } + + @pytest.mark.parametrize( 'ser_json_bytes,properties', [ @@ -1819,12 +1840,14 @@ class Model(BaseModel): @pytest.mark.parametrize( 'ser_json_timedelta,properties', [ - ('float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('seconds_float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('milliseconds_float', {'duration': {'default': 300000.0, 'title': 'Duration', 'type': 'number'}}), ('iso8601', {'duration': {'default': 'PT5M', 'format': 'duration', 'title': 'Duration', 'type': 'string'}}), ], ) def test_dataclass_default_timedelta( - ser_json_timedelta: Literal['float', 'iso8601'], properties: typing.Dict[str, Any] + ser_json_timedelta: Literal['iso8601', 'milliseconds_float', 'seconds_float'], + properties: typing.Dict[str, Any], ): @dataclass(config=ConfigDict(ser_json_timedelta=ser_json_timedelta)) class Dataclass: @@ -1838,6 +1861,20 @@ class Dataclass: } +def test_dataclass_default_timedelta_float(): + with pytest.warns(PydanticDeprecatedSince210): + + @dataclass(config=ConfigDict(ser_json_timedelta='float')) + class Dataclass: + duration: timedelta = timedelta(minutes=5) + + assert TypeAdapter(Dataclass).json_schema(mode='serialization') == { + 'properties': {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}, + 'title': 'Dataclass', + 'type': 'object', + } + + @pytest.mark.parametrize( 'ser_json_bytes,properties', [ @@ -1861,24 +1898,49 @@ class Dataclass: @pytest.mark.parametrize( 'ser_json_timedelta,properties', [ - ('float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('seconds_float', {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}), + ('milliseconds_float', {'duration': {'default': 300000.0, 'title': 'Duration', 'type': 'number'}}), ('iso8601', {'duration': {'default': 'PT5M', 'format': 'duration', 'title': 'Duration', 'type': 'string'}}), ], ) def test_typeddict_default_timedelta( - ser_json_timedelta: Literal['float', 'iso8601'], properties: typing.Dict[str, Any] + ser_json_timedelta: Literal['iso8601', 'milliseconds_float', 'seconds_float'], + properties: typing.Dict[str, Any], ): class MyTypedDict(TypedDict): __pydantic_config__ = ConfigDict(ser_json_timedelta=ser_json_timedelta) duration: Annotated[timedelta, Field(timedelta(minutes=5))] - # insert_assert(TypeAdapter(MyTypedDict).json_schema(mode='serialization')) - assert TypeAdapter(MyTypedDict).json_schema(mode='serialization') == { - 'properties': properties, - 'title': 'MyTypedDict', - 'type': 'object', - } + if ser_json_timedelta == 'float': + with pytest.warns(PydanticDeprecatedSince210): + # insert_assert(TypeAdapter(MyTypedDict).json_schema(mode='serialization')) + assert TypeAdapter(MyTypedDict).json_schema(mode='serialization') == { + 'properties': properties, + 'title': 'MyTypedDict', + 'type': 'object', + } + else: + assert TypeAdapter(MyTypedDict).json_schema(mode='serialization') == { + 'properties': properties, + 'title': 'MyTypedDict', + 'type': 'object', + } + + +def test_typeddict_default_timedelta_float(): + class MyTypedDict(TypedDict): + __pydantic_config__ = ConfigDict(ser_json_timedelta='float') + + duration: Annotated[timedelta, Field(timedelta(minutes=5))] + + with pytest.warns(PydanticDeprecatedSince210): + # insert_assert(TypeAdapter(MyTypedDict).json_schema(mode='serialization')) + assert TypeAdapter(MyTypedDict).json_schema(mode='serialization') == { + 'properties': {'duration': {'default': 300.0, 'title': 'Duration', 'type': 'number'}}, + 'title': 'MyTypedDict', + 'type': 'object', + } @pytest.mark.parametrize( From e301d4aa94ccfb662aa2aa4a82eedf742c40f8d0 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 20 Sep 2024 19:52:28 +0200 Subject: [PATCH 045/412] Add support for unpacked `TypedDict` to type hint variadic keyword arguments with `@validate_call` (#10416) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- docs/errors/usage_errors.md | 54 ++++++++++++++- pydantic/_internal/_generate_schema.py | 27 +++++++- pydantic/_internal/_typing_extra.py | 16 ++++- pydantic/errors.py | 2 + tests/test_validate_call.py | 93 +++++++++++++++++++++++++- 5 files changed, 184 insertions(+), 8 deletions(-) diff --git a/docs/errors/usage_errors.md b/docs/errors/usage_errors.md index 0e06f190c0e..852fea21bf6 100644 --- a/docs/errors/usage_errors.md +++ b/docs/errors/usage_errors.md @@ -1151,7 +1151,6 @@ except PydanticUserError as exc_info: assert exc_info.code == 'model-config-invalid-field-name' ``` - ## [`with_config`][pydantic.config.with_config] is used on a `BaseModel` subclass {#with-config-on-model} This error is raised when the [`with_config`][pydantic.config.with_config] decorator is used on a class which is already a Pydantic model (use the `model_config` attribute instead). @@ -1169,7 +1168,6 @@ except PydanticUserError as exc_info: assert exc_info.code == 'with-config-on-model' ``` - ## `dataclass` is used on a `BaseModel` subclass {#dataclass-on-model} This error is raised when the Pydantic `dataclass` decorator is used on a class which is already @@ -1188,3 +1186,55 @@ try: except PydanticUserError as exc_info: assert exc_info.code == 'dataclass-on-model' ``` + +## [`Unpack`][typing.Unpack] used without a [`TypedDict`][typing.TypedDict] {#unpack-typed-dict} + +This error is raised when [`Unpack`][typing.Unpack] is used with something other than +a [`TypedDict`][typing.TypedDict] class object to type hint variadic keyword arguments. + +For reference, see the [related specification section] and [PEP 692]. + +```py +from typing_extensions import Unpack + +from pydantic import PydanticUserError, validate_call + +try: + + @validate_call + def func(**kwargs: Unpack[int]): + pass + +except PydanticUserError as exc_info: + assert exc_info.code == 'unpack-typed-dict' +``` + +## Overlapping unpacked [`TypedDict`][typing.TypedDict] fields and arguments {#overlapping-unpack-typed-dict} + +This error is raised when the typed dictionary used to type hint variadic keywords arguments +has field names overlapping with arguments (unless [positional only][positional-only_parameter]). + +For reference, see the [related specification section] and [PEP 692]. + +```py +from typing_extensions import TypedDict, Unpack + +from pydantic import PydanticUserError, validate_call + + +class TD(TypedDict): + a: int + + +try: + + @validate_call + def func(a: int, **kwargs: Unpack[TD]): + pass + +except PydanticUserError as exc_info: + assert exc_info.code == 'overlapping-unpack-typed-dict' +``` + +[related specification section]: https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments +[PEP 692]: https://peps.python.org/pep-0692/ diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index ef218ae991f..c161818e37c 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1890,6 +1890,7 @@ def _callable_schema(self, function: Callable[..., Any]) -> core_schema.CallSche arguments_list: list[core_schema.ArgumentsParameter] = [] var_args_schema: core_schema.CoreSchema | None = None var_kwargs_schema: core_schema.CoreSchema | None = None + var_kwargs_mode: core_schema.VarKwargsMode | None = None for name, p in sig.parameters.items(): if p.annotation is sig.empty: @@ -1905,7 +1906,30 @@ def _callable_schema(self, function: Callable[..., Any]) -> core_schema.CallSche var_args_schema = self.generate_schema(annotation) else: assert p.kind == Parameter.VAR_KEYWORD, p.kind - var_kwargs_schema = self.generate_schema(annotation) + + unpack_type = _typing_extra.unpack_type(annotation) + if unpack_type is not None: + if not is_typeddict(unpack_type): + raise PydanticUserError( + f'Expected a `TypedDict` class, got {unpack_type.__name__!r}', code='unpack-typed-dict' + ) + non_pos_only_param_names = { + name for name, p in sig.parameters.items() if p.kind != Parameter.POSITIONAL_ONLY + } + overlapping_params = non_pos_only_param_names.intersection(unpack_type.__annotations__) + if overlapping_params: + raise PydanticUserError( + f'Typed dictionary {unpack_type.__name__!r} overlaps with parameter' + f"{'s' if len(overlapping_params) >= 2 else ''} " + f"{', '.join(repr(p) for p in sorted(overlapping_params))}", + code='overlapping-unpack-typed-dict', + ) + + var_kwargs_mode = 'unpacked-typed-dict' + var_kwargs_schema = self._typed_dict_schema(unpack_type, None) + else: + var_kwargs_mode = 'uniform' + var_kwargs_schema = self.generate_schema(annotation) return_schema: core_schema.CoreSchema | None = None config_wrapper = self._config_wrapper @@ -1918,6 +1942,7 @@ def _callable_schema(self, function: Callable[..., Any]) -> core_schema.CallSche core_schema.arguments_schema( arguments_list, var_args_schema=var_args_schema, + var_kwargs_mode=var_kwargs_mode, var_kwargs_schema=var_kwargs_schema, populate_by_name=config_wrapper.populate_by_name, ), diff --git a/pydantic/_internal/_typing_extra.py b/pydantic/_internal/_typing_extra.py index f199b8eee9b..651cb18391b 100644 --- a/pydantic/_internal/_typing_extra.py +++ b/pydantic/_internal/_typing_extra.py @@ -13,7 +13,7 @@ from types import GetSetDescriptorType from typing import TYPE_CHECKING, Any, Final -from typing_extensions import Annotated, Literal, TypeAliasType, TypeGuard, deprecated, get_args, get_origin +from typing_extensions import Annotated, Literal, TypeAliasType, TypeGuard, Unpack, deprecated, get_args, get_origin if TYPE_CHECKING: from ._dataclasses import StandardDataclass @@ -63,7 +63,11 @@ def origin_is_union(tp: type[Any] | None) -> bool: LITERAL_TYPES: set[Any] = {Literal} if hasattr(typing, 'Literal'): - LITERAL_TYPES.add(typing.Literal) # type: ignore + LITERAL_TYPES.add(typing.Literal) + +UNPACK_TYPES: set[Any] = {Unpack} +if hasattr(typing, 'Unpack'): + UNPACK_TYPES.add(typing.Unpack) # pyright: ignore[reportAttributeAccessIssue] # Check if `deprecated` is a type to prevent errors when using typing_extensions < 4.9.0 DEPRECATED_TYPES: tuple[Any, ...] = (deprecated,) if isinstance(deprecated, type) else () @@ -116,6 +120,14 @@ def annotated_type(type_: Any) -> Any | None: return get_args(type_)[0] if is_annotated(type_) else None +def is_unpack(type_: Any) -> bool: + return get_origin(type_) in UNPACK_TYPES + + +def unpack_type(type_: Any) -> Any | None: + return get_args(type_)[0] if is_unpack(type_) else None + + def is_namedtuple(type_: type[Any]) -> bool: """Check if a given class is a named tuple. It can be either a `typing.NamedTuple` or `collections.namedtuple`. diff --git a/pydantic/errors.py b/pydantic/errors.py index b29dde88b59..7104f94d1d7 100644 --- a/pydantic/errors.py +++ b/pydantic/errors.py @@ -66,6 +66,8 @@ 'model-config-invalid-field-name', 'with-config-on-model', 'dataclass-on-model', + 'unpack-typed-dict', + 'overlapping-unpack-typed-dict', ] diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py index b13c121a7db..73870f99bdc 100644 --- a/tests/test_validate_call.py +++ b/tests/test_validate_call.py @@ -8,10 +8,18 @@ import pytest from pydantic_core import ArgsKwargs -from typing_extensions import Annotated, TypedDict +from typing_extensions import Annotated, Required, TypedDict, Unpack -from pydantic import Field, PydanticInvalidForJsonSchema, TypeAdapter, ValidationError, validate_call -from pydantic.main import BaseModel +from pydantic import ( + BaseModel, + Field, + PydanticInvalidForJsonSchema, + PydanticUserError, + TypeAdapter, + ValidationError, + validate_call, +) +from pydantic.config import with_config def test_args(): @@ -162,6 +170,85 @@ def foo(a, b, *args, d=3, **kwargs): assert foo(1, 2, kwargs=4, e=5) == "a=1, b=2, args=(), d=3, kwargs={'kwargs': 4, 'e': 5}" +def test_unpacked_typed_dict_kwargs_invalid_type() -> None: + with pytest.raises(PydanticUserError) as exc: + + @validate_call + def foo(**kwargs: Unpack[int]): + pass + + assert exc.value.code == 'unpack-typed-dict' + + +def test_unpacked_typed_dict_kwargs_overlaps() -> None: + class TD(TypedDict, total=False): + a: int + b: int + c: int + + with pytest.raises(PydanticUserError) as exc: + + @validate_call + def foo(a: int, b: int, **kwargs: Unpack[TD]): + pass + + assert exc.value.code == 'overlapping-unpack-typed-dict' + assert exc.value.message == "Typed dictionary 'TD' overlaps with parameters 'a', 'b'" + + # Works for a pos-only argument + @validate_call + def foo(a: int, /, **kwargs: Unpack[TD]): + pass + + foo(1, a=1) + + +def test_unpacked_typed_dict_kwargs() -> None: + @with_config({'strict': True}) + class TD(TypedDict, total=False): + a: int + b: Required[str] + + @validate_call + def foo(**kwargs: Unpack[TD]): + pass + + foo(a=1, b='test') + foo(b='test') + + with pytest.raises(ValidationError) as exc: + foo(a='1') + + assert exc.value.errors()[0]['type'] == 'int_type' + assert exc.value.errors()[0]['loc'] == ('a',) + assert exc.value.errors()[1]['type'] == 'missing' + assert exc.value.errors()[1]['loc'] == ('b',) + + # Make sure that when called without any arguments, + # empty kwargs are still validated against the typed dict: + with pytest.raises(ValidationError) as exc: + foo() + + assert exc.value.errors()[0]['type'] == 'missing' + assert exc.value.errors()[0]['loc'] == ('b',) + + +def test_unpacked_typed_dict_kwargs_functional_syntax() -> None: + TD = TypedDict('TD', {'in': int, 'x-y': int}) + + @validate_call + def foo(**kwargs: Unpack[TD]): + pass + + foo(**{'in': 1, 'x-y': 2}) + + with pytest.raises(ValidationError) as exc: + foo(**{'in': 'not_an_int', 'x-y': 1}) + + assert exc.value.errors()[0]['type'] == 'int_parsing' + assert exc.value.errors()[0]['loc'] == ('in',) + + def test_field_can_provide_factory() -> None: @validate_call def foo(a: int, b: int = Field(default_factory=lambda: 99), *args: int) -> int: From f66195b0a71bc10dc1f6dd41764e620c6381d594 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:42:51 +0200 Subject: [PATCH 046/412] Simplify logic in `collect_model_fields` (#10456) --- pydantic/_internal/_fields.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 216b2a5efaa..14440f903a0 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -102,6 +102,11 @@ def collect_model_fields( # noqa: C901 BaseModel = import_cached_base_model() FieldInfo_ = import_cached_field_info() + parent_fields_lookup: dict[str, FieldInfo] = {} + for base in reversed(bases): + if model_fields := getattr(base, 'model_fields', None): + parent_fields_lookup.update(model_fields) + type_hints = get_cls_type_hints_lenient(cls, types_namespace) # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older @@ -185,13 +190,10 @@ def collect_model_fields( # noqa: C901 else: # if field has no default value and is not in __annotations__ this means that it is # defined in a base class and we can take it from there - model_fields_lookup: dict[str, FieldInfo] = {} - for x in cls.__bases__[::-1]: - model_fields_lookup.update(getattr(x, 'model_fields', {})) - if ann_name in model_fields_lookup: + if ann_name in parent_fields_lookup: # The field was present on one of the (possibly multiple) base classes # copy the field to make sure typevar substitutions don't cause issues with the base classes - field_info = copy(model_fields_lookup[ann_name]) + field_info = copy(parent_fields_lookup[ann_name]) else: # The field was not found on any base classes; this seems to be caused by fields not getting # generated thanks to models not being fully defined while initializing recursive models. From 43ed0d3f5ae2a79f217b7311588a704984e175aa Mon Sep 17 00:00:00 2001 From: Oliver Parker <46482091+ollz272@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:41:30 +0100 Subject: [PATCH 047/412] test: rename test with duplicate name (#10470) --- tests/test_json_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index c1a898f23fd..58e484e9d28 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -1800,7 +1800,7 @@ class Model(BaseModel): } -def test_model_default_timedelta(): +def test_model_default_timedelta_deprecated_float(): with pytest.warns(PydanticDeprecatedSince210): class Model(BaseModel): From b195eecb21c1b392b4a89f4a9cf5a2f115de97b3 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:15:04 +0200 Subject: [PATCH 048/412] Fix recursive arguments in `Representation` (#10480) --- pydantic/_internal/_repr.py | 7 ++++++- pydantic/main.py | 7 +++++-- tests/test_main.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/pydantic/_internal/_repr.py b/pydantic/_internal/_repr.py index 54fb1d7bea3..7202f22ee44 100644 --- a/pydantic/_internal/_repr.py +++ b/pydantic/_internal/_repr.py @@ -46,12 +46,17 @@ def __repr_args__(self) -> ReprArgs: if not attrs_names and hasattr(self, '__dict__'): attrs_names = self.__dict__.keys() attrs = ((s, getattr(self, s)) for s in attrs_names) - return [(a, v) for a, v in attrs if v is not None] + return [(a, v if v is not self else self.__repr_recursion__(v)) for a, v in attrs if v is not None] def __repr_name__(self) -> str: """Name of the instance's class, used in __repr__.""" return self.__class__.__name__ + def __repr_recursion__(self, object: Any) -> str: + """Returns the string representation of a recursive object.""" + # This is copied over from the stdlib `pprint` module: + return f'' + def __repr_str__(self, join_str: str) -> str: return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__()) diff --git a/pydantic/main.py b/pydantic/main.py index 74643f15561..2c108790afc 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -1054,8 +1054,10 @@ def __repr_args__(self) -> _repr.ReprArgs: for k, v in self.__dict__.items(): field = self.model_fields.get(k) if field and field.repr: - yield k, v - + if v is not self: + yield k, v + else: + yield k, self.__repr_recursion__(v) # `__pydantic_extra__` can fail to be set if the model is not yet fully initialized. # This can happen if a `ValidationError` is raised during initialization and the instance's # repr is generated as part of the exception handling. Therefore, we use `getattr` here @@ -1071,6 +1073,7 @@ def __repr_args__(self) -> _repr.ReprArgs: # take logic from `_repr.Representation` without the side effects of inheritance, see #5740 __repr_name__ = _repr.Representation.__repr_name__ + __repr_recursion__ = _repr.Representation.__repr_recursion__ __repr_str__ = _repr.Representation.__repr_str__ __pretty__ = _repr.Representation.__pretty__ __rich_repr__ = _repr.Representation.__rich_repr__ diff --git a/tests/test_main.py b/tests/test_main.py index 6a30526c210..5d408227ca9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -117,6 +117,20 @@ def test_ultra_simple_repr(UltraSimpleModel): assert str(m) == 'a=10.2 b=10' +def test_recursive_repr() -> None: + class A(BaseModel): + a: object = None + + class B(BaseModel): + a: Optional[A] = None + + a = A() + a.a = a + b = B(a=a) + + assert re.match(r"B\(a=A\(a=''\)\)", repr(b)) is not None + + def test_default_factory_field(): def myfunc(): return 1 From c9190eedd8c536cd1445876c4a078c29b04d3aa1 Mon Sep 17 00:00:00 2001 From: Kyle Schwab Date: Wed, 25 Sep 2024 09:47:48 -0600 Subject: [PATCH 049/412] Fix representation for builtin function types. (#10479) --- pydantic/_internal/_repr.py | 2 +- tests/test_utils.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pydantic/_internal/_repr.py b/pydantic/_internal/_repr.py index 7202f22ee44..6730e14ca6f 100644 --- a/pydantic/_internal/_repr.py +++ b/pydantic/_internal/_repr.py @@ -93,7 +93,7 @@ def display_as_type(obj: Any) -> str: Takes some logic from `typing._type_repr`. """ - if isinstance(obj, types.FunctionType): + if isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)): return obj.__name__ elif obj is ...: return '...' diff --git a/tests/test_utils.py b/tests/test_utils.py index 37547e4367b..c60dfce4fe7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ import os import pickle import sys +import time from copy import copy, deepcopy from typing import Callable, Dict, Generic, List, NewType, Tuple, TypeVar, Union @@ -76,6 +77,7 @@ def get(self) -> T: ... (Tuple[str, ...], 'Tuple[str, ...]'), (Union[int, List[str], Tuple[str, int]], 'Union[int, List[str], Tuple[str, int]]'), (foobar, 'foobar'), + (time.time_ns, 'time_ns'), (LoggedVar, 'LoggedVar'), (LoggedVar(), 'LoggedVar'), ], From 520495f0907fc2e3f91436c008ca08a6314035f5 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:24:48 -0500 Subject: [PATCH 050/412] Fix annotation application issue with non numeric constraints (#10490) Co-authored-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> --- .../_internal/_known_annotated_metadata.py | 2 +- pydantic/_internal/_validators.py | 120 +++++++++++------- tests/test_types.py | 28 ++++ 3 files changed, 106 insertions(+), 44 deletions(-) diff --git a/pydantic/_internal/_known_annotated_metadata.py b/pydantic/_internal/_known_annotated_metadata.py index b34ece10d96..73a9d2e729d 100644 --- a/pydantic/_internal/_known_annotated_metadata.py +++ b/pydantic/_internal/_known_annotated_metadata.py @@ -279,7 +279,7 @@ def _apply_constraint_with_incompatibility_info( json_schema_constraint = 'minLength' if constraint == 'min_length' else 'maxLength' schema = cs.no_info_after_validator_function( - partial(get_constraint_validator(constraint), **{'constraint_value': value}), schema + partial(get_constraint_validator(constraint), **{constraint: value}), schema ) add_js_update_schema(schema, lambda: {json_schema_constraint: as_jsonable_value(value)}) # noqa: B023 elif constraint == 'allow_inf_nan' and value is False: diff --git a/pydantic/_internal/_validators.py b/pydantic/_internal/_validators.py index 6380fe83968..4082a617195 100644 --- a/pydantic/_internal/_validators.py +++ b/pydantic/_internal/_validators.py @@ -14,7 +14,6 @@ from pydantic_core import PydanticCustomError, core_schema from pydantic_core._pydantic_core import PydanticKnownError -from pydantic_core.core_schema import ErrorType def sequence_validator( @@ -253,57 +252,92 @@ def forbid_inf_nan_check(x: Any) -> Any: return x -_InputType = typing.TypeVar('_InputType') +def _safe_repr(v: Any) -> int | float | str: + """The context argument for `PydanticKnownError` requires a number or str type, so we do a simple repr() coercion for types like timedelta. + See tests/test_types.py::test_annotated_metadata_any_order for some context. + """ + if isinstance(v, (int, float, str)): + return v + return repr(v) -def create_constraint_validator( - constraint_id: str, - predicate: Callable[[_InputType, Any], bool], - error_type: ErrorType, - context_gen: Callable[[Any, Any], dict[str, Any]] | None = None, -) -> Callable[[_InputType, Any], _InputType]: - """Create a validator function for a given constraint. - Args: - constraint_id: The constraint identifier, used to identify the constraint in error messages, ex 'gt'. - predicate: The predicate function to apply to the input value, ex `lambda x, gt: x > gt`. - error_type: The error type to raise if the predicate fails. - context_gen: A function to generate the error context from the constraint value and the input value. - """ +def greater_than_validator(x: Any, gt: Any) -> Any: + try: + if not (x > gt): + raise PydanticKnownError('greater_than', {'gt': _safe_repr(gt)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'gt' to supplied value {x}") - def validator(x: _InputType, constraint_value: Any) -> _InputType: - try: - if not predicate(x, constraint_value): - raise PydanticKnownError( - error_type, context_gen(constraint_value, x) if context_gen else {constraint_id: constraint_value} - ) - except TypeError: - raise TypeError(f"Unable to apply constraint '{constraint_id}' to supplied value {x}") + +def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: + try: + if not (x >= ge): + raise PydanticKnownError('greater_than_equal', {'ge': _safe_repr(ge)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'ge' to supplied value {x}") + + +def less_than_validator(x: Any, lt: Any) -> Any: + try: + if not (x < lt): + raise PydanticKnownError('less_than', {'lt': _safe_repr(lt)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'lt' to supplied value {x}") + + +def less_than_or_equal_validator(x: Any, le: Any) -> Any: + try: + if not (x <= le): + raise PydanticKnownError('less_than_equal', {'le': _safe_repr(le)}) return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'le' to supplied value {x}") + + +def multiple_of_validator(x: Any, multiple_of: Any) -> Any: + try: + if x % multiple_of: + raise PydanticKnownError('multiple_of', {'multiple_of': _safe_repr(multiple_of)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'multiple_of' to supplied value {x}") + - return validator +def min_length_validator(x: Any, min_length: Any) -> Any: + try: + if not (len(x) >= min_length): + raise PydanticKnownError( + 'too_short', {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)} + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'min_length' to supplied value {x}") + + +def max_length_validator(x: Any, max_length: Any) -> Any: + try: + if len(x) > max_length: + raise PydanticKnownError( + 'too_long', + {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'max_length' to supplied value {x}") _CONSTRAINT_TO_VALIDATOR_LOOKUP: dict[str, Callable] = { - 'gt': create_constraint_validator('gt', lambda x, gt: x > gt, 'greater_than'), - 'ge': create_constraint_validator('ge', lambda x, ge: x >= ge, 'greater_than_equal'), - 'lt': create_constraint_validator('lt', lambda x, lt: x < lt, 'less_than'), - 'le': create_constraint_validator('le', lambda x, le: x <= le, 'less_than_equal'), - 'multiple_of': create_constraint_validator( - 'multiple_of', lambda x, multiple_of: x % multiple_of == 0, 'multiple_of' - ), - 'min_length': create_constraint_validator( - 'min_length', - lambda x, min_length: len(x) >= min_length, - 'too_short', - lambda c_val, x: {'field_type': 'Value', 'min_length': c_val, 'actual_length': len(x)}, - ), - 'max_length': create_constraint_validator( - 'max_length', - lambda x, max_length: len(x) <= max_length, - 'too_long', - lambda c_val, x: {'field_type': 'Value', 'max_length': c_val, 'actual_length': len(x)}, - ), + 'gt': greater_than_validator, + 'ge': greater_than_or_equal_validator, + 'lt': less_than_validator, + 'le': less_than_or_equal_validator, + 'multiple_of': multiple_of_validator, + 'min_length': min_length_validator, + 'max_length': max_length_validator, } diff --git a/tests/test_types.py b/tests/test_types.py index 1514e055334..541e36d4588 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -63,6 +63,7 @@ Base64UrlBytes, Base64UrlStr, BaseModel, + BeforeValidator, ByteSize, ConfigDict, DirectoryPath, @@ -6993,3 +6994,30 @@ def test_fraction_serialization() -> None: def test_fraction_json_schema() -> None: ta = TypeAdapter(Fraction) assert ta.json_schema() == {'type': 'string', 'format': 'fraction'} + + +def test_annotated_metadata_any_order() -> None: + def validator(v): + if isinstance(v, (int, float)): + return timedelta(days=v) + return v + + class BeforeValidatorAfterLe(BaseModel): + v: Annotated[timedelta, annotated_types.Le(timedelta(days=365)), BeforeValidator(validator)] + + class BeforeValidatorBeforeLe(BaseModel): + v: Annotated[timedelta, BeforeValidator(validator), annotated_types.Le(timedelta(days=365))] + + try: + BeforeValidatorAfterLe(v=366) + except ValueError as ex: + assert '365 days' in str(ex) + + # in this case, the Le constraint comes after the BeforeValidator, so we use functional validators + # from pydantic._internal._validators.py (in this case, less_than_or_equal_validator) + # which doesn't have access to fancy pydantic-core formatting for timedelta, so we get + # the raw timedelta repr in the error `datetime.timedelta(days=365`` vs the above `365 days` + try: + BeforeValidatorBeforeLe(v=366) + except ValueError as ex: + assert 'datetime.timedelta(days=365)' in str(ex) From c7497c56a71504a9ddd4c374dd5479f408484043 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:34:38 -0500 Subject: [PATCH 051/412] Add warning re deprecation of `schema_generator` (#10491) --- pydantic/_internal/_config.py | 10 ++++++++++ pydantic/config.py | 15 ++++++++++++++- tests/test_config.py | 16 +++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index 65da2e7226e..5953c8cb140 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -26,6 +26,7 @@ DeprecationWarning = PydanticDeprecatedSince20 if TYPE_CHECKING: + from .._internal._schema_generation_shared import GenerateSchema from ..fields import ComputedFieldInfo, FieldInfo DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.' @@ -79,6 +80,7 @@ class ConfigWrapper: hide_input_in_errors: bool defer_build: bool plugin_settings: dict[str, object] | None + schema_generator: type[GenerateSchema] | None json_schema_serialization_defaults_required: bool json_schema_mode_override: Literal['validation', 'serialization', None] coerce_numbers_to_str: bool @@ -166,6 +168,13 @@ def core_config(self, obj: Any) -> core_schema.CoreConfig: """ config = self.config_dict + if config.get('schema_generator') is not None: + warnings.warn( + 'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.', + PydanticDeprecatedSince210, + stacklevel=2, + ) + if config.get('ser_json_timedelta') == 'float': warnings.warn( 'The `float` option for `ser_json_timedelta` has been deprecated in favor of `seconds_float`. Please use this setting instead.', @@ -267,6 +276,7 @@ def push(self, config_wrapper: ConfigWrapper | ConfigDict | None): hide_input_in_errors=False, json_encoders=None, defer_build=False, + schema_generator=None, plugin_settings=None, json_schema_serialization_defaults_required=False, json_schema_mode_override=None, diff --git a/pydantic/config.py b/pydantic/config.py index 260dc5548b3..57d540ace9d 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -11,6 +11,7 @@ from .errors import PydanticUserError if TYPE_CHECKING: + from ._internal._generate_schema import GenerateSchema as _GenerateSchema from .fields import ComputedFieldInfo, FieldInfo __all__ = ('ConfigDict', 'with_config') @@ -570,7 +571,9 @@ class Transaction(BaseModel): - `'iso8601'` will serialize timedeltas to ISO 8601 durations. - `'seconds_float'` will serialize timedeltas to the total number of seconds. - `'milliseconds_float'` will serialize timedeltas to the total number of milliseconds. - NOTE: `'float' is deprecated in v2.10 in favour of `'milliseconds_float'` + + !!! warning + `'float' is deprecated in v2.10 in favour of `'milliseconds_float'` """ ser_json_bytes: Literal['utf8', 'base64', 'hex'] @@ -741,6 +744,16 @@ class Model(BaseModel): plugin_settings: dict[str, object] | None """A `dict` of settings for plugins. Defaults to `None`.""" + schema_generator: type[_GenerateSchema] | None + """ + !!! warning + `schema_generator` is deprecated in v2.10. + + Prior to v2.10, this setting was advertised as highly subject to change. + It's possible that this interface may once again become public once the internal core schema generation + API is more stable, but that will likely come after significant performance improvements have been made. + """ + json_schema_serialization_defaults_required: bool """ Whether fields with default values should be marked as required in the serialization schema. Defaults to `False`. diff --git a/tests/test_config.py b/tests/test_config.py index 5495dac85ed..d95245a773b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -23,6 +23,7 @@ with_config, ) from pydantic._internal._config import ConfigWrapper, config_defaults +from pydantic._internal._generate_schema import GenerateSchema from pydantic._internal._mock_val_ser import MockValSer from pydantic._internal._typing_extra import get_type_hints from pydantic.config import ConfigDict, JsonValue @@ -31,7 +32,7 @@ from pydantic.errors import PydanticUserError from pydantic.fields import ComputedFieldInfo, FieldInfo from pydantic.type_adapter import TypeAdapter -from pydantic.warnings import PydanticDeprecationWarning +from pydantic.warnings import PydanticDeprecatedSince210, PydanticDeprecationWarning from .conftest import CallCounter @@ -519,6 +520,8 @@ class Child(Mixin, Parent): def test_config_wrapper_match(): localns = { + '_GenerateSchema': GenerateSchema, + 'GenerateSchema': GenerateSchema, 'JsonValue': JsonValue, 'FieldInfo': FieldInfo, 'ComputedFieldInfo': ComputedFieldInfo, @@ -566,6 +569,8 @@ def check_foo(cls, v): def test_config_defaults_match(): localns = { + '_GenerateSchema': GenerateSchema, + 'GenerateSchema': GenerateSchema, 'FieldInfo': FieldInfo, 'ComputedFieldInfo': ComputedFieldInfo, } @@ -938,3 +943,12 @@ class Model(BaseModel): model_config: ConfigDict = {} assert Model.model_config == {} + + +def test_generate_schema_deprecation_warning() -> None: + with pytest.warns( + PydanticDeprecatedSince210, match='The `schema_generator` setting has been deprecated since v2.10.' + ): + + class Model(BaseModel): + model_config = ConfigDict(schema_generator=GenerateSchema) From e838139435dd0fb23667c6932e30b846effd5703 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:28:59 -0400 Subject: [PATCH 052/412] Use `b64decode` and `b64encode` for `Base64Bytes` type (#10486) --- pydantic/types.py | 61 ++++++++++++++++++++++++++++++++++++---- tests/test_root_model.py | 4 +-- tests/test_types.py | 18 +++++------- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/pydantic/types.py b/pydantic/types.py index 2beba262993..5a97a2fcd37 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -2262,7 +2262,7 @@ def decode(cls, data: bytes) -> bytes: The decoded data. """ try: - return base64.decodebytes(data) + return base64.b64decode(data) except ValueError as e: raise PydanticCustomError('base64_decode', "Base64 decoding error: '{error}'", {'error': str(e)}) @@ -2276,7 +2276,7 @@ def encode(cls, value: bytes) -> bytes: Returns: The encoded data. """ - return base64.encodebytes(value) + return base64.b64encode(value) @classmethod def get_json_format(cls) -> Literal['base64']: @@ -2517,11 +2517,51 @@ def __hash__(self) -> int: """A bytes type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. Note: - Under the hood, `Base64Bytes` use standard library `base64.encodebytes` and `base64.decodebytes` functions. + Under the hood, `Base64Bytes` uses the standard library `base64.b64encode` and `base64.b64decode` functions. As a result, attempting to decode url-safe base64 data using the `Base64Bytes` type may fail or produce an incorrect decoding. +Warning: + In versions of Pydantic prior to v2.10, `Base64Bytes` used [`base64.encodebytes`][base64.encodebytes] + and [`base64.decodebytes`][base64.decodebytes] functions. According to the [base64 documentation](https://docs.python.org/3/library/base64.html), + these methods are considered legacy implementation, and thus, Pydantic v2.10+ now uses the modern + [`base64.b64encode`][base64.b64encode] and [`base64.b64decode`][base64.b64decode] functions. + + If you'd still like to use these legacy encoders / decoders, you can achieve this by creating a custom annotated type, + like follows: + + ```py + import base64 + from typing import Literal + from pydantic import EncoderProtocol, EncodedBytes + from pydantic_core import PydanticCustomError + + from typing_extensions import Annotated + + class LegacyBase64Encoder(EncoderProtocol): + @classmethod + def decode(cls, data: bytes) -> bytes: + try: + return base64.decodebytes(data) + except ValueError as e: + raise PydanticCustomError( + 'base64_decode', + "Base64 decoding error: '{error}'", + {'error': str(e)}, + ) + + @classmethod + def encode(cls, value: bytes) -> bytes: + return base64.encodebytes(value) + + @classmethod + def get_json_format(cls) -> Literal['base64']: + return 'base64' + + LegacyBase64Bytes = Annotated[bytes, EncodedBytes(encoder=LegacyBase64Encoder)] + ``` + ```py from pydantic import Base64Bytes, BaseModel, ValidationError @@ -2537,7 +2577,7 @@ class Model(BaseModel): # Serialize into the base64 form print(m.model_dump()) -#> {'base64_bytes': b'VGhpcyBpcyB0aGUgd2F5\n'} +#> {'base64_bytes': b'VGhpcyBpcyB0aGUgd2F5'} # Validate base64 data try: @@ -2555,11 +2595,20 @@ class Model(BaseModel): """A str type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. Note: - Under the hood, `Base64Bytes` use standard library `base64.encodebytes` and `base64.decodebytes` functions. + Under the hood, `Base64Str` uses the standard library `base64.b64encode` and `base64.b64decode` functions. As a result, attempting to decode url-safe base64 data using the `Base64Str` type may fail or produce an incorrect decoding. +Warning: + In versions of Pydantic prior to v2.10, `Base64Str` used [`base64.encodebytes`][base64.encodebytes] + and [`base64.decodebytes`][base64.decodebytes] functions. According to the [base64 documentation](https://docs.python.org/3/library/base64.html), + these methods are considered legacy implementation, and thus, Pydantic v2.10+ now uses the modern + [`base64.b64encode`][base64.b64encode] and [`base64.b64decode`][base64.b64decode] functions. + + See the [`Base64Bytes`](#pydantic.types.Base64Bytes) type for more information on how to + replicate the old behavior with the legacy encoders / decoders. + ```py from pydantic import Base64Str, BaseModel, ValidationError @@ -2575,7 +2624,7 @@ class Model(BaseModel): # Serialize into the base64 form print(m.model_dump()) -#> {'base64_str': 'VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y\n'} +#> {'base64_str': 'VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y'} # Validate base64 data try: diff --git a/tests/test_root_model.py b/tests/test_root_model.py index c5b09fd59a5..a491cba61f9 100644 --- a/tests/test_root_model.py +++ b/tests/test_root_model.py @@ -182,7 +182,7 @@ class Base64Root(RootModel[Base64Str]): pass v = Base64Root.model_construct('test') - assert v.model_dump() == 'dGVzdA==\n' + assert v.model_dump() == 'dGVzdA==' def test_construct_nested(): @@ -190,7 +190,7 @@ class Base64RootProperty(BaseModel): data: RootModel[Base64Str] v = Base64RootProperty.model_construct(data=RootModel[Base64Str].model_construct('test')) - assert v.model_dump() == {'data': 'dGVzdA==\n'} + assert v.model_dump() == {'data': 'dGVzdA=='} # Note: model_construct requires the inputs to be valid; the root model value does not get "validated" into # an actual root model instance: diff --git a/tests/test_types.py b/tests/test_types.py index 541e36d4588..3dc8a8aac7b 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -5531,23 +5531,19 @@ class Model(BaseModel): @pytest.mark.parametrize( ('field_type', 'input_data', 'expected_value', 'serialized_data'), [ - pytest.param(Base64Bytes, b'Zm9vIGJhcg==\n', b'foo bar', b'Zm9vIGJhcg==\n', id='Base64Bytes-reversible'), - pytest.param(Base64Str, 'Zm9vIGJhcg==\n', 'foo bar', 'Zm9vIGJhcg==\n', id='Base64Str-reversible'), - pytest.param(Base64Bytes, b'Zm9vIGJhcg==', b'foo bar', b'Zm9vIGJhcg==\n', id='Base64Bytes-bytes-input'), - pytest.param(Base64Bytes, 'Zm9vIGJhcg==', b'foo bar', b'Zm9vIGJhcg==\n', id='Base64Bytes-str-input'), + pytest.param(Base64Bytes, b'Zm9vIGJhcg==', b'foo bar', b'Zm9vIGJhcg==', id='Base64Bytes-bytes-input'), + pytest.param(Base64Bytes, 'Zm9vIGJhcg==', b'foo bar', b'Zm9vIGJhcg==', id='Base64Bytes-str-input'), pytest.param( - Base64Bytes, bytearray(b'Zm9vIGJhcg=='), b'foo bar', b'Zm9vIGJhcg==\n', id='Base64Bytes-bytearray-input' - ), - pytest.param(Base64Str, b'Zm9vIGJhcg==', 'foo bar', 'Zm9vIGJhcg==\n', id='Base64Str-bytes-input'), - pytest.param(Base64Str, 'Zm9vIGJhcg==', 'foo bar', 'Zm9vIGJhcg==\n', id='Base64Str-str-input'), - pytest.param( - Base64Str, bytearray(b'Zm9vIGJhcg=='), 'foo bar', 'Zm9vIGJhcg==\n', id='Base64Str-bytearray-input' + Base64Bytes, bytearray(b'Zm9vIGJhcg=='), b'foo bar', b'Zm9vIGJhcg==', id='Base64Bytes-bytearray-input' ), + pytest.param(Base64Str, b'Zm9vIGJhcg==', 'foo bar', 'Zm9vIGJhcg==', id='Base64Str-bytes-input'), + pytest.param(Base64Str, 'Zm9vIGJhcg==', 'foo bar', 'Zm9vIGJhcg==', id='Base64Str-str-input'), + pytest.param(Base64Str, bytearray(b'Zm9vIGJhcg=='), 'foo bar', 'Zm9vIGJhcg==', id='Base64Str-bytearray-input'), pytest.param( Base64Bytes, b'BCq+6+1/Paun/Q==', b'\x04*\xbe\xeb\xed\x7f=\xab\xa7\xfd', - b'BCq+6+1/Paun/Q==\n', + b'BCq+6+1/Paun/Q==', id='Base64Bytes-bytes-alphabet-vanilla', ), ], From 643ea798a348b0b42500689916caa460f343f5af Mon Sep 17 00:00:00 2001 From: ProphetJeremy Date: Thu, 26 Sep 2024 17:43:05 +0200 Subject: [PATCH 053/412] docs: More specific `model_post_init` explanation (#10453) --- docs/concepts/models.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/models.md b/docs/concepts/models.md index 3b3d7110837..8b14347bc60 100644 --- a/docs/concepts/models.md +++ b/docs/concepts/models.md @@ -157,7 +157,7 @@ Models possess the following methods and attributes: * [`model_extra`][pydantic.main.BaseModel.model_extra]: The extra fields set during validation. * [`model_fields_set`][pydantic.main.BaseModel.model_fields_set]: The set of fields which were explicitly provided when the model was initialized. * [`model_parametrized_name()`][pydantic.main.BaseModel.model_parametrized_name]: Computes the class name for parametrizations of generic classes. -* [`model_post_init()`][pydantic.main.BaseModel.model_post_init]: Performs additional actions after the model is initialized. +* [`model_post_init()`][pydantic.main.BaseModel.model_post_init]: Performs additional actions after the model is initialized and all field validators are applied. * [`model_rebuild()`][pydantic.main.BaseModel.model_rebuild]: Rebuilds the model schema, which also supports building recursive generic models. See [Rebuilding model schema](#rebuilding-model-schema). From 8fe7c8e55a22be7eb43ecff78884b9b88ac7ff0d Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:31:20 +0200 Subject: [PATCH 054/412] Simplify dataclass check in `modify_model_json_schema` (#10503) --- pydantic/_internal/_generate_schema.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index c161818e37c..61d8d186443 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -250,7 +250,7 @@ def modify_model_json_schema( schema_or_field: CoreSchemaOrField, handler: GetJsonSchemaHandler, *, - cls: Any, + cls: type[Any], title: str | None = None, ) -> JsonSchemaValue: """Add title and description for model-like classes' JSON schema. @@ -264,9 +264,7 @@ def modify_model_json_schema( Returns: JsonSchemaValue: The updated JSON schema. """ - from ..dataclasses import is_pydantic_dataclass from ..root_model import RootModel - from ._dataclasses import is_builtin_dataclass BaseModel = import_cached_base_model() @@ -276,8 +274,8 @@ def modify_model_json_schema( original_schema['title'] = title elif 'title' not in original_schema: original_schema['title'] = cls.__name__ - # BaseModel + Dataclass; don't use cls.__doc__ as it will contain the verbose class signature by default - docstring = None if cls is BaseModel or is_builtin_dataclass(cls) or is_pydantic_dataclass(cls) else cls.__doc__ + # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default + docstring = None if cls is BaseModel or dataclasses.is_dataclass(cls) else cls.__doc__ if docstring and 'description' not in original_schema: original_schema['description'] = inspect.cleandoc(docstring) elif issubclass(cls, RootModel) and cls.model_fields['root'].description: From 728d3d9f41c480d35c1188f56cc360bae93f0543 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:42:41 -0400 Subject: [PATCH 055/412] Using `__pydantic_fields__` and `__pydantic_computed_fields__` under the hood (#10493) Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> --- pydantic/_internal/_fields.py | 4 +- pydantic/_internal/_generate_schema.py | 6 +- pydantic/_internal/_model_construction.py | 40 +++++++++--- pydantic/deprecated/copy_internals.py | 10 +-- pydantic/deprecated/decorator.py | 10 ++- pydantic/main.py | 74 ++++++++++++++++------- pydantic/root_model.py | 4 +- tests/test_computed_fields.py | 23 +++++++ 8 files changed, 124 insertions(+), 47 deletions(-) diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 14440f903a0..060d166a1cb 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -104,7 +104,7 @@ def collect_model_fields( # noqa: C901 parent_fields_lookup: dict[str, FieldInfo] = {} for base in reversed(bases): - if model_fields := getattr(base, 'model_fields', None): + if model_fields := getattr(base, '__pydantic_fields__', None): parent_fields_lookup.update(model_fields) type_hints = get_cls_type_hints_lenient(cls, types_namespace) @@ -125,7 +125,7 @@ def collect_model_fields( # noqa: C901 if ann_name.startswith(protected_namespace): for b in bases: if hasattr(b, ann_name): - if not (issubclass(b, BaseModel) and ann_name in b.model_fields): + if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})): raise NameError( f'Field "{ann_name}" conflicts with member {getattr(b, ann_name)}' f' of protected namespace "{protected_namespace}".' diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 61d8d186443..6afe7f997ec 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -278,8 +278,8 @@ def modify_model_json_schema( docstring = None if cls is BaseModel or dataclasses.is_dataclass(cls) else cls.__doc__ if docstring and 'description' not in original_schema: original_schema['description'] = inspect.cleandoc(docstring) - elif issubclass(cls, RootModel) and cls.model_fields['root'].description: - original_schema['description'] = cls.model_fields['root'].description + elif issubclass(cls, RootModel) and cls.__pydantic_fields__['root'].description: + original_schema['description'] = cls.__pydantic_fields__['root'].description return json_schema @@ -663,7 +663,7 @@ def _model_schema(self, cls: type[BaseModel]) -> core_schema.CoreSchema: if maybe_schema is not None: return maybe_schema - fields = cls.model_fields + fields = getattr(cls, '__pydantic_fields__', {}) decorators = cls.__pydantic_decorators__ computed_fields = decorators.computed_fields check_decorator_fields_exist( diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 0946f85041b..316d5c9fe8b 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -41,8 +41,8 @@ from ._validate_call import ValidateCallWrapper if typing.TYPE_CHECKING: + from ..fields import ComputedFieldInfo, FieldInfo, ModelPrivateAttr from ..fields import Field as PydanticModelField - from ..fields import FieldInfo, ModelPrivateAttr from ..fields import PrivateAttr as PydanticModelPrivateAttr from ..main import BaseModel else: @@ -234,7 +234,9 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: # If this is placed before the complete_model_class call above, # the generic computed fields return type is set to PydanticUndefined - cls.model_computed_fields = {k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items()} + cls.__pydantic_computed_fields__ = { + k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items() + } set_deprecated_descriptors(cls) @@ -340,7 +342,7 @@ def _collect_bases_data(bases: tuple[type[Any], ...]) -> tuple[set[str], set[str for base in bases: if issubclass(base, BaseModel) and base is not BaseModel: # model_fields might not be defined yet in the case of generics, so we use getattr here: - field_names.update(getattr(base, 'model_fields', {}).keys()) + field_names.update(getattr(base, '__pydantic_fields__', {}).keys()) class_vars.update(base.__class_vars__) private_attributes.update(base.__private_attributes__) return field_names, class_vars, private_attributes @@ -351,7 +353,25 @@ def __fields__(self) -> dict[str, FieldInfo]: warnings.warn( 'The `__fields__` attribute is deprecated, use `model_fields` instead.', PydanticDeprecatedSince20 ) - return self.model_fields # type: ignore + return self.model_fields + + @property + def model_fields(self) -> dict[str, FieldInfo]: + """Get metadata about the fields defined on the model. + + Returns: + A mapping of field names to [`FieldInfo`][pydantic.fields.FieldInfo] objects. + """ + return getattr(self, '__pydantic_fields__', {}) + + @property + def model_computed_fields(self) -> dict[str, ComputedFieldInfo]: + """Get metadata about the computed fields defined on the model. + + Returns: + A mapping of computed field names to [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects. + """ + return getattr(self, '__pydantic_computed_fields__', {}) def __dir__(self) -> list[str]: attributes = list(super().__dir__()) @@ -540,7 +560,7 @@ def set_default_hash_func(cls: type[BaseModel], bases: tuple[type[Any], ...]) -> def make_hash_func(cls: type[BaseModel]) -> Any: - getter = operator.itemgetter(*cls.model_fields.keys()) if cls.model_fields else lambda _: 0 + getter = operator.itemgetter(*cls.__pydantic_fields__.keys()) if cls.__pydantic_fields__ else lambda _: 0 def hash_func(self: Any) -> int: try: @@ -558,7 +578,7 @@ def hash_func(self: Any) -> int: def set_model_fields( cls: type[BaseModel], bases: tuple[type[Any], ...], config_wrapper: ConfigWrapper, types_namespace: dict[str, Any] ) -> None: - """Collect and set `cls.model_fields` and `cls.__class_vars__`. + """Collect and set `cls.__pydantic_fields__` and `cls.__class_vars__`. Args: cls: BaseModel or dataclass. @@ -569,7 +589,7 @@ def set_model_fields( typevars_map = get_model_typevars_map(cls) fields, class_vars = collect_model_fields(cls, bases, config_wrapper, types_namespace, typevars_map=typevars_map) - cls.model_fields = fields + cls.__pydantic_fields__ = fields cls.__class_vars__.update(class_vars) for k in class_vars: @@ -665,20 +685,20 @@ def complete_model_class( # set __signature__ attr only for model class, but not for its instances cls.__signature__ = ClassAttribute( '__signature__', - generate_pydantic_signature(init=cls.__init__, fields=cls.model_fields, config_wrapper=config_wrapper), + generate_pydantic_signature(init=cls.__init__, fields=cls.__pydantic_fields__, config_wrapper=config_wrapper), ) return True def set_deprecated_descriptors(cls: type[BaseModel]) -> None: """Set data descriptors on the class for deprecated fields.""" - for field, field_info in cls.model_fields.items(): + for field, field_info in cls.__pydantic_fields__.items(): if (msg := field_info.deprecation_message) is not None: desc = _DeprecatedFieldDescriptor(msg) desc.__set_name__(cls, field) setattr(cls, field, desc) - for field, computed_field_info in cls.model_computed_fields.items(): + for field, computed_field_info in cls.__pydantic_computed_fields__.items(): if ( (msg := computed_field_info.deprecation_message) is not None # Avoid having two warnings emitted: diff --git a/pydantic/deprecated/copy_internals.py b/pydantic/deprecated/copy_internals.py index efe5de28996..00e0a8a9512 100644 --- a/pydantic/deprecated/copy_internals.py +++ b/pydantic/deprecated/copy_internals.py @@ -40,11 +40,11 @@ def _iter( # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case. if exclude is not None: exclude = _utils.ValueItems.merge( - {k: v.exclude for k, v in self.model_fields.items() if v.exclude is not None}, exclude + {k: v.exclude for k, v in self.__pydantic_fields__.items() if v.exclude is not None}, exclude ) if include is not None: - include = _utils.ValueItems.merge({k: True for k in self.model_fields}, include, intersect=True) + include = _utils.ValueItems.merge({k: True for k in self.__pydantic_fields__}, include, intersect=True) allowed_keys = _calculate_keys(self, include=include, exclude=exclude, exclude_unset=exclude_unset) # type: ignore if allowed_keys is None and not (to_dict or by_alias or exclude_unset or exclude_defaults or exclude_none): @@ -68,15 +68,15 @@ def _iter( if exclude_defaults: try: - field = self.model_fields[field_key] + field = self.__pydantic_fields__[field_key] except KeyError: pass else: if not field.is_required() and field.default == v: continue - if by_alias and field_key in self.model_fields: - dict_key = self.model_fields[field_key].alias or field_key + if by_alias and field_key in self.__pydantic_fields__: + dict_key = self.__pydantic_fields__[field_key].alias or field_key else: dict_key = field_key diff --git a/pydantic/deprecated/decorator.py b/pydantic/deprecated/decorator.py index 0c0ea7445c6..27ee4603845 100644 --- a/pydantic/deprecated/decorator.py +++ b/pydantic/deprecated/decorator.py @@ -170,10 +170,10 @@ def build_values(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Dict[st duplicate_kwargs = [] fields_alias = [ field.alias - for name, field in self.model.model_fields.items() + for name, field in self.model.__pydantic_fields__.items() if name not in (self.v_args_name, self.v_kwargs_name) ] - non_var_fields = set(self.model.model_fields) - {self.v_args_name, self.v_kwargs_name} + non_var_fields = set(self.model.__pydantic_fields__) - {self.v_args_name, self.v_kwargs_name} for k, v in kwargs.items(): if k in non_var_fields or k in fields_alias: if k in self.positional_only_args: @@ -193,7 +193,11 @@ def build_values(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Dict[st return values def execute(self, m: BaseModel) -> Any: - d = {k: v for k, v in m.__dict__.items() if k in m.__pydantic_fields_set__ or m.model_fields[k].default_factory} + d = { + k: v + for k, v in m.__dict__.items() + if k in m.__pydantic_fields_set__ or m.__pydantic_fields__[k].default_factory + } var_kwargs = d.pop(self.v_kwargs_name, {}) if self.v_args_name in d: diff --git a/pydantic/main.py b/pydantic/main.py index 2c108790afc..b6528161d8f 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -101,15 +101,16 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): __pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model. __pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model. + __pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. + __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects. + __pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] is set to `'allow'`. __pydantic_fields_set__: The names of fields explicitly set during instantiation. __pydantic_private__: Values of private attributes set on the model instance. """ - # Class attributes: - # `model_fields` and `__pydantic_decorators__` must be set for - # `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation. + # Note: Many of the below class vars are defined in the metaclass, but we define them here for type checking purposes. model_config: ClassVar[ConfigDict] = ConfigDict() """ @@ -118,17 +119,6 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): # Because `dict` is in the local namespace of the `BaseModel` class, we use `Dict` for annotations. # TODO v3 fallback to `dict` when the deprecated `dict` method gets removed. - model_fields: ClassVar[Dict[str, FieldInfo]] = {} # noqa: UP006 - """ - Metadata about the fields defined on the model, - mapping of field names to [`FieldInfo`][pydantic.fields.FieldInfo] objects. - - This replaces `Model.__fields__` from Pydantic V1. - """ - - model_computed_fields: ClassVar[Dict[str, ComputedFieldInfo]] = {} # noqa: UP006 - """A dictionary of computed field names and their corresponding `ComputedFieldInfo` objects.""" - __class_vars__: ClassVar[set[str]] """The names of the class variables defined on the model.""" @@ -147,6 +137,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): __pydantic_custom_init__: ClassVar[bool] """Whether the model has a custom `__init__` method.""" + # Must be set for `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation. __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] = _decorators.DecoratorInfos() """Metadata containing the decorators defined on the model. This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.""" @@ -170,6 +161,14 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] """The `pydantic-core` `SchemaValidator` used to validate instances of the model.""" + __pydantic_fields__: ClassVar[Dict[str, FieldInfo]] # noqa: UP006 + """A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. + This replaces `Model.__fields__` from Pydantic V1. + """ + + __pydantic_computed_fields__: ClassVar[Dict[str, ComputedFieldInfo]] # noqa: UP006 + """A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.""" + __pydantic_extra__: dict[str, Any] | None = _model_construction.NoInitField(init=False) """A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] is set to `'allow'`.""" @@ -221,6 +220,35 @@ def __init__(self, /, **data: Any) -> None: # The following line sets a flag that we use to determine when `__init__` gets overridden by the user __init__.__pydantic_base_init__ = True # pyright: ignore[reportFunctionMemberAccess] + # TODO: V3 - remove `model_fields` and `model_computed_fields` properties from the `BaseModel` class - they should only + # be accessible on the model class, not on instances. We have these purely for backwards compatibility with Pydantic dict[str, FieldInfo]: + """Get metadata about the fields defined on the model. + + Deprecation warning: you should be getting this information from the model class, not from an instance. + In V3, this property will be removed from the `BaseModel` class. + + Returns: + A mapping of field names to [`FieldInfo`][pydantic.fields.FieldInfo] objects. + """ + # Must be set for `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation, hence the default here. + return getattr(self, '__pydantic_fields__', {}) + + @property + def model_computed_fields(self) -> dict[str, ComputedFieldInfo]: + """Get metadata about the computed fields defined on the model. + + Deprecation warning: you should be getting this information from the model class, not from an instance. + In V3, this property will be removed from the `BaseModel` class. + + Returns: + A mapping of computed field names to [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects. + """ + # Must be set for `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation, hence the default here. + return getattr(self, '__pydantic_computed_fields__', {}) + @property def model_extra(self) -> dict[str, Any] | None: """Get extra fields set during validation. @@ -267,7 +295,7 @@ def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> fields_values: dict[str, Any] = {} fields_set = set() - for name, field in cls.model_fields.items(): + for name, field in cls.__pydantic_fields__.items(): if field.alias is not None and field.alias in values: fields_values[name] = values.pop(field.alias) fields_set.add(name) @@ -338,7 +366,7 @@ def model_copy(self, *, update: dict[str, Any] | None = None, deep: bool = False if update: if self.model_config.get('extra') == 'allow': for k, v in update.items(): - if k in self.model_fields: + if k in self.__pydantic_fields__: copied.__dict__[k] = v else: if copied.__pydantic_extra__ is None: @@ -880,10 +908,10 @@ def __setattr__(self, name: str, value: Any) -> None: attr.__set__(self, value) elif self.model_config.get('validate_assignment', None): self.__pydantic_validator__.validate_assignment(self, name, value) - elif self.model_config.get('extra') != 'allow' and name not in self.model_fields: + elif self.model_config.get('extra') != 'allow' and name not in self.__pydantic_fields__: # TODO - matching error raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"') - elif self.model_config.get('extra') == 'allow' and name not in self.model_fields: + elif self.model_config.get('extra') == 'allow' and name not in self.__pydantic_fields__: if self.model_extra and name in self.model_extra: self.__pydantic_extra__[name] = value # type: ignore else: @@ -915,7 +943,7 @@ def __delattr__(self, item: str) -> Any: self._check_frozen(item, None) - if item in self.model_fields: + if item in self.__pydantic_fields__: object.__delattr__(self, item) elif self.__pydantic_extra__ is not None and item in self.__pydantic_extra__: del self.__pydantic_extra__[item] @@ -928,7 +956,7 @@ def __delattr__(self, item: str) -> Any: def _check_frozen(self, name: str, value: Any) -> None: if self.model_config.get('frozen', None): typ = 'frozen_instance' - elif getattr(self.model_fields.get(name), 'frozen', False): + elif getattr(self.__pydantic_fields__.get(name), 'frozen', False): typ = 'frozen_field' else: return @@ -985,7 +1013,7 @@ def __eq__(self, other: Any) -> bool: # We don't want to trigger unnecessary costly filtering of __dict__ on all unequal objects, so we return # early if there are no keys to ignore (we would just return False later on anyway) - model_fields = type(self).model_fields.keys() + model_fields = type(self).__pydantic_fields__.keys() if self.__dict__.keys() <= model_fields and other.__dict__.keys() <= model_fields: return False @@ -1052,7 +1080,7 @@ def __repr__(self) -> str: def __repr_args__(self) -> _repr.ReprArgs: for k, v in self.__dict__.items(): - field = self.model_fields.get(k) + field = self.__pydantic_fields__.get(k) if field and field.repr: if v is not self: yield k, v @@ -1069,7 +1097,7 @@ def __repr_args__(self) -> _repr.ReprArgs: if pydantic_extra is not None: yield from ((k, v) for k, v in pydantic_extra.items()) - yield from ((k, getattr(self, k)) for k, v in self.model_computed_fields.items() if v.repr) + yield from ((k, getattr(self, k)) for k, v in self.__pydantic_computed_fields__.items() if v.repr) # take logic from `_repr.Representation` without the side effects of inheritance, see #5740 __repr_name__ = _repr.Representation.__repr_name__ diff --git a/pydantic/root_model.py b/pydantic/root_model.py index e050ce02fb7..8351651dfa2 100644 --- a/pydantic/root_model.py +++ b/pydantic/root_model.py @@ -148,7 +148,9 @@ def model_dump( # type: ignore def __eq__(self, other: Any) -> bool: if not isinstance(other, RootModel): return NotImplemented - return self.model_fields['root'].annotation == other.model_fields['root'].annotation and super().__eq__(other) + return self.__pydantic_fields__['root'].annotation == other.__pydantic_fields__[ + 'root' + ].annotation and super().__eq__(other) def __repr_args__(self) -> _repr.ReprArgs: yield 'root', self.root diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index e0a29b40b92..1891c695791 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -815,3 +815,26 @@ def my_field_serializer(self, value: Any, info: FieldSerializationInfo) -> Any: return f'{info.field_name} = {value}' assert MyModel().model_dump() == {'my_field': 'my_field = foo', 'other_field': 'other_field = 42'} + + +def test_fields_on_instance_and_cls() -> None: + """For now, we support `model_fields` and `model_computed_fields` access on both instances and classes. + + In V3, we should only support class access, though we need to preserve the current behavior for V2 compatibility.""" + + class Rectangle(BaseModel): + x: int + y: int + + @computed_field + @property + def area(self) -> int: + return self.x * self.y + + r = Rectangle(x=10, y=5) + + for attr in {'model_fields', 'model_computed_fields'}: + assert getattr(r, attr) == getattr(Rectangle, attr) + + assert set(r.model_fields) == {'x', 'y'} + assert set(r.model_computed_fields) == {'area'} From 1f74db28a495a61c0ef4377cc43507736b697b03 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:57:06 +0200 Subject: [PATCH 056/412] Use identity instead of equality in `GenerateSchema` (#10505) --- pydantic/_internal/_generate_schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 6afe7f997ec..ddaa6467160 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -815,7 +815,7 @@ def _generate_schema_from_property(self, obj: Any, source: Any) -> core_schema.C elif ( (existing_schema := getattr(obj, '__pydantic_core_schema__', None)) is not None and not isinstance(existing_schema, MockCoreSchema) - and existing_schema.get('cls', None) == obj + and existing_schema.get('cls', None) is obj ): schema = existing_schema elif (validators := getattr(obj, '__get_validators__', None)) is not None: @@ -1003,7 +1003,7 @@ def match_type(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901 elif _typing_extra.is_new_type(obj): # NewType, can't use isinstance because it fails <3.10 return self.generate_schema(obj.__supertype__) - elif obj == re.Pattern: + elif obj is re.Pattern: return self._pattern_schema(obj) elif obj is collections.abc.Hashable or obj is typing.Hashable: return self._hashable_schema() @@ -1730,7 +1730,7 @@ def _pattern_schema(self, pattern_type: Any) -> core_schema.CoreSchema: ser = core_schema.plain_serializer_function_ser_schema( attrgetter('pattern'), when_used='json', return_schema=core_schema.str_schema() ) - if pattern_type == typing.Pattern or pattern_type == re.Pattern: + if pattern_type is typing.Pattern or pattern_type is re.Pattern: # bare type return core_schema.no_info_plain_validator_function( _validators.pattern_either_validator, serialization=ser, metadata=metadata From 3186a85092a6917dc12b955aef5315b1411f5c7d Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:06:30 +0200 Subject: [PATCH 057/412] Improve documentation of `@validate_call()` (#10455) --- docs/concepts/validation_decorator.md | 287 +++++++++++++------------- docs/errors/usage_errors.md | 6 +- 2 files changed, 141 insertions(+), 152 deletions(-) diff --git a/docs/concepts/validation_decorator.md b/docs/concepts/validation_decorator.md index 370bdc5a606..8d54be2a1e3 100644 --- a/docs/concepts/validation_decorator.md +++ b/docs/concepts/validation_decorator.md @@ -1,7 +1,7 @@ ??? api "API Documentation" [`pydantic.validate_call_decorator.validate_call`][pydantic.validate_call_decorator.validate_call]
-The [`@validate_call`][pydantic.validate_call] decorator allows the arguments passed to a function to be parsed +The [`validate_call()`][pydantic.validate_call] decorator allows the arguments passed to a function to be parsed and validated using the function's annotations before the function is called. While under the hood this uses the same approach of model creation and initialisation @@ -39,151 +39,160 @@ except ValidationError as exc: """ ``` -## Argument types +## Parameter types -Argument types are inferred from type annotations on the function, arguments without a type decorator are considered -as `Any`. All types listed in [types](../concepts/types.md) can be validated, including Pydantic models and -[custom types](../concepts/types.md#custom-types). -As with the rest of Pydantic, types can be coerced by the decorator before they're passed to the actual function: +Parameter types are inferred from type annotations on the function, or as [`Any`][typing.Any] if not annotated. All types listed in [types](types.md) can be validated, including Pydantic models and [custom types](types.md#custom-types). +As with the rest of Pydantic, types are by default coerced by the decorator before they're passed to the actual function: -```py test="no-print-intercept" -# TODO replace find_file with something that isn't affected the filesystem -import os -from pathlib import Path -from typing import Optional, Pattern +```py +from datetime import date -from pydantic import DirectoryPath, validate_call +from pydantic import validate_call @validate_call -def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Optional[Path]: - for i, f in enumerate(path.glob('**/*')): - if max and i > max: - return - if f.is_file() and regex.fullmatch(str(f.relative_to(path))): - return f - +def greater_than(d1: date, d2: date, *, include_equal=False) -> date: # (1)! + if include_equal: + return d1 >= d2 + else: + return d1 > d2 -# note: this_dir is a string here -this_dir = os.path.dirname(__file__) -print(find_file(this_dir, '^validation.*')) -print(find_file(this_dir, '^foobar.*', max=3)) +d1 = '2000-01-01' # (2)! +d2 = date(2001, 1, 1) +greater_than(d1, d2, include_equal=True) ``` -A few notes: +1. Because `include_equal` has no type annotation, it will be inferred as [`Any`][typing.Any]. +2. Although `d1` is a string, it will be converted to a [`date`][datetime.date] object. -- Though they're passed as strings, `path` and `regex` are converted to a `Path` object and regex respectively - by the decorator. -- `max` has no type annotation, so will be considered as `Any` by the decorator. +Type coercion like this can be extremely helpful, but also confusing or not desired (see [model data conversion](models.md#data-conversion)). [Strict mode](strict_mode.md) +can be enabled by using a [custom configuration](#custom-configuration). -Type coercion like this can be extremely helpful, but also confusing or not desired. -See [Coercion and strictness](#coercion-and-strictness) for a discussion of `@validate_call`'s limitations -in this regard. +!!! note "Validating the return value" + By default, the return value of the function is **not** validated. To do so, the `validate_return` argument + of the decorator can be set to `True`. ## Function signatures -The `@validate_call` decorator is designed to work with functions using all possible parameter configurations and -all possible combinations of these: +The [`validate_call()`][pydantic.validate_call] decorator is designed to work with functions +using all possible [parameter configurations][parameter] and all possible combinations of these: -* Positional or keyword arguments with or without defaults. -* Variable positional arguments defined via `*` (often `*args`). -* Variable keyword arguments defined via `**` (often `**kwargs`). -* Keyword-only arguments: arguments after `*,`. -* Positional-only arguments: arguments before `, /` (new in Python 3.8). +* Positional or keyword parameters with or without defaults. +* Keyword-only parameters: parameters after `*,`. +* Positional-only parameters: parameters before `, /`. +* Variable positional parameters defined via `*` (often `*args`). +* Variable keyword parameters defined via `**` (often `**kwargs`). -To demonstrate all the above parameter types: +??? example -```py -from pydantic import validate_call + ```py + from pydantic import validate_call -@validate_call -def pos_or_kw(a: int, b: int = 2) -> str: - return f'a={a} b={b}' + @validate_call + def pos_or_kw(a: int, b: int = 2) -> str: + return f'a={a} b={b}' -print(pos_or_kw(1)) -#> a=1 b=2 -print(pos_or_kw(a=1)) -#> a=1 b=2 -print(pos_or_kw(1, 3)) -#> a=1 b=3 -print(pos_or_kw(a=1, b=3)) -#> a=1 b=3 + print(pos_or_kw(1, b=3)) + #> a=1 b=3 -@validate_call -def kw_only(*, a: int, b: int = 2) -> str: - return f'a={a} b={b}' + @validate_call + def kw_only(*, a: int, b: int = 2) -> str: + return f'a={a} b={b}' -print(kw_only(a=1)) -#> a=1 b=2 -print(kw_only(a=1, b=3)) -#> a=1 b=3 + print(kw_only(a=1)) + #> a=1 b=2 + print(kw_only(a=1, b=3)) + #> a=1 b=3 -@validate_call -def pos_only(a: int, b: int = 2, /) -> str: # python 3.8 only - return f'a={a} b={b}' + @validate_call + def pos_only(a: int, b: int = 2, /) -> str: + return f'a={a} b={b}' -print(pos_only(1)) -#> a=1 b=2 -print(pos_only(1, 2)) -#> a=1 b=2 + print(pos_only(1)) + #> a=1 b=2 -@validate_call -def var_args(*args: int) -> str: - return str(args) + @validate_call + def var_args(*args: int) -> str: + return str(args) -print(var_args(1)) -#> (1,) -print(var_args(1, 2)) -#> (1, 2) -print(var_args(1, 2, 3)) -#> (1, 2, 3) + print(var_args(1)) + #> (1,) + print(var_args(1, 2, 3)) + #> (1, 2, 3) -@validate_call -def var_kwargs(**kwargs: int) -> str: - return str(kwargs) + @validate_call + def var_kwargs(**kwargs: int) -> str: + return str(kwargs) -print(var_kwargs(a=1)) -#> {'a': 1} -print(var_kwargs(a=1, b=2)) -#> {'a': 1, 'b': 2} + print(var_kwargs(a=1)) + #> {'a': 1} + print(var_kwargs(a=1, b=2)) + #> {'a': 1, 'b': 2} -@validate_call -def armageddon( - a: int, - /, # python 3.8 only - b: int, - *c: int, - d: int, - e: int = None, - **f: int, -) -> str: - return f'a={a} b={b} c={c} d={d} e={e} f={f}' - - -print(armageddon(1, 2, d=3)) -#> a=1 b=2 c=() d=3 e=None f={} -print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11)) -#> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11} -``` + @validate_call + def armageddon( + a: int, + /, + b: int, + *c: int, + d: int, + e: int = None, + **f: int, + ) -> str: + return f'a={a} b={b} c={c} d={d} e={e} f={f}' + + + print(armageddon(1, 2, d=3)) + #> a=1 b=2 c=() d=3 e=None f={} + print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11)) + #> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11} + ``` + +!!! note "[`Unpack`][typing.Unpack] for keyword parameters" + [`Unpack`][typing.Unpack] and typed dictionaries can be used to annotate the variable + keyword parameters of a function: + + ```python + from typing_extensions import TypedDict, Unpack + + from pydantic import validate_call -## Using Field to describe function arguments -[Field](json_schema.md#field-level-customization) can also be used with `@validate_call` to provide extra information about -the field and validations. In general it should be used in a type hint with -[Annotated](types.md#composing-types-via-annotated), unless `default_factory` is specified, in which case it should be -used as the default value of the field: + class Point(TypedDict): + x: int + y: int + + + @validate_call + def add_coords(**kwargs: Unpack[Point]) -> int: + return kwargs['x'] + kwargs['y'] + + + add_coords(x=1, y=2) + ``` + + For reference, see the [related specification section] and [PEP 692]. + + [related specification section]: https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments + [PEP 692]: https://peps.python.org/pep-0692/ + +## Using the [`Field()`][pydantic.Field] function to describe function parameters + +The [`Field()` function](fields.md) can also be used with the decorator to provide extra information about +the field and validations. In general it should be used in a type hint with [Annotated](types.md#composing-types-via-annotated), +unless `default_factory` is specified, in which case it should be used as the default value of the field: ```py from datetime import datetime @@ -218,7 +227,7 @@ print(type(when())) #> ``` -The [`alias`](fields.md#field-aliases) can be used with the decorator as normal. +[Aliases](fields.md#field-aliases) can be used with the decorator as normal: ```py from typing_extensions import Annotated @@ -234,18 +243,10 @@ def how_many(num: Annotated[int, Field(gt=10, alias='number')]): how_many(number=42) ``` +## Accessing the original function -## Usage with mypy - -The `validate_call` decorator should work "out of the box" with [mypy](http://mypy-lang.org/) since it's -defined to return a function with the same signature as the function it decorates. The only limitation is that -since we trick mypy into thinking the function returned by the decorator is the same as the function being -decorated; access to the [raw function](#raw-function) or other attributes will require `type: ignore`. - -## Raw function - -The raw function which was decorated is accessible, this is useful if in some scenarios you trust your input -arguments and want to call the function in the most performant way (see [notes on performance](#performance) below): +The original function which was decorated can still be accessed by using the `raw_function` attribute. +This is useful if in some scenarios you trust your input arguments and want to call the function in the most efficient way (see [notes on performance](#performance) below): ```py from pydantic import validate_call @@ -268,7 +269,7 @@ print(b) ## Async functions -`@validate_call` can also be used on async functions: +[`validate_call()`][pydantic.validate_call] can also be used on async functions: ```py class Connection: @@ -319,16 +320,19 @@ asyncio.run(main()) # requires: `conn.execute()` that will return `'testing@example.com'` ``` -## Custom config +## Compatibility with type checkers + +As the [`validate_call()`][pydantic.validate_call] decorator preserves the decorated function's signature, +it should be compatible with type checkers (such as mypy and pyright). However, due to current limitations in the Python type system, +the [`raw_function`](#accessing-the-original-function) or other attributes won't be recognized and you will +need to suppress the error using (usually with a `# type: ignore` comment). -The model behind `@validate_call` can be customised using a `config` setting, which is equivalent to -setting the [`ConfigDict`][pydantic.config.ConfigDict] sub-class in normal models. +## Custom configuration -Configuration is set using the `config` keyword argument to the decorator, it may be either a config class -or a dict of properties which are converted to a class later. +Similarly to Pydantic models, the `config` parameter of the decorator can be used to specify a custom configuration: ```py -from pydantic import ValidationError, validate_call +from pydantic import ConfigDict, ValidationError, validate_call class Foobar: @@ -342,7 +346,7 @@ class Foobar: return f'Foobar({self.v})' -@validate_call(config=dict(arbitrary_types_allowed=True)) +@validate_call(config=ConfigDict(arbitrary_types_allowed=True)) def add_foobars(a: Foobar, b: Foobar): return a + b @@ -367,7 +371,7 @@ except ValidationError as e: ## Extension — validating arguments before calling a function In some cases, it may be helpful to separate validation of a function's arguments from the function call itself. -This might be useful when a particular function is costly / time consuming. +This might be useful when a particular function is costly/time consuming. Here's an example of a workaround you can use for that pattern: @@ -392,31 +396,16 @@ print(foo()) ### Validation exception -Currently upon validation failure, a standard Pydantic [`ValidationError`][pydantic_core.ValidationError] is raised. -See [model error handling](models.md#error-handling) for details. - -This is helpful since its `str()` method provides useful details of the error which occurred and methods like -`.errors()` and `.json()` can be useful when exposing the errors to end users. However, `ValidationError` inherits -from `ValueError` **not** `TypeError`, which may be unexpected since Python would raise a `TypeError` upon invalid -or missing arguments. This may be addressed in future by either allowing a custom error or raising a different -exception by default, or both. - -### Coercion and strictness - -Pydantic currently leans on the side of trying to coerce types rather than raise an error if a type is wrong, -see [model data conversion](models.md#data-conversion) and `@validate_call` is no different. +Currently upon validation failure, a standard Pydantic [`ValidationError`][pydantic_core.ValidationError] is raised +(see [model error handling](models.md#error-handling) for details). This is also true for missing required arguments, +where Python normally raises a [`TypeError`][]. ### Performance -We've made a big effort to make Pydantic as performant as possible -and argument inspect and model creation is only performed once when the function is defined, however -there will still be a performance impact to using the `@validate_call` decorator compared to -calling the raw function. - -In many situations this will have little or no noticeable effect, however be aware that -`@validate_call` is not an equivalent or alternative to function definitions in strongly typed languages; -it never will be. - -### Return value +We've made a big effort to make Pydantic as performant as possible. While the inspection of the decorated +function is only performed once, there will still be a performance impact when making calls to the function +compared to using the original function. -The return value of the function may optionally be validated against its return type annotation. +In many situations, this will have little or no noticeable effect. However, be aware that +[`validate_call()`][pydantic.validate_call] is not an equivalent or alternative to function +definitions in strongly typed languages, and it never will be. diff --git a/docs/errors/usage_errors.md b/docs/errors/usage_errors.md index 852fea21bf6..02167c309bd 100644 --- a/docs/errors/usage_errors.md +++ b/docs/errors/usage_errors.md @@ -1190,7 +1190,7 @@ except PydanticUserError as exc_info: ## [`Unpack`][typing.Unpack] used without a [`TypedDict`][typing.TypedDict] {#unpack-typed-dict} This error is raised when [`Unpack`][typing.Unpack] is used with something other than -a [`TypedDict`][typing.TypedDict] class object to type hint variadic keyword arguments. +a [`TypedDict`][typing.TypedDict] class object to type hint variadic keyword parameters. For reference, see the [related specification section] and [PEP 692]. @@ -1211,8 +1211,8 @@ except PydanticUserError as exc_info: ## Overlapping unpacked [`TypedDict`][typing.TypedDict] fields and arguments {#overlapping-unpack-typed-dict} -This error is raised when the typed dictionary used to type hint variadic keywords arguments -has field names overlapping with arguments (unless [positional only][positional-only_parameter]). +This error is raised when the typed dictionary used to type hint variadic keywords parameters has field names +overlapping with other parameters (unless [positional only][positional-only_parameter]). For reference, see the [related specification section] and [PEP 692]. From 22ba7faad59b37d91596b915bcd80cc7a0b5b404 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:12:10 +0200 Subject: [PATCH 058/412] Schema cleaning: skip unnecessary copies during schema walking (#10286) Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> --- pydantic/_internal/_core_utils.py | 42 +++++++++++++--------- pydantic/_internal/_discriminated_union.py | 2 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pydantic/_internal/_core_utils.py b/pydantic/_internal/_core_utils.py index dea6bbcd64f..fcf99276ee7 100644 --- a/pydantic/_internal/_core_utils.py +++ b/pydantic/_internal/_core_utils.py @@ -128,7 +128,7 @@ def _record_valid_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_sche defs[ref] = s return recurse(s, _record_valid_refs) - walk_core_schema(schema, _record_valid_refs) + walk_core_schema(schema, _record_valid_refs, copy=False) return defs @@ -167,7 +167,7 @@ def _is_schema_valid(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema return s return recurse(s, _is_schema_valid) - walk_core_schema(schema, _is_schema_valid) + walk_core_schema(schema, _is_schema_valid, copy=False) return invalid @@ -180,10 +180,16 @@ def _is_schema_valid(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema # TODO: Should we move _WalkCoreSchema into pydantic_core proper? # Issue: https://github.com/pydantic/pydantic-core/issues/615 +CoreSchemaT = TypeVar('CoreSchemaT') + class _WalkCoreSchema: - def __init__(self): + def __init__(self, *, copy: bool = True): self._schema_type_to_method = self._build_schema_type_to_method() + self._copy = copy + + def _copy_schema(self, schema: CoreSchemaT) -> CoreSchemaT: + return schema.copy() if self._copy else schema # pyright: ignore[reportAttributeAccessIssue] def _build_schema_type_to_method(self) -> dict[core_schema.CoreSchemaType, Recurse]: mapping: dict[core_schema.CoreSchemaType, Recurse] = {} @@ -197,7 +203,7 @@ def walk(self, schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchem return f(schema, self._walk) def _walk(self, schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: - schema = self._schema_type_to_method[schema['type']](schema.copy(), f) + schema = self._schema_type_to_method[schema['type']](self._copy_schema(schema), f) ser_schema: core_schema.SerSchema | None = schema.get('serialization') # type: ignore if ser_schema: schema['serialization'] = self._handle_ser_schemas(ser_schema, f) @@ -213,7 +219,7 @@ def _handle_ser_schemas(self, ser_schema: core_schema.SerSchema, f: Walk) -> cor schema: core_schema.CoreSchema | None = ser_schema.get('schema', None) return_schema: core_schema.CoreSchema | None = ser_schema.get('return_schema', None) if schema is not None or return_schema is not None: - ser_schema = ser_schema.copy() + ser_schema = self._copy_schema(ser_schema) if schema is not None: ser_schema['schema'] = self.walk(schema, f) # type: ignore if return_schema is not None: @@ -243,7 +249,7 @@ def handle_definitions_schema(self, schema: core_schema.DefinitionsSchema, f: Wa # This means we'd be returning a "trivial" definitions schema that just wrapped the inner schema return new_inner_schema - new_schema = schema.copy() + new_schema = self._copy_schema(schema) new_schema['schema'] = new_inner_schema new_schema['definitions'] = new_definitions return new_schema @@ -329,13 +335,13 @@ def handle_model_fields_schema(self, schema: core_schema.ModelFieldsSchema, f: W replaced_fields: dict[str, core_schema.ModelField] = {} replaced_computed_fields: list[core_schema.ComputedField] = [] for computed_field in schema.get('computed_fields', ()): - replaced_field = computed_field.copy() + replaced_field = self._copy_schema(computed_field) replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) replaced_computed_fields.append(replaced_field) if replaced_computed_fields: schema['computed_fields'] = replaced_computed_fields for k, v in schema['fields'].items(): - replaced_field = v.copy() + replaced_field = self._copy_schema(v) replaced_field['schema'] = self.walk(v['schema'], f) replaced_fields[k] = replaced_field schema['fields'] = replaced_fields @@ -347,14 +353,14 @@ def handle_typed_dict_schema(self, schema: core_schema.TypedDictSchema, f: Walk) schema['extras_schema'] = self.walk(extras_schema, f) replaced_computed_fields: list[core_schema.ComputedField] = [] for computed_field in schema.get('computed_fields', ()): - replaced_field = computed_field.copy() + replaced_field = self._copy_schema(computed_field) replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) replaced_computed_fields.append(replaced_field) if replaced_computed_fields: schema['computed_fields'] = replaced_computed_fields replaced_fields: dict[str, core_schema.TypedDictField] = {} for k, v in schema['fields'].items(): - replaced_field = v.copy() + replaced_field = self._copy_schema(v) replaced_field['schema'] = self.walk(v['schema'], f) replaced_fields[k] = replaced_field schema['fields'] = replaced_fields @@ -364,13 +370,13 @@ def handle_dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema, replaced_fields: list[core_schema.DataclassField] = [] replaced_computed_fields: list[core_schema.ComputedField] = [] for computed_field in schema.get('computed_fields', ()): - replaced_field = computed_field.copy() + replaced_field = self._copy_schema(computed_field) replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) replaced_computed_fields.append(replaced_field) if replaced_computed_fields: schema['computed_fields'] = replaced_computed_fields for field in schema['fields']: - replaced_field = field.copy() + replaced_field = self._copy_schema(field) replaced_field['schema'] = self.walk(field['schema'], f) replaced_fields.append(replaced_field) schema['fields'] = replaced_fields @@ -379,7 +385,7 @@ def handle_dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema, def handle_arguments_schema(self, schema: core_schema.ArgumentsSchema, f: Walk) -> core_schema.CoreSchema: replaced_arguments_schema: list[core_schema.ArgumentsParameter] = [] for param in schema['arguments_schema']: - replaced_param = param.copy() + replaced_param = self._copy_schema(param) replaced_param['schema'] = self.walk(param['schema'], f) replaced_arguments_schema.append(replaced_param) schema['arguments_schema'] = replaced_arguments_schema @@ -397,9 +403,10 @@ def handle_call_schema(self, schema: core_schema.CallSchema, f: Walk) -> core_sc _dispatch = _WalkCoreSchema().walk +_dispatch_no_copy = _WalkCoreSchema(copy=False).walk -def walk_core_schema(schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: +def walk_core_schema(schema: core_schema.CoreSchema, f: Walk, *, copy: bool = True) -> core_schema.CoreSchema: """Recursively traverse a CoreSchema. Args: @@ -409,11 +416,12 @@ def walk_core_schema(schema: core_schema.CoreSchema, f: Walk) -> core_schema.Cor (not the same one you passed into this function, one level down). 2. The "next" `f` to call. This lets you for example use `f=functools.partial(some_method, some_context)` to pass data down the recursive calls without using globals or other mutable state. + copy: Whether schema should be recursively copied. Returns: core_schema.CoreSchema: A processed CoreSchema. """ - return f(schema.copy(), _dispatch) + return f(schema.copy() if copy else schema, _dispatch if copy else _dispatch_no_copy) def simplify_schema_references(schema: core_schema.CoreSchema) -> core_schema.CoreSchema: # noqa: C901 @@ -466,7 +474,7 @@ def count_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreS current_recursion_ref_count[ref] -= 1 return s - schema = walk_core_schema(schema, count_refs) + schema = walk_core_schema(schema, count_refs, copy=False) assert all(c == 0 for c in current_recursion_ref_count.values()), 'this is a bug! please report it' @@ -509,7 +517,7 @@ def inline_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.Core else: return recurse(s, inline_refs) - schema = walk_core_schema(schema, inline_refs) + schema = walk_core_schema(schema, inline_refs, copy=False) def_values = [v for v in definitions.values() if ref_counts[v['ref']] > 0] # type: ignore diff --git a/pydantic/_internal/_discriminated_union.py b/pydantic/_internal/_discriminated_union.py index a090c36f0de..73a4635f980 100644 --- a/pydantic/_internal/_discriminated_union.py +++ b/pydantic/_internal/_discriminated_union.py @@ -55,7 +55,7 @@ def inner(s: core_schema.CoreSchema, recurse: _core_utils.Recurse) -> core_schem s = apply_discriminator(s, discriminator, global_definitions) return s - return _core_utils.walk_core_schema(schema, inner) + return _core_utils.walk_core_schema(schema, inner, copy=False) def apply_discriminator( From 01b59297ef840d16049f125a8b0abef9fe0c991f Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:10:41 -0400 Subject: [PATCH 059/412] Add python validators for decimal constraints (`max_digits` and `decimal_places`) (#10506) Co-authored-by: Alex Hall --- .../_internal/_known_annotated_metadata.py | 23 +++--- pydantic/_internal/_validators.py | 82 +++++++++++++++++-- tests/test_annotated.py | 18 ++++ tests/test_internal.py | 18 ++++ 4 files changed, 122 insertions(+), 19 deletions(-) diff --git a/pydantic/_internal/_known_annotated_metadata.py b/pydantic/_internal/_known_annotated_metadata.py index 73a9d2e729d..a1df68c693a 100644 --- a/pydantic/_internal/_known_annotated_metadata.py +++ b/pydantic/_internal/_known_annotated_metadata.py @@ -201,7 +201,7 @@ def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | No """ import annotated_types as at - from ._validators import forbid_inf_nan_check, get_constraint_validator + from ._validators import NUMERIC_VALIDATOR_LOOKUP, forbid_inf_nan_check schema = schema.copy() schema_update, other_metadata = collect_known_metadata([annotation]) @@ -263,10 +263,8 @@ def _apply_constraint_with_incompatibility_info( _apply_constraint_with_incompatibility_info, cs.str_schema(**{constraint: value}) ) ) - elif constraint in {*NUMERIC_CONSTRAINTS, *LENGTH_CONSTRAINTS}: - if constraint in NUMERIC_CONSTRAINTS: - json_schema_constraint = constraint - elif constraint in LENGTH_CONSTRAINTS: + elif constraint in NUMERIC_VALIDATOR_LOOKUP: + if constraint in LENGTH_CONSTRAINTS: inner_schema = schema while inner_schema['type'] in {'function-before', 'function-wrap', 'function-after'}: inner_schema = inner_schema['schema'] # type: ignore @@ -274,14 +272,16 @@ def _apply_constraint_with_incompatibility_info( if inner_schema_type == 'list' or ( inner_schema_type == 'json-or-python' and inner_schema['json_schema']['type'] == 'list' # type: ignore ): - json_schema_constraint = 'minItems' if constraint == 'min_length' else 'maxItems' + js_constraint_key = 'minItems' if constraint == 'min_length' else 'maxItems' else: - json_schema_constraint = 'minLength' if constraint == 'min_length' else 'maxLength' + js_constraint_key = 'minLength' if constraint == 'min_length' else 'maxLength' + else: + js_constraint_key = constraint schema = cs.no_info_after_validator_function( - partial(get_constraint_validator(constraint), **{constraint: value}), schema + partial(NUMERIC_VALIDATOR_LOOKUP[constraint], **{constraint: value}), schema ) - add_js_update_schema(schema, lambda: {json_schema_constraint: as_jsonable_value(value)}) # noqa: B023 + add_js_update_schema(schema, lambda: {js_constraint_key: as_jsonable_value(value)}) # noqa: B023 elif constraint == 'allow_inf_nan' and value is False: schema = cs.no_info_after_validator_function( forbid_inf_nan_check, @@ -295,8 +295,11 @@ def _apply_constraint_with_incompatibility_info( for annotation in other_metadata: if (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()): constraint = at_to_constraint_map[annotation_type] + validator = NUMERIC_VALIDATOR_LOOKUP.get(constraint) + if validator is None: + raise ValueError(f'Unknown constraint {constraint}') schema = cs.no_info_after_validator_function( - partial(get_constraint_validator(constraint), {constraint: getattr(annotation, constraint)}), schema + partial(validator, {constraint: getattr(annotation, constraint)}), schema ) continue elif isinstance(annotation, (at.Predicate, at.Not)): diff --git a/pydantic/_internal/_validators.py b/pydantic/_internal/_validators.py index 4082a617195..31362d2a121 100644 --- a/pydantic/_internal/_validators.py +++ b/pydantic/_internal/_validators.py @@ -8,6 +8,7 @@ import math import re import typing +from decimal import Decimal from fractions import Fraction from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from typing import Any, Callable @@ -330,7 +331,76 @@ def max_length_validator(x: Any, max_length: Any) -> Any: raise TypeError(f"Unable to apply constraint 'max_length' to supplied value {x}") -_CONSTRAINT_TO_VALIDATOR_LOOKUP: dict[str, Callable] = { +def _extract_decimal_digits_info(decimal: Decimal) -> tuple[int, int]: + """Compute the total number of digits and decimal places for a given [`Decimal`][decimal.Decimal] instance. + + This function handles both normalized and non-normalized Decimal instances. + Example: Decimal('1.230') -> 4 digits, 3 decimal places + + Args: + decimal (Decimal): The decimal number to analyze. + + Returns: + tuple[int, int]: A tuple containing the number of decimal places and total digits. + + Though this could be divided into two separate functions, the logic is easier to follow if we couple the computation + of the number of decimals and digits together. + """ + decimal_tuple = decimal.as_tuple() + if not isinstance(decimal_tuple.exponent, int): + raise TypeError(f'Unable to extract decimal digits info from supplied value {decimal}') + exponent = decimal_tuple.exponent + num_digits = len(decimal_tuple.digits) + + if exponent >= 0: + # A positive exponent adds that many trailing zeros + # Ex: digit_tuple=(1, 2, 3), exponent=2 -> 12300 -> 0 decimal places, 5 digits + num_digits += exponent + decimal_places = 0 + else: + # If the absolute value of the negative exponent is larger than the + # number of digits, then it's the same as the number of digits, + # because it'll consume all the digits in digit_tuple and then + # add abs(exponent) - len(digit_tuple) leading zeros after the decimal point. + # Ex: digit_tuple=(1, 2, 3), exponent=-2 -> 1.23 -> 2 decimal places, 3 digits + # Ex: digit_tuple=(1, 2, 3), exponent=-4 -> 0.0123 -> 4 decimal places, 4 digits + decimal_places = abs(exponent) + num_digits = max(num_digits, decimal_places) + + return decimal_places, num_digits + + +def max_digits_validator(x: Any, max_digits: Any) -> Any: + _, num_digits = _extract_decimal_digits_info(x) + _, normalized_num_digits = _extract_decimal_digits_info(x.normalize()) + + try: + if (num_digits > max_digits) and (normalized_num_digits > max_digits): + raise PydanticKnownError( + 'decimal_max_digits', + {'max_digits': max_digits}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'max_digits' to supplied value {x}") + + +def decimal_places_validator(x: Any, decimal_places: Any) -> Any: + decimal_places_, _ = _extract_decimal_digits_info(x) + normalized_decimal_places, _ = _extract_decimal_digits_info(x.normalize()) + + try: + if (decimal_places_ > decimal_places) and (normalized_decimal_places > decimal_places): + raise PydanticKnownError( + 'decimal_max_places', + {'decimal_places': decimal_places}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'decimal_places' to supplied value {x}") + + +NUMERIC_VALIDATOR_LOOKUP: dict[str, Callable] = { 'gt': greater_than_validator, 'ge': greater_than_or_equal_validator, 'lt': less_than_validator, @@ -338,17 +408,11 @@ def max_length_validator(x: Any, max_length: Any) -> Any: 'multiple_of': multiple_of_validator, 'min_length': min_length_validator, 'max_length': max_length_validator, + 'max_digits': max_digits_validator, + 'decimal_places': decimal_places_validator, } -def get_constraint_validator(constraint: str) -> Callable: - """Fetch the validator function for the given constraint.""" - try: - return _CONSTRAINT_TO_VALIDATOR_LOOKUP[constraint] - except KeyError: - raise TypeError(f'Unknown constraint {constraint}') - - IP_VALIDATOR_LOOKUP: dict[type, Callable] = { IPv4Address: ip_v4_address_validator, IPv6Address: ip_v6_address_validator, diff --git a/tests/test_annotated.py b/tests/test_annotated.py index a4160dc7c81..15b1ad1eafe 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,6 +1,7 @@ import datetime as dt import sys from dataclasses import dataclass +from decimal import Decimal from typing import Any, Callable, Generic, Iterator, List, Optional, Set, TypeVar import pytest @@ -644,3 +645,20 @@ def test_compatible_metadata_raises_correct_validation_error() -> None: ta = TypeAdapter(Annotated[str, BeforeValidator(lambda x: x), Field(pattern='abc')]) with pytest.raises(ValidationError, match="String should match pattern 'abc'"): ta.validate_python('def') + + +def test_decimal_constraints_after_annotation() -> None: + DecimalAnnotation = Annotated[Decimal, BeforeValidator(lambda v: v), Field(max_digits=10, decimal_places=4)] + + ta = TypeAdapter(DecimalAnnotation) + assert ta.validate_python(Decimal('123.4567')) == Decimal('123.4567') + + with pytest.raises(ValidationError) as e: + ta.validate_python(Decimal('123.45678')) + + assert e.value.errors()[0]['type'] == 'decimal_max_places' + + with pytest.raises(ValidationError) as e: + ta.validate_python(Decimal('12345678.901')) + + assert e.value.errors()[0]['type'] == 'decimal_max_digits' diff --git a/tests/test_internal.py b/tests/test_internal.py index 862fe30c5c2..026126bd1fd 100644 --- a/tests/test_internal.py +++ b/tests/test_internal.py @@ -3,6 +3,7 @@ """ from dataclasses import dataclass +from decimal import Decimal import pytest from pydantic_core import CoreSchema, SchemaValidator @@ -16,6 +17,7 @@ walk_core_schema, ) from pydantic._internal._repr import Representation +from pydantic._internal._validators import _extract_decimal_digits_info def remove_metadata(schema: CoreSchema) -> CoreSchema: @@ -207,3 +209,19 @@ def test_schema_is_valid(): collect_invalid_schemas(cs.nullable_schema(cs.int_schema(metadata={HAS_INVALID_SCHEMAS_METADATA_KEY: True}))) is True ) + + +@pytest.mark.parametrize( + 'decimal,decimal_places,digits', + [ + (Decimal('0.0'), 1, 1), + (Decimal('0.'), 0, 1), + (Decimal('0.000'), 3, 3), + (Decimal('0.0001'), 4, 4), + (Decimal('.0001'), 4, 4), + (Decimal('123.123'), 3, 6), + (Decimal('123.1230'), 4, 7), + ], +) +def test_decimal_digits_calculation(decimal: Decimal, decimal_places: int, digits: int) -> None: + assert _extract_decimal_digits_info(decimal) == (decimal_places, digits) From ebe80206074857d1c7ab41cfdbc6a74c9f9be621 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:37:05 +0200 Subject: [PATCH 060/412] Fix handling of `Literal` in `has_instance_in_type` (#10514) --- pydantic/_internal/_generate_schema.py | 2 +- pydantic/_internal/_generics.py | 4 +++- pydantic/_internal/_typing_extra.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index ddaa6467160..3d1e974f343 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1265,7 +1265,7 @@ def _common_field_schema( # C901 # Update FieldInfo annotation if appropriate: FieldInfo = import_cached_field_info() - if has_instance_in_type(field_info.annotation, ForwardRef): + if has_instance_in_type(field_info.annotation, (ForwardRef, str)): types_namespace = self._types_namespace if self._typevars_map: types_namespace = (types_namespace or {}).copy() diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py index 69d68a82e32..0fcf2f80b99 100644 --- a/pydantic/_internal/_generics.py +++ b/pydantic/_internal/_generics.py @@ -14,7 +14,7 @@ from ._core_utils import get_type_ref from ._forward_ref import PydanticRecursiveRef -from ._typing_extra import TypeVarType, typing_base +from ._typing_extra import LITERAL_TYPES, TypeVarType, typing_base from ._utils import all_identical, is_model_class if sys.version_info >= (3, 10): @@ -357,6 +357,8 @@ def has_instance_in_type(type_: Any, isinstance_target: Any) -> bool: if origin_type is typing_extensions.Annotated: annotated_type, *annotations = type_args return has_instance_in_type(annotated_type, isinstance_target) + elif origin_type in LITERAL_TYPES: + return False # Having type args is a good indicator that this is a typing module # class instantiation or a generic alias of some sort. diff --git a/pydantic/_internal/_typing_extra.py b/pydantic/_internal/_typing_extra.py index 651cb18391b..f81187a84b1 100644 --- a/pydantic/_internal/_typing_extra.py +++ b/pydantic/_internal/_typing_extra.py @@ -100,6 +100,8 @@ def literal_values(type_: type[Any]) -> tuple[Any, ...]: return get_args(type_) +# TODO remove when we drop support for Python 3.8 +# (see https://docs.python.org/3/whatsnew/3.9.html#id4). def all_literal_values(type_: type[Any]) -> list[Any]: """This method is used to retrieve all Literal values as Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) From 4bcbaa14aea3069d573dd67d41be4f3a4757f37d Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:39:40 +0200 Subject: [PATCH 061/412] Only fetch `__pydantic_core_schema__` from the current class during schema generation (#10518) --- pydantic/_internal/_generate_schema.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 3d1e974f343..ead9346fad8 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -813,9 +813,12 @@ def _generate_schema_from_property(self, obj: Any, source: Any) -> core_schema.C source, CallbackGetCoreSchemaHandler(self._generate_schema_inner, self, ref_mode=ref_mode) ) elif ( - (existing_schema := getattr(obj, '__pydantic_core_schema__', None)) is not None + hasattr(obj, '__dict__') + # In some cases (e.g. a stdlib dataclass subclassing a Pydantic dataclass), + # doing an attribute access to get the schema will result in the parent schema + # being fetched. Thus, only look for the current obj's dict: + and (existing_schema := obj.__dict__.get('__pydantic_core_schema__')) is not None and not isinstance(existing_schema, MockCoreSchema) - and existing_schema.get('cls', None) is obj ): schema = existing_schema elif (validators := getattr(obj, '__get_validators__', None)) is not None: From 03276265596485cb15487df380e08c534dbe391d Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:46:51 -0400 Subject: [PATCH 062/412] Fix `stacklevel` on deprecation warnings for `BaseModel` (#10520) --- pydantic/_internal/_model_construction.py | 4 ++- pydantic/main.py | 41 +++++++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py index 316d5c9fe8b..d3e8fb7b10d 100644 --- a/pydantic/_internal/_model_construction.py +++ b/pydantic/_internal/_model_construction.py @@ -351,7 +351,9 @@ def _collect_bases_data(bases: tuple[type[Any], ...]) -> tuple[set[str], set[str @deprecated('The `__fields__` attribute is deprecated, use `model_fields` instead.', category=None) def __fields__(self) -> dict[str, FieldInfo]: warnings.warn( - 'The `__fields__` attribute is deprecated, use `model_fields` instead.', PydanticDeprecatedSince20 + 'The `__fields__` attribute is deprecated, use `model_fields` instead.', + PydanticDeprecatedSince20, + stacklevel=2, ) return self.model_fields diff --git a/pydantic/main.py b/pydantic/main.py index b6528161d8f..74a299afc4c 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -1116,7 +1116,9 @@ def __str__(self) -> str: ) def __fields__(self) -> dict[str, FieldInfo]: warnings.warn( - 'The `__fields__` attribute is deprecated, use `model_fields` instead.', category=PydanticDeprecatedSince20 + 'The `__fields__` attribute is deprecated, use `model_fields` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) return self.model_fields @@ -1129,6 +1131,7 @@ def __fields_set__(self) -> set[str]: warnings.warn( 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', category=PydanticDeprecatedSince20, + stacklevel=2, ) return self.__pydantic_fields_set__ @@ -1143,7 +1146,11 @@ def dict( # noqa: D102 exclude_defaults: bool = False, exclude_none: bool = False, ) -> Dict[str, Any]: # noqa UP006 - warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20) + warnings.warn( + 'The `dict` method is deprecated; use `model_dump` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) return self.model_dump( include=include, exclude=exclude, @@ -1168,7 +1175,9 @@ def json( # noqa: D102 **dumps_kwargs: Any, ) -> str: warnings.warn( - 'The `json` method is deprecated; use `model_dump_json` instead.', category=PydanticDeprecatedSince20 + 'The `json` method is deprecated; use `model_dump_json` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) if encoder is not PydanticUndefined: raise TypeError('The `encoder` argument is no longer supported; use field serializers instead.') @@ -1189,7 +1198,9 @@ def json( # noqa: D102 @typing_extensions.deprecated('The `parse_obj` method is deprecated; use `model_validate` instead.', category=None) def parse_obj(cls, obj: Any) -> Self: # noqa: D102 warnings.warn( - 'The `parse_obj` method is deprecated; use `model_validate` instead.', category=PydanticDeprecatedSince20 + 'The `parse_obj` method is deprecated; use `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) return cls.model_validate(obj) @@ -1212,6 +1223,7 @@ def parse_raw( # noqa: D102 'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, ' 'otherwise load the data then use `model_validate` instead.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import parse @@ -1265,6 +1277,7 @@ def parse_file( # noqa: D102 'The `parse_file` method is deprecated; load the data from file, then if your data is JSON ' 'use `model_validate_json`, otherwise `model_validate` instead.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import parse @@ -1288,6 +1301,7 @@ def from_orm(cls, obj: Any) -> Self: # noqa: D102 'The `from_orm` method is deprecated; set ' "`model_config['from_attributes']=True` and use `model_validate` instead.", category=PydanticDeprecatedSince20, + stacklevel=2, ) if not cls.model_config.get('from_attributes', None): raise PydanticUserError( @@ -1299,7 +1313,9 @@ def from_orm(cls, obj: Any) -> Self: # noqa: D102 @typing_extensions.deprecated('The `construct` method is deprecated; use `model_construct` instead.', category=None) def construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: # noqa: D102 warnings.warn( - 'The `construct` method is deprecated; use `model_construct` instead.', category=PydanticDeprecatedSince20 + 'The `construct` method is deprecated; use `model_construct` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) return cls.model_construct(_fields_set=_fields_set, **values) @@ -1342,6 +1358,7 @@ def copy( 'The `copy` method is deprecated; use `model_copy` instead. ' 'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import copy_internals @@ -1385,7 +1402,9 @@ def schema( # noqa: D102 cls, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE ) -> Dict[str, Any]: # noqa UP006 warnings.warn( - 'The `schema` method is deprecated; use `model_json_schema` instead.', category=PydanticDeprecatedSince20 + 'The `schema` method is deprecated; use `model_json_schema` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) return cls.model_json_schema(by_alias=by_alias, ref_template=ref_template) @@ -1400,6 +1419,7 @@ def schema_json( # noqa: D102 warnings.warn( 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', category=PydanticDeprecatedSince20, + stacklevel=2, ) import json @@ -1415,7 +1435,9 @@ def schema_json( # noqa: D102 @typing_extensions.deprecated('The `validate` method is deprecated; use `model_validate` instead.', category=None) def validate(cls, value: Any) -> Self: # noqa: D102 warnings.warn( - 'The `validate` method is deprecated; use `model_validate` instead.', category=PydanticDeprecatedSince20 + 'The `validate` method is deprecated; use `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, ) return cls.model_validate(value) @@ -1428,6 +1450,7 @@ def update_forward_refs(cls, **localns: Any) -> None: # noqa: D102 warnings.warn( 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', category=PydanticDeprecatedSince20, + stacklevel=2, ) if localns: # pragma: no cover raise TypeError('`localns` arguments are not longer accepted.') @@ -1440,6 +1463,7 @@ def _iter(self, *args: Any, **kwargs: Any) -> Any: warnings.warn( 'The private method `_iter` will be removed and should no longer be used.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import copy_internals @@ -1453,6 +1477,7 @@ def _copy_and_set_values(self, *args: Any, **kwargs: Any) -> Any: warnings.warn( 'The private method `_copy_and_set_values` will be removed and should no longer be used.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import copy_internals @@ -1467,6 +1492,7 @@ def _get_value(cls, *args: Any, **kwargs: Any) -> Any: warnings.warn( 'The private method `_get_value` will be removed and should no longer be used.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import copy_internals @@ -1480,6 +1506,7 @@ def _calculate_keys(self, *args: Any, **kwargs: Any) -> Any: warnings.warn( 'The private method `_calculate_keys` will be removed and should no longer be used.', category=PydanticDeprecatedSince20, + stacklevel=2, ) from .deprecated import copy_internals From d587553df8628a935979157e47d19b2149ffcaaf Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:58:13 -0400 Subject: [PATCH 063/412] Adding contextualized examples (#10452) --- .../{validators.md => custom_validators.md} | 4 +- docs/examples/files.md | 236 ++++++++++++++++++ docs/examples/orms.md | 56 +++++ docs/examples/queues.md | 70 ++++++ docs/examples/requests.md | 74 ++++++ docs/examples/secrets.md | 139 ----------- mkdocs.yml | 11 +- pydantic/types.py | 83 ++++++ 8 files changed, 530 insertions(+), 143 deletions(-) rename docs/examples/{validators.md => custom_validators.md} (97%) create mode 100644 docs/examples/files.md create mode 100644 docs/examples/orms.md create mode 100644 docs/examples/queues.md create mode 100644 docs/examples/requests.md delete mode 100644 docs/examples/secrets.md diff --git a/docs/examples/validators.md b/docs/examples/custom_validators.md similarity index 97% rename from docs/examples/validators.md rename to docs/examples/custom_validators.md index 9cafd33e241..e84818c7507 100644 --- a/docs/examples/validators.md +++ b/docs/examples/custom_validators.md @@ -2,8 +2,10 @@ This page is a work in progress. This page provides example snippets for creating more complex, custom validators in Pydantic. +Many of these examples are adapted from Pydantic issues and discussions, and are intended to showcase +the flexibility and power of Pydantic's validation system. -## Using Custom Validators with [`Annotated`][typing.Annotated] Metadata +## Custom `datetime` Validator via [`Annotated`][typing.Annotated] Metadata In this example, we'll construct a custom validator, attached to an [`Annotated`][typing.Annotated] type, that ensures a [`datetime`][datetime.datetime] object adheres to a given timezone constraint. diff --git a/docs/examples/files.md b/docs/examples/files.md new file mode 100644 index 00000000000..75bc1e01f2d --- /dev/null +++ b/docs/examples/files.md @@ -0,0 +1,236 @@ +`pydantic` is a great tool for validating data coming from various sources. +In this section, we will look at how to validate data from different types of files. + +!!! Note: + If you're using any of the below file formats to parse configuration / settings, you might want to + consider using the [`pydantic-settings`][pydantic_settings] library, which offers builtin + support for parsing this type of data. + +## JSON data + +`.json` files are a common way to store key / value data in a human-readable format. +Here is an example of a `.json` file: + +```json +{ + "name": "John Doe", + "age": 30, + "email": "john@example.com" +} +``` + +To validate this data, we can use a `pydantic` model: + +```python test="skip" +import pathlib + +from pydantic import BaseModel, EmailStr, PositiveInt + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +json_string = pathlib.Path('person.json').read_text() +person = Person.model_validate_json(json_string) +print(repr(person)) +#> Person(name='John Doe', age=30, email='john@example.com') +``` + +If the data in the file is not valid, `pydantic` will raise a [`ValidationError`][pydantic_core.ValidationError]. +Let's say we have the following `.json` file: + +```json +{ + "age": -30, + "email": "not-an-email-address" +} +``` + +This data is flawed for three reasons: +1. It's missing the `name` field. +2. The `age` field is negative. +3. The `email` field is not a valid email address. + +When we try to validate this data, `pydantic` raises a [`ValidationError`][pydantic_core.ValidationError] with all of the +above issues: + +```python test="skip" +import pathlib + +from pydantic import BaseModel, EmailStr, PositiveInt, ValidationError + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +json_string = pathlib.Path('person.json').read_text() +try: + person = Person.model_validate_json(json_string) +except ValidationError as err: + print(err) + """ + 3 validation errors for Person + name + Field required [type=missing, input_value={'age': -30, 'email': 'not-an-email-address'}, input_type=dict] + For further information visit https://errors.pydantic.dev/2.10/v/missing + age + Input should be greater than 0 [type=greater_than, input_value=-30, input_type=int] + For further information visit https://errors.pydantic.dev/2.10/v/greater_than + email + value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='not-an-email-address', input_type=str] + """ +``` + +Often, it's the case that you have an abundance of a certain type of data within a `.json` file. +For example, you might have a list of people: + +```json +[ + { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + { + "name": "Jane Doe", + "age": 25, + "email": "jane@example.com" + } +] +``` + +In this case, you can validate the data against a `List[Person]` model: + +```python test="skip" +import pathlib +from typing import List + +from pydantic import BaseModel, EmailStr, PositiveInt, TypeAdapter + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +person_list_adapter = TypeAdapter(List[Person]) # (1)! + +json_string = pathlib.Path('people.json').read_text() +people = person_list_adapter.validate_json(json_string) +print(people) +#> [Person(name='John Doe', age=30, email='john@example.com'), Person(name='Jane Doe', age=25, email='jane@example.com')] +``` + +1. We use [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] to validate a list of `Person` objects. +[`TypeAdapter`][pydantic.type_adapter.TypeAdapter] is a Pydantic construct used to validate data against a single type. + +## JSON lines files + +Similar to validating a list of objects from a `.json` file, you can validate a list of objects from a `.jsonl` file. +`.jsonl` files are a sequence of JSON objects separated by newlines. + +Consider the following `.jsonl` file: + +```json +{"name": "John Doe", "age": 30, "email": "john@example.com"} +{"name": "Jane Doe", "age": 25, "email": "jane@example.com"} +``` + +We can validate this data with a similar approach to the one we used for `.json` files: + +```python test="skip" +import pathlib + +from pydantic import BaseModel, EmailStr, PositiveInt + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +json_lines = pathlib.Path('people.jsonl').read_text().splitlines() +people = [Person.model_validate_json(line) for line in json_lines] +print(people) +#> [Person(name='John Doe', age=30, email='john@example.com'), Person(name='Jane Doe', age=25, email='jane@example.com')] +``` + +## CSV files + +CSV is one of the most common file formats for storing tabular data. +To validate data from a CSV file, you can use the `csv` module from the Python standard library to load +the data and validate it against a Pydantic model. + +Consider the following CSV file: + +```csv +name,age,email +John Doe,30,john@example.com +Jane Doe,25,jane@example.com +``` + +Here's how we validate that data: + +```py test="skip" +import csv + +from pydantic import BaseModel, EmailStr, PositiveInt + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +with open('people.csv') as f: + reader = csv.DictReader(f) + people = [Person.model_validate(row) for row in reader] + +print(people) +#> [Person(name='John Doe', age=30, email='john@example.com'), Person(name='Jane Doe', age=25, email='jane@example.com')] +``` + +## TOML files + +TOML files are often used for configuration due to their simplicity and readability. + +Consider the following TOML file: + +```toml +name = "John Doe" +age = 30 +email = "john@example.com" +``` + +Here's how we validate that data: + +```py test="skip" +import tomllib + +from pydantic import BaseModel, EmailStr, PositiveInt + + +class Person(BaseModel): + name: str + age: PositiveInt + email: EmailStr + + +with open('person.toml', 'rb') as f: + data = tomllib.load(f) + +person = Person.model_validate(data) +print(repr(person)) +#> Person(name='John Doe', age=30, email='john@example.com') +``` + + diff --git a/docs/examples/orms.md b/docs/examples/orms.md new file mode 100644 index 00000000000..7aeb5777b2f --- /dev/null +++ b/docs/examples/orms.md @@ -0,0 +1,56 @@ +!!! warning "🚧 Work in Progress" + This page is a work in progress. More examples will be added soon. + +Pydantic serves as a great tool for defining models for ORM (object relational mapping) libraries. +ORMs are used to map objects to database tables, and vice versa. + +## SQLAlchemy + +Pydantic can pair with SQLAlchemy, as it can be used to define the schema of the database models. + +!!! warning "Code Duplication" + If you use Pydantic with SQLAlchemy, you might experience some frustration with code duplication. + If you find yourself experiencing this difficulty, you might also consider [`SQLModel`](https://sqlmodel.tiangolo.com/) which integrates Pydantic with SQLAlchemy such that much of the code duplication is eliminated. + +If you'd prefer to use pure Pydantic with SQLAlchemy, we recommend using Pydantic models alongside of SQLAlchemy models +as shown in the example below. In this case, we take advantage of Pydantic's aliases feature to name a `Column` after a reserved SQLAlchemy field, thus avoiding conflicts. + +```py +import typing + +import sqlalchemy as sa +from sqlalchemy.orm import declarative_base + +from pydantic import BaseModel, ConfigDict, Field + + +class MyModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + metadata: typing.Dict[str, str] = Field(alias='metadata_') + + +Base = declarative_base() + + +class MyTableModel(Base): + __tablename__ = 'my_table' + id = sa.Column('id', sa.Integer, primary_key=True) + # 'metadata' is reserved by SQLAlchemy, hence the '_' + metadata_ = sa.Column('metadata', sa.JSON) + + +sql_model = MyTableModel(metadata_={'key': 'val'}, id=1) +pydantic_model = MyModel.model_validate(sql_model) + +print(pydantic_model.model_dump()) +#> {'metadata': {'key': 'val'}} +print(pydantic_model.model_dump(by_alias=True)) +#> {'metadata_': {'key': 'val'}} +``` + +!!! note + The example above works because aliases have priority over field names for + field population. Accessing `SQLModel`'s `metadata` attribute would lead to a `ValidationError`. + + diff --git a/docs/examples/queues.md b/docs/examples/queues.md new file mode 100644 index 00000000000..49141e75192 --- /dev/null +++ b/docs/examples/queues.md @@ -0,0 +1,70 @@ +!!! warning "🚧 Work in Progress" + This page is a work in progress. + +Pydantic is quite helpful for validating data that goes into and comes out of queues. Below, +we'll explore how to validate / serialize data with various queue systems. + +## Redis queue + +Redis is a popular in-memory data structure store. + +In order to run this example locally, you'll first need to [install Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) +and start your server up locally. + +Here's a simple example of how you can use Pydantic to: +1. Serialize data to push to the queue +2. Deserialize and validate data when it's popped from the queue + +```python test="skip" +import redis + +from pydantic import BaseModel, EmailStr + + +class User(BaseModel): + id: int + name: str + email: EmailStr + + +r = redis.Redis(host='localhost', port=6379, db=0) +QUEUE_NAME = 'user_queue' + + +def push_to_queue(user_data: User) -> None: + serialized_data = user_data.model_dump_json() + r.rpush(QUEUE_NAME, user_data.model_dump_json()) + print(f'Added to queue: {serialized_data}') + + +user1 = User(id=1, name='John Doe', email='john@example.com') +user2 = User(id=2, name='Jane Doe', email='jane@example.com') + +push_to_queue(user1) +#> Added to queue: {"id":1,"name":"John Doe","email":"john@example.com"} + +push_to_queue(user2) +#> Added to queue: {"id":2,"name":"Jane Doe","email":"jane@example.com"} + + +def pop_from_queue() -> None: + data = r.lpop(QUEUE_NAME) + + if data: + user = User.model_validate_json(data) + print(f'Validated user: {repr(user)}') + else: + print('Queue is empty') + + +pop_from_queue() +#> Validated user: User(id=1, name='John Doe', email='john@example.com') + +pop_from_queue() +#> Validated user: User(id=2, name='Jane Doe', email='jane@example.com') + +pop_from_queue() +#> Queue is empty +``` + + diff --git a/docs/examples/requests.md b/docs/examples/requests.md new file mode 100644 index 00000000000..14813099939 --- /dev/null +++ b/docs/examples/requests.md @@ -0,0 +1,74 @@ +!!! warning "🚧 Work in Progress" + This page is a work in progress. More examples will be added soon. + +## `httpx` requests + +[`httpx`](https://www.python-httpx.org/) is a HTTP client for Python 3 with synchronous and asynchronous APIs. +In the below example, we query the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/) to get a user's data and validate it with a Pydantic model. + +```python test="skip" +import httpx + +from pydantic import BaseModel, EmailStr + + +class User(BaseModel): + id: int + name: str + email: EmailStr + + +url = 'https://jsonplaceholder.typicode.com/users/1' + +response = httpx.get(url) +response.raise_for_status() + +user = User.model_validate(response.json()) +print(repr(user)) +#> User(id=1, name='Leanne Graham', email='Sincere@april.biz') +``` + +The [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] tool from Pydantic often comes in quite +handy when working with HTTP requests. Consider a similar example where we are validating a list of users: + +```py test="skip" +from pprint import pprint +from typing import List + +import httpx + +from pydantic import BaseModel, EmailStr, TypeAdapter + + +class User(BaseModel): + id: int + name: str + email: EmailStr + + +url = 'https://jsonplaceholder.typicode.com/users/' # (1)! + +response = httpx.get(url) +response.raise_for_status() + +users_list_adapter = TypeAdapter(List[User]) + +users = users_list_adapter.validate_python(response.json()) +pprint([u.name for u in users]) +""" +['Leanne Graham', + 'Ervin Howell', + 'Clementine Bauch', + 'Patricia Lebsack', + 'Chelsey Dietrich', + 'Mrs. Dennis Schulist', + 'Kurtis Weissnat', + 'Nicholas Runolfsdottir V', + 'Glenna Reichert', + 'Clementina DuBuque'] +""" +``` + +1. Note, we're querying the `/users/` endpoint here to get a list of users. + + diff --git a/docs/examples/secrets.md b/docs/examples/secrets.md deleted file mode 100644 index 00c18b5fd01..00000000000 --- a/docs/examples/secrets.md +++ /dev/null @@ -1,139 +0,0 @@ -!!! warning "🚧 Work in Progress" - This page is a work in progress. - -## Serialize `SecretStr` and `SecretBytes` as plain-text - -By default, [`SecretStr`][pydantic.types.SecretStr] and [`SecretBytes`][pydantic.types.SecretBytes] -will be serialized as `**********` when serializing to json. - -You can use the [`field_serializer`][pydantic.functional_serializers.field_serializer] to dump the -secret as plain-text when serializing to json. - -```py -from pydantic import BaseModel, SecretBytes, SecretStr, field_serializer - - -class Model(BaseModel): - password: SecretStr - password_bytes: SecretBytes - - @field_serializer('password', 'password_bytes', when_used='json') - def dump_secret(self, v): - return v.get_secret_value() - - -model = Model(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes') -print(model) -#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********') -print(model.password) -#> ********** -print(model.model_dump()) -""" -{ - 'password': SecretStr('**********'), - 'password_bytes': SecretBytes(b'**********'), -} -""" -print(model.model_dump_json()) -#> {"password":"IAmSensitive","password_bytes":"IAmSensitiveBytes"} -``` - -## Create your own Secret field - -Pydantic provides the generic `Secret` class as a mechanism for creating custom secret types. - -??? api "API Documentation" - [`pydantic.types.Secret`][pydantic.types.Secret]
- -Pydantic provides the generic `Secret` class as a mechanism for creating custom secret types. -You can either directly parametrize `Secret`, or subclass from a parametrized `Secret` to customize the `str()` and `repr()` of a secret type. - -```py -from datetime import date - -from pydantic import BaseModel, Secret - -# Using the default representation -SecretDate = Secret[date] - - -# Overwriting the representation -class SecretSalary(Secret[float]): - def _display(self) -> str: - return '$****.**' - - -class Employee(BaseModel): - date_of_birth: SecretDate - salary: SecretSalary - - -employee = Employee(date_of_birth='1990-01-01', salary=42) - -print(employee) -#> date_of_birth=Secret('**********') salary=SecretSalary('$****.**') - -print(employee.salary) -#> $****.** - -print(employee.salary.get_secret_value()) -#> 42.0 - -print(employee.date_of_birth) -#> ********** - -print(employee.date_of_birth.get_secret_value()) -#> 1990-01-01 -``` - -You can enforce constraints on the underlying type through annotations: -For example: - -```py -from typing_extensions import Annotated - -from pydantic import BaseModel, Field, Secret, ValidationError - -SecretPosInt = Secret[Annotated[int, Field(gt=0, strict=True)]] - - -class Model(BaseModel): - sensitive_int: SecretPosInt - - -m = Model(sensitive_int=42) -print(m.model_dump()) -#> {'sensitive_int': Secret('**********')} - -try: - m = Model(sensitive_int=-42) # (1)! -except ValidationError as exc_info: - print(exc_info.errors(include_url=False, include_input=False)) - """ - [ - { - 'type': 'greater_than', - 'loc': ('sensitive_int',), - 'msg': 'Input should be greater than 0', - 'ctx': {'gt': 0}, - } - ] - """ - -try: - m = Model(sensitive_int='42') # (2)! -except ValidationError as exc_info: - print(exc_info.errors(include_url=False, include_input=False)) - """ - [ - { - 'type': 'int_type', - 'loc': ('sensitive_int',), - 'msg': 'Input should be a valid integer', - } - ] - """ -``` - -1. The input value is not greater than 0, so it raises a validation error. -2. The input value is not an integer, so it raises a validation error because the `SecretPosInt` type has strict mode enabled. diff --git a/mkdocs.yml b/mkdocs.yml index e4e16f01379..0c881f64085 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -147,8 +147,11 @@ nav: - ULID: api/pydantic_extra_types_ulid.md - Architecture: architecture.md - Examples: - - Secrets: examples/secrets.md - - Validators: examples/validators.md + - Validating File Data: examples/files.md + - Web and API Requests: examples/requests.md + - Queues: examples/queues.md + - Databases: examples/orms.md + - Custom Validators: examples/custom_validators.md - Error Messages: - Error Handling: errors/errors.md - Validation Errors: errors/validation_errors.md @@ -232,7 +235,7 @@ plugins: 'usage/rich.md': 'integrations/rich.md' 'usage/linting.md': 'integrations/linting.md' 'usage/types.md': 'concepts/types.md' - 'usage/types/secrets.md': 'examples/secrets.md' + 'usage/types/secrets.md': 'api/types.md#pydantic.types.Secret' 'usage/types/string_types.md': 'api/types.md#pydantic.types.StringConstraints' 'usage/types/file_types.md': 'api/types.md#pydantic.types.FilePath' 'api/main.md': 'api/base_model.md' @@ -288,3 +291,5 @@ plugins: 'blog/pydantic-v2-alpha.md': 'https://blog.pydantic.dev/blog/2023/04/03/pydantic-v2-pre-release/' 'blog/pydantic-v2-final.md': 'https://blog.pydantic.dev/blog/2023/06/30/pydantic-v2-is-here/' 'blog/pydantic-v2.md': 'https://blog.pydantic.dev/blog/2022/07/10/pydantic-v2-plan/' + 'examples/secrets.md': 'api/types.md#pydantic.types.Secret' + 'examples/validators.md': 'examples/custom_validators.md' diff --git a/pydantic/types.py b/pydantic/types.py index 5a97a2fcd37..2ead11f56cf 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -1578,6 +1578,56 @@ class Model(BaseModel): ``` The value returned by the `_display` method will be used for `repr()` and `str()`. + + You can enforce constraints on the underlying type through annotations: + For example: + + ```py + from typing_extensions import Annotated + + from pydantic import BaseModel, Field, Secret, ValidationError + + SecretPosInt = Secret[Annotated[int, Field(gt=0, strict=True)]] + + class Model(BaseModel): + sensitive_int: SecretPosInt + + m = Model(sensitive_int=42) + print(m.model_dump()) + #> {'sensitive_int': Secret('**********')} + + try: + m = Model(sensitive_int=-42) # (1)! + except ValidationError as exc_info: + print(exc_info.errors(include_url=False, include_input=False)) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('sensitive_int',), + 'msg': 'Input should be greater than 0', + 'ctx': {'gt': 0}, + } + ] + ''' + + try: + m = Model(sensitive_int='42') # (2)! + except ValidationError as exc_info: + print(exc_info.errors(include_url=False, include_input=False)) + ''' + [ + { + 'type': 'int_type', + 'loc': ('sensitive_int',), + 'msg': 'Input should be a valid integer', + } + ] + ''' + ``` + + 1. The input value is not greater than 0, so it raises a validation error. + 2. The input value is not an integer, so it raises a validation error because the `SecretPosInt` type has strict mode enabled. """ def _display(self) -> str | bytes: @@ -1713,6 +1763,39 @@ class User(BaseModel): print((SecretStr('password'), SecretStr(''))) #> (SecretStr('**********'), SecretStr('')) ``` + + As seen above, by default, [`SecretStr`][pydantic.types.SecretStr] (and [`SecretBytes`][pydantic.types.SecretBytes]) + will be serialized as `**********` when serializing to json. + + You can use the [`field_serializer`][pydantic.functional_serializers.field_serializer] to dump the + secret as plain-text when serializing to json. + + ```py + from pydantic import BaseModel, SecretBytes, SecretStr, field_serializer + + class Model(BaseModel): + password: SecretStr + password_bytes: SecretBytes + + @field_serializer('password', 'password_bytes', when_used='json') + def dump_secret(self, v): + return v.get_secret_value() + + model = Model(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes') + print(model) + #> password=SecretStr('**********') password_bytes=SecretBytes(b'**********') + print(model.password) + #> ********** + print(model.model_dump()) + ''' + { + 'password': SecretStr('**********'), + 'password_bytes': SecretBytes(b'**********'), + } + ''' + print(model.model_dump_json()) + #> {"password":"IAmSensitive","password_bytes":"IAmSensitiveBytes"} + ``` """ _inner_schema: ClassVar[CoreSchema] = core_schema.str_schema() From 1faa60d2daa7eb27fda309feaa9d7f5251c01cd9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:52:12 -0400 Subject: [PATCH 064/412] =?UTF-8?q?=F0=9F=91=A5=20Update=20Pydantic=20Peop?= =?UTF-8?q?le=20(#10529)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/plugins/people.yml | 114 +++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/docs/plugins/people.yml b/docs/plugins/people.yml index 19836ce9b35..9771ea6d3aa 100644 --- a/docs/plugins/people.yml +++ b/docs/plugins/people.yml @@ -1,57 +1,57 @@ maintainers: -- login: samuelcolvin - answers: 295 - prs: 399 - avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 - url: https://github.com/samuelcolvin -- login: adriangb - answers: 41 - prs: 199 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 - url: https://github.com/adriangb +- login: Kludex + answers: 22 + prs: 112 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=0934928aa44d3b75af26ad47cb0227c8af30c8ec&v=4 + url: https://github.com/Kludex - login: sydney-runkle answers: 36 - prs: 262 + prs: 292 avatarUrl: https://avatars.githubusercontent.com/u/54324534?u=3a4ffd00a8270b607922250d3a2d9c9af38b9cf9&v=4 url: https://github.com/sydney-runkle -- login: dmontagu - answers: 55 - prs: 315 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu -- login: davidhewitt - answers: 2 - prs: 40 - avatarUrl: https://avatars.githubusercontent.com/u/1939362?u=b4b48981c3a097daaad16c4c5417aa7a3e5e32d9&v=4 - url: https://github.com/davidhewitt -- login: hramezani - answers: 22 +- login: adriangb + answers: 41 prs: 199 - avatarUrl: https://avatars.githubusercontent.com/u/3122442?u=f387fc2dbc0c681f23e80e2ad705790fafcec9a2&v=4 - url: https://github.com/hramezani + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 + url: https://github.com/adriangb +- login: samuelcolvin + answers: 295 + prs: 399 + avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 + url: https://github.com/samuelcolvin - login: alexmojaki answers: 0 prs: 18 avatarUrl: https://avatars.githubusercontent.com/u/3627481?u=9bb2e0cf1c5ef3d0609d2e639a135b7b4ca8b463&v=4 url: https://github.com/alexmojaki -- login: Kludex +- login: hramezani answers: 22 - prs: 111 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex + prs: 199 + avatarUrl: https://avatars.githubusercontent.com/u/3122442?u=f387fc2dbc0c681f23e80e2ad705790fafcec9a2&v=4 + url: https://github.com/hramezani +- login: davidhewitt + answers: 2 + prs: 40 + avatarUrl: https://avatars.githubusercontent.com/u/1939362?u=b4b48981c3a097daaad16c4c5417aa7a3e5e32d9&v=4 + url: https://github.com/davidhewitt +- login: dmontagu + answers: 55 + prs: 315 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu experts: - login: PrettyWood count: 143 avatarUrl: https://avatars.githubusercontent.com/u/18406791?u=20a4953f7d7e9d49d054b81e1582b08e87b2125f&v=4 url: https://github.com/PrettyWood +- login: Viicos + count: 94 + avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 + url: https://github.com/Viicos - login: uriyyo count: 93 avatarUrl: https://avatars.githubusercontent.com/u/32038156?u=bbcf79839cafe7b6249326c3b3b5383f2981595a&v=4 url: https://github.com/uriyyo -- login: Viicos - count: 87 - avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 - url: https://github.com/Viicos - login: lesnik512 count: 21 avatarUrl: https://avatars.githubusercontent.com/u/2184855?u=9c720fa595336aa83eef20729d4230ba7424d9fa&v=4 @@ -70,7 +70,7 @@ experts: url: https://github.com/ybressler last_month_active: - login: Viicos - count: 15 + count: 7 avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 url: https://github.com/Viicos top_contributors: @@ -79,7 +79,7 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/18406791?u=20a4953f7d7e9d49d054b81e1582b08e87b2125f&v=4 url: https://github.com/PrettyWood - login: Viicos - count: 77 + count: 105 avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 url: https://github.com/Viicos - login: dependabot-preview @@ -146,6 +146,10 @@ top_contributors: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/70970900?u=573a3175906348e0d1529104d56b391e93ca0250&v=4 url: https://github.com/NeevCohen +- login: kc0506 + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/89458301?u=75f53e971fcba3ff61836c389505a420bddd865c&v=4 + url: https://github.com/kc0506 - login: layday count: 7 avatarUrl: https://avatars.githubusercontent.com/u/31134424?u=e8afd95a97b5556c467d1be27788950e67378ef1&v=4 @@ -174,10 +178,6 @@ top_contributors: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/9677399?u=386c330f212ce467ce7119d9615c75d0e9b9f1ce&v=4 url: https://github.com/ofek -- login: kc0506 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/89458301?u=75f53e971fcba3ff61836c389505a420bddd865c&v=4 - url: https://github.com/kc0506 - login: hmvp count: 4 avatarUrl: https://avatars.githubusercontent.com/u/1734544?v=4 @@ -218,11 +218,19 @@ top_contributors: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/45747761?u=c1515d2ccf4877c0b64b5ea5a8c51631affe35de&v=4 url: https://github.com/dAIsySHEng1 +- login: AdolfoVillalobos + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/16639270?u=faa71bcfb3273a32cd81711a56998e115bca7fcc&v=4 + url: https://github.com/AdolfoVillalobos top_reviewers: - login: PrettyWood count: 211 avatarUrl: https://avatars.githubusercontent.com/u/18406791?u=20a4953f7d7e9d49d054b81e1582b08e87b2125f&v=4 url: https://github.com/PrettyWood +- login: Viicos + count: 115 + avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 + url: https://github.com/Viicos - login: lig count: 103 avatarUrl: https://avatars.githubusercontent.com/u/38705?v=4 @@ -231,10 +239,6 @@ top_reviewers: count: 77 avatarUrl: https://avatars.githubusercontent.com/u/370316?u=eb206070cfe47f242d5fcea2e6c7514f4d0f27f5&v=4 url: https://github.com/tpdorsey -- login: Viicos - count: 77 - avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 - url: https://github.com/Viicos - login: tiangolo count: 44 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 @@ -255,6 +259,10 @@ top_reviewers: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=b417e3cea56fd0f67983006108f6a1a83d4652a0&v=4 url: https://github.com/ybressler +- login: hyperlint-ai + count: 12 + avatarUrl: https://avatars.githubusercontent.com/in/718456?v=4 + url: https://github.com/apps/hyperlint-ai - login: uriyyo count: 11 avatarUrl: https://avatars.githubusercontent.com/u/32038156?u=bbcf79839cafe7b6249326c3b3b5383f2981595a&v=4 @@ -279,6 +287,10 @@ top_reviewers: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/31134424?u=e8afd95a97b5556c467d1be27788950e67378ef1&v=4 url: https://github.com/layday +- login: MarkusSintonen + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/12939780?v=4 + url: https://github.com/MarkusSintonen - login: pilosus count: 6 avatarUrl: https://avatars.githubusercontent.com/u/6400248?u=2b30c6675f888c2e47640aed2f1c1a956baae224&v=4 @@ -291,10 +303,6 @@ top_reviewers: count: 6 avatarUrl: https://avatars.githubusercontent.com/u/10811879?u=c0cfe7f7be82474d0deb2ba27601ec96f4f43515&v=4 url: https://github.com/JeanArhancet -- login: MarkusSintonen - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/12939780?v=4 - url: https://github.com/MarkusSintonen - login: tlambert03 count: 5 avatarUrl: https://avatars.githubusercontent.com/u/1609449?u=922abf0524b47739b37095e553c99488814b05db&v=4 @@ -303,10 +311,10 @@ top_reviewers: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/537700?u=7b64bd12eda862fbf72228495aada9c470df7a90&v=4 url: https://github.com/christianbundy -- login: hyperlint-ai +- login: nix010 count: 5 - avatarUrl: https://avatars.githubusercontent.com/in/718456?v=4 - url: https://github.com/apps/hyperlint-ai + avatarUrl: https://avatars.githubusercontent.com/u/16438204?u=f700f440b89e715795c3bc091800b8d3f39c58d9&v=4 + url: https://github.com/nix010 - login: graingert count: 4 avatarUrl: https://avatars.githubusercontent.com/u/413772?u=64b77b6aa405c68a9c6bcf45f84257c66eea5f32&v=4 @@ -335,7 +343,7 @@ top_reviewers: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/59469646?u=86d6a20768cc4cc65622eafd86672147321bd8f8&v=4 url: https://github.com/JensHeinrich -- login: nix010 +- login: kc0506 count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/16438204?u=f700f440b89e715795c3bc091800b8d3f39c58d9&v=4 - url: https://github.com/nix010 + avatarUrl: https://avatars.githubusercontent.com/u/89458301?u=75f53e971fcba3ff61836c389505a420bddd865c&v=4 + url: https://github.com/kc0506 From beda9c0257e1985d6b8df848ae87dc4427a615f1 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:00:29 +0200 Subject: [PATCH 065/412] Fix warning stacklevel in `BaseModel.__init__` (#10526) --- pydantic/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/main.py b/pydantic/main.py index 74a299afc4c..734c0f254fd 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -214,7 +214,7 @@ def __init__(self, /, **data: Any) -> None: 'A custom validator is returning a value other than `self`.\n' "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n" 'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.', - category=None, + stacklevel=2, ) # The following line sets a flag that we use to determine when `__init__` gets overridden by the user From 9de06485247366119c8c0d08410ae1e47fa8cf02 Mon Sep 17 00:00:00 2001 From: Huan-Cheng Chang Date: Wed, 2 Oct 2024 14:13:16 +0100 Subject: [PATCH 066/412] Sanitize names of module files created in tests (#10538) --- tests/conftest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0f406dbace7..cc83add5fdc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,7 +43,11 @@ def _extract_source_code_from_function(function: FunctionType): def _create_module_file(code: str, tmp_path: Path, name: str) -> tuple[str, str]: - name = f'{name}_{secrets.token_hex(5)}' + # Max path length in Windows is 260. Leaving some buffer here + max_name_len = 240 - len(str(tmp_path)) + # Windows does not allow these characters in paths. Linux bans slashes only. + sanitized_name = re.sub('[' + re.escape('<>:"/\\|?*') + ']', '-', name)[:max_name_len] + name = f'{sanitized_name}_{secrets.token_hex(5)}' path = tmp_path / f'{name}.py' path.write_text(code) return name, str(path) From 296f2caca7909887cd70e16fae4370db9eacdc14 Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:40:43 -0400 Subject: [PATCH 067/412] Support compiled patterns in `protected_namespaces` (#10522) Co-authored-by: David Montague <35119617+dmontagu@users.noreply.github.com> --- pydantic/_internal/_config.py | 3 ++- pydantic/_internal/_fields.py | 23 ++++++++++++++++++----- pydantic/config.py | 34 +++++++++++++++++++++++----------- tests/test_main.py | 9 +++++++++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py index 5953c8cb140..acb619eab95 100644 --- a/pydantic/_internal/_config.py +++ b/pydantic/_internal/_config.py @@ -2,6 +2,7 @@ import warnings from contextlib import contextmanager +from re import Pattern from typing import ( TYPE_CHECKING, Any, @@ -76,7 +77,7 @@ class ConfigWrapper: # whether to validate default values during validation, default False validate_default: bool validate_return: bool - protected_namespaces: tuple[str, ...] + protected_namespaces: tuple[str | Pattern[str], ...] hide_input_in_errors: bool defer_build: bool plugin_settings: dict[str, object] | None diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 060d166a1cb..2c00e591672 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -7,7 +7,7 @@ import warnings from copy import copy from functools import lru_cache -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Pattern from pydantic_core import PydanticUndefined @@ -121,8 +121,15 @@ def collect_model_fields( # noqa: C901 # Note: we may need to change this logic if/when we introduce a `BareModel` class with no # protected namespaces (where `model_config` might be allowed as a field name) continue + for protected_namespace in config_wrapper.protected_namespaces: - if ann_name.startswith(protected_namespace): + ns_violation: bool = False + if isinstance(protected_namespace, Pattern): + ns_violation = protected_namespace.match(ann_name) is not None + elif isinstance(protected_namespace, str): + ns_violation = ann_name.startswith(protected_namespace) + + if ns_violation: for b in bases: if hasattr(b, ann_name): if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})): @@ -131,9 +138,15 @@ def collect_model_fields( # noqa: C901 f' of protected namespace "{protected_namespace}".' ) else: - valid_namespaces = tuple( - x for x in config_wrapper.protected_namespaces if not ann_name.startswith(x) - ) + valid_namespaces = () + for pn in config_wrapper.protected_namespaces: + if isinstance(pn, Pattern): + if not pn.match(ann_name): + valid_namespaces += (f're.compile({pn.pattern})',) + else: + if not ann_name.startswith(pn): + valid_namespaces += (pn,) + warnings.warn( f'Field "{ann_name}" in {cls.__name__} has conflict with protected namespace "{protected_namespace}".' '\n\nYou may be able to resolve this warning by setting' diff --git a/pydantic/config.py b/pydantic/config.py index 57d540ace9d..8ece6b85370 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -2,6 +2,7 @@ from __future__ import annotations as _annotations +from re import Pattern from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, TypeVar, Union from typing_extensions import Literal, TypeAlias, TypedDict @@ -612,9 +613,11 @@ class Transaction(BaseModel): validate_return: bool """whether to validate the return value from call validators. Defaults to `False`.""" - protected_namespaces: tuple[str, ...] + protected_namespaces: tuple[str | Pattern[str], ...] """ - A `tuple` of strings that prevent model to have field which conflict with them. + A `tuple` of strings and/or patterns that prevent models from having fields with names that conflict with them. + For strings, we match on a prefix basis. Ex, if 'dog' is in the protected namespace, 'dog_name' will be protected. + For patterns, we match on the entire field name. Ex, if `re.compile(r'^dog$')` is in the protected namespace, 'dog' will be protected, but 'dog_name' will not be. Defaults to `('model_', )`). Pydantic prevents collisions between model attributes and `BaseModel`'s own methods by @@ -643,29 +646,38 @@ class Model(BaseModel): You can customize this behavior using the `protected_namespaces` setting: - ```py + ```py test="skip" + import re import warnings from pydantic import BaseModel, ConfigDict - warnings.filterwarnings('error') # Raise warnings as errors - - try: + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter('always') # Catch all warnings class Model(BaseModel): - model_prefixed_field: str + safe_field: str also_protect_field: str + protect_this: str model_config = ConfigDict( - protected_namespaces=('protect_me_', 'also_protect_') + protected_namespaces=( + 'protect_me_', + 'also_protect_', + re.compile('^protect_this$'), + ) ) - except UserWarning as e: - print(e) + for warning in caught_warnings: + print(f'{warning.message}\n') ''' Field "also_protect_field" in Model has conflict with protected namespace "also_protect_". - You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_',)`. + You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_', re.compile('^protect_this$'))`. + + Field "protect_this" in Model has conflict with protected namespace "re.compile('^protect_this$')". + + You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_', 'also_protect_')`. ''' ``` diff --git a/tests/test_main.py b/tests/test_main.py index 5d408227ca9..fcf6f3b742a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2641,6 +2641,15 @@ class Model(BaseModel): model_config = ConfigDict(protected_namespaces=('protect_me_', 'also_protect_')) +def test_protected_namespace_pattern() -> None: + with pytest.warns(UserWarning, match=r'Field "perfect_match" in Model has conflict with protected namespace .*'): + + class Model(BaseModel): + perfect_match: str + + model_config = ConfigDict(protected_namespaces=(re.compile(r'^perfect_match$'),)) + + def test_model_get_core_schema() -> None: class Model(BaseModel): @classmethod From 281c11c06f3b0ec65add39f1aba0df3620eb8fe7 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 2 Oct 2024 17:01:48 +0100 Subject: [PATCH 068/412] Logfire Annoucement (#10541) --- README.md | 6 +- docs/concepts/json.md | 2 +- docs/extra/fluff.js | 73 +++++++++++++++++++--- docs/extra/tweaks.css | 34 ++++++++++ docs/img/logfire-pydantic-integration.png | Bin 0 -> 85272 bytes docs/img/rich_pydantic.png | Bin 29432 -> 12914 bytes docs/img/samuelcolvin.jpg | Bin 35372 -> 0 bytes docs/img/terrencedorsey.jpg | Bin 67084 -> 0 bytes docs/index.md | 52 +++++++++------ docs/theme/announce.html | 8 ++- mkdocs.yml | 6 +- pydantic/types.py | 10 ++- 12 files changed, 149 insertions(+), 42 deletions(-) create mode 100644 docs/img/logfire-pydantic-integration.png delete mode 100644 docs/img/samuelcolvin.jpg delete mode 100644 docs/img/terrencedorsey.jpg diff --git a/README.md b/README.md index 685f363ac91..946f3a6ee12 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ Data validation using Python type hints. Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. Define how data should be in pure, canonical Python 3.8+; validate it with Pydantic. -## Pydantic Company :rocket: +## Pydantic Logfire :fire: -We've started a company based on the principles that I believe have led to Pydantic's success. -Learn more from the [Company Announcement](https://blog.pydantic.dev/blog/2023/02/16/company-announcement--pydantic/). +We've recently launched Pydantic Logfire to help you monitor your applications. +[Learn more](https://pydantic.dev/articles/logfire-announcement) ## Pydantic V1.10 vs. V2 diff --git a/docs/concepts/json.md b/docs/concepts/json.md index fd027d67d21..1b7fa7ff641 100644 --- a/docs/concepts/json.md +++ b/docs/concepts/json.md @@ -97,7 +97,7 @@ print(dog_dict) !!! tip "Validating LLM Output" This feature is particularly beneficial for validating LLM outputs. - We've written some blog posts about this topic, which you can find [here](https://blog.pydantic.dev/blog/category/llms/). + We've written some blog posts about this topic, which you can find [here](https://pydantic.dev/articles). In future versions of Pydantic, we expect to expand support for this feature through either Pydantic's other JSON validation functions ([`pydantic.main.BaseModel.model_validate_json`][pydantic.main.BaseModel.model_validate_json] and diff --git a/docs/extra/fluff.js b/docs/extra/fluff.js index 493856309ac..e0fb8c0646e 100644 --- a/docs/extra/fluff.js +++ b/docs/extra/fluff.js @@ -1,11 +1,66 @@ -async function set_download_count(el) { - const r = await fetch('https://errors.pydantic.dev/download-count.txt'); - if (r.status === 200) { - el.innerText = await r.text(); +// set the download count in the "why pydantic" page +(async function() { + const downloadCount = document.getElementById('download-count'); + if (downloadCount) { + const r = await fetch('https://errors.pydantic.dev/download-count.txt'); + if (r.status === 200) { + downloadCount.innerText = await r.text(); + } } -} +})(); -const download_count = document.getElementById('download-count'); -if (download_count) { - set_download_count(download_count) -} +// update the announcement banner to change the app type +(function() { + const el = document.getElementById('logfire-app-type'); + const appTypes = [ + ['/integrations/pydantic/', 'Pydantic validations.'], + ['/integrations/fastapi/', 'FastAPI app.'], + ['/integrations/openai/', 'OpenAI integration.'], + ['/integrations/asyncpg/', 'Postgres queries.'], + ['/integrations/redis/', 'task queue.'], + ['/integrations/system-metrics/', 'system metrics.'], + ['/integrations/httpx/', 'API calls.'], + ['/integrations/logging/', 'std lib logging.'], + ['/integrations/django/', 'Django app.'], + ['/integrations/anthropic/', 'Anthropic API calls.'], + ['/integrations/fastapi/', 'Flask app.'], + ['/integrations/mysql/', 'MySQL queries.'], + ['/integrations/sqlalchemy/', 'SQLAlchemy queries.'], + ['/integrations/structlog/', 'Structlog logs.'], + ['/integrations/stripe/', 'Stripe API calls.'], + ]; + const docsUrl = 'https://logfire.pydantic.dev/docs'; + let counter = 0; + + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + // avoid multiple replaceText running at the same time (e.g. when the user has left the page) + let running = false; + + const replaceText = async () => { + if (running) { + return; + } + running = true; + try { + const text = el.textContent; + for (let i = text.length; i >= 0; i--) { + el.textContent = text.slice(0, i); + await sleep(30); + } + await sleep(30); + counter++; + // change the link halfway through the animation + const [link, newText] = appTypes[counter % appTypes.length]; + el.href = docsUrl + link; + await sleep(30); + for (let i = 0; i <= newText.length; i++) { + el.textContent = newText.slice(0, i); + await sleep(30); + } + } finally { + running = false; + } + }; + setInterval(replaceText, 4000); +})(); diff --git a/docs/extra/tweaks.css b/docs/extra/tweaks.css index 6257fc77d93..34e0bc7b7c7 100644 --- a/docs/extra/tweaks.css +++ b/docs/extra/tweaks.css @@ -111,6 +111,40 @@ aside.blog img { mask-image: var(--md-admonition-icon--api); } +/* Logfire link admonition */ +:root { + --md-admonition-icon--logfire: url('data:image/svg+xml;charset=utf-8, ') +} +.md-typeset .admonition.logfire, .md-typeset details.logfire { + border-color: #e620e9; +} +.md-typeset .logfire > .admonition-title, .md-typeset .logfire > summary { + background-color: #e620e91a; +} +.md-typeset .logfire > .admonition-title::before, .md-typeset .logfire > summary::before { + background-color: #e620e9; + -webkit-mask-image: var(--md-admonition-icon--logfire); + mask-image: var(--md-admonition-icon--logfire); +} + +/* Hide the run button in logfire admonitions */ +.admonition.logfire .run-code-btn { + display: none; +} + +/* add border to screenshots in the logfire admonitions `img[src*="logfire"]` to differentiate from emojis */ +.admonition.logfire img[src*="logfire"] { + border: 1px solid #448aff; + border-radius: 0.2rem; + padding: 0.2rem; +} + +/* banner slightly larger */ +.md-banner__inner { + font-size: 0.8rem; + margin: 0.3rem auto; +} + /* Revert hue value to that of pre mkdocs-material v9.4.0 */ [data-md-color-scheme="slate"] { --md-hue: 230; diff --git a/docs/img/logfire-pydantic-integration.png b/docs/img/logfire-pydantic-integration.png new file mode 100644 index 0000000000000000000000000000000000000000..750e784a295f53eccb82a673dcf84d13da2da80e GIT binary patch literal 85272 zcmcG#Rajh2&;~dJCcxk@I6;HV0Kp|dAcMP4a3{D!fFOYk?(T%(8l2!ZcyJFvf)hLt zT(gt!|M%I8-M!e0y*W=$cb~4Rx2mh_obyhEx~d!=hzbM%0Pqy#r8NNn5ULF<#6m~) zKpcg60e~kD>dM+OH#awb|NdQEUiV^qrku%`Gi~ zfq^x(wbRox$|@>~ii-L91wz8Yo}Qi#4i1}J+Yu3wX=!Pal9JikpGL>VS6A2c^bHsp z8Ta=0ot<5Rf`aAXwzG3{wzjrONy+^D{AK0kB_*XgI=X-U{L#?R@bU49jg31wJ;lPp z>h4C$$jDe)zOAVE;_mL=(9lp=SR^7MA}23j@wN3S?>lpI^9g+}Ts%Ay5|ZMYM$BiN zP$(45HqOw{h=YT}*YCsH#&$+VmX?M`~7-)k+G`4D^d^ZVHB{ExL?kx^0U`DMLhKUOn?#^J29 z4hr9wSB592+xrJ&v+_ctla^yV%*@PY78d8<>*ZC{op1hZ8<_g_`)o1HesFDX)yjSB045*OIv&z`UM>W-Gjqc$$5H_=`O*M?{dn$BI265 zM?ZO3j(*>&8yw?t3rei-e5a+*G5bd`YoW%^p>}RPp>03LQfDmX<7I7~+vo3+GV&6Z z1s`iRPj&VFytO({OuQZ%>WuI(3+Xuz4yhbH`Qznvo_BdsSa>%1MJ%d<(KGe5BjhaO zg4jBO%IuBb$+kTH+ zh4cWxYo`PR>PL$V=m`L@MFznF0OAe)uL0c>_22W^?G^71fZv)1OKvb8C#t^uy7Pf^^k`F037+V88c*kUqA6N zv$pMfRP}+XPtRu-S2y9B3_vtMO?azI_Hv~ZfqJZa-j?j4-;zEEQj7Pa~AqYIm4UCetzxw>Gp)Q+9F#pLHMJ6BCL8`li zD}kE5SN`G@mxk3k9xLk|bfFL2wj7_C{uzx4Nw)=2Rj!=%pu`ca;s~k!AW}hmALE&Q@LFHg7MJ_y0~{HURXe@lhoifEX1#F58YuPH?jc3emNK^?_S2kS zEmc=bM=c$29P!2XWOJQ-G{*N^WYDh* z!?WF%w4J$UIRu#NMq8CYa?%@I6b{`Im&d=p+H&KrdD9a9;qKEnRd6Bp1%%O(E(Kj!Dqd&EP}CmD7HW=}k-&XB>=;4-S*#%BR0B zc5V}_ZLBqF^Bf7QWoC|#P2L$v@~|i0DM1{&?4IwbFW^;3#Xzvu|9oru{HmQFLE&-m zT@N&WyJtp*-~g8Vd^L1ysh0&BwtJI8`Z>Rx{Pzln=?qd$Jt74`7S(TF+d!ZNu^}i+ zFA06qzt=Vo4tDXD*s?Sy5nq+&NDA(fG9x9*drIvLwnh+Slqq=;l!G{7%oS|% zMn+qm951R?gx8zM-FP%cxGUWJ2Rr=^=nQV9ehGC zGs_S93(Z^GVwZ#ZXK=Mi;diOtNk;2@+t_W&$IZ(RQWh)ovipGQs%3U$Q zglCUx7|eaPO<1bDoHW2NwJ)4N39{cLg~j}7FQ&og;}a7EtPT;uSGn^D+nj;5jxs$X z9Mz7Gio*5G%`}|N1g<37LZQ6({UPC(%nw(S41I8JOA}_sgKjIcDC9MP-zB6V#4$FTRjz0O6 zHy=~ck+}bzdbO&C7+_?*bT|02@L3oc{o(prKyyK!mZ_~fyaXXS9gffKLSO9l+TVwT zRqe@ZH%$!pShb&_yrC>69~H7#ScQa`fnV=1m^_DS<#vZLIDkTA5x04{x_1}rVIknz zP=9h#fmc@#cRf8lNvtz`d}^H&Q;ZE|KYZ(%%S{+|MpH~zI2Z20%S5_3R`mHcMQe5z z^00-Z9{`o_B*O~$mqz5>U~o?xVJ$^nNd`i+Nh zPTHTPCy51sqZ=8Hon%2)-oyCsFN>&fBGXB5>&Rw-jQ3sNL-W>D_SgCJnBDa;$Hf93 z5t(@!*Z#)v`HB5W6%#37b{X-PHXwBlLcqe`sb~$_tLc()ksz}<7(5q`_>LV#R1axp zruq3qWl}D3V37&frWG7vnfqmPeRF`BfMMPQVdFbjjPR7s0%V%*@hjF)I%Oo*1UkEc z79*ubEc$2N8AMV!ffkE%X1e_HIu`0eM3CBM3JwU2=yBDc5~EBK+m6#)DrIiyR|qRV z&}2*Ob54$N`mZ^;R>E1$6CCaC3ii(EITg=33cxjIxSzDXUG`K)nAxK9EmX+knc4i}XI( z3E{Z0C{8NlI1vukr+XhhwHjPEC38Fl7%)7?RbwT6S4U!M%@*@hn7!S2BP{zN#LLg@ zdA_NqUls$f#R5XwLEegC+Uk79R`QUgq^XXTIN0y?trEaP%y}s4@-dC%F_y}phVNKv z^=MoYMD=;gqII>>)tJS@sCkjgxB2fn?-m6-Lr4N z{Nmp}97-@2SkVbZzx2;tiuDLA>Zi~m)OZ5mDrT?_LUi0pb@WA}@k(R;c$~OtHI*lX zeManH{u#i;Fap&X2~56_#4nUjV*rl))+O|S*xY^-;0A{Lfebyki{ewgWoMu@@W(G?9OvOdFSO-hS4Zc_>)KD-^MO!%jze@BuYg1yZ?z^rF8F@+<^I^*!4FJA z9Yzx)5%;eMtqJ|}jI5nUyj_fVfNi!>q{bSqYzoL=u^MNCbU9^7JkI8sD2h`I^LAxj zRJd76Sap&cF~aH^t0j#LzO9jl*+8*-*49nm8QA$>1UNAySVW>Dd|&%0;bJsIp8 zZaor=fddeQXP9Cd!~lSf4nJ{piymm-bB?XxIX=FdBzN%|?F2S4<$-yZTiElLNJ^^X z5P!|&cs3))^CZ4v(~{>M!Pzhceay9Znl%wBX5{1tDNv4xr&&6NT-ZW&gF&L_=flYQ zsApMKxbfP4qL?u!VI6!}wp$={mk)%0m=ZM~hvdzp6+B|{6;wP)#!9C!zGlWOrKi6u zN>sOhYs6-N1M(##;4BKu(O;1KuC!NFa;d&Mr$tay@)JZ0#qt?1nlu*S`>SllGB8z% zT^R4Imo5~Wk=EYye)aM9gL7!OWVbLnnkpboL{hsvpEp=MYVa&MZgBI}Em60+m^kje zr(w`ivhrEUHzNk&L#)rBZt6&+8MkG+tym2*2wio$zb9K2mnlLow+PaU-CAxV@hs z`@IQEI35p7I-0)+WBv2h#aI!g3{Xx@ja~ES(Drr-@nDML_I*R*=Lgw333M-Ym>|-i z#Z+RW(EM*fi$6ej=cBe$x1*Ce4F-TABK>ScXpG5oP6H-nt{ES&4%}h!AT^W|^Ke^D zQJG2UbqiaJqTPkEjc$a_x7^E;ZaF~Z6k7KsnoTwD{6!g`9%%nT+eqmC`{a~JKA7mL zc|KpI@*VDC1K9=K<*BQtfh@@v$Nf+XvUo~>iO zIIk=1pG=Uc3?xuwzFtIR-wl_fZ zm&tYvf45Y{8$fX@C_#T%S2ZTg0iqZY%7>U zS43aE5^an3%t$4UnBXexjjA7}G&0Pff2885S((PDG&^Yt0jbzl2cmBbXz1w5h5CXI&bk~LQnv3{Y(->2U? ziKw(YLoO=Tfi5fT&6E&sIc*0FsX49_0UwJl|5h19-$Jn%JM+LJ*ZYcELbOJ&b=OmM zkIClSec2h0pcctkSg2U{_bLl9xx!oFC z9>p47pDV_jaL8!yo-GH+@Guf|#f9v>sP=eIv)Di-Bn`v~mwaFg3)JN;p1Xlk`lvP5zu8msdwU^;0mNw% zCfQR<*5oCm-!N0G3A5b7kx!IJ?++TG!XJeXs!JBb1!V>WVOGWwS*~}j)#Yp4=56VeH^@PsUC;QuU$P|AUbMI3U;x&WHPO{?J{X#o zI$jT&elCA*1x62i6qBK$=XtFS2*lQ)LBn1TXDkOfd4Jt1*Acv7m0|od`?DN!s09MZ zvhDMi=hi6P2+h6xBl89ZT8g%1QmLY8>igNOSlxOsM&AvN5LaK0kgnGM9Ic{*bb_Dz zsD?lJqTVpN8m=KQkiqgts$l(+nMaJDDR8))DiHTr>qFI}KFkPcn}-Pq1~QU=egRda zc+=MN-0_@^PDxw>-BCaGyE+m}O?6%e7V`^$PsSa{MrN;cVAp^ySPmT$P3h`>uC1qi zoYmti25McwmjOEJE&Du`rCleZ&{hu%J7Y|=ipCED)j_?9!XC*DlY#;C(~ zK1U5>mZY3T&E#(d4N7F^j2If{e9|! zq;r_pG$v*X=9ig1wagBOY`3Q8czwli)Q_~+t`Q0g8g5E3YX0Z1F9`P?hy>RZEr8?C zS|t(7AN!46D^jsqTh^^iiMOl?>09JN%N3vDE_h(`hV?aF-_)Ukm2z7b(yQ~MC%V9v z!<|2|spoX0%9MR$)yw|VdfTUwyJM6I$3f+Y+i}0Da*#OK7G`=z0vUidYo@%Q8(OG4 z$I3(o%JU|81Xd817zsMiO)bPgclkcFyGEW&9xJ=)EI5?VG;#OXD--&9o3>dYDC8)s z^9}onDEoRBHI+xijHk3+lDC${K@>?kR-1JcLS?3>L^EShZ?-vI zmQZ0+b)?+ln#fI;_laSlESoIjkaS>6FUF}7w*QCg!87;Md^d=z-}?{uQrnt7TSFx# zMmg}p+!Efu(-n5}x8Y=+@CtvHqVcX~6U16f83$%NAEm&te z`5K1auM1prBcRC=h8gjx$_&}ej-qi1qF5i3P$F26vtIy;wdBo~a!3)%uCn4X3f-j# z1*wv2&|wBdK9eQYj@f0H!m2Q(Y%#)1zy7^#fp=;M;cB8mm+|}T~dC9+<>N@5|-*8 zvB-Rk&_K)V=^b>{)Qy*zGd>m<^U7{k0G=UOevJ%s9Rm1}dyQ>o%9o*z!IY=o zfwijC?}OoPUyIl@AI2bl*R&DrA*GQ#1Q6T>sAvh%)PrunE(hg$y|49U z?PVc1q|cY^S=CBvffB0JfvTR==STmhZonSJAmVb|1)bv+TWG_PVVQO>_LZ*1iVXzN z-gM{hQ*YG3fL`G}wjp8(X*05;(1opGS0(RIxI-?tse}4h+iV({40^;_(rFS2&-0Va0Cbec>8&u}SJgktu0JUA@4ADV*+-?r`!@WYeSZbL zk#0V+;>TR;JX|_+h6X*5b@Eo^4lPCQ zMl;=VXsai72tA$kouJTmQ8Xy8pLtFieEc+%{zTfP;ARO-YWdhACLxg|lyqbKVPly< zf-ZpTh~3-p<*Uy7VBm@U&BO{Em2pzAc}dLrIGHDyeyz2~W@&TdfSih!KQ^oXLb{U- zrP(fTp5uPVK%u}8M2W@VsaLUfq!NF3WPZ^}@uhO>0?Iabix|C*mis6OUsj zobQBCLoqab`6+31(#H-T=)ye;=yU~=7=YF!ldlHUP1Gf6Y?o_|w=_2<`do@6W1Nje zNaZObYQrN-5ogl&-8Phcd_0Lij_P4q^P8y#$_LBs{S7#-x2@A!ea6S~Y)!0*w}% zLG|;{>(>YMW-nHI$=_)cig)u~N$2^#HPYMQt`bev0A6vE8+PL^7b2R3U@mW7HP8_S z8Gc(T&GlG;v9g7y+fOt$P<$_=@8c$@Ush=;%L>zbZ5~;10S?#nkJBl6n7diYsYUuT zie#2nBWO*h70(q4f&NEkO~k82y$1|kLK+1K{Cvp6o>zzPHeqH&oTaFL65ICollD~5<h@jjXfTj-EUJQ_2& zzK7ZpOD!2G{;|1SHRMJ(NOzu**L#4)$pDqcbobRGM@A-ap}LtGBpRy@?I2ZWR_!49 zXR)!g#h0_N4I8X%sZx|<>iY`a^`C{(QY9M#C$Mu0B;3I2@u@U1A@D!F9%><8lLWnP#X&O^rg1CQs zWkV4x-q*jFct+@2Q^4mzPyr3siVcf9(shG`zWjmSnLs3EGjR1G`W#$5%;|7X>Jftv-*~#%E~L3A z;DnmG19fNf(9#lJqqyqe=DNk&&W~a+hpNI55J;~tWYUG6BjbR(!ItwmDekHc_AYdY z#|9tcyWqPZ9-JZRH@rd?#STN+7gHp8Nw-j4jF6WJEI%p?=gd|uhJZ&^a3uKcOFPG< z)T%EX@J&kdN_BrbML3MC9F9YZi^gB#HV@rR9gnsx*P*9U?~(~0KsiZA#pQi745u5a zJ9l7a!^oZ-fSGQbU<~POGO0kw-6j-)VqI3zby?hX(pmLU=YQxs?f71VjA@R4%^aF? zLdcg+kXtw2?`^{RM)o3XRrF^MWtoDB(%AE(KRNrBUvUf)ZQC2Y?bB3k_3kW3)HsG~ zT{8JpYR8-8ImnGxQn7}UkFfDA0b|}EgN+!`bcE|xkwB)Mnp{lgwekC&wr|=62(AF% ztNN?XyDEOD<1;2^LK8M>6`dDF5+z*X0e?63KcL|_xUiEq2*QqqZPFfPcM7M^c%g-w z^H8?ZXLhq=Z?VK-DeZp+Dk~TsXnShJj-of(5+w>XpaBpMtNcQKa#b~=p)h$ga_f9@a0yJn<&_k-i+NUa?<-=I_k+)Fop&~ zpgKLeZbF9y}=4 z_%$0N?bw&{x9RMuggjTnn6jk7>*;1yb)3|1vUsl97${1pbjVR$>?F)VuO%fVbzA~rT)Zd?ayAoKpk7#|-``Z~ zd4WbFD*f>mnqJs%JNP<*jU}*8M8huE-U^~b{UY`(xT7&5Z&$VwB!VNo9`K{BX_9(!7#*CHrO zUa8*MR*p5wJxI$ersrI!a;Ud#B2ryWs8=8mFVnnEcAu~ng0gMb%4nf?6a_s{c&t*^ zT75UqV|%$fRsZhDjZGP^C&*e$D{4t4d6ahZtIZig6 zSk`PMaiN!qR=g&rrDs9CS^BdO|L;fyuC7{LR2wx23I#&NwLr&ZkyRleq z0!z@)fNE+sqLc59s1Tc5Lp96#_2oU9b_v0R2~}|yGdJ`#eqDm&(1Yw9wxROReF=-= z@+D%X;;^B)K0gKIiFs$DI!he?12<6aNV_paqb^2=@m+HC2}?_(Z9WrFkG{&`8|2T~ z>Z!I-)7R5d>Vo%jKn0kfD$frEWCo%^hSJ@@!SHG|;z7D0BT22Z3vuWvQr;()A9}?= ztRoo;U`Q0O;__1g~v<#$Odl54%if80xX$26a=L&m|D z1xfcqOhI+&(VJzh&UuTpg_#e2mV78UtTLX}lL+`{A=bC=8HSIY4DZ0vg|lSloexmb z1W*0pC5o=RPrrK+wvfk5m;`E`&$jKIKZQ&{sPi#H|NQ7_5?0(VWXEiL=ktLVOG9XE zX_+Oty4D1LqAQO{o_*>gpf!n#Ydp`LFF=_08OI_SET;8K265L3mqigi$UqZe(7|UF zWJ@d$Qn~8`WqHOUH4d&DTI6wxsblE1!_U+)CD1X{aM&yWc~jGXeg8na3LlSt zSLH}6KL6Vz{x`M@jC!T|t|raNvd^%036$p*md3h$ZS(8ZFM(J7oXAC7SF^QD&3#~+z35RDzwS`nHz4-fWNt7%G5 zt&Gv8^rbV|<3n*{Pb;>sfhcy#i3bQ@zAProS{j|&6B;6A*&0}A>dUmlBIKwzeiT!- zzPjB5wlw;1`hGAWt@zlD1*5B&{`=b2a5uKSGR~ikY5S*699NqLqc10}M(`G0TgFtG z{*h+XO6XOVtEBCOVbwEGTUx{!XYez(-^jOCl1 zUwN}26!$0hYyWBgxd{%3-jL`h2c^u~6M3XW3ZiaFUd{I*k-f$z728}DsaVUeUI}3) zhUSzeN6is$)i2*reho&M6KJ}chv5{cfs{x?)TJvs`zAT7s~{ab3n=lGQ8f=`x6ZfLpHDvG zLGI_8b}|5~)eZied^LF+9%8W+Fyj2|R1q}&sk#^O+QH+-RPo0Bh~0OrJO&7R1GQp{N}xyZ8Fd*7gDEJ^%$Xp!-$Br9nEwV`w8s zmzQQdEnEUJ1Q}vo3RafIJJcb7U51N%MBxc=A*l5C=jOPMX_y=Ql?N_-b@Qajw6~Pr zBmro3*kV>2CW82%X^sRqRZOJ@gFy&mMVJrwXyNJ$1*FkVzJGZF1r1sL_&*{Pz);%W zgo5*01v~VW0NYUG4l^*r(p2T66t`EFNXUQd5IO2|Tj|-x6{%^7IO_3yZh8vTI|tHP z2meqQ`sG8kV42wh|Hm&AMw zyU4xu)?O{TPXX_eyq=BsRZFyz=6?Q1LIb#vJ6n8FdE@g z1Y7w$ePQ0YNfO%c7K-4*_NHftd%b{er$rZBuf|g}hU*M4M&J>@vEm6Iihx9IhlOM= zq))k!_ynk7fXEy9OM;$|Hmq4-L*(E7Yj;C{Wna`#`cuT>00PxfUZYR_NP)Vb)Cgxr z!D+4l0|e^+*Z=>iD==(D1Qh}R0O6MNt05U811J`trU*s#`v1RCYca?|*!P+cqkq93 z1Pj@gyuYyJvqlqh_oZ&aW3eJA_1n^q4WM>Kd1pt`T?odLq{;08))S(qD`|N^cJy4o z*=8!Gd0&zG7kXAmxbk$jSl8T*yVDvP;Q9ZM8g>68HJbm&c8vIs?fCyN!1KL!H8o1f z@k;t%ECymwHi&C|JSKr$o46HS4w*ey|J~%iQ5I{+ib@_QC3bOTtDWPUnTS-qk&&mV zd-DAKib(MfAGqius(HRFuCmZ+{|Mqy`7w~5e^_2swPb4j$t=RUxXPAoT}1}v(|k5B zt*W%sjmJ4rF7r%8N$Z;wyyC@Fc}0?o1sMsv>JB6MJ7BR7y}_0?W?g}yBA4D5_!2TRg4!@bGZYUpu!{ zYi2H)3y;=XlQ&<%900hUO_;=1$*xi<_Eir=_mKo5Yy~1FBATkMnHhgdeKlME5WoPe z*wF>8gcTJaFjSRi4i{@i)eXq(E;P%;4aK?9NfS0|%xQ;Psnp{O#f#r#e*)afXr4^+ zQ^y5XQ8q(y=45}bPtD~Qj${n;h@3L`^I2LJt|@oCp)<7$b+awlw<=|J(|M?4l|)(8 zV0W%OV?2~urHHKlD+!uEwORN$y%&ZTT3C*u|0+0tEFS+!r?x+UM9+(yoB12kJwjQd zMINLYecc8BN_6?osEeBMGx1X(-`}n@~%4QEi!-+ zXtc@`8YV6hLcgipZ=^>mM$uip>$A=IWy1|#gDLUb!n}pxtH1YmWGIRGa5{mb z+CpLMW41H3LV8>ceG)^ZRrZCzIpVjMWF^FVz0Q}sGea0ZY2jyeTRjm04o*!vCw9q! z{V!t!tg1~(itv1aLeYy)RV%XziHL|Q4=Of%S#8Db7mxgvVH3u~oSG+NH-Aj+K;juM zDk`6}dFLRke?K?q4cai_Bl~?dl|1C0+^B>^!)Mk$X`jN^zE5nePZRa<+zj1 z;B11-OJu^N|Hb*(<>y8A=9j=MdEuBV&@gaT%ww)ePB=9@Jte@rG}3%S!@1QLa_ttNSwo;?E|GzFcfJ1o zBS(YSY@;IVS^75wjyoo#SseP`78Cqf+>xjqR=#_iMh|os7L5+-ZN;a+oxv~LsTk&RJ~NU*&YjR_lY(AwK#R9ZS9cF%^$(56v(IF5TR4I(@$ z$Lsq(UK#0$d9n5>G$!)C6U^c3%qKghjHC%{eVz^oP2|QD;Y10^{~HKuWlzk{i*%y* z_2$daly*c~y44_5qck+}U6*Ye&-VfBRX{q??{T%!&SjPh-?G=&Ah31uSqt(RyA$Ae8 z&VH;y?`Pj5E^9_^wlGK}ASySpleaNvjR*o)(>z$^*`hWW=kdy&-(H44+C;womo&@? zm^E{9-QmR1_=kw+v#*-}rKZ0jYUMzJZQn*8vbq3BbCKV;@XPBm^mBd32Df0ehJYKb zS*yCKW4XDrx{cRd!;wGaQIWX&m(cL>ZaBWkyLUBZV_N~aYKj5=w*hw(Tk`6D`{*P( zsJI40()qjLZ(08Bg0yeeD)0Rnm10Wt*WP46(k&Ue!T(Z;(snG_wV05;rtrD5ZGwTX z_m81fZBaLxu(sWQtJT$h{cYv8ndfr)UJt&Nat^t-A>{8@We7iN^AwNV) zAuZQSyF8(V#fQdR zQ~|Y%9DLY3>lGt~FFtwZY?=laE2s5K<6#=6JL_eyunrvX3gQQ#V;5-5F+v_vxl&n!CG|&TH7Zihe z-juAx7ucX=2&$Ax)9!1z2+MGoqh!6((q~ka@&_Heq3WMfG3}zY{&Xc&Gdpo`eHA4n zR|_S!^GBNn7+aD)Fkdr|f{H?QSKwAKY4l+x#5Q%T&0zAPL(5OGJlOG{3IFexmppiY z6A6Dcvh@ExMtxa+3? zGI``GzIG65ApJ);*x|;E6ZL2nn7pHg%*5oTNJao&E6$Xx=^^w_o}>CRUVTCcd~R4v zLz!%K=>gg?BmQ@%2+)AG?>HPBOy_xIDg$G~?X6LACBmYm5Y9aAQDI+tkN&l^?eum~ zf0W-0kKaOGYy#eXze@1B?hwpT_ai+V<^7sN@cmGgFb+!uYU$L&04?sU1ZNn^kN4*A z{zq_6S=`+IHbmEwuZ~a&23X5$#nEZ?*#Z0Qi90Z)A^_d!@9CV*Z zlDGwaJP_TIJtsN~vEn_R4@8 z(8(ZkeY6pO{lSJRs%uxSV9RKpfd#rkV?JV?k@Tv;S?F=LATC^&S23rb;i87G_{VDCrmR$P6e|{qLuk6lE1v_oqke?V| zq05*gY|h#wo1R3@FzAa}k^fz12Sj(0QL@+Z#$8dC!Hs@m(ifTqZ?7L_`l~9>S$~Az+Qw17o$f_g zY5sX!%Q}5u==VdJ?#HYN3A?MBN8@YP!Q67RX9*e|_G%X~D-|KyZ|`&*b1S91Y~sy* z!YH1r(8g+m{iI^Nr@C$;=h3#xYU9ez1{<&_ETk<$KN|dArHpVT-cz>*I~3v!OwmRR z-g)Lp`-JfJhM}UcY=ZbBT9jCk(GZI1S4-2n+ zhv!u9UT?#0IlNtgEgY?^S2#Yo_nsFs?DAYUobw}qzwn{7mf?WwNB}eHb=jw6 zw(n31{j)Mh;mEyWwn6Rpn=cu}^<1v&HcGux(2RMh6 zl0i!IS%sdvQ8?L~<2kY$7Q3Wc`afJ4_TceANBZ(YBD>Jr0fUzehySZewjao!{ ztKXvJ^Y<`o*d@}xZN6Cw)jE~C8^Z`q=E@p&>^-gZIo)9Uy6|{BZhLoL1xLv8JMklAT={koqq??%UjI3DcH2g%TGZil0s`Iq%DpX_r6e3aRY0Y7S7D z`9vi(=Ui92d{t5^9F=~Nz4Bst{%vWEo-xg%@4~}Vf%s{3lCYLYYjt?o`>Wt}y17T+ z@J94^ZvB8-l(dX&CeC%{mbehbtf3Fj#UH;9fxFTU&wY ziO95nV8d&e4j5PJR&nH2fhl61;Z+GsAe1@RV+3&&78Z$sxuOkf-n7dtsYZzr5>|iA zPe*e}KR#SE6~@dJrc5=-@8YltAS;F4^ zRiMera0KwxQR$Yi-d}q|<0Q?W$j?7)7aXX0RsOMQJ&G$@=EzMke{CEL*V7(>XHo*z zajVKwsTGbsXa(&a3B7xc1Lb7p3XWOg`}=5g`d3^aw0zalqUE>RSI40rVqK7B>55k~ zI=uk|ZL7Joseo(p{GTxRvz;F#p&zt=FKf-^lc|HOHsBIc5`lD%8GLI2!Z;z!PEDe{zk8zb`~QrdNlf$#zs6(9 zX|(&m*{Jx{aZM?+gl_fqi961-KNYW=zo0GHAqOh)&6aGmq|$8Kp7{Ng2@9gA=^+6~ z7K_xn1DDWtiyc$y=(>Sk`$d9pxup54zccKL`z_}@VLKWBUQJ7ZcQA+!_hfw)uvQ!W zBE>V=l3@h$iw7WAQA0n{l9;nk;WB%pbZXWX7>{TSy)( zrew6YJ)(j=6D^OxTveFW4a!~i^4Wq-8F_8ehpS|+rM|s*s^i{FBZBt|=h-sSlicG>!f`(WpE0CZ@#rSTw``1XHB_>_Rz{6SwZ@jniMJ zhpK?)l*QsFr4zgH8)xVaZD!KzhSK~c7M+=yFJw#$S{qd<`K8}}-*Nj+XZASd<-X}V zPfxE`2u%+VQ$HBM*Beg>sATGzq96WHz!_yK$cV_iY$h#WxKq*pEO}GUx%8Usj~C{M zOFxyMkGeD0T!TK}JOdu|rSdpNN!)&^atdz($Md&P2>U?^^<7Kl*AbW1Jo>#7Ix!(V z()+%2bq(vQ22XLG&EIhKIOaC$!(I$)V6`3vOHn6O*V;G!R7ekBs%?$YxEd6xWQu{t z6w76eUS+R)7mn|%f~5+vymc_xEVgK>qY6*wdSZG(^K9q+{_jS-&o6J6)53RH;+`z- zkcMBsKS<}b$sJbp?-BdZQz#q<_+UcxBvGau;ngC1!v5Vi)%~z&*Wxzjsm3L>BR1Kk z1tCnucR6w@{(+@Z?VGtjXQ8?ncy45|@jriNu3Pzct||RN1#O|S%k96Qg^+xcbo(!8 zOUCf%YfTPB!6edhvT*p)m_<2NFV45|aolBXV8M#9*YonVU%8?9M4tKK^#KWwvBJPi>g%v}LT-e)m%eZ^EB%t-X}U>Z5Yg1~&U{L@^Q2UZ9=rNeJgp!7SOMNQ zl$@FLdP8oWuEmxqx84(GWn z?*Y%|BmeKF4i68P18Ko`dFk{b9$%%>J7fCKJIP~8TPw;e@v^AxLluUbIMpTXA=a z6I_bB1ZWEtEVyeaP^`EY*CN5)i%W3V7Ft?LPx_wwy=UAXcib_~pFNVje6A^b&o$?> z>E{9Y{yV*$b-8;MahG(21*f7bWC%~Ig_E0r2a4k}Z$6d2bI~!AMj0LosZO9i?uU#9 zAF0y_5%=u5AF&Dyh72&Dj5NFi#;?hl|B+syuQgRwc}4-{DXzh2CG}UwN|9clAHep+ zO7;{1^_O+KB5Jj*N}Hpn1zkp&C*rd*)>0w5)SS$7tuEsS9k`Y{;85 zZ&(&~n+}L&?A~0gs@rt1;AUh8|GQNiBy7wA3gC?kqx+}jXuH=ibVYb~<=Gt`wwE=w4oVig!?62O^I|LSx^IApbd>3HQ2r?I z-wyKo2-+HV2T_1>;wswY-E{&+Ct~|=UrX4a?Eq|k$`IMofr`nyb+`d57 z0vqC#Pv(*@UO5Jed^`pj`}=={Dm@c7uClQsaP3*DE-^c*kSg2dhmh0DRm zSelx4hkd5Z#HW7STls>MJu1Or1_)N;EWqR4QLR99gjQ^x+Z)2gl4ERBne{OQs3ISG z9QDRV&*?WzLwAI(+xjjO_8N)q85n_wC530}OXKJ|SLi znnz`|PwvFPQFL-XkSTC(7e{@V3ztglZeqO3xkk$OoU2eZ2j4bLKGg8l!N>Q@_aVj4 zCKoAfd780v>_;JrgG8A9p6LSXLZHQ<0$GSmj0T1@WyPUwWzWW=U+Py-CZP{+ zLXq?xx9>u&9j%bm{+92y9^@#3sO?b8xqWF}@-I$Gw;$XBxtwdsi93Fi5k>!e@n~oq zCQiPm`S|&=&ckAzbwO3ulg?%)jS9=}j3uBq=Sw8#QgcwTSKxO6y!5}{d5)$*3`#Wb z*b_$Pl~ll|z`m>k=PgvO>y=_BVySSQ&;_y52zvNn2vvM5aK^p~HdL^|*8c3`7O(khmk!OjP0Geh#DyJ55J=1bZ!Hh@#S>eD zN%wp;e|@}gyZM*W9>gG1lJDEIRq|+(4Fxy2!SFNtaAq|cV7@Rn|HfUu1T|v^lE9(& zgrY1S5s4ZO!J8TbXG>8c`gA{{tx8ZB=$l|TFGl3Y3a3^xvm*m|!hA%~T{Ps7{=dUn z_IV7*MBoAqb-0XC)h&7jIqG=l7Cl`>6~v>O+lt#36aYk(sL*5;&s9pnaY)tnT+22$vvr z%odV!7ZjPci!*pq=R@fyx7LzRNzVg`J_^4WMNx}W$bknZvPSW^OV#qZU8Bb`vQV@1o)RQI}b0d2%DJFU(Yn+(q8+x;pi zvj47G}1iwVMpamdf=nOR-;~ z6#pHij(<8v`^&7a(2h^JCidcdMlTEiXhBO2sd$RUS{gXs**c?e-{A!SbgL}gH?@(x zZ+LJ4x-~L`=ji(@tzXfO!tmpkI*$z<%IZ%IRNDN0b? zQGb%i0hNAtm-ogHF^^w>bBpfGQG?ppKs5l&_OP%c3z13`rt;mu2DFdMhO3**%<^0v z$!p8thu7-c;hk_es$Ux$@F<_<_hCA^6*c*Em_PxS26CAbSN8++{s2WbOD_3z)In7F z6Ci6gHAb06f{7+_gB_QLrjYL$5=+i7-2fgVjzO4dmx2Hgnrb8C^+;9(2US3igzd$F zzu~W_v+855(EoPrK)-4fS6Paf~ zRU#i=nYIn6CWyQd{}GxvS63es@X97Wmsn3mo{Lvm;l5-;)kdXi=3y2Yz zg!sT#2HXn#F;t!UV8HuI%;yW@A8KwO|R}JlN{Q=iER~@>#i@M z`c2lQz60$uf;N%z#ERI8%QmMjxVg6k*lz(0_Qb{0B=7aXiEb8VR(2d%mR1bG}w2$|VN~gH;WtS6d;6|BmossR*7o^(pguyOh$8f=m8|x3n5~zDLlo zx#ZiU9sgSY&|~1GYwW-qIQh4!uTN(&1idP*J{-lWM-JXSJo%{e zJPHkk)YDF3oK$auz)(+7IQIQkj2anmc^O2NGFE=Eu_{~X0DAMg=$zEY&`V-n2Vfn~ zm(A1}?Z?b=9LGl+$+oTMvHG&%!DQu`BhmIrQE7nf$-=hud5N`(@w*5~Tp`h7RIE1N zSM`>lQ1{E9uMUkO{*bTOxVo)uDl6fe^Qm(jU$9b@IeA&^FIxOgKj!GA|NfrTKEVWn z42G=`h?s(`&dUn}jgoy$SNNoINgdoiAWlLR$M4%cYID}UB`vE0bQgZCJHdooen&Zn z_Z5i_tSQe=d6gH5oeFf%Hs|bb8WM(o$W8a6JCgp^968RPn38)NUHx&_#?L@^W&)3P z9|&%su*gaHj4MF#dr5Ip9oO50{I=O?I0)9vdVs0l^%wv8*4 zX`b9Y25=-rwZ8l4<}`lnKFe)W|4*Fp&#NB-iF1SsizScP9BZ=s{+^4L_=dpXyR9H zo7>vP(Kn1JKWBxhWD&oK<}N~IM2YBtMvK?xZui_IN1N~z1&?zmvhFy=9aYkla@3H? z3G(eM(I}{)Fkg%e_p8jJ{1Kb%_&WJIdo~{l$#=V9AqkI*tSt?6w}Wf&*(&64ErYo6 zXDokEjFUO&;6LkERiawy%Au&b>O`D*jC;%l;A0Bx=Px2D>>t7@?)x^{&KkYLFn)H+ z{rYP*OnJ@1{8pTXADf0Q)PrbnFqr>|HPNSxPzst6MASYu^MI$X%@r*=x`hsQjQPD_ zN*`iKCl^EV;fDSTfuy53$V2huf@Rsp$sP1BfM96Q$TJlW%3ihbK4`nFHR7{~0Dz z+3f9R5u&-F?U8{&rfeZ1djs@dv(}EJ`r#Y&a4mnR66H1ey;ivIxraf2_j)PfIrFyO zqWbD2GmE2no;DCij%3XCfV?7K$W+)9N3ydd6!g$pCb$4Y{;dWf`fQPG=LpyB=f+6< z;Ah@9NBb!rSa-CjS}R@OeR{DKa9Kr6ExYez@$4?9MMe_EFcG|M>@Y@<2Yb-e6f(Cx`5Z!0Jc}DVTROR^8 zpMH*7oe!7;oG138CVkb*W+VHLYC_W+r>_sOPD7!v3zUA9jMUVXb=VZ#+Y1y_(3a=XD~qj*iJp zAacWBcdaWKy2v@Lin`47-?cF)^mrB?0?T!X)J``P7un;oiLuba907xlBL2%_0Hg3(pu)pk&ey{{ttpm*BL1 zer``%)`N}N2fDlhL_yZZHC@uru|TV%UrWFHPoKKscXSekh;9^S26^(s zGKgP@ed`+Gw>_xm`?0g_Birh| z`ytYM__%Szr-R|8Y5BoTFtJ>0;|68t)K~vXy*If~PM!2zkh2@`v8ArnJ0i)bphwoB zZ&G4UxPnhk@LwqreCcTGs$$~{M|J+b?JFJg0>}s(A>9Bn*#3R*FH6!der5f+;{>m< zNkmB6_lBKt@Lg}KP|YA?f?kBDzYc99M!={DFcSW@@SIV{#w0thg1{^2X%+S_n_JAI&;$IUcDF`nle`M5J3EuQG z;W;~%+oP&FP`joqo2D-sdCC#t!$B7!AaMdXEMVMYwijxav;cm!7JGVl1ugSpTo5JQ zEJO%YC1U`Y-;fXE`S>2H+2+_3=Gqa8n_9JshUX4TLzfT?fWFvHCb2$Gz#$P!$?YX4 zQ&axSM^}F`srE*Dt*2i`jXm$A+8oyN0?(Y>lwb({1oI0rD0y{-Z9KIEvPHJ?M2^-9 zQx3xOiVer{`pM?6AK;B+t!aunt;_Ye|Xdf7)TRB$U91|6i zexy(D+)REe?bh0!S*{=EIJt>q-p)VG%ajw#)(Nj$!2}&iSLG(MPSy`~cyH38li~|` zxvHg~h~b(QH8nd2c*eHS79W%$3}77jXH2i)@^_0)-WlTqk^q0d0gk}0O$cLaxZ*uF ziq$~g<;^Chg|M#td73dxA9(6P-k;e*N(G1$WwktspFg1ufEP!ZwSj7Cp_Eax7QpEB zY_98|9bec8AXrlE0B~tWak|9%x!s8WnK|?A$V!n|u-=nwtYDM6a)efvSW*eu{9#Ex zM*5;h4EpT6K)MC0<`nTWAZO7~*OKe7m=yI$1e7H)5{pxA9XC9&QLu~c83uHyMpc&O z5u51MDH_N}HX+=E-EdjIH{=Kn5CWDsJtvpX;b2(k*8G1g*MgbcV{`1;U z%=dEYFB1k3mam;cO$slbs#4Q@o5kP{N(9|*iSe=GAl|_70qo>r*%&68Zx#mPfl}XS zER=t>fgDpCWB@x3)Ku^BY@Y!S^u^Z3hJs#n|?}{Y96yZw=1K z1L1HDN#kiG@QyKWqzyr3tT5W$)>VjjK8Dfh7VLKZ%J^otbP(##N_PLiv7bnQDI=`{ zEL5WTy^W;j*N#$Y1*zDNP8rrhr{XRFJ*1r&Dw2;e|WW5i3Z2dE?|?^7&=?#KF&>WE;XG_V4a5<#B~yk_Aa?0u_}!0D5LA zwk9669#|tkNWhwUwA4fQjOmIJd*AvZS00z3O+q&9cYpAGI3E~xcXR&kYHGn-Fh5yo zGH_P5=v`iI<0?6wBUP0+SJbjhur3Da;)!^mg|J{l6!mUrVpRECSncD**0*P4k{0=s zaILaKW*;7BEm*i+JT4FJf}u0Urm#K|Y{~!1J zT>s9+m*CG@2YiGU@Q%yyR0Owi))oI0dU%`9SVZVOa>Vss(jNZmybLAG&>nn+ol0oP ze|m&5$dqmthT+rI->3!LTkThtJ`rmM#o;n&U0ab5M~vjNv`q8mx#oH_xkAg70{}x> zub1qsVjKVM+sVIuJB8`ya`Wg=!nCHBf#T2dCP?R+{xCBJ+6ELcuzySDA=|x-#TQvN z0rIs9S@pIQq|g!dL=Kgq`1N|azWLEov(HEvBN@_1NzQ!0*?>VTht~^*kU_y2lJPpk zFQRSvg4emx&WV1nMESKpLYIo7$uZNg_V|)0Rt3NCs;Ake zf=z!_kD9|YoK_VcsdC>2+I`@Bp$as|e5he7XvQhakL8@?`k3#XyC;%Yyfkyp4~8MO zrt?ency@dFOuF4;8oNAmq5Jn|o>yk|qEom$FZhXa&@ndnfz!LrUorP zBxN$Gl{k++usTs${CfA$LOMBs6a1M63{(Bvl`hofs&9;>-Q+*(KoH;0ZXXLksX4MR z-pib*W%76KLk}IJbK60b++cfC9tmbS3YUC@fqmVGf#Wy+Om?k@42E7JTwM?a_|&9~ zB-yD4))F5KaGB8q+N7%hjLHm*Ie4{+4UrxNM8P0aZYWgjS1vWN0aCfX3=mwYzZU$} zcA$9O23m$)=p8D955`)Q1E0_@c5A1tQxfTeY))=(42bxeLw)lO{qd<`P-ipHwn z_hHgYjs*0!Eq}MVqV-W|GH3Pw5WR1~tncfk<%qhSwjt6}GsGTfPFsP}T{nh@5Fcar z&!3`&f(OZEgY$NLA;w}sicZSOP?>dB8Gvl@7|Qr?LRPL7 zBT;G1r)QH?M;<+1M{h|8gMS!)KGi=lhZDC?w9T%SO;bpkQOauw#8CJ%<4_Rr%?L<( z-2K^i656Wxjla?K5gi7zHn9;>^qtnFpjM*VxH&lc(?r7#vTLaiHmxPhl5FmvZ4;T2 z{~v<)eUjIakguxDmyxfsd!aJaI~+asd5#vI@uazqp2ITys*?$g(SjBCMOQwDrpn+L zCxPbsBfhtz#kSZ_X4*ltGC_-Sjll}QP$u}66(%??=6VvEF(@@h^9};pL0AGOtU;|| zO8&(=H&s#9?`#;>MhQ0EJV)@Ii{Dc^Lrn%flia_uaij$b2+?b;86tC5TL|rnl0ioD zvx8?fThBa2FW6)73rgbq@`%3l@92PUD)7Fq?Pye-Zv0T6nd^ zn6xfUwMc^6x4w1y#75Q|UFPV@h%@+Ow2X`mLWe~z$+{?woTbp}$+Y5oFq5(>c0;Fq zz{A13I`FkCHFp(adP^S$PoB4l60S@W_}?#LujR>WLMM!rfovfxIZ!v?L2#WK8uJ3F zTfF^7URDm1il8Udu(qX_Ds1=AA(XX5n&MytvjpKnwoxY^l#d`OBH(;6=k_yY_WuyR z(?iKvUIcS($dzft9bVieOD)!5Tj9?cNolS@n6KGO<-8w)DDV<$g0gT)4p+y%JsHZk)~Co{T*l=iEz7VU8|7+Mmt!d0wC37VG0a0NMO`e&2^CvD!WT9S206%aV4}Ibg>8FibYhyB--7kCr{OfkEKZJ+2X~W8{d`mff^_s44W^{i z?2}j&lFZpm`2nL*QuE)lx5(iUacVO3@LfjSB;Z>{(oY$-6WWkNOuJb1u@7g@PKfp7 zt7ihn=_$SABgwllM~E%(*LF;9huF1(FZ1+}B5{A~K$IE5-Pk`z*i@ES@iDPUZFQyKrj62zN8>N8!3#cvX~4}r_1j6O zml@+1r#b7|bgC4=Q^4a)Gu$w#WbTHaAKcG|%iZ)>Ngftd*tO&EtQ9aTfGI#u`4|%Y zZ+*(Ui*loen@nWH)3Gwoij?pgSt{Nv=d9Od`Y*VVrC4X(d0tleQBEym$xNPR-VqW# zNZ6W3Tz=VnzQ4%o=u45C`Ih|e*y2a*VA)M`9SRv=Gvg$;y*W(Yn5mv08}Ejs{=9=y zbCC&J=FfL)4ZooWMBtfzERrzCc?OqTIPi;}FU=q3)U=lBjLuftX?L4qUMfc!Jhc z8B%#XpAQC{fCmH$gB(iWU0H?i$HNj^T)uH+V+_T98R#5n_?D2D6h~$L??K;@ISLd% z%ke!`YK}uz3s)2)lWlMl&T6Ig>6=L{VA~5Nbc-ggc@mNYH>z`h6dmwIH>B;P7YTT0F0og>L~O|K zNzLE~?q|Bpl~OidY|=HDsWHKE0MEyTEu4xjm;yhICd_8Z(>1ZPi63VrbbiS+i-}E6 zA7t6|72mK;YG*r#&_|&TzkHm0Vs9Ae>kXfvPiLdc& z?_`d9aH%i-`dBdaO&O#~r1E6s*VG9VnH+Ve41QE<5564F@A% zfwixlCq&gQ_`wv2t75~;$9~eLz>g(AYvMsfh3f2@@^oto{qG|{$HX8-^+tTP+YC}y zK@>;5g=UTZ*bdEW}c!L8!c|l zU9}9Qc7d%r(q#%tq@MZ0koD5E-3bt0-EK{k?%YXA0u=Z4c<}V+91#15Kk@Rl*}vGk z`FZR{n2)(KiktD+XY10{p4lazw^4X8Yf#l7Y8+VWK?wXBcFw2mazL$w&A+}+nm@+d zJ;YEY(%0Q)iaR>xsRG4U&QTi_xYHqG*@P=XGzGTL?*BH`0w{B9{Y|(|gB3pge6kH8 zG`qU{!vL4xvGh!F_(>L4%Gp>iL_%&B@P$pmm%{#CxC}|sMsnztgJDufUIwL{jX-g4Y_U}_PGgVKL&A>5 zW-1VH!cMwN`izt0F&_nR4n`urz=l2m{5Lo&|#iafoTG_I8e z)`RMa4Z%J|8)tP?*pmk8z&}y#s2hDR8?1UzFUa=pGTc*hrKt|nW$k&JVfFjF2E=#f z?H3U%tJk+q7yGd$lj2@dqArJHi=$DdE7`cR@i*FpH_IQ1ptzq{Jokpi1C4u>fp1S* zMd(^H2eS5N>ay0AkWZy^ z`qs>o&sX@=s)lIsN+Nr=`Yr|Jvo3iznM|%njtr6JGb=Jyn1;!3PW_28 zNxZb5qh(1i4Dc|V?v81CE>U5`EuU2d9DtU%mZx}KMnuz=4dWOmzR_bu-Ldi4W*>~ng+J&^6|O&h-mvsDcs z7!+=snxw&qUUmhj>(~3zhbJ5NP;G`#Rx>z1$pnTXNoOwXbG}s77Q)LoG{L{Q{|oJ7 z!drxzGZVI_GY{jZ7LpP;yVNIC*-81fGEPylnq=e13x*Jm>?}--yr5AWmd^N#4wTMb z52&DEZ!4%zKvD0=w)M**ctd`EXo^%pH+qM`w-vH&nY7so=4BwTBE^J z!(IJ9tsTI-zWs4692ZuZv_H>xtJed+S^A+pLT@nPrfuN^cd*&(=>7N8pK z3u9*gm^ySy|APe3K#0CVSXdqO$Xy*NZe@BAzW8z5-+`#7hC&-@vBxdu0Ku*00S@^O z-F@Gqzo5pf<#I5n^|;>C&3Wc&hAx8u6R((u0H+#l6c$d%ns!V0aU24WSd2R3_8;TK z{hSX6HEW$mlotf`GtN!Z05O_Nt*PHLeW^`>v^HCx|9Yq<3)pmE!@bl5Md;!6oJNYD`* z-ARZ_SmwW+LXDz)R!r+nG-%4{vOF3Afgp_kZ;g{}9)2~53|-OwKLH;97grP?5F-Qq zogN5o5;|Zcl$r@0Vxv#OgpL&XzrCWCBnxv@DmWAJu(H_6^%YMaLV42CDOXd1Ysoz7 z^5vlQM$gnp<~ep0&&cq$X836WmNg}w+eY@!Q*Wl<1Dg===Zq8IZ3V^W42e!}M6?onuDLn37#) zyB?HgKAcL{gq#mNJfv|IRTDlr{aQQ125e6qVVLwZa2Xv@a~T(3U@b4dii|dD_fX=HAm_k7!>uC!3N_;$b;L20$UxoCFOQxWI#-56 zIfmev&jKjI5pRFuG|X$ZnfDDZ zqa2d(5Qa%dq9Xa}Zb}gtLB-n!3fa!ijO;Cy3S-D5DcEQ`(&lEZ=2hNQ%ak`-m5Da83T6W+xV#{Kx!p96NlUV>v2E*a?pQB`qK{)>TTjzrUh`C> z;$wL*pc;XHa}ysoXLnWQ1qDO>)*r-K4T=Xl_o`78<2}=HCG zDpe!L%c@n`gCa9kA;t^@4fPu%`5pF5RJDQp7UI5(X(t@L>#SzLLPPE*Lw>W!wlCn! zm&xqmEe4e+(yFuMcPq{BLG05e!)zCj$K}WhUR9P4QvzI-J+5pA@s?pC4YvJRW#R@q zJ2Yx-%W}4{*WOEdc_1^#W$LLIe)QI0x;2nI&Xw2t&S7bfzcY5LOqbR7OJ+mHd% zOAtx3L|pYihrbh7BN|!oyv#&sf-gf9^VWzBY$b!j_k z3B~+W5n-q5fIZb7;G3m8w7|X}s?U$9_!;UPrXac}ja)59Y+g4r?L_|XopO2PsZ@y~ zdZk`pGcdSX4y*msq-MF%Kc$^!Y(3iOa-5IoT2ekOx3gk%?qG@H5z>*~}!xQsSMtpDvUK2Jl+=mw*=LeRZeU*gp~$ zsYP!&zGc(a3d?jD2T5v|5#+vndr(#OvP?G!TTgz^17>%Gd>i%g73qU1TPaFlJp~ z!>^7#Iq>+{2{t=AV(D2{28R}{^LTN;=D&G%I9JF-armyJ4{P7h*=8^u%RB=T+gFCv z`wW{s^9zAwvWFN*ltz)7DZd+ECrR{Ia6t3DEpYhPddu-IiBhQ3%yBUWBd2ejMpU~ zPqctwP7FNme?b`BM8oVAO#3CwSMbeNR?n~0+8dU)2FNFqb%)vOMtcU-#cYfDG`oiA zJ?->$)MfFAsC=LpM@?6P7&gnkZLO9eHX{Mn|Lz)U45!jn?5B}q4m zdS9>#p&2v-Rg>gz&GH8llP|xP;MHsn)_&Qa`3TY{C%qpZDC zBvg!yD=9zB-46_l{sUE8Fa?K)2RzPiAIW)L6 z#fWYe`aoj8O@i)Q!@~+OzmC_1MuSr~f7UPirrA78K}28Awb5qvGd)^VIlys~+2yKG z8_W|)^P+!}TC@uZ-i}GH&%pKmVN~7#N|?9I*jPgGMIE>UnoQdfpyU2PF#RfzL@?w+ z1iu7xi39sJ=66)`TlU~13U#9j!07-zaFI{E9`M6ilSeC#N9JWo9hHN z<|DL~Gy!7n$2saeHhGqcgp{m9Y_Sn;<<>&?$?%@;4dS3bzJ~+X{PaYB^F4zenC|Dk z6}5Uphc7^ae{s-{JO}K8flZV5qkCO{)Y97dJuO6Dab*@VBt`$(O#xq96C6lmSX~2m zY#-lw-XwxS7_rd_za{<%)TeJ;O}Pv^^56d;zz%mSN1Zq|-+oiw8u~;P3l5aNhpj(Q zKm2ObN0Wx2lBtiM;M+Cqa;kQt=IyCWn#RxRH^1XKf<|hBvIjr!|G1DQ%A8z|hR?Zp zFh9*^blCOslKRtHF8VvrCD3ORMmRyW#h4rMH>o8kvirlL?5WPVS$BD=ODWBpIGxyFey=8JBl9-8gCsime48WA zyS^#AI9tNlIAh8$s(31CH#^_{<&oxo_3L4^sqhI}2x*MuBkmG&elNSQ#B7UoiM8GF z(9Oxsk_}utX^u0^n(0-(dW4~eU^k2R*eP%^tHiRiXmx%R{uuHZRggiF6;b>OM}T=9 z#d?Vwfag2;qD0PP+EDbMp*E+@x0JiPZIPyUUQWMu%X&52dj;E_Y01aW->(3WpoMGh z$;-0A?}$eqG4V9R(6>Xo#I&uUrP=#UltbUEx+$ku%n@{J3Akt9fR{u+kxViRw&w zIFt^D5rl)?+QvzLA6ZB3UuBz6Ylm@0KxC*9CTaKPQK1esZlH=TVc>gx4MNAz)<>s} z?Q9vUoNmYJPbBO{@=D(lHYC36Lwt!|Q^;)aq~RKxIBo8z$dKP|omq)|>H1DQO^cPt zR9^E^$v4cpW~M^ir#3;(=JVaE!o0Za*T|5PM)n0Ax;A0x-y}rHxDrGVPqP)m$tI>Q zt(c5O$)T|`#~HYPzSFk&*jhg33?mOpnWv(`>abQ6J4o89TxsWWDY5%iid~hJ)kRYS z)cYpTjaf6V?#Wmfah;kK$SU*(&isxeMEs$SV1H19T>akcD+TP2GYDFoMmtU35AInO ze_CUCIy%CJ_vNXA3cc-+C7VUE#WM}|2>ZQrR;bjp=e0a;cl-se`h!!(1rFTPX#H$A z=3b3V{RdUufU%eue@Su#x8OPRB_D)(zfgJHPoF;piBpjK>0rsicgmA2ob*WnpPKrb zhXNW2KUu~)oR_w@kzz~g`Qtv0ZI(tj8c)op~r&isr{1axN zds7H0OVf7+KsbHm0cY7Ct+mH$AJ5xIin=LL>ftGwZl|*Kw);778DGfw=W!H+~o*jbvAi4BsKdKVelRO#W}WHlFc=WIQu6TZz2jZSZ=8opp2Hx zZxU&olFN3Bq7C5DOHOG=1yoDidP6UBr}SHSAgyxSm>XjcaO;U@NInyG@0U=8Mnv6Y zo^&;&?AHthOamg4L_yJJiKFd=(*iA6ddfcZo;{0mG~IK+5=RPkAvxFwGzE7!(`{lW zye8W$Us3OOyuB=I-yBR9DCMLZv>D-U^f&ZwgSPVUX(82ZSvl1}fw5b_I()?+^iu4} zgN6mnQmmjF-A}C=FR_UMW&&AcV#f5svxz z9mC)|CTj|!ybGdc_Ysi2{GtBW!SDUzd^H;cXSBC0(Ba3jFSx6|B>z~|Ko?RxpRcdG z+oOIaesQ@FhYHCqM@4ln)G-5F9NwKQ-IO%N9RTF}n(I8%TTf|e+}#;Za~8IQ zcN<%1m)6Z*B~|+gKS%;Mu>}@W5&HgaPi(W@-R`iY`>Eq?qtefY{uoRP{esURq!qr? zE)XtOU-?4B5I-nXBJL@&2LrCdF}zm7{yQ0f{^b)2S6XA5$PKeRj(F)g^l7dq4qcAu zICb0gJtm9v7oJ@MiW9k14wXG@SyIVv+TpcHZdSK9Ne5ILWFP;!fp}+~Vs&l^JWxV) zhB#m5eiLrNf}_Z0-Y&3PW+4OxBaM}W)KU-7J}cn%3&Om6iZsbr#R7$&GmJj`Z^nr- zL~M~R2LWWQZd6uT_Y(&naXZZhA=<)U2}cZlaZT7N^W*XXd_Zo{>d z{(~krJ7BsfJ{zk6mw|jLwc*f~|3ZjKeBDv?FO@?65V&Rv(c0CND z*AEdk9iI0hHln*JQk&-_um12seo}-0S$>5DLevy-$G;P3iPhIhd2TW1S;*;b9Q$C( z$30I+K)Bn9$x3%cw=MknVyPwb5Ia8|gV^|h4rbg6jjxQYbbQB}yDtT8DRhMY%h}0@ zcn@nk|KEk-Cb5Y@m`Giclz@{Z(Ok+e5^hV{g}x3_jaWaj+!^+C2M1Ng{I!vb(GK~8 zPBFt51UM@g^RIu^+Tf|Hrz)=2K3=L*0F-6%URxwG=VfV``28+H9UJ#+97Gn~kmLmb z$5Z`*N>OF@%yJ)!QEB0*k4K4|+E+7lOiw!=5$K5mJI!UJKO&03OJm*KY0t0QTIAk5 zbzwH0{jbIyw_|M*1Eg^gvJweBC8=tY{307a)!R%eZZAPz_%!=AgmhU8-(RXrlE;4K@5L?F0EpT|_N!+po6!-Z^`kO2QJb_mHS2CV4XR8x&3g>ILE*-u@U zoa?E#S8Y{2Bp(4R*STc(@Rp6B25*EuepCgOR$+jc2btS^4}Yw0t; z_};$a^TTk*CF}49*rIXY-~AO_88m+Jj%C#-^}7<1v9IBn?i$+i=DT5mf7Wkx*n0_W z>6Ji_SgUDe%^G}ymCGSZ$Fa059N&MhHk*s5sF>prBq4e@KfM3(P!e;)V(_@~V~Y;# z15qu92vE}FD~b50{exyERehI(U<`c{>Q7M{zgwxR|7val-1H6LTA*ChO1FPq$aU>8 zVW|$GW--tiX4aod*!u+~Y}^X&xac`uHPa37t%qIogF)O0i58CNcs!Tu3+5bu2N^2H8?UaCc2_ z*~F@cU&~Xc<{|>{&BXaQ8)h5Rm~NSYA>7IecBg{ky36Jcu7~VLCk!beK$o+7P`vTIWeGUka4B&IXc1|~pl3Lch3m`h5R8c0* z@^V}<4fb9mMK~(po{A9C(ntf}n{)64Z1DdKDr?dvLBP@Q**#)0y(O8o$}>3mgkGTS zzY;y~6hXaT{8{_UqWv0`7g?}!RSABfiHA-t+}A7i(uc2lL#E$zds_IJ)ox=>1JT)X zNeKSP^f0Cmg(`GHqD4)321#Ki*BH)^&{=kB@)?R=O6gxTU#VpZ0~<`3u><}k@Ii2W z58F5=d4aM1ySRs#|4+r{+Q{Pkw))r2RJ;bN?k!zkyl(WT-?W%dUF_{TdE}&~th`a< zecDWk^*?{3kVX)1c21&?x_xkY^M8$zM?<>WtzFybd;Z0O5ZS*hNg^%*@Vk zkkRrouIUy2hSAv~>Q7qG%Y(*LMCq3tsFTK;d)|dlio~L(Pv7RFLrinc(5JOyw@m2# z%cBzOG;#LCHugxvgYvJN?i@fxM&sS#SN4CZueA^GiP71CS-qhLa$Nqm8;0oI!Z|IZ zVW7gOV&Fl$`EIDhk@$A}t!kvAYk)rxg4<5Z0o2*W(mL})VN(Nw3}bwtzxr{k{xb}U zLG}q7@jr-e1(s!eHlhLx=OsEj(Xmor5#I{!_vc?< zRatmlNEJk-q%L#3s>$B;zli$kur}IeZ!Ab~3GPlP?i35|?rsH&L(xJh1b26LcP}ml zid%~ncPs9c=gafH=bUe^EB_?B*}d=a*_q!gn3sZ4+(VaBnj<=#ka+prfmL`I7=Z*K z1`dS)4w!r@d-G(XUL0$n%YiP2^TubA3UOR_90!K_% zSOeswDckHdgbK!79#kUfR!rb{uiV{psbnkqeI4Mh_gaDbHcWuxSc+sNpG)#1hZX`@ z!)6~MA`QUe9-KjV3j@xMaaM>3Vv+BAoK(|hzX~nA&-NnxIYkc!(7DfLyk<}8B=L7n zTbwWrCt*wA7ng1Qm{ow?_je%a2HO#g;>1!i%ZOUDW``s0y+2edjQotvynSE*uq(a( zEyv^suwcSgtTdpl;t|e+%O2R40KKdSHA+FE!zBoFC;PnDQLafa=X8$qawl(ke{kT~d^?CH3^ zX-Mdz#2hAAu^L*OoCKOU&XUwyhywr0+#ghkR>Gdh9sW)nwgj0xWENX<7A6Ipghu+j zG+)oUe=y!PHdIpE?SEApNWJakiu_*TN5D2L^{0_!hIE%DJMscloSzy1%TvtDEPYr0 zymA?J5Od0!wQnIVQ})%Ex7%yp=HkN=A7@n7?ZeY52&^av(d{CGxIe0{+Olh(JWp>xrCrVP;qDdREZc&tvmRSP-y; zg$0964=Su!U{Z^j0HP<}kEXZpKiOR7xhYs%Y*)I4?uXW)gyFqhgNa`gr|$;$uh#D! z%KSR<6vgea&AtkVAos-FnM2;p74JU2C{dmJ4Kn-aQ9>c!W+TDaBKdc3UYs45PCq@5 zvHfftvPW=7t}5tm(?1&+ET&=rf(N3b8X5-zjrupBXLxjH0jI*2Rci_d2u?L1@mkAktJt@smcZ4z-Ge?v7G|BO zJd3t?NWv9BJz=B*7pj)IrA3)GY}O?J&r_r0N^S_?%SzuJV(lup7>W5hj3nM&HrFj< zECcw;%hmrpca`)Shu25k@D5$Hm#i2%KyA47DOPx%-Z7VX>vya{go<$qG`Zmqy;6T~Z4RdX z)jBUu5A)>hCKNBKIRYia{7m9oS455OYQMAI^sj|ItVxo!KNcHDCkR*iEkJu-jvq{W zP(3+5$X_YCtoE2{q6n+UUv3P&L%|z;GeM(Rqs?;ObXDk0@Kl9zjdJS4uQ#FW8Bs07 zM$#TRz&gu+EJ5!EOQqSVCr?v^=$Yeaxdt-&!U!s(@wxtl7*#}vvmY}?oYc9Brz^1e z1r>j-g}ND?GFq_n3=n$ZUq-0%XAX+ZFTB>lbCA)Xx}s;>6%R@YoQ(EN7?M|_Yd6F! z!2P3%baQV!+#$}MvD{0IQ9McT>xv6MhDx3onQBlO-YzQ9 zO?z3bS#?$F1Kfkkh2Vn&I3`CVO^I<+*^bfu;oHyZ7jaeQGGo?QzG4gjBg<5g>5S^f2hsSKWFs&i6I`qV`S5^P&-F)Wv1AJn!{D-3LSD)P(XjIp{(Cf+!nw zNX8A>570n+Pew+PK}JS~79vo$27_aaC!%`J?El%)ged3Wn?Lf&k6o4*U)jRU&^p+| z8vZ>6uFV)1D|aX1$HZ30+EMSJm2GGG@SdgzLE zWy<90_8xRni2W|rIbH!I5~$~?)XyZW7!jv=RgU7Nv>&ynsOJJ6zC zU5n{u?!I0s-r^||p9_ti?f{eBgz!ard5iU>0miVXpO$I%kef}9el#!Iuu070An=G-?K*Awju6WN$eOQ`5D$$A}K ze><0XK5p|ZgwQW@W*QF}e~UL*Y8^F>Hmt86%J{1M*A*@fHvvq{*)N0xK8m{>%d!Bj ze_LeZAg!r~CU-#SaB{ZwyRWsbY%SySiv4Qe(pe*bPLA_EB6OobU-Dk0U`Ju zFQ+%Any$d=!?+t1uciwF_;&HHuUfch?7p7LU;YT1$rygzqAYv-K8$@meQbVLSVS%P z_nI^Id_-%Y2@481EVgH(3Zvbr5Eapufi?th~eu_n23)LJuT#-vHulm*SzQ;7a zg^9>X;bX8%ohL}6vG4O(1UD(|gxqA%i|!$Yf|_?7^jTd1%3sG*33cH7AVa$5kA$oI z*&n^MN_)WGZ{RRfnqGcNU~1f$wIePhY53;c(#(cx>Np6fi!)Y|IA&rSvc~-w)papt z)1PK!HPo8cSVOJjcC)_1J2_Iw=ClwgU-s~^jMSjW3tu0*?ZDkeOJ^Vr{{z1{I>RQQ zu=#j`sG+$}rT)ZW=={cW@dqWv7*^chA4oAN>#q+!;4qZxA;VsgA~7?)>Mg;4HJ#p0 zuek@XU)&sD4_HtUn_s{AL}9vT@;IB#&k=5U ztRSaa#{gVMC4}L%mx#+h*;yW9$Qq0p8!%Q?wODg*m%ApVai=xYlOG0>+WSj|8W7zi1^ga98-V z-0)A?Px_nMoamGnf;VVud?-3LLFf09Qh)Ai3#MGWRST-jJvf)1#;2f}w`K%ck^Fcc z`?Jy=0O5>&9@KzIkb1lk+|Mkg7j#aQcPFjT^rC;&4{g5ZW+%7F->#vo_GH#7>e^d; z&y58t3b58=XnOpDmq);9=Pru=y?39600mJ6`74{Hl$Qdhf?GEbD?M(;C(|3zxe$uR zD0ZeaN-SuY=SQGm{(Ga<@ao}hIqXyD;;{w3XcdYWrA{ecEj-0|{CDFRPjP6d0weVR z>4|K~A3_vxN660rje8oa0DY4vZcRWBffw7O0-%}p#o+V-yN1J)IYX|ty22bI(rU!t z)IbHZ7Me`k(?uf?&pStEh6o^U(Pz>oiu2Vjl=S%VchC)LpabZ!v&qzabaJUR#(56B z$F-_c$>7lhgfsd1P!rluJz?EYK+goj*`E3$VfsdC4qP#3ocSR3V!iUvM0jNMw%7bn z_mS6yNl$0MOe4W_W9@pQ*=Td)*>gF z;0mxq1G=SCIIiZ48r1yyn|a>Z$V$eoMMt~$&?o4_2;t67N;j`>$tlKpX}{M<->Be+s zQ+A`?twm)_Is3t0M0jN?PAckh=7FYVO~?ncx)VK_f~;h}UtgeQO?YleXm`+w1vDnG z+8z!u8tAbitX&HqNC4vLeo3I0HwRs7(TfysOLx!VnRavNxcW4OqgcYhYgjuc^Z$8h zhAtQaIAp2|WQ*tQHWGmIisF_)Goc77QtFDGwUBE%`u4d)rSLu6oY!k6QB{9cHJJTN z#4N0#O?l+bG#Td3#7OvYRvG@o+D%coX_P?jy$lsL8K(kvF7;0a6bHJ0b_@q}zOggr zO6(M9mGFx?p-bDeWDAvaMFH81rX9UHY$&KbuPnGv1=X>WxfZ?8+He`%s$ixG%r?&K zJb|Az!D-@eRUaadbMde%9l@1PgBK}>EFgdlJh9*&&Qaj6Ns{w-luu&)@4_?@h`At^ z-@?}i3?a49^gE?!T8WsYN0mj3QwSrWPQGCS&2nd@S$s2majVM(y^ zJ#M*?S4Nx4eXWq5HD#JOUv~A=FNfXJdVCux#U>?)buTjK26ayJfB6&X0jEI3R4{Rc zRU~F3LQO38m;NU0fxb*fCIffFVSwYO$l6HN#^M=JH$g3wG*-6Q*9vU6rKfS|Bpl%) zUHZUoqUPpc7d*zvuYpLBQ}Cc zW}=T#l5OSP$*(9SWwa7+7fz;-aYY>wlZ3)$?WwUuIn(m2s_3A3GtYU|hzMg!F_&u( zaMP)BP3fo;7leCNzjLbn;e6U77p|-NY|9gK#LG3X5S6Ul`zfPOH?$Jpe(rJj)Vf0m zA0_2Zc}u=zKLn_H(^6<2pkxC)MU4pwTCsYH`^dmrp?ZHQu`IRHL#4m{PZm2+e5CGw zMA(iuOQ00mG9Q0~T8%aMv1@x*`24Rv=>B2fyZzB{AWUDW!jYQ4GUPQxT=jKvIJxhi zZ6TVF0WNWobmlom0}DEf#_PI;gS&N!Wg99RLI2@e+jO^YH#aR#R=K#CjtLlSoR;XA z&Y5Yoc585dk+R*&hXffM=ljS+^!XD>&JV0`mt66tXE)Lf!=D?w>glW6olsxBM0JiR85GqsU&e*qh5R zhn%?51Tm8n8zETGt&G@J-u?z3`#9s+w#4Tbn=@4KoMcyM>^gurhivS^z<>AZ<$gEb zWgXDFD}XeFCP7<~28|ZcbWN0U9AwZ*hzmQGwNU1iG#M61?5E$vG%mC{drdp1@XcVy z;}2flXil(LwD}y`s7ofGU^ks`k?@DWwh_Hn` zXjnJA$u+qRDBB_22^sW3`V?th6M3-$_e3_VG0b!nBeD09J;CA1A+f427g_g782|fSBc!y1P^-Z|n-T4u<&^L+@gM3x^xepsNSm0us=#_PLY1en!FbyV$ zf+v%u_S_w6kVtbkYf=nrkkI_sAs~%Ch^;7i{^Z8+O23a2xhC@o1km6Z$*=azXkpp& z?ibpZ7Dp5mlrD=}kd8-HFLzN4s~VsJ&25TFQiV$=Alb%G$V-M;(CFPQ|6fng zfh*pOb0Rmbi#XwPHeS%QBnfjRdYHk3eT%e#H%6sFYaVrzE)8x0H+!8@p?@7Hukk)E zt)5q#FQ)FYb+M4UwVWFxG$i52pq9Z)EAmVpa8klg&U@mjo)wLrw4jReQU3XDE=`TK z^(OsC6GWee0XHRuTf0Xq(JHbGF|I>M%yBY^@E&5EO;PCVe9vf}%D>zI#H)=$E5JFA z5Ln))(D@X=1^x0ijVaVyDjc|w6OY?lk{pg1pYx5;Jgln?m{%AdZTM}5O!bC$6R>T_ zH7LcQ^)F|Hqs&JYjb@VD2-w5BhGpmuO#Ey3|J51=&|TlRqG?gBq0vKe;Wxlp>g;t* z2 z$g?0yT8cD6U=7W6y&L5wK+3^n!W8IPogmN|Huhw-WXs-n9yS%DC)J##<4%_awZGh# z53oe|iiNEJx%bRV-GWt)7kmWPu@N7H^DzTf=AIm|6fdWMD0WBRXqv>CccBvf(VjkNWbK zoM#wwdggRwRB*pWh>pX&T`zJLNX7+u77dXMXI$vqSFenV{@V5@-J68)_OXq{2A5Nh zx`_aU{(#TWxi)1Y&|do8zQ+FFm#C8S-ncN_LGp*CM;KbT*mLj1Kch5P-Wgt_S_!+1 z$<&YRVSYexPkws`+D&tLf9bBckFzlO`kNV6;HxA`6eXl~B%@ z(L>vBt|P@v(XPIfFO_Vg-^gD+DY(CV=!iQajNyK)*J|*N&XyPLaQvDM%|D{ZTHl!Z zdV>65yU|nF{wC&2KF>=L)T%k{jHk&N;KLlm;$;U6PPWEm48r?qE@U(lR6Xmjg{}Cj z0I#~_z30rI&sN>+pIZx3Ecl}tE_J_S64&J&_ZzTAeBw0bxAN)L(dn{`efXwa)GW2|vw;Mkxx@qcTQj>};hlGV1yy@}lW#3TL7aStcA! zydZaeqI}5q7ewMZ0#= z`yaH`ZND@JVG;-52rpzo{qnz<_M$`g*4D@v08gR=k-vhuLh~F9QDiyYflXr_$NgHD ziWH5KO;qz9lmoaf1Dhr<1lsiTDJU%Z3_u@R9b%k?&*^XO^53=y=x>05IgH%IZ9gm` z_ALl7;$!LEvE?C zClnYUAdb!$P*EO2l~o;B`;$1JbqQ{i{N^NH@=+AQs`T{*;8X|W6_DoC3o=WFVsN=g zqPcnYa3J0twcuC@f_9)NL%!O}jMM{a782@?DnqUxSnP0Y8@ZiWIy@jO z9k*Z0%muSZ}DNjZ^0_kM4yZ!Rtsn8U-P%t5Iok`Qezdl@bs0uc^ai)JN zE13A!{ZL+c`kkM>>ErU!e##=lQQ8t7<_m#L&v6Hq%D~a{i!_pQ@7D9G{nTg4pX444 zY3LX2n%wF_U)Dsv3*I^r3B(Oi9x`|XCqA~(-GA|Nh7ZwXB^%{h`b&}rRamhFdnzSS zd%*TVPgorA^bbXzowx=0j1gvoW`kihhyisH7l2{u#p=c!p9?(;(7Gr?&x7inQFIyb z3zi&&8q(VKaUYr?->I%canGRbt zZl=SME$nv)9+&-IHR`xb)TOn;nzOxi zG%q~S<#~ZlCaO1&=6P&RiGTXTT2+vUD3-Vg)om7(vUxh<7o!mFAw+ll*+E`vg(%RA zAVn*9W;e9Tbei@X#YWq*P>X%z+JbMs}FFb7i}(OUdt650T=Z zHd2<0?=g)WgSi*izAQe`*>;?9&KRm%4S&tcLRWGrl;I@0V|kjh?)6FD1H^rQyX>&| z{Pc1O5dI{;;E&Yx+VvmK=ME;&mZu;PO49Dvq>&C4%N(X&#C!}?FaQwK!?WJX6Fz$s z!8NO}Hdu=CN@`!=S*wa@sjmzWNK~xj1%b`O?;+^<-<4WFi2Jc{w6=COWPA?7o`gPM zzB7#K`Cbl`kkI)l*HNa~!suRQk)Z76SpK1tr}%rf7S5_XtgMkoL*`w3OO6tdKc?DQ zpGAK@FGI_vIm|y2#UugUQ4B{{2hDb8b!hPOU?Y3IGt1+%3UVC3_!^ zq6km%UnZKDG-C0Qv3$QhgzAAC^o$jP)>~` z#~%|=r(FM@wkKU&b`=O@PxY`p7sUI$jN)#Na)dWdr~jo_8>j?Wk(O+&CtS5_VzG&! z1TLM!1qvP#jj9gN(`;>%zKL6`M7Ys&h-un1CSe90yu&Q#JH+FO8p2?Rr@ zk;)fOT}V>#Ai1~-LpiS`Hb{}2TGg_JREb*A9@V|%4{RVGemub>+fH0O0NvuNZWl#S zstx}v_HT1Q*2x2M)L#Ps%Gn>kTeFV282fD!pd^kBNk+?JOaL=04C;cZgO|vrJ!J@^ zMJaXn-|rhMxAaLcL{5n=mvvMcRRknEq^#km9mfUmZIpeok*IwONdNe>{>n*o1Q+Rx0U-hkuaW}cW zinP^+h-5^)@|ni5Sj{5iN(Za&N2?NwS)4%Zlv1K^5AgcjeqKCR239q_xM(YN8B_bT?%!@g956`ENRp%x7 zd;XpD9C&wCF(b9wF=+$#@k-N(+qcbbiwQ0hjr&NJO)Vw`@@QchWs{Q-VizP-0r@xi zzXy>MhJ5Lw?oLCpNch_Y3Pq{jQbX2?F9W`9*0jHtkd>5+Ahnb-wu^9sJdj26upVyL z%sIiXkHLAc?n!zQ{&>8NOBYq89Kl(10c%IpuT`o zax*9-DUYR~#D)%QRBJ=VlHs}({wdEfp|GgqRUV(xo>jXCV5tJUD}UW4(K8M+(DG{y zP$uc#(Uoi*e88&GNGEWhx5?df9``pB{Yvk4tNHBc)yu%>gJCY^1S?2?3ENOdp-fpu2kqrr>2r zt?lpll!(4e;InB^kxpidzeK`bcxmgaez17li4iu6YSrhtS%7ECHaIKDSURMr) z*4n}7*5%Sq;CI*icF`0>@>ubpo|R)BA#SC;k%fa@od$lQ*vs9!9OcJEGQ94!8Dk=8 z`N??YB!cu6X~27Oqgw*L%Oki==8hE!z>MlWGZRV~P|N^n&!~FKBQYU2voNL-sz$5@ zP@xP4+GE5aeSzX4o_U!WRai-s7(oPOWtV>-n6L`g)q*^h>Osc4#(hhOnIMA&l^B15VioS4^BBSUi~@#e_urKBu>8a3Um44xj=7A3@k^YQb4E)u zcP3jjeD|GiAB&(gxj^JR3S~+mkv3VdPeVjZCiv0%NkBUR@Ax7Xm*wVM=hFVEJ9PKu z4yTc0{qb{uwc2wTovKOG$vJTYZ}|GR&gp5Um)A7p#}{bpXKq5#jj=7*XoWv*o+jgj zm(kB}z~y%j!5mQAsZHG4>Q!xs6=fw5NNMkaEEr6;Q_IWvLMCs8r;^kohHSE~aK(e6p*|v4IqfR>q8K=Je}5 zQeI`gppY!!^)8>AV&P}AoO|J6Ct-FmY*+522#)zr(A0ifS zZ|T}N2K0F@wa`&CIOa03R)0|6*}?JXk2=Zj7xR^6DTI}GaMnMO^2EM6ZyUtVu#PtU zbm1CAu|SV!B0^4JtX@^=QN(+;_u`Q=VwsB0Aa1dbG4g3Us&z|ChO#Rm8YgI0XSO5i z>#Z(-ur(uxbI(pgZZRa`T9-1ajeMyv4}ih4zJIa&u;j2dfftxEkL1K7^So2`>4t3WFWGC6wD!0q&;XBS(cf8Jz1B)-n207w8QBR2<#X+1Nkfe1Gvd-t^d3Ni zgK>0^Pc{mOHDmm=)FO-Q?IkUe9-;S8-klmL`zIQfd#HxgVi49J;#rUnzrep)7cLN>^YS=W7T#P({ZzYtn9LdGrIn?3v6= zHl0Baka3K6;&9YKicGIAzmwgy`EZ*jyW0E!HT@r7a_4NP`blmdkjHi#uFI@bujO;) z^=MxaG}E;Fn|0eiYgJo6c>;%8h+0_LC^&YbgpdDvr zEPacwZ><;@ck?}YT+i{03Qm2d#em!-6?QDD{qi4GoUTtEm<^)T{zjmdp_xZtE?_#F83duv z3k{9$iKpLy4zyRQhCeK^-fDWuB8J>!Rt9B3-6_{gE3TSho3}=Rn|3Qguq0+Dm$~`a zPRxaL?|H}i1*RT`)dd+NTb2!dtPQE%v3GY!4i^ZM%;`i3b#Dbq<}I-)#lmK&$S;5qiGRj? z!mG*8Bu!jDLq*Yz#s`>qcbIz6`1K$}ouEi0U%ua2X}UjG`WTY@#JL@E=_9r2Kpv{G z&RRt+>$f4wbH$iH1DD^2+sH4k$pdH-ml>qvDn$8ZG;4d@Xk|VowggfnX~ns?vE{m3 zGPv!JeH;A7tHaZ#b6w@)u1_1@qQ%(WuDAG{f4cU4D0M+@&%<|&G{p-X4OQ1wWX3TN z>LqyeB6+~B=9axs{&c>h&H2lOM3cK5;cOgf?z4^=maX67`u+Pg+aHU@JxuOCY4Vo6 zUuk7cpMJV-Ejl`onkMn0AG2e&&x9SLqjg@9Cj3x7RClxo58r(m_hCB}CX1umPG*wlZm$WMJXb3O-jzbR7 zkmK*8I61BJm^Mhz>=t{|PeS=q4&{1G%`K&l&OfxR?8~DtD`3joD}348qobHQP3}|u z>Qn)(`(jhBn{uJbSiQrJEZ+QLs?6vxq@iJ)r8jVFLUg`BiLMut2T9<0*Cc5Cuf2_Z z`lI7Yryo2bH!RWT{)>u~fH-JNKD*!5Yx}gkwPhJ}=62#u=%TJ*E$x@$mz*W|IP#pT zX`)}HVSK4pSPA5&$IzhZb&`zOvvlZbQ&a?to%{W@kJN&U>(~7WKg8i^JGY7Y1I~5s z&{eniXk)5H-(3IL*`|w*lf;a{rkm*3wr#qXewaO@iG{Wd-;9(-veU4d{o|5BFM#R16X8Yj` zyA{t(zS5ONehEnX36BCf2XZ(v(WauMOSC!j%x?aDes*H|Q_#$w&TPciN7;1K%*uA)vD0mG%{cln?ZMwW_2{8?T9UciL#@xS0CN9 zbrAX;%2M(U@dNT-nIerJYsmzYI)U7d5L2rn?l5pgqbX9AX0y8P0&RbwAvX6r&F~QC z&xg6+gOdqW0hq|g##*1?PQEU2tG_)_W{*hSrOauv}^cW4VUVD zQn3+ju!I4^@@I!u7C><2$tLZXLcdk1fOxn3^dMMs2c9PjV)^V`8CUZq@I^PO;3~B! zl5F!ywQe-hmk#uJfKi2LCB=c1!;fP0l{g4CXMNCk^yo0KWv z#Erkm!@riT1z=$D%0J3nhlI4spw&ktYgShGP&FHF>(83uV zX=!rro+(I>qUYUV5e=pOsvz768GQb$eF-4&B$F4Lvq;)7HZfV<4n(&38nj+r%_^*f z?U&DsGGcF8ypOQb)ozi$jEFd%;x?L2%ij%!SPoDYJ_|GxLbVR zwlFcIDshlKf0&QX5^qFlu4vq(IvqhS(B$l@(2G+vJVaVF%b>kaPc;O3tQUbD@jq}= zDpBE|>x|NJd0l<&LZ!*6^EFx>@~aMdv@c1UT5mWhGA4M)q4_j*0VSMX8Q}s|(?&2@ zb3-Ux7;~%WJIJlJY&k#i7fwr9VCav_XXv2Bz!0%f`L83S40`<)F`uI61NGNC2aee9 zg&>1F6lw;w4p9X{h|lcui(Y^4(JWd@Kg2PysZ%0%!g zcH@-YMkKXvccvO=IHWPK0@dUT=b_A5Ha1R@w`|*rd(5xXUJEyWZ;(OcQ(M10)sS|k zc0sAojEvMzz+lufCm44~v#+QaIsZmeN1S^EFMpy)TH5?gG@(lLhu>|USS=>IXFDo) zO|G>oa!2_%)P6mRvQ``=vrhl_{O~x%)f~YjE7b;Y8HLa6TRAYVKhN3eFqJ(lTTdXB zyn664ouBbg7G`iX{C*jo4$9<4`4#Z?=d?9!NCPmVB<5Q=x5bcdb+Ru&yu;c9E>Zem z1#43ucYl|2#bi<~$sV9rilW3wX!&$l@#a0-k+&o2zbr!oj(C?pPF>v3uocSh(j9)# z_4bK^%D2rq;X>#oG@{r zbJr@2t+q@w4|vp36tp;@u)xW9$fpy6Vv0K;nNz>RWJp0Vn4AwRoN8#3zOt8hTr{82 zI^nU`vSMm9c^q|e2r?J7UB8%{uWAM6M3{D9<0vU4G4)@5XoDsMGoj{O=a z1COoUp+s15TwO*fJ!v(fJ5)lGz(`l6PT!SB>zOZaQ{J zd-hYse4!SkHv-Ko-QBH%@e7{MolZ8`E&>KTLcm2C-x@WTyzA$XKI0pmloMC(nHl6G z(#|a6`-+8uE|wB8v`wU}OxZuAjq{`(I1gHfQZIQ`z12wP>PpDWm(k4KdAL<7H55~T zy?IQxrgDJ40CA5xA!9yCSti^cLw{Pk&+Z^&f|WlUb=`22c*`KuSK6B>BKWS61^#{S z0MJ+ogG2t=LLvE?FG<7p08at}iqTA!wxKTinyHLux)4i4`FFya_ZRErb5W01pigI3 zsEij6%EAwV)w)LDbyrKGl`xQKR99U{S0L56a!}u@vk|CkTi9d+xo$`QyK3MpjgWrO zc|({B#0nbkPz5~Ex))d$TMVu|w?g$>pN2k-WPt4~uLW0J$)zcsp(r-$vwIK`eL&)e zK<`^S+v6_`B0i=ee|q+B)cPc`%4r^!bH5&&sedsfLQ@`SMy5>S^UsWVd;W&e9

~ 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 literal 29432 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
OO{C;(@FN{$3$${#;3#FQ+d% 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 q)#Ca_;aT4B!NDrm1oCN7LM9fnteQZ? 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 diff --git a/docs/img/terrencedorsey.jpg b/docs/img/terrencedorsey.jpg deleted file mode 100644 index 2c55d16062971548f9069404d4f5738428af60fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67084 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 diff --git a/docs/index.md b/docs/index.md index 6cdda04b8f2..3d15621de6f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,36 +13,48 @@ Pydantic is the most widely used data validation library for Python. Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. Define how data should be in pure, canonical Python 3.8+; validate it with Pydantic. -!!! success "Migrating to Pydantic V2" - Using Pydantic V1? See the [Migration Guide](migration.md) for notes on upgrading to Pydantic V2 in your applications! +!!! logfire "Monitor Pydantic with Logfire :fire:" + Built by the same team as Pydantic, **[Logfire](https://pydantic.dev/logfire)** is an application monitoring tool that is as simple to use and powerful as Pydantic itself. -```py title="Pydantic Example" requires="3.10" -from datetime import datetime -from typing import Tuple + Logfire integrates with many popular Python libraries including FastAPI, OpenAI and Pydantic itself, so you can use Logfire to monitor Pydantic validations and understand why some inputs fail validation: -from pydantic import BaseModel + ```py title="Monitoring Pydantic with Logfire" test="skip" + from datetime import datetime + import logfire -class Delivery(BaseModel): - timestamp: datetime - dimensions: Tuple[int, int] + from pydantic import BaseModel + logfire.configure() + logfire.instrument_pydantic() # (1)! -m = Delivery(timestamp='2020-01-02T03:04:05Z', dimensions=['10', '20']) -print(repr(m.timestamp)) -#> datetime.datetime(2020, 1, 2, 3, 4, 5, tzinfo=TzInfo(UTC)) -print(m.dimensions) -#> (10, 20) -``` -!!! question "Why is Pydantic named the way it is?" + class Delivery(BaseModel): + timestamp: datetime + dimensions: tuple[int, int] + + + # this will record details of a successful validation to logfire + m = Delivery(timestamp='2020-01-02T03:04:05Z', dimensions=['10', '20']) + print(repr(m.timestamp)) + #> datetime.datetime(2020, 1, 2, 3, 4, 5, tzinfo=TzInfo(UTC)) + print(m.dimensions) + #> (10, 20) + + Delivery(timestamp='2020-01-02T03:04:05Z', dimensions=['10']) # (2)! + ``` + + 1. Set logfire record all both successful and failed validations, use `record='failure'` to only record failed validations, [learn more](https://logfire.pydantic.dev/docs/integrations/pydantic/). + 2. This will raise a `ValidationError` since there are too few `dimensions`, details of the input data and validation errors will be recorded in Logfire. + + Would give you a view like this in the Logfire platform: + + [![Logfire Pydantic Integration](img/logfire-pydantic-integration.png)](https://logfire.pydantic.dev/docs/guides/web-ui/live/) - The name "Pydantic" is a portmanteau of "Py" and "pedantic." The "Py" part indicates that the library is associated with Python, and - "pedantic" refers to the library's meticulous approach to data validation and type enforcement. + This is just a toy example, but hopefully makes clear the potential value of instrumenting a more complex application. - Combining these elements, "Pydantic" describes our Python library that provides detail-oriented, rigorous data validation. + **[Learn more about Pydantic Logfire](https://logfire.pydantic.dev/docs/)** - We’re aware of the irony that Pydantic V1 was not strict in its validation, so if we're being pedantic, "Pydantic" was a misnomer until V2 😉. ## Why use Pydantic? diff --git a/docs/theme/announce.html b/docs/theme/announce.html index 41f585a8746..71941bdd598 100644 --- a/docs/theme/announce.html +++ b/docs/theme/announce.html @@ -1,5 +1,7 @@ -We're live! Pydantic Logfire is out in open beta! 🎉
-Logfire is a new observability tool for Python, from the creators of Pydantic, with great Pydantic support. -Please try it, and tell us what you think! +What's new — we've launched +Pydantic Logfire +🔥 +to help you monitor and understand your +Pydantic validations. diff --git a/mkdocs.yml b/mkdocs.yml index 0c881f64085..d1f3bc4b7f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -288,8 +288,8 @@ plugins: 'usage/types/sequence_iterable.md': 'api/standard_library_types.md#other-iterables' 'usage/types/set_types.md': 'api/standard_library_types.md#sets' 'usage/types/uuids.md': 'api/standard_library_types.md#uuid' - 'blog/pydantic-v2-alpha.md': 'https://blog.pydantic.dev/blog/2023/04/03/pydantic-v2-pre-release/' - 'blog/pydantic-v2-final.md': 'https://blog.pydantic.dev/blog/2023/06/30/pydantic-v2-is-here/' - 'blog/pydantic-v2.md': 'https://blog.pydantic.dev/blog/2022/07/10/pydantic-v2-plan/' + 'blog/pydantic-v2-alpha.md': 'https://pydantic.dev/articles/pydantic-v2-alpha' + 'blog/pydantic-v2-final.md': 'https://pydantic.dev/articles/pydantic-v2-final' + 'blog/pydantic-v2.md': 'https://pydantic.dev/articles/pydantic-v2' 'examples/secrets.md': 'api/types.md#pydantic.types.Secret' 'examples/validators.md': 'examples/custom_validators.md' diff --git a/pydantic/types.py b/pydantic/types.py index 2ead11f56cf..57816e03481 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -1143,9 +1143,10 @@ class UuidVersion: Example: ```python - from typing_extensions import Annotated from uuid import UUID + from typing_extensions import Annotated + from pydantic.types import UuidVersion UUID1 = Annotated[UUID, UuidVersion(1)] @@ -2617,11 +2618,12 @@ def __hash__(self) -> int: ```py import base64 from typing import Literal - from pydantic import EncoderProtocol, EncodedBytes - from pydantic_core import PydanticCustomError + from pydantic_core import PydanticCustomError from typing_extensions import Annotated + from pydantic import EncodedBytes, EncoderProtocol + class LegacyBase64Encoder(EncoderProtocol): @classmethod def decode(cls, data: bytes) -> bytes: @@ -3196,7 +3198,9 @@ class FailFast(_fields.PydanticMetadata, BaseMetadata): ```py from typing import List + from typing_extensions import Annotated + from pydantic import BaseModel, FailFast, ValidationError class Model(BaseModel): From d040df85a4b35c6044367f15a3de64dcf9f3a6cc Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:43:50 -0400 Subject: [PATCH 069/412] Adding `logfire` examples to `integrations` page (#10535) --- docs/img/basic_logfire.png | Bin 0 -> 68753 bytes docs/img/logfire_instrument.png | Bin 0 -> 26201 bytes docs/img/logfire_span.png | Bin 0 -> 39140 bytes docs/integrations/logfire.md | 78 ++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 79 insertions(+) create mode 100644 docs/img/basic_logfire.png create mode 100644 docs/img/logfire_instrument.png create mode 100644 docs/img/logfire_span.png create mode 100644 docs/integrations/logfire.md diff --git a/docs/img/basic_logfire.png b/docs/img/basic_logfire.png new file mode 100644 index 0000000000000000000000000000000000000000..d49cfa2998f1f80c922055f15ed997c3db9ae90c GIT binary patch literal 68753 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 literal 0 HcmV?d00001 diff --git a/docs/img/logfire_instrument.png b/docs/img/logfire_instrument.png new file mode 100644 index 0000000000000000000000000000000000000000..115749ebd88053d1f12981870f08112bc30bb6e2 GIT binary patch literal 26201 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 sBgvncbCTrB3MVM8*Ya$#SE!MP;dTlmGw# literal 0 HcmV?d00001 diff --git a/docs/img/logfire_span.png b/docs/img/logfire_span.png new file mode 100644 index 0000000000000000000000000000000000000000..01bc28fe829e90e0e9986d1ff8b84c359827079b GIT binary patch literal 39140 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