diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index 4e1b7bcb6e..c009da8fdc 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -8,6 +8,7 @@ from dataclasses import is_dataclass from typing import ( Annotated, Any, + TypeVar, Union, ) @@ -15,7 +16,9 @@ from fastapi.types import UnionType from pydantic import BaseModel from pydantic.version import VERSION as PYDANTIC_VERSION from starlette.datastructures import UploadFile -from typing_extensions import get_args, get_origin +from typing_extensions import TypeGuard, get_args, get_origin + +_T = TypeVar("_T") # Copy from Pydantic: pydantic/_internal/_typing_extra.py if sys.version_info < (3, 10): @@ -39,13 +42,13 @@ sequence_annotation_to_type = { deque: deque, } -sequence_types = tuple(sequence_annotation_to_type.keys()) +sequence_types: tuple[type[Any], ...] = tuple(sequence_annotation_to_type.keys()) -# Copy of Pydantic: pydantic/_internal/_utils.py +# Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard def lenient_issubclass( - cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...], None] -) -> bool: + cls: Any, class_or_tuple: Union[type[_T], tuple[type[_T], ...], None] +) -> TypeGuard[type[_T]]: try: return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] except TypeError: # pragma: no cover diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 0a1961e707..efc20fa58b 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -12,7 +12,7 @@ from typing import ( cast, ) -from fastapi._compat import shared +from fastapi._compat import lenient_issubclass, shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap, UnionType from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model @@ -23,7 +23,6 @@ from pydantic._internal._schema_generation_shared import ( # type: ignore[attr- GetJsonSchemaHandler as GetJsonSchemaHandler, ) from pydantic._internal._typing_extra import eval_type_lenient -from pydantic._internal._utils import lenient_issubclass as lenient_issubclass from pydantic.fields import FieldInfo as FieldInfo from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue @@ -416,10 +415,11 @@ def get_flat_models_from_annotation( origin = get_origin(annotation) if origin is not None: for arg in get_args(annotation): - if lenient_issubclass(arg, (BaseModel, Enum)) and arg not in known_models: - known_models.add(arg) - if lenient_issubclass(arg, BaseModel): - get_flat_models_from_model(arg, known_models=known_models) + if lenient_issubclass(arg, (BaseModel, Enum)): + if arg not in known_models: + known_models.add(arg) # type: ignore[arg-type] + if lenient_issubclass(arg, BaseModel): + get_flat_models_from_model(arg, known_models=known_models) else: get_flat_models_from_annotation(arg, known_models=known_models) return known_models