mirror of https://github.com/tiangolo/fastapi.git
Merge branch 'master' into deferred-init
Signed-off-by: Jan Vollmer <jan@vllmr.dev>
This commit is contained in:
commit
23160db12b
|
|
@ -9,6 +9,13 @@ hide:
|
|||
|
||||
### 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
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Refactor internals, simplify Pydantic v2/v1 utils, `create_model_field`, better types for `lenient_issubclass`. PR [#14860](https://github.com/fastapi/fastapi/pull/14860) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ♻️ Simplify internals, remove Pydantic v1 only logic, no longer needed. PR [#14857](https://github.com/fastapi/fastapi/pull/14857) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ♻️ Refactor internals, cleanup unneeded Pydantic v1 specific logic. PR [#14856](https://github.com/fastapi/fastapi/pull/14856) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.128.3"
|
||||
__version__ = "0.128.4"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
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 field_annotation_is_scalar as field_annotation_is_scalar
|
||||
from .shared import is_pydantic_v1_model_class as is_pydantic_v1_model_class
|
||||
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_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
|
||||
|
|
@ -12,27 +19,21 @@ from .shared import (
|
|||
from .shared import lenient_issubclass as lenient_issubclass
|
||||
from .shared import sequence_types as sequence_types
|
||||
from .shared import value_is_sequence as value_is_sequence
|
||||
from .v2 import BaseConfig as BaseConfig
|
||||
from .v2 import ModelField as ModelField
|
||||
from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
||||
from .v2 import RequiredParam as RequiredParam
|
||||
from .v2 import Undefined as Undefined
|
||||
from .v2 import UndefinedType as UndefinedType
|
||||
from .v2 import Url as Url
|
||||
from .v2 import Validator as Validator
|
||||
from .v2 import _regenerate_error_with_loc as _regenerate_error_with_loc
|
||||
from .v2 import copy_field_info as copy_field_info
|
||||
from .v2 import create_body_model as create_body_model
|
||||
from .v2 import evaluate_forwardref as evaluate_forwardref
|
||||
from .v2 import get_cached_model_fields as get_cached_model_fields
|
||||
from .v2 import get_definitions as get_definitions
|
||||
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_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 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_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 (
|
||||
with_info_plain_validator_function as with_info_plain_validator_function,
|
||||
|
|
|
|||
|
|
@ -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,15 +42,13 @@ sequence_annotation_to_type = {
|
|||
deque: deque,
|
||||
}
|
||||
|
||||
sequence_types = tuple(sequence_annotation_to_type.keys())
|
||||
|
||||
Url: type[Any]
|
||||
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
|
||||
|
|
@ -177,16 +178,26 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
|
|||
|
||||
|
||||
def is_pydantic_v1_model_instance(obj: Any) -> bool:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
from pydantic import v1
|
||||
# TODO: remove this function once the required version of Pydantic fully
|
||||
# removes pydantic.v1
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
from pydantic import v1
|
||||
except ImportError: # pragma: no cover
|
||||
return False
|
||||
return isinstance(obj, v1.BaseModel)
|
||||
|
||||
|
||||
def is_pydantic_v1_model_class(cls: Any) -> bool:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
from pydantic import v1
|
||||
# TODO: remove this function once the required version of Pydantic fully
|
||||
# removes pydantic.v1
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
from pydantic import v1
|
||||
except ImportError: # pragma: no cover
|
||||
return False
|
||||
return lenient_issubclass(cls, v1.BaseModel)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,29 +23,20 @@ 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
|
||||
from pydantic_core import CoreSchema as CoreSchema
|
||||
from pydantic_core import PydanticUndefined, PydanticUndefinedType
|
||||
from pydantic_core import PydanticUndefined
|
||||
from pydantic_core import Url as Url
|
||||
from pydantic_core.core_schema import (
|
||||
with_info_plain_validator_function as with_info_plain_validator_function,
|
||||
)
|
||||
from typing_extensions import Literal, get_args, get_origin
|
||||
|
||||
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
|
||||
|
||||
# TODO: remove when dropping support for Pydantic < v2.12.3
|
||||
_Attrs = {
|
||||
|
|
@ -87,14 +78,6 @@ def asdict(field_info: FieldInfo) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
pass
|
||||
|
||||
|
||||
class ErrorWrapper(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModelField:
|
||||
field_info: FieldInfo
|
||||
|
|
@ -119,18 +102,10 @@ class ModelField:
|
|||
sa = self.field_info.serialization_alias
|
||||
return sa or None
|
||||
|
||||
@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:
|
||||
with warnings.catch_warnings():
|
||||
# Pydantic >= 2.12.0 warns about field specific metadata that is unused
|
||||
|
|
@ -143,8 +118,8 @@ class ModelField:
|
|||
warnings.simplefilter(
|
||||
"ignore", category=UnsupportedFieldAttributeWarning
|
||||
)
|
||||
# TODO: remove after dropping support for Python 3.8 and
|
||||
# setting the min Pydantic to v2.12.3 that adds asdict()
|
||||
# TODO: remove after setting the min Pydantic to v2.12.3
|
||||
# that adds asdict(), and use self.field_info.asdict() instead
|
||||
field_dict = asdict(self.field_info)
|
||||
annotated_args = (
|
||||
field_dict["annotation"],
|
||||
|
|
@ -284,9 +259,9 @@ def get_definitions(
|
|||
for model in flat_serialization_models
|
||||
]
|
||||
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 = {
|
||||
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 = [
|
||||
(
|
||||
|
|
@ -321,22 +296,6 @@ def is_scalar_field(field: ModelField) -> bool:
|
|||
) 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:
|
||||
cls = type(field_info)
|
||||
merged_field_info = cls.from_annotation(annotation)
|
||||
|
|
@ -432,10 +391,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
|
||||
|
|
@ -444,7 +404,7 @@ def get_flat_models_from_annotation(
|
|||
def get_flat_models_from_field(
|
||||
field: ModelField, known_models: TypeModelSet
|
||||
) -> TypeModelSet:
|
||||
field_type = field.type_
|
||||
field_type = field.field_info.annotation
|
||||
if lenient_issubclass(field_type, BaseModel):
|
||||
if field_type in known_models:
|
||||
return known_models
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ from fastapi._compat import (
|
|||
create_body_model,
|
||||
evaluate_forwardref,
|
||||
field_annotation_is_scalar,
|
||||
field_annotation_is_scalar_sequence,
|
||||
field_annotation_is_sequence,
|
||||
get_cached_model_fields,
|
||||
get_missing_field_error,
|
||||
is_bytes_field,
|
||||
is_bytes_sequence_field,
|
||||
is_bytes_or_nonable_bytes_annotation,
|
||||
is_bytes_sequence_annotation,
|
||||
is_scalar_field,
|
||||
is_scalar_sequence_field,
|
||||
is_sequence_field,
|
||||
is_uploadfile_or_nonable_uploadfile_annotation,
|
||||
is_uploadfile_sequence_annotation,
|
||||
lenient_issubclass,
|
||||
|
|
@ -182,8 +182,10 @@ def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
|
|||
if not fields:
|
||||
return fields
|
||||
first_field = fields[0]
|
||||
if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
|
||||
fields_to_extract = get_cached_model_fields(first_field.type_)
|
||||
if len(fields) == 1 and lenient_issubclass(
|
||||
first_field.field_info.annotation, BaseModel
|
||||
):
|
||||
fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
|
||||
return fields_to_extract
|
||||
return fields
|
||||
|
||||
|
|
@ -512,7 +514,6 @@ def analyze_param(
|
|||
type_=use_annotation_from_field_info,
|
||||
default=field_info.default,
|
||||
alias=alias,
|
||||
required=field_info.default in (RequiredParam, Undefined),
|
||||
field_info=field_info,
|
||||
)
|
||||
if is_path_param:
|
||||
|
|
@ -522,12 +523,8 @@ def analyze_param(
|
|||
elif isinstance(field_info, params.Query):
|
||||
assert (
|
||||
is_scalar_field(field)
|
||||
or is_scalar_sequence_field(field)
|
||||
or (
|
||||
lenient_issubclass(field.type_, BaseModel)
|
||||
# For Pydantic v1
|
||||
and getattr(field, "shape", 1) == 1
|
||||
)
|
||||
or field_annotation_is_scalar_sequence(field.field_info.annotation)
|
||||
or lenient_issubclass(field.field_info.annotation, BaseModel)
|
||||
), f"Query parameter {param_name!r} must be one of the supported types"
|
||||
|
||||
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
|
||||
|
|
@ -713,7 +710,7 @@ def _validate_value_with_model_field(
|
|||
*, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
|
||||
) -> tuple[Any, list[Any]]:
|
||||
if value is None:
|
||||
if field.required:
|
||||
if field.field_info.is_required():
|
||||
return None, [get_missing_field_error(loc=loc)]
|
||||
else:
|
||||
return deepcopy(field.default), []
|
||||
|
|
@ -730,7 +727,7 @@ def _get_multidict_value(
|
|||
alias = alias or get_validation_alias(field)
|
||||
if (
|
||||
(not _is_json_field(field))
|
||||
and is_sequence_field(field)
|
||||
and field_annotation_is_sequence(field.field_info.annotation)
|
||||
and isinstance(values, (ImmutableMultiDict, Headers))
|
||||
):
|
||||
value = values.getlist(alias)
|
||||
|
|
@ -743,9 +740,12 @@ def _get_multidict_value(
|
|||
and isinstance(value, str) # For type checks
|
||||
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
|
||||
else:
|
||||
return deepcopy(field.default)
|
||||
|
|
@ -766,8 +766,10 @@ def request_params_to_args(
|
|||
fields_to_extract = fields
|
||||
single_not_embedded_field = False
|
||||
default_convert_underscores = True
|
||||
if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
|
||||
fields_to_extract = get_cached_model_fields(first_field.type_)
|
||||
if len(fields) == 1 and lenient_issubclass(
|
||||
first_field.field_info.annotation, BaseModel
|
||||
):
|
||||
fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
|
||||
single_not_embedded_field = True
|
||||
# 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
|
||||
|
|
@ -871,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
|
||||
if (
|
||||
isinstance(first_field.field_info, params.Form)
|
||||
and not lenient_issubclass(first_field.type_, BaseModel)
|
||||
and not is_union_of_base_models(first_field.type_)
|
||||
and not lenient_issubclass(first_field.field_info.annotation, BaseModel)
|
||||
and not is_union_of_base_models(first_field.field_info.annotation)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
|
@ -889,12 +891,12 @@ async def _extract_form_body(
|
|||
field_info = field.field_info
|
||||
if (
|
||||
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)
|
||||
):
|
||||
value = await value.read()
|
||||
elif (
|
||||
is_bytes_sequence_field(field)
|
||||
is_bytes_sequence_annotation(field.field_info.annotation)
|
||||
and isinstance(field_info, params.File)
|
||||
and value_is_sequence(value)
|
||||
):
|
||||
|
|
@ -941,10 +943,10 @@ async def request_body_to_args(
|
|||
|
||||
if (
|
||||
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)
|
||||
):
|
||||
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):
|
||||
body_to_process = await _extract_form_body(fields_to_extract, received_body)
|
||||
|
|
@ -997,7 +999,9 @@ def get_body_field(
|
|||
BodyModel = create_body_model(
|
||||
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] = {
|
||||
"annotation": BodyModel,
|
||||
"alias": "body",
|
||||
|
|
@ -1021,7 +1025,6 @@ def get_body_field(
|
|||
final_field = create_model_field(
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
required=required,
|
||||
alias="body",
|
||||
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -143,10 +143,7 @@ class Schema(BaseModelWithConfig):
|
|||
else_: Optional["SchemaOrBool"] = Field(default=None, alias="else")
|
||||
dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None
|
||||
prefixItems: Optional[list["SchemaOrBool"]] = None
|
||||
# TODO: uncomment and remove below when deprecating Pydantic v1
|
||||
# It generates a list of schemas for tuples, before prefixItems was available
|
||||
# items: Optional["SchemaOrBool"] = None
|
||||
items: Optional[Union["SchemaOrBool", list["SchemaOrBool"]]] = None
|
||||
items: Optional["SchemaOrBool"] = None
|
||||
contains: Optional["SchemaOrBool"] = None
|
||||
properties: Optional[dict[str, "SchemaOrBool"]] = None
|
||||
patternProperties: Optional[dict[str, "SchemaOrBool"]] = None
|
||||
|
|
|
|||
|
|
@ -10,12 +10,10 @@ from fastapi._compat import (
|
|||
ModelField,
|
||||
Undefined,
|
||||
get_definitions,
|
||||
get_schema_from_model_field,
|
||||
lenient_issubclass,
|
||||
)
|
||||
from fastapi._compat.v2 import (
|
||||
get_flat_models_from_fields,
|
||||
get_model_name_map,
|
||||
get_schema_from_model_field,
|
||||
lenient_issubclass,
|
||||
)
|
||||
from fastapi.datastructures import DefaultPlaceholder
|
||||
from fastapi.dependencies.models import Dependant
|
||||
|
|
@ -131,7 +129,7 @@ def _get_openapi_operation_parameters(
|
|||
default_convert_underscores = True
|
||||
if len(flat_dependant.header_params) == 1:
|
||||
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(
|
||||
first_field.field_info, "convert_underscores", True
|
||||
)
|
||||
|
|
@ -163,7 +161,7 @@ def _get_openapi_operation_parameters(
|
|||
parameter = {
|
||||
"name": name,
|
||||
"in": param_type.value,
|
||||
"required": param.required,
|
||||
"required": param.field_info.is_required(),
|
||||
"schema": param_schema,
|
||||
}
|
||||
if field_info.description:
|
||||
|
|
@ -200,7 +198,7 @@ def get_openapi_operation_request_body(
|
|||
)
|
||||
field_info = cast(Body, body_field.field_info)
|
||||
request_media_type = field_info.media_type
|
||||
required = body_field.required
|
||||
required = body_field.field_info.is_required()
|
||||
request_body_oai: dict[str, Any] = {}
|
||||
if required:
|
||||
request_body_oai["required"] = required
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ from fastapi import params
|
|||
from fastapi._compat import (
|
||||
ModelField,
|
||||
Undefined,
|
||||
annotation_is_pydantic_v1,
|
||||
lenient_issubclass,
|
||||
)
|
||||
from fastapi.datastructures import Default, DefaultPlaceholder
|
||||
|
|
@ -53,7 +52,6 @@ from fastapi.encoders import jsonable_encoder
|
|||
from fastapi.exceptions import (
|
||||
EndpointContext,
|
||||
FastAPIError,
|
||||
PydanticV1NotSupportedError,
|
||||
RequestValidationError,
|
||||
ResponseValidationError,
|
||||
WebSocketRequestValidationError,
|
||||
|
|
@ -639,11 +637,7 @@ class APIRoute(routing.Route):
|
|||
assert is_body_allowed_for_status_code(status_code), (
|
||||
f"Status code {status_code} must not have a response body"
|
||||
)
|
||||
if annotation_is_pydantic_v1(self.response_model):
|
||||
raise PydanticV1NotSupportedError(
|
||||
"pydantic.v1 models are no longer supported by FastAPI."
|
||||
f" Please update the response model {self.response_model!r}."
|
||||
)
|
||||
|
||||
self.dependencies = list(dependencies or [])
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
# if a "form feed" character (page break) is found in the description text,
|
||||
|
|
@ -677,11 +671,6 @@ class APIRoute(routing.Route):
|
|||
f"Status code {additional_status_code} must not have a response body"
|
||||
)
|
||||
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
||||
if annotation_is_pydantic_v1(model):
|
||||
raise PydanticV1NotSupportedError(
|
||||
"pydantic.v1 models are no longer supported by FastAPI."
|
||||
f" In responses={{}}, please update {model}."
|
||||
)
|
||||
response_field = create_model_field(
|
||||
name=response_name, type_=model, mode="serialization"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,9 @@ from typing import (
|
|||
|
||||
import fastapi
|
||||
from fastapi._compat import (
|
||||
BaseConfig,
|
||||
ModelField,
|
||||
PydanticSchemaGenerationError,
|
||||
Undefined,
|
||||
UndefinedType,
|
||||
Validator,
|
||||
annotation_is_pydantic_v1,
|
||||
)
|
||||
from fastapi.datastructures import DefaultPlaceholder, DefaultType
|
||||
|
|
@ -63,26 +60,19 @@ _invalid_args_message = (
|
|||
def create_model_field(
|
||||
name: str,
|
||||
type_: Any,
|
||||
class_validators: Optional[dict[str, Validator]] = None,
|
||||
default: Optional[Any] = Undefined,
|
||||
required: Union[bool, UndefinedType] = Undefined,
|
||||
model_config: Union[type[BaseConfig], None] = None,
|
||||
field_info: Optional[FieldInfo] = None,
|
||||
alias: Optional[str] = None,
|
||||
mode: Literal["validation", "serialization"] = "validation",
|
||||
version: Literal["1", "auto"] = "auto",
|
||||
) -> ModelField:
|
||||
if annotation_is_pydantic_v1(type_):
|
||||
raise PydanticV1NotSupportedError(
|
||||
"pydantic.v1 models are no longer supported by FastAPI."
|
||||
f" Please update the response model {type_!r}."
|
||||
)
|
||||
class_validators = class_validators or {}
|
||||
|
||||
field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias)
|
||||
kwargs = {"mode": mode, "name": name, "field_info": field_info}
|
||||
try:
|
||||
return v2.ModelField(**kwargs) # type: ignore[arg-type]
|
||||
return v2.ModelField(mode=mode, name=name, field_info=field_info)
|
||||
except PydanticSchemaGenerationError:
|
||||
raise fastapi.exceptions.FastAPIError(
|
||||
_invalid_args_message.format(type_=type_)
|
||||
|
|
|
|||
Loading…
Reference in New Issue