From 7df594d284cabf837774dffc40e542cca9333ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 19 Oct 2025 21:12:22 +0200 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A7=20Add=20sponsor=20Requestly=20?= =?UTF-8?q?(#14205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/sponsors.yml | 3 +++ docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/img/sponsors/requestly.png | Bin 0 -> 18379 bytes 3 files changed, 4 insertions(+) create mode 100644 docs/en/docs/img/sponsors/requestly.png diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index ae28410e7..7a015e404 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -58,3 +58,6 @@ bronze: - url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png + - url: https://requestly.com/fastapi + title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support. + img: https://fastapi.tiangolo.com/img/sponsors/requestly.png diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 62ba6a84c..14f55805c 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -46,3 +46,4 @@ logins: - madisonredtfeldt - railwayapp - subtotal + - requestly diff --git a/docs/en/docs/img/sponsors/requestly.png b/docs/en/docs/img/sponsors/requestly.png new file mode 100644 index 0000000000000000000000000000000000000000..a167aa017441dca85deb0079db72d0cc4c248644 GIT binary patch literal 18379 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 M07*qoM6N<$g60jPApigX literal 0 HcmV?d00001 From 6e49dc029541aaa896fa63c47647ccd3f066df67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 19 Oct 2025 19:12:47 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1853b54f9..489bfd0fc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 🔧 Add sponsor Requestly. PR [#14205](https://github.com/fastapi/fastapi/pull/14205) by [@tiangolo](https://github.com/tiangolo). * 🔧 Configure reminder for `waiting` label in `issue-manager`. PR [#14156](https://github.com/fastapi/fastapi/pull/14156) by [@YuriiMotov](https://github.com/YuriiMotov). ## 0.119.0 From d8c691f7f09989fa51b331b8a7a1ecfe93815a2f Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Mon, 20 Oct 2025 13:26:49 +0200 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=90=9B=20Fix=20internal=20Pydantic=20?= =?UTF-8?q?v1=20compatibility=20(warnings)=20for=20Python=203.14=20and=20P?= =?UTF-8?q?ydantic=202.12.1=20(#14186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- fastapi/_compat/__init__.py | 8 +- fastapi/_compat/main.py | 127 +++++++++++++----- fastapi/_compat/may_v1.py | 123 +++++++++++++++++ fastapi/_compat/shared.py | 10 +- fastapi/_compat/v1.py | 34 +---- fastapi/_compat/v2.py | 4 +- fastapi/dependencies/utils.py | 23 ++-- fastapi/encoders.py | 18 +-- fastapi/temp_pydantic_v1_params.py | 2 +- fastapi/utils.py | 15 ++- tests/test_compat.py | 12 +- ...t_get_model_definitions_formfeed_escape.py | 3 +- ...est_response_model_as_return_annotation.py | 6 +- 13 files changed, 283 insertions(+), 102 deletions(-) create mode 100644 fastapi/_compat/may_v1.py diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index b2ae5adc7..0aadd68de 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -30,6 +30,10 @@ from .main import serialize_sequence_value as serialize_sequence_value from .main import ( with_info_plain_validator_function as with_info_plain_validator_function, ) +from .may_v1 import CoreSchema as CoreSchema +from .may_v1 import GetJsonSchemaHandler as GetJsonSchemaHandler +from .may_v1 import JsonSchemaValue as JsonSchemaValue +from .may_v1 import _normalize_errors as _normalize_errors from .model_field import ModelField as ModelField from .shared import PYDANTIC_V2 as PYDANTIC_V2 from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE @@ -44,7 +48,3 @@ from .shared import ( from .shared import lenient_issubclass as lenient_issubclass from .shared import sequence_types as sequence_types from .shared import value_is_sequence as value_is_sequence -from .v1 import CoreSchema as CoreSchema -from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler -from .v1 import JsonSchemaValue as JsonSchemaValue -from .v1 import _normalize_errors as _normalize_errors diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py index 3f758f072..e5275950e 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -1,3 +1,4 @@ +import sys from functools import lru_cache from typing import ( Any, @@ -8,7 +9,7 @@ from typing import ( Type, ) -from fastapi._compat import v1 +from fastapi._compat import may_v1 from fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass from fastapi.types import ModelNameMap from pydantic import BaseModel @@ -50,7 +51,9 @@ else: @lru_cache def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: - if lenient_issubclass(model, v1.BaseModel): + if lenient_issubclass(model, may_v1.BaseModel): + from fastapi._compat import v1 + return v1.get_model_fields(model) else: from . import v2 @@ -59,7 +62,7 @@ def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: def _is_undefined(value: object) -> bool: - if isinstance(value, v1.UndefinedType): + if isinstance(value, may_v1.UndefinedType): return True elif PYDANTIC_V2: from . import v2 @@ -69,7 +72,9 @@ def _is_undefined(value: object) -> bool: def _get_model_config(model: BaseModel) -> Any: - if isinstance(model, v1.BaseModel): + if isinstance(model, may_v1.BaseModel): + from fastapi._compat import v1 + return v1._get_model_config(model) elif PYDANTIC_V2: from . import v2 @@ -80,7 +85,9 @@ def _get_model_config(model: BaseModel) -> Any: def _model_dump( model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any ) -> Any: - if isinstance(model, v1.BaseModel): + if isinstance(model, may_v1.BaseModel): + from fastapi._compat import v1 + return v1._model_dump(model, mode=mode, **kwargs) elif PYDANTIC_V2: from . import v2 @@ -89,7 +96,7 @@ def _model_dump( def _is_error_wrapper(exc: Exception) -> bool: - if isinstance(exc, v1.ErrorWrapper): + if isinstance(exc, may_v1.ErrorWrapper): return True elif PYDANTIC_V2: from . import v2 @@ -99,7 +106,9 @@ def _is_error_wrapper(exc: Exception) -> bool: def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - if isinstance(field_info, v1.FieldInfo): + if isinstance(field_info, may_v1.FieldInfo): + from fastapi._compat import v1 + return v1.copy_field_info(field_info=field_info, annotation=annotation) else: assert PYDANTIC_V2 @@ -111,7 +120,9 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: def create_body_model( *, fields: Sequence[ModelField], model_name: str ) -> Type[BaseModel]: - if fields and isinstance(fields[0], v1.ModelField): + if fields and isinstance(fields[0], may_v1.ModelField): + from fastapi._compat import v1 + return v1.create_body_model(fields=fields, model_name=model_name) else: assert PYDANTIC_V2 @@ -123,7 +134,9 @@ def create_body_model( def get_annotation_from_field_info( annotation: Any, field_info: FieldInfo, field_name: str ) -> Any: - if isinstance(field_info, v1.FieldInfo): + if isinstance(field_info, may_v1.FieldInfo): + from fastapi._compat import v1 + return v1.get_annotation_from_field_info( annotation=annotation, field_info=field_info, field_name=field_name ) @@ -137,7 +150,9 @@ def get_annotation_from_field_info( def is_bytes_field(field: ModelField) -> bool: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.is_bytes_field(field) else: assert PYDANTIC_V2 @@ -147,7 +162,9 @@ def is_bytes_field(field: ModelField) -> bool: def is_bytes_sequence_field(field: ModelField) -> bool: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.is_bytes_sequence_field(field) else: assert PYDANTIC_V2 @@ -157,7 +174,9 @@ def is_bytes_sequence_field(field: ModelField) -> bool: def is_scalar_field(field: ModelField) -> bool: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.is_scalar_field(field) else: assert PYDANTIC_V2 @@ -167,7 +186,9 @@ def is_scalar_field(field: ModelField) -> bool: def is_scalar_sequence_field(field: ModelField) -> bool: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.is_scalar_sequence_field(field) else: assert PYDANTIC_V2 @@ -177,7 +198,9 @@ def is_scalar_sequence_field(field: ModelField) -> bool: def is_sequence_field(field: ModelField) -> bool: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.is_sequence_field(field) else: assert PYDANTIC_V2 @@ -187,7 +210,9 @@ def is_sequence_field(field: ModelField) -> bool: def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.serialize_sequence_value(field=field, value=value) else: assert PYDANTIC_V2 @@ -197,7 +222,9 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: def _model_rebuild(model: Type[BaseModel]) -> None: - if lenient_issubclass(model, v1.BaseModel): + if lenient_issubclass(model, may_v1.BaseModel): + from fastapi._compat import v1 + v1._model_rebuild(model) elif PYDANTIC_V2: from . import v2 @@ -206,9 +233,18 @@ def _model_rebuild(model: Type[BaseModel]) -> None: def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: - v1_model_fields = [field for field in fields if isinstance(field, v1.ModelField)] - v1_flat_models = v1.get_flat_models_from_fields(v1_model_fields, known_models=set()) # type: ignore[attr-defined] - all_flat_models = v1_flat_models + v1_model_fields = [ + field for field in fields if isinstance(field, may_v1.ModelField) + ] + if v1_model_fields: + from fastapi._compat import v1 + + v1_flat_models = v1.get_flat_models_from_fields( + v1_model_fields, known_models=set() + ) + all_flat_models = v1_flat_models + else: + all_flat_models = set() if PYDANTIC_V2: from . import v2 @@ -222,6 +258,8 @@ def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: model_name_map = v2.get_model_name_map(all_flat_models) return model_name_map + from fastapi._compat import v1 + model_name_map = v1.get_model_name_map(all_flat_models) return model_name_map @@ -232,17 +270,35 @@ def get_definitions( model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, ) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue], + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + may_v1.JsonSchemaValue, + ], Dict[str, Dict[str, Any]], ]: - v1_fields = [field for field in fields if isinstance(field, v1.ModelField)] - v1_field_maps, v1_definitions = v1.get_definitions( - fields=v1_fields, - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - if not PYDANTIC_V2: - return v1_field_maps, v1_definitions + if sys.version_info < (3, 14): + v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] + v1_field_maps, v1_definitions = may_v1.get_definitions( + fields=v1_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + if not PYDANTIC_V2: + return v1_field_maps, v1_definitions + else: + from . import v2 + + v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] + v2_field_maps, v2_definitions = v2.get_definitions( + fields=v2_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + all_definitions = {**v1_definitions, **v2_definitions} + all_field_maps = {**v1_field_maps, **v2_field_maps} + return all_field_maps, all_definitions + + # Pydantic v1 is not supported since Python 3.14 else: from . import v2 @@ -252,9 +308,7 @@ def get_definitions( model_name_map=model_name_map, separate_input_output_schemas=separate_input_output_schemas, ) - all_definitions = {**v1_definitions, **v2_definitions} - all_field_maps = {**v1_field_maps, **v2_field_maps} - return all_field_maps, all_definitions + return v2_field_maps, v2_definitions def get_schema_from_model_field( @@ -262,11 +316,14 @@ def get_schema_from_model_field( field: ModelField, model_name_map: ModelNameMap, field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue + Tuple[ModelField, Literal["validation", "serialization"]], + may_v1.JsonSchemaValue, ], separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: - if isinstance(field, v1.ModelField): + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + return v1.get_schema_from_model_field( field=field, model_name_map=model_name_map, @@ -286,7 +343,7 @@ def get_schema_from_model_field( def _is_model_field(value: Any) -> bool: - if isinstance(value, v1.ModelField): + if isinstance(value, may_v1.ModelField): return True elif PYDANTIC_V2: from . import v2 @@ -296,7 +353,7 @@ def _is_model_field(value: Any) -> bool: def _is_model_class(value: Any) -> bool: - if lenient_issubclass(value, v1.BaseModel): + if lenient_issubclass(value, may_v1.BaseModel): return True elif PYDANTIC_V2: from . import v2 diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py new file mode 100644 index 000000000..beea4d167 --- /dev/null +++ b/fastapi/_compat/may_v1.py @@ -0,0 +1,123 @@ +import sys +from typing import Any, Dict, List, Literal, Sequence, Tuple, Type, Union + +from fastapi.types import ModelNameMap + +if sys.version_info >= (3, 14): + + class AnyUrl: + pass + + class BaseConfig: + pass + + class BaseModel: + pass + + class Color: + pass + + class CoreSchema: + pass + + class ErrorWrapper: + pass + + class FieldInfo: + pass + + class GetJsonSchemaHandler: + pass + + class JsonSchemaValue: + pass + + class ModelField: + pass + + class NameEmail: + pass + + class RequiredParam: + pass + + class SecretBytes: + pass + + class SecretStr: + pass + + class Undefined: + pass + + class UndefinedType: + pass + + class Url: + pass + + from .v2 import ValidationError, create_model + + def get_definitions( + *, + fields: List[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, + ) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + Dict[str, Dict[str, Any]], + ]: + return {}, {} # pragma: no cover + + +else: + from .v1 import AnyUrl as AnyUrl + from .v1 import BaseConfig as BaseConfig + from .v1 import BaseModel as BaseModel + from .v1 import Color as Color + from .v1 import CoreSchema as CoreSchema + from .v1 import ErrorWrapper as ErrorWrapper + from .v1 import FieldInfo as FieldInfo + from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler + from .v1 import JsonSchemaValue as JsonSchemaValue + from .v1 import ModelField as ModelField + from .v1 import NameEmail as NameEmail + from .v1 import RequiredParam as RequiredParam + from .v1 import SecretBytes as SecretBytes + from .v1 import SecretStr as SecretStr + from .v1 import Undefined as Undefined + from .v1 import UndefinedType as UndefinedType + from .v1 import Url as Url + from .v1 import ValidationError, create_model + from .v1 import get_definitions as get_definitions + + +RequestErrorModel: Type[BaseModel] = create_model("Request") + + +def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + use_errors: List[Any] = [] + for error in errors: + if isinstance(error, ErrorWrapper): + new_errors = ValidationError( # type: ignore[call-arg] + errors=[error], model=RequestErrorModel + ).errors() + use_errors.extend(new_errors) + elif isinstance(error, list): + use_errors.extend(_normalize_errors(error)) + else: + use_errors.append(error) + return use_errors + + +def _regenerate_error_with_loc( + *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] +) -> List[Dict[str, Any]]: + updated_loc_errors: List[Any] = [ + {**err, "loc": loc_prefix + err.get("loc", ())} + for err in _normalize_errors(errors) + ] + + return updated_loc_errors diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index 495d5c5f7..cabf48228 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -16,7 +16,7 @@ from typing import ( Union, ) -from fastapi._compat import v1 +from fastapi._compat import may_v1 from fastapi.types import UnionType from pydantic import BaseModel from pydantic.version import VERSION as PYDANTIC_VERSION @@ -98,7 +98,9 @@ def value_is_sequence(value: Any) -> bool: def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: return ( - lenient_issubclass(annotation, (BaseModel, v1.BaseModel, Mapping, UploadFile)) + lenient_issubclass( + annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile) + ) or _annotation_is_sequence(annotation) or is_dataclass(annotation) ) @@ -195,12 +197,12 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool: def annotation_is_pydantic_v1(annotation: Any) -> bool: - if lenient_issubclass(annotation, v1.BaseModel): + if lenient_issubclass(annotation, may_v1.BaseModel): return True origin = get_origin(annotation) if origin is Union or origin is UnionType: for arg in get_args(annotation): - if lenient_issubclass(arg, v1.BaseModel): + if lenient_issubclass(arg, may_v1.BaseModel): return True if field_annotation_is_sequence(annotation): for sub_annotation in get_args(annotation): diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py index f0ac51634..e17ce8bea 100644 --- a/fastapi/_compat/v1.py +++ b/fastapi/_compat/v1.py @@ -54,13 +54,15 @@ if not PYDANTIC_V2: from pydantic.schema import TypeModelSet as TypeModelSet from pydantic.schema import ( field_schema, - get_flat_models_from_fields, model_process_schema, ) from pydantic.schema import ( get_annotation_from_field_info as get_annotation_from_field_info, ) from pydantic.schema import get_flat_models_from_field as get_flat_models_from_field + from pydantic.schema import ( + get_flat_models_from_fields as get_flat_models_from_fields, + ) from pydantic.schema import get_model_name_map as get_model_name_map from pydantic.types import SecretBytes as SecretBytes from pydantic.types import SecretStr as SecretStr @@ -99,7 +101,6 @@ else: from pydantic.v1.schema import TypeModelSet as TypeModelSet from pydantic.v1.schema import ( field_schema, - get_flat_models_from_fields, model_process_schema, ) from pydantic.v1.schema import ( @@ -108,6 +109,9 @@ else: from pydantic.v1.schema import ( get_flat_models_from_field as get_flat_models_from_field, ) + from pydantic.v1.schema import ( + get_flat_models_from_fields as get_flat_models_from_fields, + ) from pydantic.v1.schema import get_model_name_map as get_model_name_map from pydantic.v1.types import ( # type: ignore[assignment] SecretBytes as SecretBytes, @@ -215,32 +219,6 @@ def is_pv1_scalar_sequence_field(field: ModelField) -> bool: return False -def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: - use_errors: List[Any] = [] - for error in errors: - if isinstance(error, ErrorWrapper): - new_errors = ValidationError( # type: ignore[call-arg] - errors=[error], model=RequestErrorModel - ).errors() - use_errors.extend(new_errors) - elif isinstance(error, list): - use_errors.extend(_normalize_errors(error)) - else: - use_errors.append(error) - return use_errors - - -def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] -) -> List[Dict[str, Any]]: - updated_loc_errors: List[Any] = [ - {**err, "loc": loc_prefix + err.get("loc", ())} - for err in _normalize_errors(errors) - ] - - return updated_loc_errors - - def _model_rebuild(model: Type[BaseModel]) -> None: model.update_forward_refs() diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 29606b9f3..fb2c691d8 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -15,7 +15,7 @@ from typing import ( cast, ) -from fastapi._compat import shared, v1 +from fastapi._compat import may_v1, shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap from pydantic import BaseModel, TypeAdapter, create_model @@ -116,7 +116,7 @@ class ModelField: None, ) except ValidationError as exc: - return None, v1._regenerate_error_with_loc( + return None, may_v1._regenerate_error_with_loc( errors=exc.errors(include_url=False), loc_prefix=loc ) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 675ad6faf..aa06dd2a9 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -43,9 +43,9 @@ from fastapi._compat import ( is_uploadfile_or_nonable_uploadfile_annotation, is_uploadfile_sequence_annotation, lenient_issubclass, + may_v1, sequence_types, serialize_sequence_value, - v1, value_is_sequence, ) from fastapi._compat.shared import annotation_is_pydantic_v1 @@ -380,7 +380,7 @@ def analyze_param( fastapi_annotations = [ arg for arg in annotated_args[1:] - if isinstance(arg, (FieldInfo, v1.FieldInfo, params.Depends)) + if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends)) ] fastapi_specific_annotations = [ arg @@ -397,21 +397,21 @@ def analyze_param( ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[FieldInfo, v1.FieldInfo, params.Depends, None] = ( - fastapi_specific_annotations[-1] - ) + fastapi_annotation: Union[ + FieldInfo, may_v1.FieldInfo, params.Depends, None + ] = fastapi_specific_annotations[-1] else: fastapi_annotation = None # Set default for Annotated FieldInfo - if isinstance(fastapi_annotation, (FieldInfo, v1.FieldInfo)): + if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)): # Copy `field_info` because we mutate `field_info.default` below. field_info = copy_field_info( field_info=fastapi_annotation, annotation=use_annotation ) assert field_info.default in { Undefined, - v1.Undefined, - } or field_info.default in {RequiredParam, v1.RequiredParam}, ( + may_v1.Undefined, + } or field_info.default in {RequiredParam, may_v1.RequiredParam}, ( f"`{field_info.__class__.__name__}` default value cannot be set in" f" `Annotated` for {param_name!r}. Set the default value with `=` instead." ) @@ -435,7 +435,7 @@ def analyze_param( ) depends = value # Get FieldInfo from default value - elif isinstance(value, (FieldInfo, v1.FieldInfo)): + elif isinstance(value, (FieldInfo, may_v1.FieldInfo)): assert field_info is None, ( "Cannot specify FastAPI annotations in `Annotated` and default value" f" together for {param_name!r}" @@ -524,7 +524,8 @@ def analyze_param( type_=use_annotation_from_field_info, default=field_info.default, alias=alias, - required=field_info.default in (RequiredParam, v1.RequiredParam, Undefined), + required=field_info.default + in (RequiredParam, may_v1.RequiredParam, Undefined), field_info=field_info, ) if is_path_param: @@ -741,7 +742,7 @@ def _validate_value_with_model_field( if _is_error_wrapper(errors_): # type: ignore[arg-type] return None, [errors_] elif isinstance(errors_, list): - new_errors = v1._regenerate_error_with_loc(errors=errors_, loc_prefix=()) + new_errors = may_v1._regenerate_error_with_loc(errors=errors_, loc_prefix=()) return None, new_errors else: return v_, [] diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 8ff7d58dd..bba9c970e 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -17,7 +17,7 @@ from types import GeneratorType from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union from uuid import UUID -from fastapi._compat import v1 +from fastapi._compat import may_v1 from fastapi.types import IncEx from pydantic import BaseModel from pydantic.color import Color @@ -59,7 +59,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, - v1.Color: str, + may_v1.Color: str, datetime.date: isoformat, datetime.datetime: isoformat, datetime.time: isoformat, @@ -76,19 +76,19 @@ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { IPv6Interface: str, IPv6Network: str, NameEmail: str, - v1.NameEmail: str, + may_v1.NameEmail: str, Path: str, Pattern: lambda o: o.pattern, SecretBytes: str, - v1.SecretBytes: str, + may_v1.SecretBytes: str, SecretStr: str, - v1.SecretStr: str, + may_v1.SecretStr: str, set: list, UUID: str, Url: str, - v1.Url: str, + may_v1.Url: str, AnyUrl: str, - v1.AnyUrl: str, + may_v1.AnyUrl: str, } @@ -220,10 +220,10 @@ def jsonable_encoder( include = set(include) if exclude is not None and not isinstance(exclude, (set, dict)): exclude = set(exclude) - if isinstance(obj, (BaseModel, v1.BaseModel)): + if isinstance(obj, (BaseModel, may_v1.BaseModel)): # TODO: remove when deprecating Pydantic v1 encoders: Dict[Any, Any] = {} - if isinstance(obj, v1.BaseModel): + if isinstance(obj, may_v1.BaseModel): encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] if custom_encoder: encoders = {**encoders, **custom_encoder} diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index 0535ee727..e41d71230 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -5,8 +5,8 @@ from fastapi.openapi.models import Example from fastapi.params import ParamTypes from typing_extensions import Annotated, deprecated +from ._compat.may_v1 import FieldInfo, Undefined from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE -from ._compat.v1 import FieldInfo, Undefined _Unset: Any = Undefined diff --git a/fastapi/utils.py b/fastapi/utils.py index 3ea9271b1..2e79ee6b1 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -25,7 +25,7 @@ from fastapi._compat import ( Validator, annotation_is_pydantic_v1, lenient_issubclass, - v1, + may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType from pydantic import BaseModel @@ -87,8 +87,8 @@ def create_model_field( ) -> ModelField: class_validators = class_validators or {} - v1_model_config = v1.BaseConfig - v1_field_info = field_info or v1.FieldInfo() + v1_model_config = may_v1.BaseConfig + v1_field_info = field_info or may_v1.FieldInfo() v1_kwargs = { "name": name, "field_info": v1_field_info, @@ -102,9 +102,11 @@ def create_model_field( if ( annotation_is_pydantic_v1(type_) - or isinstance(field_info, v1.FieldInfo) + or isinstance(field_info, may_v1.FieldInfo) or version == "1" ): + from fastapi._compat import v1 + try: return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] except RuntimeError: @@ -122,6 +124,8 @@ def create_model_field( raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None # Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be # a Pydantic v1 type, like a constrained int + from fastapi._compat import v1 + try: return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] except RuntimeError: @@ -138,6 +142,9 @@ def create_cloned_field( if isinstance(field, v2.ModelField): return field + + from fastapi._compat import v1 + # cloned_types caches already cloned types to support recursive models and improve # performance by avoiding unnecessary cloning if cloned_types is None: diff --git a/tests/test_compat.py b/tests/test_compat.py index f79dbdabc..0184c9a2e 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -7,7 +7,7 @@ from fastapi._compat import ( get_cached_model_fields, is_scalar_field, is_uploadfile_sequence_annotation, - v1, + may_v1, ) from fastapi._compat.shared import is_bytes_sequence_annotation from fastapi.testclient import TestClient @@ -27,7 +27,10 @@ def test_model_field_default_required(): assert field.default is Undefined +@needs_py_lt_314 def test_v1_plain_validator_function(): + from fastapi._compat import v1 + # For coverage def func(v): # pragma: no cover return v @@ -135,6 +138,8 @@ def test_is_uploadfile_sequence_annotation(): @needs_py_lt_314 def test_is_pv1_scalar_field(): + from fastapi._compat import v1 + # For coverage class Model(v1.BaseModel): foo: Union[str, Dict[str, Any]] @@ -143,8 +148,11 @@ def test_is_pv1_scalar_field(): assert not is_scalar_field(fields[0]) +@needs_py_lt_314 def test_get_model_fields_cached(): - class Model(v1.BaseModel): + from fastapi._compat import v1 + + class Model(may_v1.BaseModel): foo: str non_cached_fields = v1.get_model_fields(Model) diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index 439e6d448..6601585ef 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -5,7 +5,6 @@ import fastapi.openapi.utils import pydantic.schema import pytest from fastapi import FastAPI -from fastapi._compat import v1 from pydantic import BaseModel from starlette.testclient import TestClient @@ -165,6 +164,8 @@ def test_model_description_escaped_with_formfeed(sort_reversed: bool): Test `get_model_definitions` with models passed in different order. """ + from fastapi._compat import v1 + all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes) flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set()) diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index c3c0ed6c4..1745c69b6 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -2,12 +2,13 @@ from typing import List, Union import pytest from fastapi import FastAPI -from fastapi._compat import v1 from fastapi.exceptions import FastAPIError, ResponseValidationError from fastapi.responses import JSONResponse, Response from fastapi.testclient import TestClient from pydantic import BaseModel +from tests.utils import needs_pydanticv1 + class BaseUser(BaseModel): name: str @@ -511,7 +512,10 @@ def test_invalid_response_model_field(): # TODO: remove when dropping Pydantic v1 support +@needs_pydanticv1 def test_invalid_response_model_field_pv1(): + from fastapi._compat import v1 + app = FastAPI() class Model(v1.BaseModel): From 43f15d3b43235323059879b4d6f47b2efba0f52f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Oct 2025 11:27:39 +0000 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 489bfd0fc..a07aed05f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix internal Pydantic v1 compatibility (warnings) for Python 3.14 and Pydantic 2.12.1. PR [#14186](https://github.com/fastapi/fastapi/pull/14186) by [@svlandeg](https://github.com/svlandeg). + ### Docs * 📝 Replace `starlette.io` by `starlette.dev` and `uvicorn.org` by `uvicorn.dev`. PR [#14176](https://github.com/fastapi/fastapi/pull/14176) by [@Kludex](https://github.com/Kludex). From 864b569cf8453654fc3bc2c64108c0f644e2918c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 20 Oct 2025 13:28:38 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.119.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a07aed05f..25fb91eb4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.119.1 + ### Fixes * 🐛 Fix internal Pydantic v1 compatibility (warnings) for Python 3.14 and Pydantic 2.12.1. PR [#14186](https://github.com/fastapi/fastapi/pull/14186) by [@svlandeg](https://github.com/svlandeg). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 2091f0d1f..a7164d18f 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.119.0" +__version__ = "0.119.1" from starlette import status as status From 847280450a0e2b6d82f4e912978cbc6e46535e9b Mon Sep 17 00:00:00 2001 From: Nils-Hero Lindemann Date: Mon, 20 Oct 2025 16:00:08 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=8C=90=20Sync=20German=20docs=20(#141?= =?UTF-8?q?88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync German docs with #14168 Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md diff --git a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..7f60492ee --- /dev/null +++ b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,133 @@ +# Von Pydantic v1 zu Pydantic v2 migrieren { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Wenn Sie eine ältere FastAPI-App haben, nutzen Sie möglicherweise Pydantic Version 1. + +FastAPI unterstützt seit Version 0.100.0 sowohl Pydantic v1 als auch v2. + +Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet. + +Pydantic v1 ist jetzt deprecatet und die Unterstützung dafür wird in den nächsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes. + +/// warning | Achtung + +Außerdem hat das Pydantic-Team die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**. + +Wenn Sie die neuesten Features von Python nutzen möchten, müssen Sie sicherstellen, dass Sie Pydantic v2 verwenden. + +/// + +Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen. + +## Offizieller Leitfaden { #official-guide } + +Pydantic hat einen offiziellen Migrationsleitfaden von v1 zu v2. + +Er enthält auch, was sich geändert hat, wie Validierungen nun korrekter und strikter sind, mögliche Stolpersteine, usw. + +Sie können ihn lesen, um besser zu verstehen, was sich geändert hat. + +## Tests { #tests } + +Stellen Sie sicher, dass Sie [Tests](../tutorial/testing.md){.internal-link target=_blank} für Ihre App haben und diese in Continuous Integration (CI) ausführen. + +Auf diese Weise können Sie das Update durchführen und sicherstellen, dass weiterhin alles wie erwartet funktioniert. + +## `bump-pydantic` { #bump-pydantic } + +In vielen Fällen, wenn Sie reguläre Pydantic-Modelle ohne Anpassungen verwenden, können Sie den Großteil des Prozesses der Migration von Pydantic v1 auf Pydantic v2 automatisieren. + +Sie können `bump-pydantic` vom selben Pydantic-Team verwenden. + +Dieses Tool hilft Ihnen, den Großteil des zu ändernden Codes automatisch anzupassen. + +Danach können Sie die Tests ausführen und prüfen, ob alles funktioniert. Falls ja, sind Sie fertig. 😎 + +## Pydantic v1 in v2 { #pydantic-v1-in-v2 } + +Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`. + +Das bedeutet, Sie können die neueste Version von Pydantic v2 installieren und die alten Pydantic‑v1‑Komponenten aus diesem Untermodul importieren und verwenden, als hätten Sie das alte Pydantic v1 installiert. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### FastAPI-Unterstützung für Pydantic v1 in v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Seit FastAPI 0.119.0 gibt es außerdem eine teilweise Unterstützung für Pydantic v1 innerhalb von Pydantic v2, um die Migration auf v2 zu erleichtern. + +Sie könnten also Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass das Untermodul `pydantic.v1` verwendet wird, und in vielen Fällen würde es einfach funktionieren. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Achtung + +Beachten Sie, dass, da das Pydantic‑Team Pydantic v1 in neueren Python‑Versionen nicht mehr unterstützt, beginnend mit Python 3.14, auch die Verwendung von `pydantic.v1` unter Python 3.14 und höher nicht unterstützt wird. + +/// + +### Pydantic v1 und v2 in derselben App { #pydantic-v1-and-v2-on-the-same-app } + +Es wird von Pydantic **nicht unterstützt**, dass ein Pydantic‑v2‑Modell Felder hat, die als Pydantic‑v1‑Modelle definiert sind, und umgekehrt. + +```mermaid +graph TB + subgraph "❌ Nicht unterstützt" + direction TB + subgraph V2["Pydantic-v2-Modell"] + V1Field["Pydantic-v1-Modell"] + end + subgraph V1["Pydantic-v1-Modell"] + V2Field["Pydantic-v2-Modell"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +... aber Sie können getrennte Modelle, die Pydantic v1 bzw. v2 nutzen, in derselben App verwenden. + +```mermaid +graph TB + subgraph "✅ Unterstützt" + direction TB + subgraph V2["Pydantic-v2-Modell"] + V2Field["Pydantic-v2-Modell"] + end + subgraph V1["Pydantic-v1-Modell"] + V1Field["Pydantic-v1-Modell"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +In einigen Fällen ist es sogar möglich, sowohl Pydantic‑v1‑ als auch Pydantic‑v2‑Modelle in derselben **Pfadoperation** Ihrer FastAPI‑App zu verwenden: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +Im obigen Beispiel ist das Eingabemodell ein Pydantic‑v1‑Modell, und das Ausgabemodell (definiert in `response_model=ItemV2`) ist ein Pydantic‑v2‑Modell. + +### Pydantic v1 Parameter { #pydantic-v1-parameters } + +Wenn Sie einige der FastAPI-spezifischen Tools für Parameter wie `Body`, `Query`, `Form`, usw. zusammen mit Pydantic‑v1‑Modellen verwenden müssen, können Sie die aus `fastapi.temp_pydantic_v1_params` importieren, während Sie die Migration zu Pydantic v2 abschließen: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### In Schritten migrieren { #migrate-in-steps } + +/// tip | Tipp + +Probieren Sie zuerst `bump-pydantic` aus. Wenn Ihre Tests erfolgreich sind und das funktioniert, sind Sie mit einem einzigen Befehl fertig. ✨ + +/// + +Wenn `bump-pydantic` für Ihren Anwendungsfall nicht funktioniert, können Sie die Unterstützung für Pydantic‑v1‑ und Pydantic‑v2‑Modelle in derselben App nutzen, um die Migration zu Pydantic v2 schrittweise durchzuführen. + +Sie könnten zuerst Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass für all Ihre Modelle `pydantic.v1` verwendet wird. + +Anschließend können Sie beginnen, Ihre Modelle gruppenweise von Pydantic v1 auf v2 zu migrieren – in kleinen, schrittweisen Etappen. 🚶 From 046d49b5a9ab1113a1dccf6767ab7def7ec35349 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Oct 2025 14:00:33 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 25fb91eb4..03f8df30e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🌐 Sync German docs. PR [#14188](https://github.com/fastapi/fastapi/pull/14188) by [@nilslindemann](https://github.com/nilslindemann). + ## 0.119.1 ### Fixes