diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py index 94008d9a0..859126344 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -31,24 +31,26 @@ if PYDANTIC_V2: with_info_plain_validator_function as with_info_plain_validator_function, ) else: - from .v1 import BaseConfig as BaseConfig + from .v1 import BaseConfig as BaseConfig # type: ignore[assignment] from .v1 import FieldInfo as FieldInfo - from .v1 import PydanticSchemaGenerationError as PydanticSchemaGenerationError + from .v1 import ( # type: ignore[assignment] + PydanticSchemaGenerationError as PydanticSchemaGenerationError, + ) from .v1 import RequiredParam as RequiredParam from .v1 import Undefined as Undefined from .v1 import UndefinedType as UndefinedType - from .v1 import Url as Url + from .v1 import Url as Url # type: ignore[attr-defined, no-redef] from .v1 import Validator as Validator from .v1 import evaluate_forwardref as evaluate_forwardref from .v1 import get_missing_field_error as get_missing_field_error - from .v1 import ( + from .v1 import ( # type: ignore[assignment] with_info_plain_validator_function as with_info_plain_validator_function, ) @lru_cache def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: - if lenient_issubclass(model, v1.BaseModel): # type: ignore[attr-defined] + if lenient_issubclass(model, v1.BaseModel): return v1.get_model_fields(model) else: from . import v2 @@ -67,7 +69,7 @@ def _is_undefined(value: object) -> bool: def _get_model_config(model: BaseModel) -> Any: - if isinstance(model, v1.BaseModel): # type: ignore[attr-defined] + if isinstance(model, v1.BaseModel): return v1._get_model_config(model) elif PYDANTIC_V2: from . import v2 @@ -78,7 +80,7 @@ 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): # type: ignore[attr-defined] + if isinstance(model, v1.BaseModel): return v1._model_dump(model, mode=mode, **kwargs) elif PYDANTIC_V2: from . import v2 @@ -195,7 +197,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: def _model_rebuild(model: Type[BaseModel]) -> None: - if lenient_issubclass(model, v1.BaseModel): # type: ignore[attr-defined] + if lenient_issubclass(model, v1.BaseModel): v1._model_rebuild(model) elif PYDANTIC_V2: from . import v2 @@ -295,7 +297,7 @@ def _is_model_field(value: Any) -> bool: def _is_model_class(value: Any) -> bool: - if lenient_issubclass(value, v1.BaseModel): # type: ignore[attr-defined] + if lenient_issubclass(value, v1.BaseModel): return True elif PYDANTIC_V2: from . import v2 diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index 50c847d4e..495d5c5f7 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -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)) # type: ignore[attr-defined] + lenient_issubclass(annotation, (BaseModel, 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): # type: ignore[attr-defined] + if lenient_issubclass(annotation, 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): # type: ignore[attr-defined] + if lenient_issubclass(arg, 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 bf20d81bf..f0ac51634 100644 --- a/fastapi/_compat/v1.py +++ b/fastapi/_compat/v1.py @@ -26,19 +26,13 @@ PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 RequiredParam: Any = Ellipsis if not PYDANTIC_V2: - from pydantic import AnyUrl as Url # noqa: F401 - from pydantic import ( - BaseConfig as BaseConfig, # noqa: F401 - ) - from pydantic import BaseModel, create_model - from pydantic import ValidationError as ValidationError # noqa: F401 - from pydantic.class_validators import ( - Validator as Validator, # noqa: F401 - ) - from pydantic.color import Color as Color # noqa: F401 - from pydantic.error_wrappers import ( - ErrorWrapper as ErrorWrapper, # noqa: F401 - ) + from pydantic import BaseConfig as BaseConfig + from pydantic import BaseModel as BaseModel + from pydantic import ValidationError as ValidationError + from pydantic import create_model as create_model + from pydantic.class_validators import Validator as Validator + from pydantic.color import Color as Color + from pydantic.error_wrappers import ErrorWrapper as ErrorWrapper from pydantic.errors import MissingError from pydantic.fields import ( # type: ignore[attr-defined] SHAPE_FROZENSET, @@ -52,11 +46,11 @@ if not PYDANTIC_V2: from pydantic.fields import FieldInfo as FieldInfo from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined] from pydantic.fields import Undefined as Undefined # type: ignore[attr-defined] - from pydantic.fields import ( - UndefinedType as UndefinedType, # type: ignore[attr-defined] # noqa: F401 + from pydantic.fields import ( # type: ignore[attr-defined] + UndefinedType as UndefinedType, ) - from pydantic.networks import AnyUrl as AnyUrl # noqa: F401 - from pydantic.networks import NameEmail as NameEmail # noqa: F401 + from pydantic.networks import AnyUrl as AnyUrl + from pydantic.networks import NameEmail as NameEmail from pydantic.schema import TypeModelSet as TypeModelSet from pydantic.schema import ( field_schema, @@ -64,33 +58,26 @@ if not PYDANTIC_V2: model_process_schema, ) from pydantic.schema import ( - get_annotation_from_field_info as get_annotation_from_field_info, # noqa: F401 + 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_model_name_map as get_model_name_map # noqa: F401 - from pydantic.types import SecretBytes as SecretBytes # noqa: F401 - from pydantic.types import SecretStr as SecretStr # noqa: F401 - from pydantic.typing import evaluate_forwardref as evaluate_forwardref # noqa: F401 - from pydantic.utils import lenient_issubclass as lenient_issubclass # noqa: F401 + 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 + from pydantic.typing import evaluate_forwardref as evaluate_forwardref + from pydantic.utils import lenient_issubclass as lenient_issubclass else: - from pydantic.v1 import AnyUrl as Url # noqa: F401 - from pydantic.v1 import ( - BaseConfig as BaseConfig, # type: ignore[assignment] # noqa: F401 - ) - from pydantic.v1 import ( - BaseModel as BaseModel, # type: ignore[assignment] # noqa: F401 - ) - from pydantic.v1 import ( - ValidationError as ValidationError, # type: ignore[assignment] # noqa: F401 + from pydantic.v1 import BaseConfig as BaseConfig # type: ignore[assignment] + from pydantic.v1 import BaseModel as BaseModel # type: ignore[assignment] + from pydantic.v1 import ( # type: ignore[assignment] + ValidationError as ValidationError, ) - from pydantic.v1 import create_model as create_model # noqa: F401 - from pydantic.v1.class_validators import Validator as Validator # noqa: F401 - from pydantic.v1.color import ( - Color as Color, # type: ignore[assignment] # noqa: F401 - ) - from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper # noqa: F401 + from pydantic.v1 import create_model as create_model # type: ignore[no-redef] + from pydantic.v1.class_validators import Validator as Validator + from pydantic.v1.color import Color as Color # type: ignore[assignment] + from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper from pydantic.v1.errors import MissingError from pydantic.v1.fields import ( SHAPE_FROZENSET, @@ -102,12 +89,12 @@ else: SHAPE_TUPLE_ELLIPSIS, ) from pydantic.v1.fields import FieldInfo as FieldInfo # type: ignore[assignment] - from pydantic.v1.fields import ModelField as ModelField # noqa: F401 + from pydantic.v1.fields import ModelField as ModelField from pydantic.v1.fields import Undefined as Undefined - from pydantic.v1.fields import UndefinedType as UndefinedType # noqa: F401 - from pydantic.v1.networks import AnyUrl as AnyUrl # noqa: F401 - from pydantic.v1.networks import ( - NameEmail as NameEmail, # type: ignore[assignment] # noqa: F401 + from pydantic.v1.fields import UndefinedType as UndefinedType + from pydantic.v1.networks import AnyUrl as AnyUrl + from pydantic.v1.networks import ( # type: ignore[assignment] + NameEmail as NameEmail, ) from pydantic.v1.schema import TypeModelSet as TypeModelSet from pydantic.v1.schema import ( @@ -116,30 +103,26 @@ else: model_process_schema, ) from pydantic.v1.schema import ( - get_annotation_from_field_info as get_annotation_from_field_info, # noqa: F401 - ) - from pydantic.v1.schema import ( - get_flat_models_from_field as get_flat_models_from_field, # noqa: F401 + get_annotation_from_field_info as get_annotation_from_field_info, ) from pydantic.v1.schema import ( - get_model_name_map as get_model_name_map, # noqa: F401 + get_flat_models_from_field as get_flat_models_from_field, ) - from pydantic.v1.types import ( - SecretBytes as SecretBytes, # type: ignore[assignment] # noqa: F401 + 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, ) - from pydantic.v1.types import ( - SecretStr as SecretStr, # type: ignore[assignment] # noqa: F401 + from pydantic.v1.types import ( # type: ignore[assignment] + SecretStr as SecretStr, ) - from pydantic.v1.typing import ( - evaluate_forwardref as evaluate_forwardref, # noqa: F401 - ) - from pydantic.v1.utils import lenient_issubclass as lenient_issubclass # noqa: F401 + from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref + from pydantic.v1.utils import lenient_issubclass as lenient_issubclass GetJsonSchemaHandler = Any JsonSchemaValue = Dict[str, Any] CoreSchema = Any - +Url = AnyUrl sequence_shapes = { SHAPE_LIST, diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index c5ac1f660..85800d18f 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -27,7 +27,7 @@ from pydantic._internal._schema_generation_shared import ( # type: ignore[attr- ) 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.fields import FieldInfo as FieldInfo from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic_core import CoreSchema as CoreSchema @@ -196,7 +196,7 @@ def get_schema_from_model_field( def get_definitions( *, - fields: List[ModelField], + fields: Sequence[ModelField], model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, ) -> Tuple[ @@ -219,7 +219,7 @@ def get_definitions( inputs = [ (field, override_mode or field.mode, field._type_adapter.core_schema) - for field in fields + list(unique_flat_model_fields) + for field in list(fields) + list(unique_flat_model_fields) ] field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) for item_def in cast(Dict[str, Dict[str, Any]], definitions).values(): @@ -277,25 +277,30 @@ def _remap_definitions_and_field_mappings( field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], -): +) -> Tuple[ + Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[str, Any], +]: old_name_to_new_name_map = {} - for key, value in field_mapping.items(): - model = key[0].type_ + for field_key, schema in field_mapping.items(): + model = field_key[0].type_ if model not in model_name_map: continue new_name = model_name_map[model] - old_name = value["$ref"].split("/")[-1] + old_name = schema["$ref"].split("/")[-1] if old_name in {f"{new_name}-Input", f"{new_name}-Output"}: continue old_name_to_new_name_map[old_name] = new_name - new_field_mapping = {} - for key, value in field_mapping.items(): - new_value = _replace_refs( - schema=value, + new_field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ] = {} + for field_key, schema in field_mapping.items(): + new_schema = _replace_refs( + schema=schema, old_name_to_new_name_map=old_name_to_new_name_map, ) - new_field_mapping[key] = new_value + new_field_mapping[field_key] = new_schema new_definitions = {} for key, value in definitions.items(): diff --git a/fastapi/encoders.py b/fastapi/encoders.py index b8b6f400a..29acd7621 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -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)): # type: ignore[attr-defined] + if isinstance(obj, (BaseModel, v1.BaseModel)): # TODO: remove when deprecating Pydantic v1 encoders: Dict[Any, Any] = {} - if isinstance(obj, v1.BaseModel): # type: ignore[attr-defined] + if isinstance(obj, v1.BaseModel): encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] if custom_encoder: encoders = {**encoders, **custom_encoder} diff --git a/fastapi/utils.py b/fastapi/utils.py index eb9691682..8402d8e19 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -144,10 +144,10 @@ def create_cloned_field( original_type = original_type.__pydantic_model__ use_type = original_type if lenient_issubclass(original_type, v1.BaseModel): - original_type = cast(Type[v1.BaseModel], original_type) # type: ignore[name-defined] + original_type = cast(Type[v1.BaseModel], original_type) use_type = cloned_types.get(original_type) if use_type is None: - use_type = v1.create_model(original_type.__name__, __base__=original_type) # type: ignore[attr-defined] + use_type = v1.create_model(original_type.__name__, __base__=original_type) cloned_types[original_type] = use_type for f in original_type.__fields__.values(): use_type.__fields__[f.name] = create_cloned_field(