Browse Source

♻️ Refactor ModelField.type_, use internal parts

pull/14862/head
Sebastián Ramírez 5 months ago
parent
commit
7231a0b694
  1. 14
      fastapi/_compat/v2.py
  2. 22
      fastapi/dependencies/utils.py
  3. 2
      fastapi/openapi/utils.py

14
fastapi/_compat/v2.py

@ -110,10 +110,6 @@ class ModelField:
def default(self) -> Any: def default(self) -> Any:
return self.get_default() return self.get_default()
@property
def type_(self) -> Any:
return self.field_info.annotation
def __post_init__(self) -> None: def __post_init__(self) -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
# Pydantic >= 2.12.0 warns about field specific metadata that is unused # Pydantic >= 2.12.0 warns about field specific metadata that is unused
@ -267,9 +263,9 @@ def get_definitions(
for model in flat_serialization_models for model in flat_serialization_models
] ]
flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields
input_types = {f.type_ for f in fields} input_types = {f.field_info.annotation for f in fields}
unique_flat_model_fields = { unique_flat_model_fields = {
f for f in flat_model_fields if f.type_ not in input_types f for f in flat_model_fields if f.field_info.annotation not in input_types
} }
inputs = [ inputs = [
( (
@ -313,11 +309,11 @@ def is_scalar_sequence_field(field: ModelField) -> bool:
def is_bytes_field(field: ModelField) -> bool: def is_bytes_field(field: ModelField) -> bool:
return shared.is_bytes_or_nonable_bytes_annotation(field.type_) return shared.is_bytes_or_nonable_bytes_annotation(field.field_info.annotation)
def is_bytes_sequence_field(field: ModelField) -> bool: def is_bytes_sequence_field(field: ModelField) -> bool:
return shared.is_bytes_sequence_annotation(field.type_) return shared.is_bytes_sequence_annotation(field.field_info.annotation)
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
@ -428,7 +424,7 @@ def get_flat_models_from_annotation(
def get_flat_models_from_field( def get_flat_models_from_field(
field: ModelField, known_models: TypeModelSet field: ModelField, known_models: TypeModelSet
) -> TypeModelSet: ) -> TypeModelSet:
field_type = field.type_ field_type = field.field_info.annotation
if lenient_issubclass(field_type, BaseModel): if lenient_issubclass(field_type, BaseModel):
if field_type in known_models: if field_type in known_models:
return known_models return known_models

22
fastapi/dependencies/utils.py

@ -182,8 +182,10 @@ def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
if not fields: if not fields:
return fields return fields
first_field = fields[0] first_field = fields[0]
if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): if len(fields) == 1 and lenient_issubclass(
fields_to_extract = get_cached_model_fields(first_field.type_) first_field.field_info.annotation, BaseModel
):
fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
return fields_to_extract return fields_to_extract
return fields return fields
@ -522,7 +524,7 @@ def analyze_param(
assert ( assert (
is_scalar_field(field) is_scalar_field(field)
or is_scalar_sequence_field(field) or is_scalar_sequence_field(field)
or lenient_issubclass(field.type_, BaseModel) or lenient_issubclass(field.field_info.annotation, BaseModel)
), f"Query parameter {param_name!r} must be one of the supported types" ), f"Query parameter {param_name!r} must be one of the supported types"
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
@ -761,8 +763,10 @@ def request_params_to_args(
fields_to_extract = fields fields_to_extract = fields
single_not_embedded_field = False single_not_embedded_field = False
default_convert_underscores = True default_convert_underscores = True
if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): if len(fields) == 1 and lenient_issubclass(
fields_to_extract = get_cached_model_fields(first_field.type_) first_field.field_info.annotation, BaseModel
):
fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
single_not_embedded_field = True single_not_embedded_field = True
# If headers are in a Pydantic model, the way to disable convert_underscores # If headers are in a Pydantic model, the way to disable convert_underscores
# would be with Header(convert_underscores=False) at the Pydantic model level # would be with Header(convert_underscores=False) at the Pydantic model level
@ -866,8 +870,8 @@ def _should_embed_body_fields(fields: list[ModelField]) -> bool:
# otherwise it has to be embedded, so that the key value pair can be extracted # otherwise it has to be embedded, so that the key value pair can be extracted
if ( if (
isinstance(first_field.field_info, params.Form) isinstance(first_field.field_info, params.Form)
and not lenient_issubclass(first_field.type_, BaseModel) and not lenient_issubclass(first_field.field_info.annotation, BaseModel)
and not is_union_of_base_models(first_field.type_) and not is_union_of_base_models(first_field.field_info.annotation)
): ):
return True return True
return False return False
@ -936,10 +940,10 @@ async def request_body_to_args(
if ( if (
single_not_embedded_field single_not_embedded_field
and lenient_issubclass(first_field.type_, BaseModel) and lenient_issubclass(first_field.field_info.annotation, BaseModel)
and isinstance(received_body, FormData) and isinstance(received_body, FormData)
): ):
fields_to_extract = get_cached_model_fields(first_field.type_) fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
if isinstance(received_body, FormData): if isinstance(received_body, FormData):
body_to_process = await _extract_form_body(fields_to_extract, received_body) body_to_process = await _extract_form_body(fields_to_extract, received_body)

2
fastapi/openapi/utils.py

@ -129,7 +129,7 @@ def _get_openapi_operation_parameters(
default_convert_underscores = True default_convert_underscores = True
if len(flat_dependant.header_params) == 1: if len(flat_dependant.header_params) == 1:
first_field = flat_dependant.header_params[0] first_field = flat_dependant.header_params[0]
if lenient_issubclass(first_field.type_, BaseModel): if lenient_issubclass(first_field.field_info.annotation, BaseModel):
default_convert_underscores = getattr( default_convert_underscores = getattr(
first_field.field_info, "convert_underscores", True first_field.field_info, "convert_underscores", True
) )

Loading…
Cancel
Save