From 17084fe4be6850e232721b6d7116af296b7b4c5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:56:40 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20format?= =?UTF-8?q?=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/__init__.py | 8 +- fastapi/_compat/main.py | 29 ++++++- fastapi/_compat/may_v1.py | 78 ++++++++----------- fastapi/_compat/shared.py | 4 +- fastapi/_compat/v2.py | 2 +- fastapi/dependencies/utils.py | 11 +-- fastapi/temp_pydantic_v1_params.py | 2 +- fastapi/utils.py | 5 +- tests/test_compat.py | 2 + ...t_get_model_definitions_formfeed_escape.py | 1 + ...est_response_model_as_return_annotation.py | 1 + 11 files changed, 82 insertions(+), 61 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 06ab55a21..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 .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 diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py index b38264117..a10e064f0 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -52,6 +52,7 @@ else: def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: if lenient_issubclass(model, may_v1.BaseModel): from fastapi._compat import v1 + return v1.get_model_fields(model) else: from . import v2 @@ -61,7 +62,6 @@ def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: def _is_undefined(value: object) -> bool: if isinstance(value, may_v1.UndefinedType): - from fastapi._compat import v1 return True elif PYDANTIC_V2: from . import v2 @@ -73,6 +73,7 @@ def _is_undefined(value: object) -> bool: def _get_model_config(model: BaseModel) -> Any: if isinstance(model, may_v1.BaseModel): from fastapi._compat import v1 + return v1._get_model_config(model) elif PYDANTIC_V2: from . import v2 @@ -85,6 +86,7 @@ def _model_dump( ) -> Any: 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 @@ -105,6 +107,7 @@ def _is_error_wrapper(exc: Exception) -> bool: def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> 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 @@ -118,6 +121,7 @@ def create_body_model( ) -> Type[BaseModel]: 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 @@ -131,6 +135,7 @@ def get_annotation_from_field_info( ) -> Any: 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 ) @@ -146,6 +151,7 @@ def get_annotation_from_field_info( def is_bytes_field(field: ModelField) -> bool: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.is_bytes_field(field) else: assert PYDANTIC_V2 @@ -157,6 +163,7 @@ def is_bytes_field(field: ModelField) -> bool: def is_bytes_sequence_field(field: ModelField) -> bool: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.is_bytes_sequence_field(field) else: assert PYDANTIC_V2 @@ -168,6 +175,7 @@ def is_bytes_sequence_field(field: ModelField) -> bool: def is_scalar_field(field: ModelField) -> bool: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.is_scalar_field(field) else: assert PYDANTIC_V2 @@ -179,6 +187,7 @@ def is_scalar_field(field: ModelField) -> bool: def is_scalar_sequence_field(field: ModelField) -> bool: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.is_scalar_sequence_field(field) else: assert PYDANTIC_V2 @@ -190,6 +199,7 @@ def is_scalar_sequence_field(field: ModelField) -> bool: def is_sequence_field(field: ModelField) -> bool: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.is_sequence_field(field) else: assert PYDANTIC_V2 @@ -201,6 +211,7 @@ def is_sequence_field(field: ModelField) -> bool: def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 + return v1.serialize_sequence_value(field=field, value=value) else: assert PYDANTIC_V2 @@ -212,6 +223,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: def _model_rebuild(model: Type[BaseModel]) -> None: if lenient_issubclass(model, may_v1.BaseModel): from fastapi._compat import v1 + v1._model_rebuild(model) elif PYDANTIC_V2: from . import v2 @@ -220,10 +232,15 @@ 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, may_v1.ModelField)] + v1_model_fields = [ + field for field in fields if isinstance(field, may_v1.ModelField) + ] if len(v1_model_fields) > 0: from fastapi._compat import v1 - v1_flat_models = v1.get_flat_models_from_fields(v1_model_fields, known_models=set()) # type: ignore[attr-defined] + + 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 else: all_flat_models = set() @@ -241,6 +258,7 @@ 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 @@ -251,7 +269,10 @@ def get_definitions( model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, ) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], may_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, may_v1.ModelField)] diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py index fc9c54c41..4d34fa176 100644 --- a/fastapi/_compat/may_v1.py +++ b/fastapi/_compat/may_v1.py @@ -1,104 +1,94 @@ import sys from typing import Any, Dict, List, Literal, Sequence, Tuple, Union -from fastapi.types import ModelNameMap +from fastapi.types import ModelNameMap if sys.version_info >= (3, 14): - class BaseConfig(): + + class BaseConfig: pass - class FieldInfo(): + class FieldInfo: pass - class BaseModel(): + class BaseModel: pass - class UndefinedType(): + class UndefinedType: pass - class ErrorWrapper(): + class ErrorWrapper: pass - class ModelField(): + class ModelField: pass - class JsonSchemaValue(): + class JsonSchemaValue: pass - class Color(): + class Color: pass - class NameEmail(): + class NameEmail: pass - class SecretBytes(): + class SecretBytes: pass - class SecretStr(): + class SecretStr: pass - class AnyUrl(): + class AnyUrl: pass - class Url(): + class Url: pass - class CoreSchema(): + class CoreSchema: pass - class GetJsonSchemaHandler(): + class GetJsonSchemaHandler: pass - class JsonSchemaValue(): + class JsonSchemaValue: pass - class Undefined(): + class Undefined: pass - class RequiredParam(): + class RequiredParam: pass def get_definitions( - *, - fields: List[ModelField], - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, + *, + fields: List[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, ) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], Dict[str, Dict[str, Any]], ]: return {}, {} - def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] + *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] ) -> List[Dict[str, Any]]: return [] - def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: return [] else: - from fastapi._compat import v1 from .v1 import BaseConfig as BaseConfig # type: ignore[assignment] - from .v1 import FieldInfo as FieldInfo - from .v1 import BaseModel - from .v1 import UndefinedType as UndefinedType - from .v1 import ErrorWrapper - from .v1 import ModelField - from .v1 import JsonSchemaValue - from .v1 import Color - from .v1 import NameEmail - from .v1 import SecretBytes - from .v1 import SecretStr - from .v1 import AnyUrl - from .v1 import Url - from .v1 import get_definitions - from .v1 import _regenerate_error_with_loc from .v1 import CoreSchema as CoreSchema + from .v1 import FieldInfo as FieldInfo from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler + from .v1 import ( + JsonSchemaValue, + ModelField, + ) from .v1 import JsonSchemaValue as JsonSchemaValue + from .v1 import UndefinedType as UndefinedType from .v1 import _normalize_errors as _normalize_errors - from .v1 import Undefined - from .v1 import RequiredParam diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index fa90518e9..cabf48228 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -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, may_v1.BaseModel, Mapping, UploadFile)) + lenient_issubclass( + annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile) + ) or _annotation_is_sequence(annotation) or is_dataclass(annotation) ) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 024c329bc..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, may_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 diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 2656abba0..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, - may_v1, value_is_sequence, ) from fastapi._compat.shared import annotation_is_pydantic_v1 @@ -397,9 +397,9 @@ def analyze_param( ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[FieldInfo, may_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 @@ -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, may_v1.RequiredParam, Undefined), + required=field_info.default + in (RequiredParam, may_v1.RequiredParam, Undefined), field_info=field_info, ) if is_path_param: diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index f9cc9b8e6..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.shared import PYDANTIC_VERSION_MINOR_TUPLE from ._compat.may_v1 import FieldInfo, Undefined +from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE _Unset: Any = Undefined diff --git a/fastapi/utils.py b/fastapi/utils.py index 12ae2a4ed..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, - may_v1 + may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType from pydantic import BaseModel @@ -106,6 +106,7 @@ def create_model_field( or version == "1" ): from fastapi._compat import v1 + try: return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] except RuntimeError: @@ -124,6 +125,7 @@ def create_model_field( # 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: @@ -142,6 +144,7 @@ def create_cloned_field( 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 16d264d3e..0184c9a2e 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -139,6 +139,7 @@ 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]] @@ -150,6 +151,7 @@ def test_is_pv1_scalar_field(): @needs_py_lt_314 def test_get_model_fields_cached(): from fastapi._compat import v1 + class Model(may_v1.BaseModel): foo: str diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index 84d2dc530..6601585ef 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -165,6 +165,7 @@ 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 74a872da8..1745c69b6 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -515,6 +515,7 @@ def test_invalid_response_model_field(): @needs_pydanticv1 def test_invalid_response_model_field_pv1(): from fastapi._compat import v1 + app = FastAPI() class Model(v1.BaseModel):