about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/fastapi/_compat.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/fastapi/_compat.py')
-rw-r--r--.venv/lib/python3.12/site-packages/fastapi/_compat.py659
1 files changed, 659 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/fastapi/_compat.py b/.venv/lib/python3.12/site-packages/fastapi/_compat.py
new file mode 100644
index 00000000..c07e4a3b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/fastapi/_compat.py
@@ -0,0 +1,659 @@
+from collections import deque
+from copy import copy
+from dataclasses import dataclass, is_dataclass
+from enum import Enum
+from functools import lru_cache
+from typing import (
+    Any,
+    Callable,
+    Deque,
+    Dict,
+    FrozenSet,
+    List,
+    Mapping,
+    Sequence,
+    Set,
+    Tuple,
+    Type,
+    Union,
+)
+
+from fastapi.exceptions import RequestErrorModel
+from fastapi.types import IncEx, ModelNameMap, UnionType
+from pydantic import BaseModel, create_model
+from pydantic.version import VERSION as PYDANTIC_VERSION
+from starlette.datastructures import UploadFile
+from typing_extensions import Annotated, Literal, get_args, get_origin
+
+PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
+PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
+
+
+sequence_annotation_to_type = {
+    Sequence: list,
+    List: list,
+    list: list,
+    Tuple: tuple,
+    tuple: tuple,
+    Set: set,
+    set: set,
+    FrozenSet: frozenset,
+    frozenset: frozenset,
+    Deque: deque,
+    deque: deque,
+}
+
+sequence_types = tuple(sequence_annotation_to_type.keys())
+
+Url: Type[Any]
+
+if PYDANTIC_V2:
+    from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
+    from pydantic import TypeAdapter
+    from pydantic import ValidationError as ValidationError
+    from pydantic._internal._schema_generation_shared import (  # type: ignore[attr-defined]
+        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
+    from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema
+    from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
+    from pydantic_core import CoreSchema as CoreSchema
+    from pydantic_core import PydanticUndefined, PydanticUndefinedType
+    from pydantic_core import Url as Url
+
+    try:
+        from pydantic_core.core_schema import (
+            with_info_plain_validator_function as with_info_plain_validator_function,
+        )
+    except ImportError:  # pragma: no cover
+        from pydantic_core.core_schema import (
+            general_plain_validator_function as with_info_plain_validator_function,  # noqa: F401
+        )
+
+    RequiredParam = PydanticUndefined
+    Undefined = PydanticUndefined
+    UndefinedType = PydanticUndefinedType
+    evaluate_forwardref = eval_type_lenient
+    Validator = Any
+
+    class BaseConfig:
+        pass
+
+    class ErrorWrapper(Exception):
+        pass
+
+    @dataclass
+    class ModelField:
+        field_info: FieldInfo
+        name: str
+        mode: Literal["validation", "serialization"] = "validation"
+
+        @property
+        def alias(self) -> str:
+            a = self.field_info.alias
+            return a if a is not None else self.name
+
+        @property
+        def required(self) -> bool:
+            return self.field_info.is_required()
+
+        @property
+        def default(self) -> Any:
+            return self.get_default()
+
+        @property
+        def type_(self) -> Any:
+            return self.field_info.annotation
+
+        def __post_init__(self) -> None:
+            self._type_adapter: TypeAdapter[Any] = TypeAdapter(
+                Annotated[self.field_info.annotation, self.field_info]
+            )
+
+        def get_default(self) -> Any:
+            if self.field_info.is_required():
+                return Undefined
+            return self.field_info.get_default(call_default_factory=True)
+
+        def validate(
+            self,
+            value: Any,
+            values: Dict[str, Any] = {},  # noqa: B006
+            *,
+            loc: Tuple[Union[int, str], ...] = (),
+        ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]:
+            try:
+                return (
+                    self._type_adapter.validate_python(value, from_attributes=True),
+                    None,
+                )
+            except ValidationError as exc:
+                return None, _regenerate_error_with_loc(
+                    errors=exc.errors(include_url=False), loc_prefix=loc
+                )
+
+        def serialize(
+            self,
+            value: Any,
+            *,
+            mode: Literal["json", "python"] = "json",
+            include: Union[IncEx, None] = None,
+            exclude: Union[IncEx, None] = None,
+            by_alias: bool = True,
+            exclude_unset: bool = False,
+            exclude_defaults: bool = False,
+            exclude_none: bool = False,
+        ) -> Any:
+            # What calls this code passes a value that already called
+            # self._type_adapter.validate_python(value)
+            return self._type_adapter.dump_python(
+                value,
+                mode=mode,
+                include=include,
+                exclude=exclude,
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+
+        def __hash__(self) -> int:
+            # Each ModelField is unique for our purposes, to allow making a dict from
+            # ModelField to its JSON Schema.
+            return id(self)
+
+    def get_annotation_from_field_info(
+        annotation: Any, field_info: FieldInfo, field_name: str
+    ) -> Any:
+        return annotation
+
+    def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
+        return errors  # type: ignore[return-value]
+
+    def _model_rebuild(model: Type[BaseModel]) -> None:
+        model.model_rebuild()
+
+    def _model_dump(
+        model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
+    ) -> Any:
+        return model.model_dump(mode=mode, **kwargs)
+
+    def _get_model_config(model: BaseModel) -> Any:
+        return model.model_config
+
+    def get_schema_from_model_field(
+        *,
+        field: ModelField,
+        schema_generator: GenerateJsonSchema,
+        model_name_map: ModelNameMap,
+        field_mapping: Dict[
+            Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+        ],
+        separate_input_output_schemas: bool = True,
+    ) -> Dict[str, Any]:
+        override_mode: Union[Literal["validation"], None] = (
+            None if separate_input_output_schemas else "validation"
+        )
+        # This expects that GenerateJsonSchema was already used to generate the definitions
+        json_schema = field_mapping[(field, override_mode or field.mode)]
+        if "$ref" not in json_schema:
+            # TODO remove when deprecating Pydantic v1
+            # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
+            json_schema["title"] = (
+                field.field_info.title or field.alias.title().replace("_", " ")
+            )
+        return json_schema
+
+    def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
+        return {}
+
+    def get_definitions(
+        *,
+        fields: List[ModelField],
+        schema_generator: GenerateJsonSchema,
+        model_name_map: ModelNameMap,
+        separate_input_output_schemas: bool = True,
+    ) -> Tuple[
+        Dict[
+            Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+        ],
+        Dict[str, Dict[str, Any]],
+    ]:
+        override_mode: Union[Literal["validation"], None] = (
+            None if separate_input_output_schemas else "validation"
+        )
+        inputs = [
+            (field, override_mode or field.mode, field._type_adapter.core_schema)
+            for field in fields
+        ]
+        field_mapping, definitions = schema_generator.generate_definitions(
+            inputs=inputs
+        )
+        return field_mapping, definitions  # type: ignore[return-value]
+
+    def is_scalar_field(field: ModelField) -> bool:
+        from fastapi import params
+
+        return field_annotation_is_scalar(
+            field.field_info.annotation
+        ) and not isinstance(field.field_info, params.Body)
+
+    def is_sequence_field(field: ModelField) -> bool:
+        return field_annotation_is_sequence(field.field_info.annotation)
+
+    def is_scalar_sequence_field(field: ModelField) -> bool:
+        return field_annotation_is_scalar_sequence(field.field_info.annotation)
+
+    def is_bytes_field(field: ModelField) -> bool:
+        return is_bytes_or_nonable_bytes_annotation(field.type_)
+
+    def is_bytes_sequence_field(field: ModelField) -> bool:
+        return is_bytes_sequence_annotation(field.type_)
+
+    def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
+        cls = type(field_info)
+        merged_field_info = cls.from_annotation(annotation)
+        new_field_info = copy(field_info)
+        new_field_info.metadata = merged_field_info.metadata
+        new_field_info.annotation = merged_field_info.annotation
+        return new_field_info
+
+    def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
+        origin_type = (
+            get_origin(field.field_info.annotation) or field.field_info.annotation
+        )
+        assert issubclass(origin_type, sequence_types)  # type: ignore[arg-type]
+        return sequence_annotation_to_type[origin_type](value)  # type: ignore[no-any-return]
+
+    def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:
+        error = ValidationError.from_exception_data(
+            "Field required", [{"type": "missing", "loc": loc, "input": {}}]
+        ).errors(include_url=False)[0]
+        error["input"] = None
+        return error  # type: ignore[return-value]
+
+    def create_body_model(
+        *, fields: Sequence[ModelField], model_name: str
+    ) -> Type[BaseModel]:
+        field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields}
+        BodyModel: Type[BaseModel] = create_model(model_name, **field_params)  # type: ignore[call-overload]
+        return BodyModel
+
+    def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+        return [
+            ModelField(field_info=field_info, name=name)
+            for name, field_info in model.model_fields.items()
+        ]
+
+else:
+    from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
+    from pydantic import AnyUrl as Url  # noqa: F401
+    from pydantic import (  # type: ignore[assignment]
+        BaseConfig as BaseConfig,  # noqa: F401
+    )
+    from pydantic import ValidationError as ValidationError  # noqa: F401
+    from pydantic.class_validators import (  # type: ignore[no-redef]
+        Validator as Validator,  # noqa: F401
+    )
+    from pydantic.error_wrappers import (  # type: ignore[no-redef]
+        ErrorWrapper as ErrorWrapper,  # noqa: F401
+    )
+    from pydantic.errors import MissingError
+    from pydantic.fields import (  # type: ignore[attr-defined]
+        SHAPE_FROZENSET,
+        SHAPE_LIST,
+        SHAPE_SEQUENCE,
+        SHAPE_SET,
+        SHAPE_SINGLETON,
+        SHAPE_TUPLE,
+        SHAPE_TUPLE_ELLIPSIS,
+    )
+    from pydantic.fields import FieldInfo as FieldInfo
+    from pydantic.fields import (  # type: ignore[no-redef,attr-defined]
+        ModelField as ModelField,  # noqa: F401
+    )
+
+    # Keeping old "Required" functionality from Pydantic V1, without
+    # shadowing typing.Required.
+    RequiredParam: Any = Ellipsis  # type: ignore[no-redef]
+    from pydantic.fields import (  # type: ignore[no-redef,attr-defined]
+        Undefined as Undefined,
+    )
+    from pydantic.fields import (  # type: ignore[no-redef, attr-defined]
+        UndefinedType as UndefinedType,  # noqa: F401
+    )
+    from pydantic.schema import (
+        field_schema,
+        get_flat_models_from_fields,
+        get_model_name_map,
+        model_process_schema,
+    )
+    from pydantic.schema import (  # type: ignore[no-redef]  # noqa: F401
+        get_annotation_from_field_info as get_annotation_from_field_info,
+    )
+    from pydantic.typing import (  # type: ignore[no-redef]
+        evaluate_forwardref as evaluate_forwardref,  # noqa: F401
+    )
+    from pydantic.utils import (  # type: ignore[no-redef]
+        lenient_issubclass as lenient_issubclass,  # noqa: F401
+    )
+
+    GetJsonSchemaHandler = Any  # type: ignore[assignment,misc]
+    JsonSchemaValue = Dict[str, Any]  # type: ignore[misc]
+    CoreSchema = Any  # type: ignore[assignment,misc]
+
+    sequence_shapes = {
+        SHAPE_LIST,
+        SHAPE_SET,
+        SHAPE_FROZENSET,
+        SHAPE_TUPLE,
+        SHAPE_SEQUENCE,
+        SHAPE_TUPLE_ELLIPSIS,
+    }
+    sequence_shape_to_type = {
+        SHAPE_LIST: list,
+        SHAPE_SET: set,
+        SHAPE_TUPLE: tuple,
+        SHAPE_SEQUENCE: list,
+        SHAPE_TUPLE_ELLIPSIS: list,
+    }
+
+    @dataclass
+    class GenerateJsonSchema:  # type: ignore[no-redef]
+        ref_template: str
+
+    class PydanticSchemaGenerationError(Exception):  # type: ignore[no-redef]
+        pass
+
+    def with_info_plain_validator_function(  # type: ignore[misc]
+        function: Callable[..., Any],
+        *,
+        ref: Union[str, None] = None,
+        metadata: Any = None,
+        serialization: Any = None,
+    ) -> Any:
+        return {}
+
+    def get_model_definitions(
+        *,
+        flat_models: Set[Union[Type[BaseModel], Type[Enum]]],
+        model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
+    ) -> Dict[str, Any]:
+        definitions: Dict[str, Dict[str, Any]] = {}
+        for model in flat_models:
+            m_schema, m_definitions, m_nested_models = model_process_schema(
+                model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
+            )
+            definitions.update(m_definitions)
+            model_name = model_name_map[model]
+            if "description" in m_schema:
+                m_schema["description"] = m_schema["description"].split("\f")[0]
+            definitions[model_name] = m_schema
+        return definitions
+
+    def is_pv1_scalar_field(field: ModelField) -> bool:
+        from fastapi import params
+
+        field_info = field.field_info
+        if not (
+            field.shape == SHAPE_SINGLETON  # type: ignore[attr-defined]
+            and not lenient_issubclass(field.type_, BaseModel)
+            and not lenient_issubclass(field.type_, dict)
+            and not field_annotation_is_sequence(field.type_)
+            and not is_dataclass(field.type_)
+            and not isinstance(field_info, params.Body)
+        ):
+            return False
+        if field.sub_fields:  # type: ignore[attr-defined]
+            if not all(
+                is_pv1_scalar_field(f)
+                for f in field.sub_fields  # type: ignore[attr-defined]
+            ):
+                return False
+        return True
+
+    def is_pv1_scalar_sequence_field(field: ModelField) -> bool:
+        if (field.shape in sequence_shapes) and not lenient_issubclass(  # type: ignore[attr-defined]
+            field.type_, BaseModel
+        ):
+            if field.sub_fields is not None:  # type: ignore[attr-defined]
+                for sub_field in field.sub_fields:  # type: ignore[attr-defined]
+                    if not is_pv1_scalar_field(sub_field):
+                        return False
+            return True
+        if _annotation_is_sequence(field.type_):
+            return True
+        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 _model_rebuild(model: Type[BaseModel]) -> None:
+        model.update_forward_refs()
+
+    def _model_dump(
+        model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
+    ) -> Any:
+        return model.dict(**kwargs)
+
+    def _get_model_config(model: BaseModel) -> Any:
+        return model.__config__  # type: ignore[attr-defined]
+
+    def get_schema_from_model_field(
+        *,
+        field: ModelField,
+        schema_generator: GenerateJsonSchema,
+        model_name_map: ModelNameMap,
+        field_mapping: Dict[
+            Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+        ],
+        separate_input_output_schemas: bool = True,
+    ) -> Dict[str, Any]:
+        # This expects that GenerateJsonSchema was already used to generate the definitions
+        return field_schema(  # type: ignore[no-any-return]
+            field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
+        )[0]
+
+    def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
+        models = get_flat_models_from_fields(fields, known_models=set())
+        return get_model_name_map(models)  # type: ignore[no-any-return]
+
+    def get_definitions(
+        *,
+        fields: List[ModelField],
+        schema_generator: GenerateJsonSchema,
+        model_name_map: ModelNameMap,
+        separate_input_output_schemas: bool = True,
+    ) -> Tuple[
+        Dict[
+            Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+        ],
+        Dict[str, Dict[str, Any]],
+    ]:
+        models = get_flat_models_from_fields(fields, known_models=set())
+        return {}, get_model_definitions(
+            flat_models=models, model_name_map=model_name_map
+        )
+
+    def is_scalar_field(field: ModelField) -> bool:
+        return is_pv1_scalar_field(field)
+
+    def is_sequence_field(field: ModelField) -> bool:
+        return field.shape in sequence_shapes or _annotation_is_sequence(field.type_)  # type: ignore[attr-defined]
+
+    def is_scalar_sequence_field(field: ModelField) -> bool:
+        return is_pv1_scalar_sequence_field(field)
+
+    def is_bytes_field(field: ModelField) -> bool:
+        return lenient_issubclass(field.type_, bytes)
+
+    def is_bytes_sequence_field(field: ModelField) -> bool:
+        return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes)  # type: ignore[attr-defined]
+
+    def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
+        return copy(field_info)
+
+    def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
+        return sequence_shape_to_type[field.shape](value)  # type: ignore[no-any-return,attr-defined]
+
+    def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:
+        missing_field_error = ErrorWrapper(MissingError(), loc=loc)  # type: ignore[call-arg]
+        new_error = ValidationError([missing_field_error], RequestErrorModel)
+        return new_error.errors()[0]  # type: ignore[return-value]
+
+    def create_body_model(
+        *, fields: Sequence[ModelField], model_name: str
+    ) -> Type[BaseModel]:
+        BodyModel = create_model(model_name)
+        for f in fields:
+            BodyModel.__fields__[f.name] = f  # type: ignore[index]
+        return BodyModel
+
+    def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+        return list(model.__fields__.values())  # type: ignore[attr-defined]
+
+
+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 _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
+    if lenient_issubclass(annotation, (str, bytes)):
+        return False
+    return lenient_issubclass(annotation, sequence_types)
+
+
+def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        for arg in get_args(annotation):
+            if field_annotation_is_sequence(arg):
+                return True
+        return False
+    return _annotation_is_sequence(annotation) or _annotation_is_sequence(
+        get_origin(annotation)
+    )
+
+
+def value_is_sequence(value: Any) -> bool:
+    return isinstance(value, sequence_types) and not isinstance(value, (str, bytes))  # type: ignore[arg-type]
+
+
+def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
+    return (
+        lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
+        or _annotation_is_sequence(annotation)
+        or is_dataclass(annotation)
+    )
+
+
+def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
+
+    return (
+        _annotation_is_complex(annotation)
+        or _annotation_is_complex(origin)
+        or hasattr(origin, "__pydantic_core_schema__")
+        or hasattr(origin, "__get_pydantic_core_schema__")
+    )
+
+
+def field_annotation_is_scalar(annotation: Any) -> bool:
+    # handle Ellipsis here to make tuple[int, ...] work nicely
+    return annotation is Ellipsis or not field_annotation_is_complex(annotation)
+
+
+def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        at_least_one_scalar_sequence = False
+        for arg in get_args(annotation):
+            if field_annotation_is_scalar_sequence(arg):
+                at_least_one_scalar_sequence = True
+                continue
+            elif not field_annotation_is_scalar(arg):
+                return False
+        return at_least_one_scalar_sequence
+    return field_annotation_is_sequence(annotation) and all(
+        field_annotation_is_scalar(sub_annotation)
+        for sub_annotation in get_args(annotation)
+    )
+
+
+def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool:
+    if lenient_issubclass(annotation, bytes):
+        return True
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        for arg in get_args(annotation):
+            if lenient_issubclass(arg, bytes):
+                return True
+    return False
+
+
+def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:
+    if lenient_issubclass(annotation, UploadFile):
+        return True
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        for arg in get_args(annotation):
+            if lenient_issubclass(arg, UploadFile):
+                return True
+    return False
+
+
+def is_bytes_sequence_annotation(annotation: Any) -> bool:
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        at_least_one = False
+        for arg in get_args(annotation):
+            if is_bytes_sequence_annotation(arg):
+                at_least_one = True
+                continue
+        return at_least_one
+    return field_annotation_is_sequence(annotation) and all(
+        is_bytes_or_nonable_bytes_annotation(sub_annotation)
+        for sub_annotation in get_args(annotation)
+    )
+
+
+def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
+    origin = get_origin(annotation)
+    if origin is Union or origin is UnionType:
+        at_least_one = False
+        for arg in get_args(annotation):
+            if is_uploadfile_sequence_annotation(arg):
+                at_least_one = True
+                continue
+        return at_least_one
+    return field_annotation_is_sequence(annotation) and all(
+        is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
+        for sub_annotation in get_args(annotation)
+    )
+
+
+@lru_cache
+def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+    return get_model_fields(model)