From eb43072f09548fba2052e646c30ec121c86d052f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 14 Oct 2025 13:56:20 +0200 Subject: [PATCH] introduce may_v1 to introduce dummies for Python 3.14 --- fastapi/_compat/__init__.py | 8 +- fastapi/_compat/main.py | 71 +++++++----- fastapi/_compat/may_v1.py | 104 ++++++++++++++++++ fastapi/_compat/shared.py | 8 +- fastapi/_compat/v2.py | 4 +- fastapi/dependencies/utils.py | 18 +-- fastapi/encoders.py | 18 +-- fastapi/temp_pydantic_v1_params.py | 2 +- fastapi/utils.py | 12 +- tests/test_compat.py | 10 +- ...t_get_model_definitions_formfeed_escape.py | 2 +- ...est_response_model_as_return_annotation.py | 5 +- 12 files changed, 200 insertions(+), 62 deletions(-) create mode 100644 fastapi/_compat/may_v1.py diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index b2ae5adc7..06ab55a21 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -44,7 +44,7 @@ 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 +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 3f758f072..b38264117 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -8,7 +8,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 +50,8 @@ 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 +60,8 @@ 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): + from fastapi._compat import v1 return True elif PYDANTIC_V2: from . import v2 @@ -69,7 +71,8 @@ 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 +83,8 @@ 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 +93,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 +103,8 @@ 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 +116,8 @@ 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 +129,8 @@ 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 +144,8 @@ 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 +155,8 @@ 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 +166,8 @@ 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 +177,8 @@ 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 +188,8 @@ 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 +199,8 @@ 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 +210,8 @@ 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 +220,13 @@ 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 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] + all_flat_models = v1_flat_models + else: + all_flat_models = set() if PYDANTIC_V2: from . import v2 @@ -222,6 +240,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 @@ -232,11 +251,11 @@ 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( + 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, @@ -266,7 +285,9 @@ def get_schema_from_model_field( ], 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 +307,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 +317,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..fc9c54c41 --- /dev/null +++ b/fastapi/_compat/may_v1.py @@ -0,0 +1,104 @@ +import sys +from typing import Any, Dict, List, Literal, Sequence, Tuple, Union +from fastapi.types import ModelNameMap + + +if sys.version_info >= (3, 14): + class BaseConfig(): + pass + + class FieldInfo(): + pass + + class BaseModel(): + pass + + class UndefinedType(): + pass + + class ErrorWrapper(): + pass + + class ModelField(): + pass + + class JsonSchemaValue(): + pass + + class Color(): + pass + + class NameEmail(): + pass + + class SecretBytes(): + pass + + class SecretStr(): + pass + + class AnyUrl(): + pass + + class Url(): + pass + + class CoreSchema(): + pass + + class GetJsonSchemaHandler(): + pass + + class JsonSchemaValue(): + pass + + class Undefined(): + pass + + class RequiredParam(): + pass + + 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 {}, {} + + + def _regenerate_error_with_loc( + *, 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 GetJsonSchemaHandler as GetJsonSchemaHandler + from .v1 import JsonSchemaValue as JsonSchemaValue + 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 495d5c5f7..fa90518e9 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,7 @@ 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 +195,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/v2.py b/fastapi/_compat/v2.py index 29606b9f3..024c329bc 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 shared, may_v1 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..2656abba0 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -45,7 +45,7 @@ from fastapi._compat import ( lenient_issubclass, sequence_types, serialize_sequence_value, - v1, + may_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_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,7 @@ 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 +741,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..f9cc9b8e6 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -6,7 +6,7 @@ from fastapi.params import ParamTypes from typing_extensions import Annotated, deprecated from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE -from ._compat.v1 import FieldInfo, Undefined +from ._compat.may_v1 import FieldInfo, Undefined _Unset: Any = Undefined diff --git a/fastapi/utils.py b/fastapi/utils.py index 3ea9271b1..12ae2a4ed 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,10 @@ 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 +123,7 @@ 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 +140,8 @@ 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..16d264d3e 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,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]] @@ -143,8 +147,10 @@ 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..84d2dc530 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,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 c3c0ed6c4..74a872da8 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,9 @@ 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):