mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-08 05:11:42 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c48539f4c6 | ||
|
|
2e7d3754cd |
@@ -7,6 +7,10 @@ hide:
|
|||||||
|
|
||||||
## Latest Changes
|
## Latest Changes
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* ♻️ Refactor and simplify Pydantic v2 (and v1) compatibility internal utils. PR [#14862](https://github.com/fastapi/fastapi/pull/14862) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
## 0.128.4
|
## 0.128.4
|
||||||
|
|
||||||
### Refactors
|
### Refactors
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
|
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
|
||||||
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
|
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
|
||||||
from .shared import field_annotation_is_scalar as field_annotation_is_scalar
|
from .shared import field_annotation_is_scalar as field_annotation_is_scalar
|
||||||
|
from .shared import (
|
||||||
|
field_annotation_is_scalar_sequence as field_annotation_is_scalar_sequence,
|
||||||
|
)
|
||||||
|
from .shared import field_annotation_is_sequence as field_annotation_is_sequence
|
||||||
|
from .shared import (
|
||||||
|
is_bytes_or_nonable_bytes_annotation as is_bytes_or_nonable_bytes_annotation,
|
||||||
|
)
|
||||||
|
from .shared import is_bytes_sequence_annotation as is_bytes_sequence_annotation
|
||||||
from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance
|
from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance
|
||||||
from .shared import (
|
from .shared import (
|
||||||
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
|
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
|
||||||
@@ -25,11 +33,7 @@ from .v2 import get_flat_models_from_fields as get_flat_models_from_fields
|
|||||||
from .v2 import get_missing_field_error as get_missing_field_error
|
from .v2 import get_missing_field_error as get_missing_field_error
|
||||||
from .v2 import get_model_name_map as get_model_name_map
|
from .v2 import get_model_name_map as get_model_name_map
|
||||||
from .v2 import get_schema_from_model_field as get_schema_from_model_field
|
from .v2 import get_schema_from_model_field as get_schema_from_model_field
|
||||||
from .v2 import is_bytes_field as is_bytes_field
|
|
||||||
from .v2 import is_bytes_sequence_field as is_bytes_sequence_field
|
|
||||||
from .v2 import is_scalar_field as is_scalar_field
|
from .v2 import is_scalar_field as is_scalar_field
|
||||||
from .v2 import is_scalar_sequence_field as is_scalar_sequence_field
|
|
||||||
from .v2 import is_sequence_field as is_sequence_field
|
|
||||||
from .v2 import serialize_sequence_value as serialize_sequence_value
|
from .v2 import serialize_sequence_value as serialize_sequence_value
|
||||||
from .v2 import (
|
from .v2 import (
|
||||||
with_info_plain_validator_function as with_info_plain_validator_function,
|
with_info_plain_validator_function as with_info_plain_validator_function,
|
||||||
|
|||||||
@@ -102,18 +102,10 @@ class ModelField:
|
|||||||
sa = self.field_info.serialization_alias
|
sa = self.field_info.serialization_alias
|
||||||
return sa or None
|
return sa or None
|
||||||
|
|
||||||
@property
|
|
||||||
def required(self) -> bool:
|
|
||||||
return self.field_info.is_required()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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 +259,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 = [
|
||||||
(
|
(
|
||||||
@@ -304,22 +296,6 @@ def is_scalar_field(field: ModelField) -> bool:
|
|||||||
) and not isinstance(field.field_info, params.Body)
|
) and not isinstance(field.field_info, params.Body)
|
||||||
|
|
||||||
|
|
||||||
def is_sequence_field(field: ModelField) -> bool:
|
|
||||||
return shared.field_annotation_is_sequence(field.field_info.annotation)
|
|
||||||
|
|
||||||
|
|
||||||
def is_scalar_sequence_field(field: ModelField) -> bool:
|
|
||||||
return shared.field_annotation_is_scalar_sequence(field.field_info.annotation)
|
|
||||||
|
|
||||||
|
|
||||||
def is_bytes_field(field: ModelField) -> bool:
|
|
||||||
return shared.is_bytes_or_nonable_bytes_annotation(field.type_)
|
|
||||||
|
|
||||||
|
|
||||||
def is_bytes_sequence_field(field: ModelField) -> bool:
|
|
||||||
return shared.is_bytes_sequence_annotation(field.type_)
|
|
||||||
|
|
||||||
|
|
||||||
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
||||||
cls = type(field_info)
|
cls = type(field_info)
|
||||||
merged_field_info = cls.from_annotation(annotation)
|
merged_field_info = cls.from_annotation(annotation)
|
||||||
@@ -428,7 +404,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
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ from fastapi._compat import (
|
|||||||
create_body_model,
|
create_body_model,
|
||||||
evaluate_forwardref,
|
evaluate_forwardref,
|
||||||
field_annotation_is_scalar,
|
field_annotation_is_scalar,
|
||||||
|
field_annotation_is_scalar_sequence,
|
||||||
|
field_annotation_is_sequence,
|
||||||
get_cached_model_fields,
|
get_cached_model_fields,
|
||||||
get_missing_field_error,
|
get_missing_field_error,
|
||||||
is_bytes_field,
|
is_bytes_or_nonable_bytes_annotation,
|
||||||
is_bytes_sequence_field,
|
is_bytes_sequence_annotation,
|
||||||
is_scalar_field,
|
is_scalar_field,
|
||||||
is_scalar_sequence_field,
|
|
||||||
is_sequence_field,
|
|
||||||
is_uploadfile_or_nonable_uploadfile_annotation,
|
is_uploadfile_or_nonable_uploadfile_annotation,
|
||||||
is_uploadfile_sequence_annotation,
|
is_uploadfile_sequence_annotation,
|
||||||
lenient_issubclass,
|
lenient_issubclass,
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -521,8 +523,8 @@ def analyze_param(
|
|||||||
elif isinstance(field_info, params.Query):
|
elif isinstance(field_info, params.Query):
|
||||||
assert (
|
assert (
|
||||||
is_scalar_field(field)
|
is_scalar_field(field)
|
||||||
or is_scalar_sequence_field(field)
|
or field_annotation_is_scalar_sequence(field.field_info.annotation)
|
||||||
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)
|
||||||
@@ -708,7 +710,7 @@ def _validate_value_with_model_field(
|
|||||||
*, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
|
*, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
|
||||||
) -> tuple[Any, list[Any]]:
|
) -> tuple[Any, list[Any]]:
|
||||||
if value is None:
|
if value is None:
|
||||||
if field.required:
|
if field.field_info.is_required():
|
||||||
return None, [get_missing_field_error(loc=loc)]
|
return None, [get_missing_field_error(loc=loc)]
|
||||||
else:
|
else:
|
||||||
return deepcopy(field.default), []
|
return deepcopy(field.default), []
|
||||||
@@ -725,7 +727,7 @@ def _get_multidict_value(
|
|||||||
alias = alias or get_validation_alias(field)
|
alias = alias or get_validation_alias(field)
|
||||||
if (
|
if (
|
||||||
(not _is_json_field(field))
|
(not _is_json_field(field))
|
||||||
and is_sequence_field(field)
|
and field_annotation_is_sequence(field.field_info.annotation)
|
||||||
and isinstance(values, (ImmutableMultiDict, Headers))
|
and isinstance(values, (ImmutableMultiDict, Headers))
|
||||||
):
|
):
|
||||||
value = values.getlist(alias)
|
value = values.getlist(alias)
|
||||||
@@ -738,9 +740,12 @@ def _get_multidict_value(
|
|||||||
and isinstance(value, str) # For type checks
|
and isinstance(value, str) # For type checks
|
||||||
and value == ""
|
and value == ""
|
||||||
)
|
)
|
||||||
or (is_sequence_field(field) and len(value) == 0)
|
or (
|
||||||
|
field_annotation_is_sequence(field.field_info.annotation)
|
||||||
|
and len(value) == 0
|
||||||
|
)
|
||||||
):
|
):
|
||||||
if field.required:
|
if field.field_info.is_required():
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return deepcopy(field.default)
|
return deepcopy(field.default)
|
||||||
@@ -761,8 +766,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 +873,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
|
||||||
@@ -884,12 +891,12 @@ async def _extract_form_body(
|
|||||||
field_info = field.field_info
|
field_info = field.field_info
|
||||||
if (
|
if (
|
||||||
isinstance(field_info, params.File)
|
isinstance(field_info, params.File)
|
||||||
and is_bytes_field(field)
|
and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation)
|
||||||
and isinstance(value, UploadFile)
|
and isinstance(value, UploadFile)
|
||||||
):
|
):
|
||||||
value = await value.read()
|
value = await value.read()
|
||||||
elif (
|
elif (
|
||||||
is_bytes_sequence_field(field)
|
is_bytes_sequence_annotation(field.field_info.annotation)
|
||||||
and isinstance(field_info, params.File)
|
and isinstance(field_info, params.File)
|
||||||
and value_is_sequence(value)
|
and value_is_sequence(value)
|
||||||
):
|
):
|
||||||
@@ -936,10 +943,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)
|
||||||
@@ -992,7 +999,9 @@ def get_body_field(
|
|||||||
BodyModel = create_body_model(
|
BodyModel = create_body_model(
|
||||||
fields=flat_dependant.body_params, model_name=model_name
|
fields=flat_dependant.body_params, model_name=model_name
|
||||||
)
|
)
|
||||||
required = any(True for f in flat_dependant.body_params if f.required)
|
required = any(
|
||||||
|
True for f in flat_dependant.body_params if f.field_info.is_required()
|
||||||
|
)
|
||||||
BodyFieldInfo_kwargs: dict[str, Any] = {
|
BodyFieldInfo_kwargs: dict[str, Any] = {
|
||||||
"annotation": BodyModel,
|
"annotation": BodyModel,
|
||||||
"alias": "body",
|
"alias": "body",
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -161,7 +161,7 @@ def _get_openapi_operation_parameters(
|
|||||||
parameter = {
|
parameter = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"in": param_type.value,
|
"in": param_type.value,
|
||||||
"required": param.required,
|
"required": param.field_info.is_required(),
|
||||||
"schema": param_schema,
|
"schema": param_schema,
|
||||||
}
|
}
|
||||||
if field_info.description:
|
if field_info.description:
|
||||||
@@ -198,7 +198,7 @@ def get_openapi_operation_request_body(
|
|||||||
)
|
)
|
||||||
field_info = cast(Body, body_field.field_info)
|
field_info = cast(Body, body_field.field_info)
|
||||||
request_media_type = field_info.media_type
|
request_media_type = field_info.media_type
|
||||||
required = body_field.required
|
required = body_field.field_info.is_required()
|
||||||
request_body_oai: dict[str, Any] = {}
|
request_body_oai: dict[str, Any] = {}
|
||||||
if required:
|
if required:
|
||||||
request_body_oai["required"] = required
|
request_body_oai["required"] = required
|
||||||
|
|||||||
Reference in New Issue
Block a user