committed by
GitHub
55 changed files with 337 additions and 8784 deletions
@ -1,20 +0,0 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi.temp_pydantic_v1_params import Form |
|||
from pydantic.v1 import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FormData(BaseModel): |
|||
username: str |
|||
password: str |
|||
|
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
|
|||
@app.post("/login/") |
|||
async def login(data: Annotated[FormData, Form()]): |
|||
return data |
|||
@ -1,18 +0,0 @@ |
|||
from fastapi import FastAPI |
|||
from fastapi.temp_pydantic_v1_params import Form |
|||
from pydantic.v1 import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FormData(BaseModel): |
|||
username: str |
|||
password: str |
|||
|
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
|
|||
@app.post("/login/") |
|||
async def login(data: FormData = Form()): |
|||
return data |
|||
@ -1,264 +0,0 @@ |
|||
import sys |
|||
from collections.abc import Sequence |
|||
from functools import lru_cache |
|||
from typing import ( |
|||
Any, |
|||
) |
|||
|
|||
from fastapi._compat import may_v1 |
|||
from fastapi._compat.shared import lenient_issubclass |
|||
from fastapi.types import ModelNameMap |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Literal |
|||
|
|||
from . import v2 |
|||
from .model_field import ModelField |
|||
from .v2 import BaseConfig as BaseConfig |
|||
from .v2 import FieldInfo as FieldInfo |
|||
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 evaluate_forwardref as evaluate_forwardref |
|||
from .v2 import get_missing_field_error as get_missing_field_error |
|||
from .v2 import ( |
|||
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, may_v1.BaseModel): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.get_model_fields(model) # type: ignore[arg-type,return-value] |
|||
else: |
|||
from . import v2 |
|||
|
|||
return v2.get_model_fields(model) # type: ignore[return-value] |
|||
|
|||
|
|||
def _is_undefined(value: object) -> bool: |
|||
if isinstance(value, may_v1.UndefinedType): |
|||
return True |
|||
|
|||
return isinstance(value, v2.UndefinedType) |
|||
|
|||
|
|||
def _get_model_config(model: BaseModel) -> Any: |
|||
if isinstance(model, may_v1.BaseModel): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1._get_model_config(model) |
|||
|
|||
return v2._get_model_config(model) |
|||
|
|||
|
|||
def _model_dump( |
|||
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any |
|||
) -> Any: |
|||
if isinstance(model, may_v1.BaseModel): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1._model_dump(model, mode=mode, **kwargs) |
|||
|
|||
return v2._model_dump(model, mode=mode, **kwargs) |
|||
|
|||
|
|||
def _is_error_wrapper(exc: Exception) -> bool: |
|||
if isinstance(exc, may_v1.ErrorWrapper): |
|||
return True |
|||
|
|||
return isinstance(exc, v2.ErrorWrapper) |
|||
|
|||
|
|||
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: |
|||
if isinstance(field_info, may_v1.FieldInfo): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.copy_field_info(field_info=field_info, annotation=annotation) |
|||
|
|||
return v2.copy_field_info(field_info=field_info, annotation=annotation) |
|||
|
|||
|
|||
def create_body_model( |
|||
*, fields: Sequence[ModelField], model_name: str |
|||
) -> type[BaseModel]: |
|||
if fields and isinstance(fields[0], may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.create_body_model(fields=fields, model_name=model_name) |
|||
|
|||
return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type] |
|||
|
|||
|
|||
def get_annotation_from_field_info( |
|||
annotation: Any, field_info: FieldInfo, field_name: str |
|||
) -> Any: |
|||
if isinstance(field_info, may_v1.FieldInfo): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.get_annotation_from_field_info( |
|||
annotation=annotation, field_info=field_info, field_name=field_name |
|||
) |
|||
|
|||
return v2.get_annotation_from_field_info( |
|||
annotation=annotation, field_info=field_info, field_name=field_name |
|||
) |
|||
|
|||
|
|||
def is_bytes_field(field: ModelField) -> bool: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.is_bytes_field(field) |
|||
|
|||
return v2.is_bytes_field(field) # type: ignore[arg-type] |
|||
|
|||
|
|||
def is_bytes_sequence_field(field: ModelField) -> bool: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.is_bytes_sequence_field(field) |
|||
|
|||
return v2.is_bytes_sequence_field(field) # type: ignore[arg-type] |
|||
|
|||
|
|||
def is_scalar_field(field: ModelField) -> bool: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.is_scalar_field(field) |
|||
|
|||
return v2.is_scalar_field(field) # type: ignore[arg-type] |
|||
|
|||
|
|||
def is_scalar_sequence_field(field: ModelField) -> bool: |
|||
return v2.is_scalar_sequence_field(field) # type: ignore[arg-type] |
|||
|
|||
|
|||
def is_sequence_field(field: ModelField) -> bool: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.is_sequence_field(field) |
|||
|
|||
return v2.is_sequence_field(field) # type: ignore[arg-type] |
|||
|
|||
|
|||
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.serialize_sequence_value(field=field, value=value) |
|||
|
|||
return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] |
|||
|
|||
|
|||
def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: |
|||
v1_model_fields = [ |
|||
field for field in fields if isinstance(field, may_v1.ModelField) |
|||
] |
|||
if v1_model_fields: |
|||
from fastapi._compat import v1 |
|||
|
|||
v1_flat_models = v1.get_flat_models_from_fields( |
|||
v1_model_fields, # type: ignore[arg-type] |
|||
known_models=set(), |
|||
) |
|||
all_flat_models = v1_flat_models |
|||
else: |
|||
all_flat_models = set() |
|||
|
|||
v2_model_fields = [field for field in fields if isinstance(field, v2.ModelField)] |
|||
v2_flat_models = v2.get_flat_models_from_fields(v2_model_fields, known_models=set()) |
|||
all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type] |
|||
|
|||
model_name_map = v2.get_model_name_map(all_flat_models) # type: ignore[arg-type] |
|||
return model_name_map |
|||
|
|||
|
|||
def get_definitions( |
|||
*, |
|||
fields: list[ModelField], |
|||
model_name_map: ModelNameMap, |
|||
separate_input_output_schemas: bool = True, |
|||
) -> tuple[ |
|||
dict[ |
|||
tuple[ModelField, Literal["validation", "serialization"]], |
|||
may_v1.JsonSchemaValue, |
|||
], |
|||
dict[str, dict[str, Any]], |
|||
]: |
|||
if sys.version_info < (3, 14): |
|||
v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] |
|||
v1_field_maps, v1_definitions = may_v1.get_definitions( |
|||
fields=v1_fields, # type: ignore[arg-type] |
|||
model_name_map=model_name_map, |
|||
separate_input_output_schemas=separate_input_output_schemas, |
|||
) |
|||
|
|||
v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] |
|||
v2_field_maps, v2_definitions = v2.get_definitions( |
|||
fields=v2_fields, |
|||
model_name_map=model_name_map, |
|||
separate_input_output_schemas=separate_input_output_schemas, |
|||
) |
|||
all_definitions = {**v1_definitions, **v2_definitions} |
|||
all_field_maps = {**v1_field_maps, **v2_field_maps} # type: ignore[misc] |
|||
return all_field_maps, all_definitions |
|||
|
|||
# Pydantic v1 is not supported since Python 3.14 |
|||
else: |
|||
v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] |
|||
v2_field_maps, v2_definitions = v2.get_definitions( |
|||
fields=v2_fields, |
|||
model_name_map=model_name_map, |
|||
separate_input_output_schemas=separate_input_output_schemas, |
|||
) |
|||
return v2_field_maps, v2_definitions |
|||
|
|||
|
|||
def get_schema_from_model_field( |
|||
*, |
|||
field: ModelField, |
|||
model_name_map: ModelNameMap, |
|||
field_mapping: dict[ |
|||
tuple[ModelField, Literal["validation", "serialization"]], |
|||
may_v1.JsonSchemaValue, |
|||
], |
|||
separate_input_output_schemas: bool = True, |
|||
) -> dict[str, Any]: |
|||
if isinstance(field, may_v1.ModelField): |
|||
from fastapi._compat import v1 |
|||
|
|||
return v1.get_schema_from_model_field( |
|||
field=field, |
|||
model_name_map=model_name_map, |
|||
field_mapping=field_mapping, |
|||
separate_input_output_schemas=separate_input_output_schemas, |
|||
) |
|||
|
|||
return v2.get_schema_from_model_field( |
|||
field=field, # type: ignore[arg-type] |
|||
model_name_map=model_name_map, |
|||
field_mapping=field_mapping, # type: ignore[arg-type] |
|||
separate_input_output_schemas=separate_input_output_schemas, |
|||
) |
|||
|
|||
|
|||
def _is_model_field(value: Any) -> bool: |
|||
if isinstance(value, may_v1.ModelField): |
|||
return True |
|||
|
|||
return isinstance(value, v2.ModelField) |
|||
|
|||
|
|||
def _is_model_class(value: Any) -> bool: |
|||
if lenient_issubclass(value, may_v1.BaseModel): |
|||
return True |
|||
|
|||
return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined] |
|||
@ -1,124 +0,0 @@ |
|||
import sys |
|||
from collections.abc import Sequence |
|||
from typing import Any, Literal, Union |
|||
|
|||
from fastapi.types import ModelNameMap |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
|
|||
class AnyUrl: |
|||
pass |
|||
|
|||
class BaseConfig: |
|||
pass |
|||
|
|||
class BaseModel: |
|||
pass |
|||
|
|||
class Color: |
|||
pass |
|||
|
|||
class CoreSchema: |
|||
pass |
|||
|
|||
class ErrorWrapper: |
|||
pass |
|||
|
|||
class FieldInfo: |
|||
pass |
|||
|
|||
class GetJsonSchemaHandler: |
|||
pass |
|||
|
|||
class JsonSchemaValue: |
|||
pass |
|||
|
|||
class ModelField: |
|||
pass |
|||
|
|||
class NameEmail: |
|||
pass |
|||
|
|||
class RequiredParam: |
|||
pass |
|||
|
|||
class SecretBytes: |
|||
pass |
|||
|
|||
class SecretStr: |
|||
pass |
|||
|
|||
class Undefined: |
|||
pass |
|||
|
|||
class UndefinedType: |
|||
pass |
|||
|
|||
class Url: |
|||
pass |
|||
|
|||
from .v2 import ValidationError, create_model |
|||
|
|||
def get_definitions( |
|||
*, |
|||
fields: list[ModelField], |
|||
model_name_map: ModelNameMap, |
|||
separate_input_output_schemas: bool = True, |
|||
) -> tuple[ |
|||
dict[ |
|||
tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue |
|||
], |
|||
dict[str, dict[str, Any]], |
|||
]: |
|||
return {}, {} # pragma: no cover |
|||
|
|||
|
|||
else: |
|||
from .v1 import AnyUrl as AnyUrl |
|||
from .v1 import BaseConfig as BaseConfig |
|||
from .v1 import BaseModel as BaseModel |
|||
from .v1 import Color as Color |
|||
from .v1 import CoreSchema as CoreSchema |
|||
from .v1 import ErrorWrapper as ErrorWrapper |
|||
from .v1 import FieldInfo as FieldInfo |
|||
from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler |
|||
from .v1 import JsonSchemaValue as JsonSchemaValue |
|||
from .v1 import ModelField as ModelField |
|||
from .v1 import NameEmail as NameEmail |
|||
from .v1 import RequiredParam as RequiredParam |
|||
from .v1 import SecretBytes as SecretBytes |
|||
from .v1 import SecretStr as SecretStr |
|||
from .v1 import Undefined as Undefined |
|||
from .v1 import UndefinedType as UndefinedType |
|||
from .v1 import Url as Url |
|||
from .v1 import ValidationError, create_model |
|||
from .v1 import get_definitions as get_definitions |
|||
|
|||
|
|||
RequestErrorModel: type[BaseModel] = create_model("Request") |
|||
|
|||
|
|||
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( |
|||
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 _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 |
|||
@ -1,50 +0,0 @@ |
|||
from typing import ( |
|||
Any, |
|||
Union, |
|||
) |
|||
|
|||
from fastapi.types import IncEx |
|||
from pydantic.fields import FieldInfo |
|||
from typing_extensions import Literal, Protocol |
|||
|
|||
|
|||
class ModelField(Protocol): |
|||
field_info: "FieldInfo" |
|||
name: str |
|||
mode: Literal["validation", "serialization"] = "validation" |
|||
_version: Literal["v1", "v2"] = "v1" |
|||
|
|||
@property |
|||
def alias(self) -> str: ... |
|||
|
|||
@property |
|||
def required(self) -> bool: ... |
|||
|
|||
@property |
|||
def default(self) -> Any: ... |
|||
|
|||
@property |
|||
def type_(self) -> Any: ... |
|||
|
|||
def get_default(self) -> Any: ... |
|||
|
|||
def validate( |
|||
self, |
|||
value: Any, |
|||
values: dict[str, Any] = {}, # noqa: B006 |
|||
*, |
|||
loc: tuple[Union[int, str], ...] = (), |
|||
) -> tuple[Any, Union[list[dict[str, Any]], None]]: ... |
|||
|
|||
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: ... |
|||
@ -1,222 +0,0 @@ |
|||
from collections.abc import Sequence |
|||
from copy import copy |
|||
from dataclasses import dataclass, is_dataclass |
|||
from enum import Enum |
|||
from typing import ( |
|||
Any, |
|||
Callable, |
|||
Union, |
|||
) |
|||
|
|||
from fastapi._compat import shared |
|||
from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX |
|||
from fastapi.types import ModelNameMap |
|||
from pydantic.v1 import BaseConfig as BaseConfig |
|||
from pydantic.v1 import BaseModel as BaseModel |
|||
from pydantic.v1 import ValidationError as ValidationError |
|||
from pydantic.v1 import create_model as create_model |
|||
from pydantic.v1.class_validators import Validator as Validator |
|||
from pydantic.v1.color import Color as Color |
|||
from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper |
|||
from pydantic.v1.fields import ( |
|||
SHAPE_FROZENSET, |
|||
SHAPE_LIST, |
|||
SHAPE_SEQUENCE, |
|||
SHAPE_SET, |
|||
SHAPE_SINGLETON, |
|||
SHAPE_TUPLE, |
|||
SHAPE_TUPLE_ELLIPSIS, |
|||
) |
|||
from pydantic.v1.fields import FieldInfo as FieldInfo |
|||
from pydantic.v1.fields import ModelField as ModelField |
|||
from pydantic.v1.fields import Undefined as Undefined |
|||
from pydantic.v1.fields import UndefinedType as UndefinedType |
|||
from pydantic.v1.networks import AnyUrl as AnyUrl |
|||
from pydantic.v1.networks import NameEmail as NameEmail |
|||
from pydantic.v1.schema import TypeModelSet as TypeModelSet |
|||
from pydantic.v1.schema import field_schema, model_process_schema |
|||
from pydantic.v1.schema import ( |
|||
get_annotation_from_field_info as get_annotation_from_field_info, |
|||
) |
|||
from pydantic.v1.schema import ( |
|||
get_flat_models_from_field as get_flat_models_from_field, |
|||
) |
|||
from pydantic.v1.schema import ( |
|||
get_flat_models_from_fields as get_flat_models_from_fields, |
|||
) |
|||
from pydantic.v1.schema import get_model_name_map as get_model_name_map |
|||
from pydantic.v1.types import SecretBytes as SecretBytes |
|||
from pydantic.v1.types import SecretStr as SecretStr |
|||
from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref |
|||
from pydantic.v1.utils import lenient_issubclass as lenient_issubclass |
|||
from pydantic.version import VERSION as PYDANTIC_VERSION |
|||
from typing_extensions import Literal |
|||
|
|||
PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) |
|||
PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 |
|||
# Keeping old "Required" functionality from Pydantic V1, without |
|||
# shadowing typing.Required. |
|||
RequiredParam: Any = Ellipsis |
|||
|
|||
|
|||
GetJsonSchemaHandler = Any |
|||
JsonSchemaValue = dict[str, Any] |
|||
CoreSchema = Any |
|||
Url = AnyUrl |
|||
|
|||
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: |
|||
ref_template: str |
|||
|
|||
|
|||
class PydanticSchemaGenerationError(Exception): |
|||
pass |
|||
|
|||
|
|||
RequestErrorModel: type[BaseModel] = create_model("Request") |
|||
|
|||
|
|||
def with_info_plain_validator_function( |
|||
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] |
|||
definitions[model_name] = m_schema |
|||
for m_schema in definitions.values(): |
|||
if "description" in m_schema: |
|||
m_schema["description"] = m_schema["description"].split("\f")[0] |
|||
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 |
|||
and not lenient_issubclass(field.type_, BaseModel) |
|||
and not lenient_issubclass(field.type_, dict) |
|||
and not shared.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: |
|||
if not all(is_pv1_scalar_field(f) for f in field.sub_fields): |
|||
return False |
|||
return True |
|||
|
|||
|
|||
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__ |
|||
|
|||
|
|||
def get_schema_from_model_field( |
|||
*, |
|||
field: ModelField, |
|||
model_name_map: ModelNameMap, |
|||
field_mapping: dict[ |
|||
tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue |
|||
], |
|||
separate_input_output_schemas: bool = True, |
|||
) -> dict[str, Any]: |
|||
return field_schema( |
|||
field, |
|||
model_name_map=model_name_map, # type: ignore[arg-type] |
|||
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], |
|||
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) # type: ignore[arg-type] |
|||
|
|||
|
|||
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 shared._annotation_is_sequence(field.type_) |
|||
|
|||
|
|||
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) |
|||
|
|||
|
|||
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] |
|||
|
|||
|
|||
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 |
|||
return BodyModel |
|||
|
|||
|
|||
def get_model_fields(model: type[BaseModel]) -> list[ModelField]: |
|||
return list(model.__fields__.values()) |
|||
@ -1,718 +0,0 @@ |
|||
import warnings |
|||
from typing import Annotated, Any, Callable, Optional, Union |
|||
|
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from fastapi.openapi.models import Example |
|||
from fastapi.params import ParamTypes |
|||
from typing_extensions import deprecated |
|||
|
|||
from ._compat.may_v1 import FieldInfo, Undefined |
|||
|
|||
_Unset: Any = Undefined |
|||
|
|||
|
|||
class Param(FieldInfo): |
|||
in_: ParamTypes |
|||
|
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
if example is not _Unset: |
|||
warnings.warn( |
|||
"`example` has been deprecated, please use `examples` instead", |
|||
category=FastAPIDeprecationWarning, |
|||
stacklevel=4, |
|||
) |
|||
self.example = example |
|||
self.include_in_schema = include_in_schema |
|||
self.openapi_examples = openapi_examples |
|||
kwargs = dict( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
alias=alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
discriminator=discriminator, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
**extra, |
|||
) |
|||
if examples is not None: |
|||
kwargs["examples"] = examples |
|||
if regex is not None: |
|||
warnings.warn( |
|||
"`regex` has been deprecated, please use `pattern` instead", |
|||
category=FastAPIDeprecationWarning, |
|||
stacklevel=4, |
|||
) |
|||
current_json_schema_extra = json_schema_extra or extra |
|||
kwargs["deprecated"] = deprecated |
|||
kwargs["regex"] = pattern or regex |
|||
kwargs.update(**current_json_schema_extra) |
|||
use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} |
|||
|
|||
super().__init__(**use_kwargs) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f"{self.__class__.__name__}({self.default})" |
|||
|
|||
|
|||
class Path(Param): |
|||
in_ = ParamTypes.path |
|||
|
|||
def __init__( |
|||
self, |
|||
default: Any = ..., |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
assert default is ..., "Path parameters cannot have a default value" |
|||
self.in_ = self.in_ |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
|
|||
|
|||
class Query(Param): |
|||
in_ = ParamTypes.query |
|||
|
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
|
|||
|
|||
class Header(Param): |
|||
in_ = ParamTypes.header |
|||
|
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
convert_underscores: bool = True, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
self.convert_underscores = convert_underscores |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
|
|||
|
|||
class Cookie(Param): |
|||
in_ = ParamTypes.cookie |
|||
|
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
|
|||
|
|||
class Body(FieldInfo): |
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
embed: Union[bool, None] = None, |
|||
media_type: str = "application/json", |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
self.embed = embed |
|||
self.media_type = media_type |
|||
if example is not _Unset: |
|||
warnings.warn( |
|||
"`example` has been deprecated, please use `examples` instead", |
|||
category=FastAPIDeprecationWarning, |
|||
stacklevel=4, |
|||
) |
|||
self.example = example |
|||
self.include_in_schema = include_in_schema |
|||
self.openapi_examples = openapi_examples |
|||
kwargs = dict( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
alias=alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
discriminator=discriminator, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
**extra, |
|||
) |
|||
if examples is not None: |
|||
kwargs["examples"] = examples |
|||
if regex is not None: |
|||
warnings.warn( |
|||
"`regex` has been deprecated, please use `pattern` instead", |
|||
category=FastAPIDeprecationWarning, |
|||
stacklevel=4, |
|||
) |
|||
current_json_schema_extra = json_schema_extra or extra |
|||
kwargs["deprecated"] = deprecated |
|||
kwargs["regex"] = pattern or regex |
|||
kwargs.update(**current_json_schema_extra) |
|||
|
|||
use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} |
|||
|
|||
super().__init__(**use_kwargs) |
|||
|
|||
def __repr__(self) -> str: |
|||
return f"{self.__class__.__name__}({self.default})" |
|||
|
|||
|
|||
class Form(Body): |
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
media_type: str = "application/x-www-form-urlencoded", |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
media_type=media_type, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
|
|||
|
|||
class File(Form): |
|||
def __init__( |
|||
self, |
|||
default: Any = Undefined, |
|||
*, |
|||
default_factory: Union[Callable[[], Any], None] = _Unset, |
|||
annotation: Optional[Any] = None, |
|||
media_type: str = "multipart/form-data", |
|||
alias: Optional[str] = None, |
|||
alias_priority: Union[int, None] = _Unset, |
|||
# TODO: update when deprecating Pydantic v1, import these types |
|||
# validation_alias: str | AliasPath | AliasChoices | None |
|||
validation_alias: Union[str, None] = None, |
|||
serialization_alias: Union[str, None] = None, |
|||
title: Optional[str] = None, |
|||
description: Optional[str] = None, |
|||
gt: Optional[float] = None, |
|||
ge: Optional[float] = None, |
|||
lt: Optional[float] = None, |
|||
le: Optional[float] = None, |
|||
min_length: Optional[int] = None, |
|||
max_length: Optional[int] = None, |
|||
pattern: Optional[str] = None, |
|||
regex: Annotated[ |
|||
Optional[str], |
|||
deprecated( |
|||
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." |
|||
), |
|||
] = None, |
|||
discriminator: Union[str, None] = None, |
|||
strict: Union[bool, None] = _Unset, |
|||
multiple_of: Union[float, None] = _Unset, |
|||
allow_inf_nan: Union[bool, None] = _Unset, |
|||
max_digits: Union[int, None] = _Unset, |
|||
decimal_places: Union[int, None] = _Unset, |
|||
examples: Optional[list[Any]] = None, |
|||
example: Annotated[ |
|||
Optional[Any], |
|||
deprecated( |
|||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " |
|||
"although still supported. Use examples instead." |
|||
), |
|||
] = _Unset, |
|||
openapi_examples: Optional[dict[str, Example]] = None, |
|||
deprecated: Union[deprecated, str, bool, None] = None, |
|||
include_in_schema: bool = True, |
|||
json_schema_extra: Union[dict[str, Any], None] = None, |
|||
**extra: Any, |
|||
): |
|||
super().__init__( |
|||
default=default, |
|||
default_factory=default_factory, |
|||
annotation=annotation, |
|||
media_type=media_type, |
|||
alias=alias, |
|||
alias_priority=alias_priority, |
|||
validation_alias=validation_alias, |
|||
serialization_alias=serialization_alias, |
|||
title=title, |
|||
description=description, |
|||
gt=gt, |
|||
ge=ge, |
|||
lt=lt, |
|||
le=le, |
|||
min_length=min_length, |
|||
max_length=max_length, |
|||
pattern=pattern, |
|||
regex=regex, |
|||
discriminator=discriminator, |
|||
strict=strict, |
|||
multiple_of=multiple_of, |
|||
allow_inf_nan=allow_inf_nan, |
|||
max_digits=max_digits, |
|||
decimal_places=decimal_places, |
|||
deprecated=deprecated, |
|||
example=example, |
|||
examples=examples, |
|||
openapi_examples=openapi_examples, |
|||
include_in_schema=include_in_schema, |
|||
json_schema_extra=json_schema_extra, |
|||
**extra, |
|||
) |
|||
File diff suppressed because it is too large
@ -1,45 +0,0 @@ |
|||
import warnings |
|||
from typing import Optional |
|||
|
|||
from fastapi import Depends, FastAPI |
|||
from pydantic.v1 import BaseModel, validator |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class ModelB(BaseModel): |
|||
username: str |
|||
|
|||
|
|||
class ModelC(ModelB): |
|||
password: str |
|||
|
|||
|
|||
class ModelA(BaseModel): |
|||
name: str |
|||
description: Optional[str] = None |
|||
model_b: ModelB |
|||
tags: dict[str, str] = {} |
|||
|
|||
@validator("name") |
|||
def lower_username(cls, name: str, values): |
|||
if not name.endswith("A"): |
|||
raise ValueError("name must end in A") |
|||
return name |
|||
|
|||
|
|||
async def get_model_c() -> ModelC: |
|||
return ModelC(username="test-user", password="test-password") |
|||
|
|||
|
|||
with warnings.catch_warnings(record=True): |
|||
warnings.simplefilter("always") |
|||
|
|||
@app.get("/model/{name}", response_model=ModelA) |
|||
async def get_model_a(name: str, model_c=Depends(get_model_c)): |
|||
return { |
|||
"name": name, |
|||
"description": "model-a-desc", |
|||
"model_b": model_c, |
|||
"tags": {"key1": "value1", "key2": "value2"}, |
|||
} |
|||
@ -1,146 +0,0 @@ |
|||
import pytest |
|||
from fastapi.exceptions import ResponseValidationError |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from ..utils import needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture(name="client") |
|||
def get_client(): |
|||
from .app_pv1 import app |
|||
|
|||
client = TestClient(app) |
|||
return client |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_filter_sub_model(client: TestClient): |
|||
response = client.get("/model/modelA") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "modelA", |
|||
"description": "model-a-desc", |
|||
"model_b": {"username": "test-user"}, |
|||
"tags": {"key1": "value1", "key2": "value2"}, |
|||
} |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_validator_is_cloned(client: TestClient): |
|||
with pytest.raises(ResponseValidationError) as err: |
|||
client.get("/model/modelX") |
|||
assert err.value.errors() == [ |
|||
{ |
|||
"loc": ("response", "name"), |
|||
"msg": "name must end in A", |
|||
"type": "value_error", |
|||
} |
|||
] |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/model/{name}": { |
|||
"get": { |
|||
"summary": "Get Model A", |
|||
"operationId": "get_model_a_model__name__get", |
|||
"parameters": [ |
|||
{ |
|||
"required": True, |
|||
"schema": {"title": "Name", "type": "string"}, |
|||
"name": "name", |
|||
"in": "path", |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/ModelA" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"title": "HTTPValidationError", |
|||
"type": "object", |
|||
"properties": { |
|||
"detail": { |
|||
"title": "Detail", |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
} |
|||
}, |
|||
}, |
|||
"ModelA": { |
|||
"title": "ModelA", |
|||
"required": ["name", "model_b"], |
|||
"type": "object", |
|||
"properties": { |
|||
"name": {"title": "Name", "type": "string"}, |
|||
"description": {"title": "Description", "type": "string"}, |
|||
"model_b": {"$ref": "#/components/schemas/ModelB"}, |
|||
"tags": { |
|||
"additionalProperties": {"type": "string"}, |
|||
"type": "object", |
|||
"title": "Tags", |
|||
"default": {}, |
|||
}, |
|||
}, |
|||
}, |
|||
"ModelB": { |
|||
"title": "ModelB", |
|||
"required": ["username"], |
|||
"type": "object", |
|||
"properties": { |
|||
"username": {"title": "Username", "type": "string"} |
|||
}, |
|||
}, |
|||
"ValidationError": { |
|||
"title": "ValidationError", |
|||
"required": ["loc", "msg", "type"], |
|||
"type": "object", |
|||
"properties": { |
|||
"loc": { |
|||
"title": "Location", |
|||
"type": "array", |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
}, |
|||
"msg": {"title": "Message", "type": "string"}, |
|||
"type": {"title": "Error Type", "type": "string"}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,99 +0,0 @@ |
|||
import sys |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi._compat.v1 import BaseModel |
|||
from fastapi.testclient import TestClient |
|||
|
|||
|
|||
def test_warns_pydantic_v1_model_in_endpoint_param() -> None: |
|||
class ParamModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.warns( |
|||
FastAPIDeprecationWarning, |
|||
match=r"pydantic\.v1 is deprecated.*Please update the param data:", |
|||
): |
|||
|
|||
@app.post("/param") |
|||
def endpoint(data: ParamModelV1): |
|||
return data |
|||
|
|||
client = TestClient(app) |
|||
response = client.post("/param", json={"name": "test"}) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"name": "test"} |
|||
|
|||
|
|||
def test_warns_pydantic_v1_model_in_return_type() -> None: |
|||
class ReturnModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.warns( |
|||
FastAPIDeprecationWarning, |
|||
match=r"pydantic\.v1 is deprecated.*Please update the response model", |
|||
): |
|||
|
|||
@app.get("/return") |
|||
def endpoint() -> ReturnModelV1: |
|||
return ReturnModelV1(name="test") |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/return") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"name": "test"} |
|||
|
|||
|
|||
def test_warns_pydantic_v1_model_in_response_model() -> None: |
|||
class ResponseModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.warns( |
|||
FastAPIDeprecationWarning, |
|||
match=r"pydantic\.v1 is deprecated.*Please update the response model", |
|||
): |
|||
|
|||
@app.get("/response-model", response_model=ResponseModelV1) |
|||
def endpoint(): |
|||
return {"name": "test"} |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/response-model") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"name": "test"} |
|||
|
|||
|
|||
def test_warns_pydantic_v1_model_in_additional_responses_model() -> None: |
|||
class ErrorModelV1(BaseModel): |
|||
detail: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.warns( |
|||
FastAPIDeprecationWarning, |
|||
match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update", |
|||
): |
|||
|
|||
@app.get( |
|||
"/responses", response_model=None, responses={400: {"model": ErrorModelV1}} |
|||
) |
|||
def endpoint(): |
|||
return {"ok": True} |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/responses") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"ok": True} |
|||
@ -0,0 +1,97 @@ |
|||
import sys |
|||
import warnings |
|||
from typing import Union |
|||
|
|||
import pytest |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi.exceptions import PydanticV1NotSupportedError |
|||
|
|||
with warnings.catch_warnings(): |
|||
warnings.simplefilter("ignore", UserWarning) |
|||
from pydantic.v1 import BaseModel |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_endpoint_param() -> None: |
|||
class ParamModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.post("/param") |
|||
def endpoint(data: ParamModelV1): # pragma: no cover |
|||
return data |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_return_type() -> None: |
|||
class ReturnModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.get("/return") |
|||
def endpoint() -> ReturnModelV1: # pragma: no cover |
|||
return ReturnModelV1(name="test") |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_response_model() -> None: |
|||
class ResponseModelV1(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.get("/response-model", response_model=ResponseModelV1) |
|||
def endpoint(): # pragma: no cover |
|||
return {"name": "test"} |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_additional_responses_model() -> None: |
|||
class ErrorModelV1(BaseModel): |
|||
detail: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.get( |
|||
"/responses", response_model=None, responses={400: {"model": ErrorModelV1}} |
|||
) |
|||
def endpoint(): # pragma: no cover |
|||
return {"ok": True} |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_union() -> None: |
|||
class ModelV1A(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.post("/union") |
|||
def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover |
|||
return data |
|||
|
|||
|
|||
def test_raises_pydantic_v1_model_in_sequence() -> None: |
|||
class ModelV1A(BaseModel): |
|||
name: str |
|||
|
|||
app = FastAPI() |
|||
|
|||
with pytest.raises(PydanticV1NotSupportedError): |
|||
|
|||
@app.post("/sequence") |
|||
def endpoint(data: list[ModelV1A]): # pragma: no cover |
|||
return data |
|||
@ -1,439 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
from typing import Any, Union |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi._compat.v1 import BaseModel |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
title: str |
|||
size: int |
|||
description: Union[str, None] = None |
|||
sub: SubItem |
|||
multi: list[SubItem] = [] |
|||
|
|||
|
|||
app = FastAPI() |
|||
|
|||
with warnings.catch_warnings(record=True): |
|||
warnings.simplefilter("always") |
|||
|
|||
@app.post("/simple-model") |
|||
def handle_simple_model(data: SubItem) -> SubItem: |
|||
return data |
|||
|
|||
@app.post("/simple-model-filter", response_model=SubItem) |
|||
def handle_simple_model_filter(data: SubItem) -> Any: |
|||
extended_data = data.dict() |
|||
extended_data.update({"secret_price": 42}) |
|||
return extended_data |
|||
|
|||
@app.post("/item") |
|||
def handle_item(data: Item) -> Item: |
|||
return data |
|||
|
|||
@app.post("/item-filter", response_model=Item) |
|||
def handle_item_filter(data: Item) -> Any: |
|||
extended_data = data.dict() |
|||
extended_data.update({"secret_data": "classified", "internal_id": 12345}) |
|||
extended_data["sub"].update({"internal_id": 67890}) |
|||
return extended_data |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_old_simple_model(): |
|||
response = client.post( |
|||
"/simple-model", |
|||
json={"name": "Foo"}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"name": "Foo"} |
|||
|
|||
|
|||
def test_old_simple_model_validation_error(): |
|||
response = client.post( |
|||
"/simple-model", |
|||
json={"wrong_name": "Foo"}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", "name"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_old_simple_model_filter(): |
|||
response = client.post( |
|||
"/simple-model-filter", |
|||
json={"name": "Foo"}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"name": "Foo"} |
|||
|
|||
|
|||
def test_item_model(): |
|||
response = client.post( |
|||
"/item", |
|||
json={ |
|||
"title": "Test Item", |
|||
"size": 100, |
|||
"description": "This is a test item", |
|||
"sub": {"name": "SubItem1"}, |
|||
"multi": [{"name": "Multi1"}, {"name": "Multi2"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "Test Item", |
|||
"size": 100, |
|||
"description": "This is a test item", |
|||
"sub": {"name": "SubItem1"}, |
|||
"multi": [{"name": "Multi1"}, {"name": "Multi2"}], |
|||
} |
|||
|
|||
|
|||
def test_item_model_minimal(): |
|||
response = client.post( |
|||
"/item", |
|||
json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "Minimal Item", |
|||
"size": 50, |
|||
"description": None, |
|||
"sub": {"name": "SubMin"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_item_model_validation_errors(): |
|||
response = client.post( |
|||
"/item", |
|||
json={"title": "Missing fields"}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
error_detail = response.json()["detail"] |
|||
assert len(error_detail) == 2 |
|||
assert { |
|||
"loc": ["body", "size"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} in error_detail |
|||
assert { |
|||
"loc": ["body", "sub"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} in error_detail |
|||
|
|||
|
|||
def test_item_model_nested_validation_error(): |
|||
response = client.post( |
|||
"/item", |
|||
json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", "sub", "name"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_item_model_invalid_type(): |
|||
response = client.post( |
|||
"/item", |
|||
json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", "size"], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_item_filter(): |
|||
response = client.post( |
|||
"/item-filter", |
|||
json={ |
|||
"title": "Filtered Item", |
|||
"size": 200, |
|||
"description": "Test filtering", |
|||
"sub": {"name": "SubFiltered"}, |
|||
"multi": [], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert result == { |
|||
"title": "Filtered Item", |
|||
"size": 200, |
|||
"description": "Test filtering", |
|||
"sub": {"name": "SubFiltered"}, |
|||
"multi": [], |
|||
} |
|||
assert "secret_data" not in result |
|||
assert "internal_id" not in result |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/simple-model": { |
|||
"post": { |
|||
"summary": "Handle Simple Model", |
|||
"operationId": "handle_simple_model_simple_model_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/SubItem"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/SubItem" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/simple-model-filter": { |
|||
"post": { |
|||
"summary": "Handle Simple Model Filter", |
|||
"operationId": "handle_simple_model_filter_simple_model_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/SubItem"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/SubItem" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item": { |
|||
"post": { |
|||
"summary": "Handle Item", |
|||
"operationId": "handle_item_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-filter": { |
|||
"post": { |
|||
"summary": "Handle Item Filter", |
|||
"operationId": "handle_item_filter_item_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"title": {"type": "string", "title": "Title"}, |
|||
"size": {"type": "integer", "title": "Size"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"sub": {"$ref": "#/components/schemas/SubItem"}, |
|||
"multi": { |
|||
"items": {"$ref": "#/components/schemas/SubItem"}, |
|||
"type": "array", |
|||
"title": "Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["title", "size", "sub"], |
|||
"title": "Item", |
|||
}, |
|||
"SubItem": { |
|||
"properties": {"name": {"type": "string", "title": "Name"}}, |
|||
"type": "object", |
|||
"required": ["name"], |
|||
"title": "SubItem", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,682 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
from typing import Any, Union |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi._compat.v1 import BaseModel |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
title: str |
|||
size: int |
|||
description: Union[str, None] = None |
|||
sub: SubItem |
|||
multi: list[SubItem] = [] |
|||
|
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
with warnings.catch_warnings(record=True): |
|||
warnings.simplefilter("always") |
|||
|
|||
@app.post("/item") |
|||
def handle_item(data: Item) -> list[Item]: |
|||
return [data, data] |
|||
|
|||
@app.post("/item-filter", response_model=list[Item]) |
|||
def handle_item_filter(data: Item) -> Any: |
|||
extended_data = data.dict() |
|||
extended_data.update({"secret_data": "classified", "internal_id": 12345}) |
|||
extended_data["sub"].update({"internal_id": 67890}) |
|||
return [extended_data, extended_data] |
|||
|
|||
@app.post("/item-list") |
|||
def handle_item_list(data: list[Item]) -> Item: |
|||
if data: |
|||
return data[0] |
|||
return Item(title="", size=0, sub=SubItem(name="")) |
|||
|
|||
@app.post("/item-list-filter", response_model=Item) |
|||
def handle_item_list_filter(data: list[Item]) -> Any: |
|||
if data: |
|||
extended_data = data[0].dict() |
|||
extended_data.update({"secret_data": "classified", "internal_id": 12345}) |
|||
extended_data["sub"].update({"internal_id": 67890}) |
|||
return extended_data |
|||
return Item(title="", size=0, sub=SubItem(name="")) |
|||
|
|||
@app.post("/item-list-to-list") |
|||
def handle_item_list_to_list(data: list[Item]) -> list[Item]: |
|||
return data |
|||
|
|||
@app.post("/item-list-to-list-filter", response_model=list[Item]) |
|||
def handle_item_list_to_list_filter(data: list[Item]) -> Any: |
|||
if data: |
|||
extended_data = data[0].dict() |
|||
extended_data.update({"secret_data": "classified", "internal_id": 12345}) |
|||
extended_data["sub"].update({"internal_id": 67890}) |
|||
return [extended_data, extended_data] |
|||
return [] |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_item_to_list(): |
|||
response = client.post( |
|||
"/item", |
|||
json={ |
|||
"title": "Test Item", |
|||
"size": 100, |
|||
"description": "This is a test item", |
|||
"sub": {"name": "SubItem1"}, |
|||
"multi": [{"name": "Multi1"}, {"name": "Multi2"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert isinstance(result, list) |
|||
assert len(result) == 2 |
|||
for item in result: |
|||
assert item == { |
|||
"title": "Test Item", |
|||
"size": 100, |
|||
"description": "This is a test item", |
|||
"sub": {"name": "SubItem1"}, |
|||
"multi": [{"name": "Multi1"}, {"name": "Multi2"}], |
|||
} |
|||
|
|||
|
|||
def test_item_to_list_filter(): |
|||
response = client.post( |
|||
"/item-filter", |
|||
json={ |
|||
"title": "Filtered Item", |
|||
"size": 200, |
|||
"description": "Test filtering", |
|||
"sub": {"name": "SubFiltered"}, |
|||
"multi": [], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert isinstance(result, list) |
|||
assert len(result) == 2 |
|||
for item in result: |
|||
assert item == { |
|||
"title": "Filtered Item", |
|||
"size": 200, |
|||
"description": "Test filtering", |
|||
"sub": {"name": "SubFiltered"}, |
|||
"multi": [], |
|||
} |
|||
# Verify secret fields are filtered out |
|||
assert "secret_data" not in item |
|||
assert "internal_id" not in item |
|||
assert "internal_id" not in item["sub"] |
|||
|
|||
|
|||
def test_list_to_item(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json=[ |
|||
{"title": "First Item", "size": 50, "sub": {"name": "First Sub"}}, |
|||
{"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}}, |
|||
], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "First Item", |
|||
"size": 50, |
|||
"description": None, |
|||
"sub": {"name": "First Sub"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_list_to_item_empty(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json=[], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "", |
|||
"size": 0, |
|||
"description": None, |
|||
"sub": {"name": ""}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_list_to_item_filter(): |
|||
response = client.post( |
|||
"/item-list-filter", |
|||
json=[ |
|||
{ |
|||
"title": "First Item", |
|||
"size": 100, |
|||
"sub": {"name": "First Sub"}, |
|||
"multi": [{"name": "Multi1"}], |
|||
}, |
|||
{"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}}, |
|||
], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert result == { |
|||
"title": "First Item", |
|||
"size": 100, |
|||
"description": None, |
|||
"sub": {"name": "First Sub"}, |
|||
"multi": [{"name": "Multi1"}], |
|||
} |
|||
# Verify secret fields are filtered out |
|||
assert "secret_data" not in result |
|||
assert "internal_id" not in result |
|||
|
|||
|
|||
def test_list_to_item_filter_no_data(): |
|||
response = client.post("/item-list-filter", json=[]) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "", |
|||
"size": 0, |
|||
"description": None, |
|||
"sub": {"name": ""}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_list_to_list(): |
|||
input_items = [ |
|||
{"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}}, |
|||
{ |
|||
"title": "Item 2", |
|||
"size": 20, |
|||
"description": "Second item", |
|||
"sub": {"name": "Sub2"}, |
|||
"multi": [{"name": "M1"}, {"name": "M2"}], |
|||
}, |
|||
{"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}}, |
|||
] |
|||
response = client.post( |
|||
"/item-list-to-list", |
|||
json=input_items, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert isinstance(result, list) |
|||
assert len(result) == 3 |
|||
assert result[0] == { |
|||
"title": "Item 1", |
|||
"size": 10, |
|||
"description": None, |
|||
"sub": {"name": "Sub1"}, |
|||
"multi": [], |
|||
} |
|||
assert result[1] == { |
|||
"title": "Item 2", |
|||
"size": 20, |
|||
"description": "Second item", |
|||
"sub": {"name": "Sub2"}, |
|||
"multi": [{"name": "M1"}, {"name": "M2"}], |
|||
} |
|||
assert result[2] == { |
|||
"title": "Item 3", |
|||
"size": 30, |
|||
"description": None, |
|||
"sub": {"name": "Sub3"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_list_to_list_filter(): |
|||
response = client.post( |
|||
"/item-list-to-list-filter", |
|||
json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert isinstance(result, list) |
|||
assert len(result) == 2 |
|||
for item in result: |
|||
assert item == { |
|||
"title": "Item 1", |
|||
"size": 100, |
|||
"description": None, |
|||
"sub": {"name": "Sub1"}, |
|||
"multi": [], |
|||
} |
|||
# Verify secret fields are filtered out |
|||
assert "secret_data" not in item |
|||
assert "internal_id" not in item |
|||
|
|||
|
|||
def test_list_to_list_filter_no_data(): |
|||
response = client.post( |
|||
"/item-list-to-list-filter", |
|||
json=[], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == [] |
|||
|
|||
|
|||
def test_list_validation_error(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json=[ |
|||
{"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}}, |
|||
{ |
|||
"title": "Invalid Item" |
|||
# Missing required fields: size and sub |
|||
}, |
|||
], |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
error_detail = response.json()["detail"] |
|||
assert len(error_detail) == 2 |
|||
assert { |
|||
"loc": ["body", 1, "size"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} in error_detail |
|||
assert { |
|||
"loc": ["body", 1, "sub"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} in error_detail |
|||
|
|||
|
|||
def test_list_nested_validation_error(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json=[ |
|||
{"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}} |
|||
], |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", 0, "sub", "name"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_list_type_validation_error(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}], |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", 0, "size"], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_invalid_list_structure(): |
|||
response = client.post( |
|||
"/item-list", |
|||
json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body"], |
|||
"msg": "value is not a valid list", |
|||
"type": "type_error.list", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/item": { |
|||
"post": { |
|||
"summary": "Handle Item", |
|||
"operationId": "handle_item_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle Item Item Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-filter": { |
|||
"post": { |
|||
"summary": "Handle Item Filter", |
|||
"operationId": "handle_item_filter_item_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle Item Filter Item Filter Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-list": { |
|||
"post": { |
|||
"summary": "Handle Item List", |
|||
"operationId": "handle_item_list_item_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": {"$ref": "#/components/schemas/Item"}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-list-filter": { |
|||
"post": { |
|||
"summary": "Handle Item List Filter", |
|||
"operationId": "handle_item_list_filter_item_list_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": {"$ref": "#/components/schemas/Item"}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-list-to-list": { |
|||
"post": { |
|||
"summary": "Handle Item List To List", |
|||
"operationId": "handle_item_list_to_list_item_list_to_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": {"$ref": "#/components/schemas/Item"}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle Item List To List Item List To List Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/item-list-to-list-filter": { |
|||
"post": { |
|||
"summary": "Handle Item List To List Filter", |
|||
"operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": {"$ref": "#/components/schemas/Item"}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle Item List To List Filter Item List To List Filter Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"title": {"type": "string", "title": "Title"}, |
|||
"size": {"type": "integer", "title": "Size"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"sub": {"$ref": "#/components/schemas/SubItem"}, |
|||
"multi": { |
|||
"items": {"$ref": "#/components/schemas/SubItem"}, |
|||
"type": "array", |
|||
"title": "Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["title", "size", "sub"], |
|||
"title": "Item", |
|||
}, |
|||
"SubItem": { |
|||
"properties": {"name": {"type": "string", "title": "Name"}}, |
|||
"type": "object", |
|||
"required": ["name"], |
|||
"title": "SubItem", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
File diff suppressed because it is too large
@ -1,137 +0,0 @@ |
|||
import warnings |
|||
|
|||
from fastapi import FastAPI |
|||
|
|||
from . import modelsv1, modelsv2, modelsv2b |
|||
|
|||
app = FastAPI() |
|||
|
|||
with warnings.catch_warnings(record=True): |
|||
warnings.simplefilter("always") |
|||
|
|||
@app.post("/v1-to-v2/item") |
|||
def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item: |
|||
return modelsv2.Item( |
|||
new_title=data.title, |
|||
new_size=data.size, |
|||
new_description=data.description, |
|||
new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), |
|||
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], |
|||
) |
|||
|
|||
@app.post("/v2-to-v1/item") |
|||
def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: |
|||
return modelsv1.Item( |
|||
title=data.new_title, |
|||
size=data.new_size, |
|||
description=data.new_description, |
|||
sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), |
|||
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], |
|||
) |
|||
|
|||
@app.post("/v1-to-v2/item-to-list") |
|||
def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]: |
|||
converted = modelsv2.Item( |
|||
new_title=data.title, |
|||
new_size=data.size, |
|||
new_description=data.description, |
|||
new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), |
|||
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], |
|||
) |
|||
return [converted, converted] |
|||
|
|||
@app.post("/v1-to-v2/list-to-list") |
|||
def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]: |
|||
result = [] |
|||
for item in data: |
|||
result.append( |
|||
modelsv2.Item( |
|||
new_title=item.title, |
|||
new_size=item.size, |
|||
new_description=item.description, |
|||
new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), |
|||
new_multi=[ |
|||
modelsv2.SubItem(new_sub_name=s.name) for s in item.multi |
|||
], |
|||
) |
|||
) |
|||
return result |
|||
|
|||
@app.post("/v1-to-v2/list-to-item") |
|||
def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item: |
|||
if data: |
|||
item = data[0] |
|||
return modelsv2.Item( |
|||
new_title=item.title, |
|||
new_size=item.size, |
|||
new_description=item.description, |
|||
new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), |
|||
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], |
|||
) |
|||
return modelsv2.Item( |
|||
new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="") |
|||
) |
|||
|
|||
@app.post("/v2-to-v1/item-to-list") |
|||
def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]: |
|||
converted = modelsv1.Item( |
|||
title=data.new_title, |
|||
size=data.new_size, |
|||
description=data.new_description, |
|||
sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), |
|||
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], |
|||
) |
|||
return [converted, converted] |
|||
|
|||
@app.post("/v2-to-v1/list-to-list") |
|||
def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]: |
|||
result = [] |
|||
for item in data: |
|||
result.append( |
|||
modelsv1.Item( |
|||
title=item.new_title, |
|||
size=item.new_size, |
|||
description=item.new_description, |
|||
sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), |
|||
multi=[ |
|||
modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi |
|||
], |
|||
) |
|||
) |
|||
return result |
|||
|
|||
@app.post("/v2-to-v1/list-to-item") |
|||
def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item: |
|||
if data: |
|||
item = data[0] |
|||
return modelsv1.Item( |
|||
title=item.new_title, |
|||
size=item.new_size, |
|||
description=item.new_description, |
|||
sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), |
|||
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], |
|||
) |
|||
return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name="")) |
|||
|
|||
@app.post("/v2-to-v1/same-name") |
|||
def handle_v2_same_name_to_v1( |
|||
item1: modelsv2.Item, item2: modelsv2b.Item |
|||
) -> modelsv1.Item: |
|||
return modelsv1.Item( |
|||
title=item1.new_title, |
|||
size=item2.dup_size, |
|||
description=item1.new_description, |
|||
sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name), |
|||
multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi], |
|||
) |
|||
|
|||
@app.post("/v2-to-v1/list-of-items-to-list-of-items") |
|||
def handle_v2_items_in_list_to_v1_item_in_list( |
|||
data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList] |
|||
) -> list[modelsv1.ItemInList]: |
|||
item1 = data1[0] |
|||
item2 = data2[0] |
|||
return [ |
|||
modelsv1.ItemInList(name1=item1.name2), |
|||
modelsv1.ItemInList(name1=item2.dup_name2), |
|||
] |
|||
@ -1,19 +0,0 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi._compat.v1 import BaseModel |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
title: str |
|||
size: int |
|||
description: Union[str, None] = None |
|||
sub: SubItem |
|||
multi: list[SubItem] = [] |
|||
|
|||
|
|||
class ItemInList(BaseModel): |
|||
name1: str |
|||
@ -1,19 +0,0 @@ |
|||
from typing import Union |
|||
|
|||
from pydantic import BaseModel |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
new_sub_name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
new_title: str |
|||
new_size: int |
|||
new_description: Union[str, None] = None |
|||
new_sub: SubItem |
|||
new_multi: list[SubItem] = [] |
|||
|
|||
|
|||
class ItemInList(BaseModel): |
|||
name2: str |
|||
@ -1,19 +0,0 @@ |
|||
from typing import Union |
|||
|
|||
from pydantic import BaseModel |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
dup_sub_name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
dup_title: str |
|||
dup_size: int |
|||
dup_description: Union[str, None] = None |
|||
dup_sub: SubItem |
|||
dup_multi: list[SubItem] = [] |
|||
|
|||
|
|||
class ItemInList(BaseModel): |
|||
dup_name2: str |
|||
@ -1,951 +0,0 @@ |
|||
import sys |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from .main import app |
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_v1_to_v2_item(): |
|||
response = client.post( |
|||
"/v1-to-v2/item", |
|||
json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"new_title": "Test", |
|||
"new_size": 10, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "SubTest"}, |
|||
"new_multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v2_to_v1_item(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "NewTest", |
|||
"new_size": 20, |
|||
"new_sub": {"new_sub_name": "NewSubTest"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"title": "NewTest", |
|||
"size": 20, |
|||
"description": None, |
|||
"sub": {"name": "NewSubTest"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v1_to_v2_item_to_list(): |
|||
response = client.post( |
|||
"/v1-to-v2/item-to-list", |
|||
json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == [ |
|||
{ |
|||
"new_title": "ListTest", |
|||
"new_size": 30, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "SubListTest"}, |
|||
"new_multi": [], |
|||
}, |
|||
{ |
|||
"new_title": "ListTest", |
|||
"new_size": 30, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "SubListTest"}, |
|||
"new_multi": [], |
|||
}, |
|||
] |
|||
|
|||
|
|||
def test_v1_to_v2_list_to_list(): |
|||
response = client.post( |
|||
"/v1-to-v2/list-to-list", |
|||
json=[ |
|||
{"title": "Item1", "size": 40, "sub": {"name": "Sub1"}}, |
|||
{"title": "Item2", "size": 50, "sub": {"name": "Sub2"}}, |
|||
], |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == [ |
|||
{ |
|||
"new_title": "Item1", |
|||
"new_size": 40, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "Sub1"}, |
|||
"new_multi": [], |
|||
}, |
|||
{ |
|||
"new_title": "Item2", |
|||
"new_size": 50, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "Sub2"}, |
|||
"new_multi": [], |
|||
}, |
|||
] |
|||
|
|||
|
|||
def test_v1_to_v2_list_to_item(): |
|||
response = client.post( |
|||
"/v1-to-v2/list-to-item", |
|||
json=[ |
|||
{"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}}, |
|||
{"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}}, |
|||
], |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"new_title": "FirstItem", |
|||
"new_size": 60, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "FirstSub"}, |
|||
"new_multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v2_to_v1_item_to_list(): |
|||
response = client.post( |
|||
"/v2-to-v1/item-to-list", |
|||
json={ |
|||
"new_title": "ListNew", |
|||
"new_size": 80, |
|||
"new_sub": {"new_sub_name": "SubListNew"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == [ |
|||
{ |
|||
"title": "ListNew", |
|||
"size": 80, |
|||
"description": None, |
|||
"sub": {"name": "SubListNew"}, |
|||
"multi": [], |
|||
}, |
|||
{ |
|||
"title": "ListNew", |
|||
"size": 80, |
|||
"description": None, |
|||
"sub": {"name": "SubListNew"}, |
|||
"multi": [], |
|||
}, |
|||
] |
|||
|
|||
|
|||
def test_v2_to_v1_list_to_list(): |
|||
response = client.post( |
|||
"/v2-to-v1/list-to-list", |
|||
json=[ |
|||
{ |
|||
"new_title": "New1", |
|||
"new_size": 90, |
|||
"new_sub": {"new_sub_name": "NewSub1"}, |
|||
}, |
|||
{ |
|||
"new_title": "New2", |
|||
"new_size": 100, |
|||
"new_sub": {"new_sub_name": "NewSub2"}, |
|||
}, |
|||
], |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == [ |
|||
{ |
|||
"title": "New1", |
|||
"size": 90, |
|||
"description": None, |
|||
"sub": {"name": "NewSub1"}, |
|||
"multi": [], |
|||
}, |
|||
{ |
|||
"title": "New2", |
|||
"size": 100, |
|||
"description": None, |
|||
"sub": {"name": "NewSub2"}, |
|||
"multi": [], |
|||
}, |
|||
] |
|||
|
|||
|
|||
def test_v2_to_v1_list_to_item(): |
|||
response = client.post( |
|||
"/v2-to-v1/list-to-item", |
|||
json=[ |
|||
{ |
|||
"new_title": "FirstNew", |
|||
"new_size": 110, |
|||
"new_sub": {"new_sub_name": "FirstNewSub"}, |
|||
}, |
|||
{ |
|||
"new_title": "SecondNew", |
|||
"new_size": 120, |
|||
"new_sub": {"new_sub_name": "SecondNewSub"}, |
|||
}, |
|||
], |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"title": "FirstNew", |
|||
"size": 110, |
|||
"description": None, |
|||
"sub": {"name": "FirstNewSub"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v1_to_v2_list_to_item_empty(): |
|||
response = client.post("/v1-to-v2/list-to-item", json=[]) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"new_title": "", |
|||
"new_size": 0, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": ""}, |
|||
"new_multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v2_to_v1_list_to_item_empty(): |
|||
response = client.post("/v2-to-v1/list-to-item", json=[]) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"title": "", |
|||
"size": 0, |
|||
"description": None, |
|||
"sub": {"name": ""}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v2_same_name_to_v1(): |
|||
response = client.post( |
|||
"/v2-to-v1/same-name", |
|||
json={ |
|||
"item1": { |
|||
"new_title": "Title1", |
|||
"new_size": 100, |
|||
"new_description": "Description1", |
|||
"new_sub": {"new_sub_name": "Sub1"}, |
|||
"new_multi": [{"new_sub_name": "Multi1"}], |
|||
}, |
|||
"item2": { |
|||
"dup_title": "Title2", |
|||
"dup_size": 200, |
|||
"dup_description": "Description2", |
|||
"dup_sub": {"dup_sub_name": "Sub2"}, |
|||
"dup_multi": [ |
|||
{"dup_sub_name": "Multi2a"}, |
|||
{"dup_sub_name": "Multi2b"}, |
|||
], |
|||
}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"title": "Title1", |
|||
"size": 200, |
|||
"description": "Description1", |
|||
"sub": {"name": "Sub1"}, |
|||
"multi": [{"name": "Multi2a"}, {"name": "Multi2b"}], |
|||
} |
|||
|
|||
|
|||
def test_v2_items_in_list_to_v1_item_in_list(): |
|||
response = client.post( |
|||
"/v2-to-v1/list-of-items-to-list-of-items", |
|||
json={ |
|||
"data1": [{"name2": "Item1"}, {"name2": "Item2"}], |
|||
"data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == [ |
|||
{"name1": "Item1"}, |
|||
{"name1": "Item3"}, |
|||
] |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200 |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/v1-to-v2/item": { |
|||
"post": { |
|||
"summary": "Handle V1 Item To V2", |
|||
"operationId": "handle_v1_item_to_v2_v1_to_v2_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{ |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/item": { |
|||
"post": { |
|||
"summary": "Handle V2 Item To V1", |
|||
"operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" |
|||
}, |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v1-to-v2/item-to-list": { |
|||
"post": { |
|||
"summary": "Handle V1 Item To V2 List", |
|||
"operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{ |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v1-to-v2/list-to-list": { |
|||
"post": { |
|||
"summary": "Handle V1 List To V2 List", |
|||
"operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle V1 List To V2 List V1 To V2 List To List Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v1-to-v2/list-to-item": { |
|||
"post": { |
|||
"summary": "Handle V1 List To V2 Item", |
|||
"operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/item-to-list": { |
|||
"post": { |
|||
"summary": "Handle V2 Item To V1 List", |
|||
"operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" |
|||
}, |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/list-to-list": { |
|||
"post": { |
|||
"summary": "Handle V2 List To V1 List", |
|||
"operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle V2 List To V1 List V2 To V1 List To List Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/list-to-item": { |
|||
"post": { |
|||
"summary": "Handle V2 List To V1 Item", |
|||
"operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/same-name": { |
|||
"post": { |
|||
"summary": "Handle V2 Same Name To V1", |
|||
"operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post" |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/list-of-items-to-list-of-items": { |
|||
"post": { |
|||
"summary": "Handle V2 Items In List To V1 Item In List", |
|||
"operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post" |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList" |
|||
}, |
|||
"type": "array", |
|||
"title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { |
|||
"properties": { |
|||
"data1": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data1", |
|||
}, |
|||
"data2": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" |
|||
}, |
|||
"type": "array", |
|||
"title": "Data2", |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["data1", "data2"], |
|||
"title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", |
|||
}, |
|||
"Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { |
|||
"properties": { |
|||
"item1": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" |
|||
}, |
|||
"item2": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["item1", "item2"], |
|||
"title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", |
|||
}, |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [ |
|||
{"type": "string"}, |
|||
{"type": "integer"}, |
|||
] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { |
|||
"properties": { |
|||
"title": {"type": "string", "title": "Title"}, |
|||
"size": {"type": "integer", "title": "Size"}, |
|||
"description": { |
|||
"type": "string", |
|||
"title": "Description", |
|||
}, |
|||
"sub": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" |
|||
}, |
|||
"multi": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" |
|||
}, |
|||
"type": "array", |
|||
"title": "Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["title", "size", "sub"], |
|||
"title": "Item", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { |
|||
"properties": {"name1": {"type": "string", "title": "Name1"}}, |
|||
"type": "object", |
|||
"required": ["name1"], |
|||
"title": "ItemInList", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { |
|||
"properties": {"name": {"type": "string", "title": "Name"}}, |
|||
"type": "object", |
|||
"required": ["name"], |
|||
"title": "SubItem", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { |
|||
"properties": { |
|||
"new_title": { |
|||
"type": "string", |
|||
"title": "New Title", |
|||
}, |
|||
"new_size": { |
|||
"type": "integer", |
|||
"title": "New Size", |
|||
}, |
|||
"new_description": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "New Description", |
|||
}, |
|||
"new_sub": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" |
|||
}, |
|||
"new_multi": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" |
|||
}, |
|||
"type": "array", |
|||
"title": "New Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["new_title", "new_size", "new_sub"], |
|||
"title": "Item", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": { |
|||
"properties": { |
|||
"new_title": { |
|||
"type": "string", |
|||
"title": "New Title", |
|||
}, |
|||
"new_size": { |
|||
"type": "integer", |
|||
"title": "New Size", |
|||
}, |
|||
"new_description": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "New Description", |
|||
}, |
|||
"new_sub": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" |
|||
}, |
|||
"new_multi": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" |
|||
}, |
|||
"type": "array", |
|||
"title": "New Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["new_title", "new_size", "new_sub"], |
|||
"title": "Item", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { |
|||
"properties": {"name2": {"type": "string", "title": "Name2"}}, |
|||
"type": "object", |
|||
"required": ["name2"], |
|||
"title": "ItemInList", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { |
|||
"properties": { |
|||
"new_sub_name": { |
|||
"type": "string", |
|||
"title": "New Sub Name", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"required": ["new_sub_name"], |
|||
"title": "SubItem", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { |
|||
"properties": { |
|||
"dup_title": { |
|||
"type": "string", |
|||
"title": "Dup Title", |
|||
}, |
|||
"dup_size": { |
|||
"type": "integer", |
|||
"title": "Dup Size", |
|||
}, |
|||
"dup_description": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Dup Description", |
|||
}, |
|||
"dup_sub": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" |
|||
}, |
|||
"dup_multi": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" |
|||
}, |
|||
"type": "array", |
|||
"title": "Dup Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["dup_title", "dup_size", "dup_sub"], |
|||
"title": "Item", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { |
|||
"properties": { |
|||
"dup_name2": { |
|||
"type": "string", |
|||
"title": "Dup Name2", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"required": ["dup_name2"], |
|||
"title": "ItemInList", |
|||
}, |
|||
"tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { |
|||
"properties": { |
|||
"dup_sub_name": { |
|||
"type": "string", |
|||
"title": "Dup Sub Name", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"required": ["dup_sub_name"], |
|||
"title": "SubItem", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
) |
|||
@ -1,692 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
from typing import Any, Union |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi._compat.v1 import BaseModel |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
from pydantic import BaseModel as NewBaseModel |
|||
|
|||
|
|||
class SubItem(BaseModel): |
|||
name: str |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
title: str |
|||
size: int |
|||
description: Union[str, None] = None |
|||
sub: SubItem |
|||
multi: list[SubItem] = [] |
|||
|
|||
|
|||
class NewSubItem(NewBaseModel): |
|||
new_sub_name: str |
|||
|
|||
|
|||
class NewItem(NewBaseModel): |
|||
new_title: str |
|||
new_size: int |
|||
new_description: Union[str, None] = None |
|||
new_sub: NewSubItem |
|||
new_multi: list[NewSubItem] = [] |
|||
|
|||
|
|||
app = FastAPI() |
|||
|
|||
with warnings.catch_warnings(record=True): |
|||
warnings.simplefilter("always") |
|||
|
|||
@app.post("/v1-to-v2/") |
|||
def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: |
|||
if data.size < 0: |
|||
return None |
|||
return NewItem( |
|||
new_title=data.title, |
|||
new_size=data.size, |
|||
new_description=data.description, |
|||
new_sub=NewSubItem(new_sub_name=data.sub.name), |
|||
new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], |
|||
) |
|||
|
|||
@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) |
|||
def handle_v1_item_to_v2_filter(data: Item) -> Any: |
|||
if data.size < 0: |
|||
return None |
|||
result = { |
|||
"new_title": data.title, |
|||
"new_size": data.size, |
|||
"new_description": data.description, |
|||
"new_sub": { |
|||
"new_sub_name": data.sub.name, |
|||
"new_sub_secret": "sub_hidden", |
|||
}, |
|||
"new_multi": [ |
|||
{"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} |
|||
for s in data.multi |
|||
], |
|||
"secret": "hidden_v1_to_v2", |
|||
} |
|||
return result |
|||
|
|||
@app.post("/v2-to-v1/item") |
|||
def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: |
|||
if data.new_size < 0: |
|||
return None |
|||
return Item( |
|||
title=data.new_title, |
|||
size=data.new_size, |
|||
description=data.new_description, |
|||
sub=SubItem(name=data.new_sub.new_sub_name), |
|||
multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], |
|||
) |
|||
|
|||
@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) |
|||
def handle_v2_item_to_v1_filter(data: NewItem) -> Any: |
|||
if data.new_size < 0: |
|||
return None |
|||
result = { |
|||
"title": data.new_title, |
|||
"size": data.new_size, |
|||
"description": data.new_description, |
|||
"sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, |
|||
"multi": [ |
|||
{"name": s.new_sub_name, "sub_secret": "sub_hidden"} |
|||
for s in data.new_multi |
|||
], |
|||
"secret": "hidden_v2_to_v1", |
|||
} |
|||
return result |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_v1_to_v2_item_success(): |
|||
response = client.post( |
|||
"/v1-to-v2/", |
|||
json={ |
|||
"title": "Old Item", |
|||
"size": 100, |
|||
"description": "V1 description", |
|||
"sub": {"name": "V1 Sub"}, |
|||
"multi": [{"name": "M1"}, {"name": "M2"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"new_title": "Old Item", |
|||
"new_size": 100, |
|||
"new_description": "V1 description", |
|||
"new_sub": {"new_sub_name": "V1 Sub"}, |
|||
"new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], |
|||
} |
|||
|
|||
|
|||
def test_v1_to_v2_item_returns_none(): |
|||
response = client.post( |
|||
"/v1-to-v2/", |
|||
json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() is None |
|||
|
|||
|
|||
def test_v1_to_v2_item_minimal(): |
|||
response = client.post( |
|||
"/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}} |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"new_title": "Minimal", |
|||
"new_size": 50, |
|||
"new_description": None, |
|||
"new_sub": {"new_sub_name": "MinSub"}, |
|||
"new_multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v1_to_v2_item_filter_success(): |
|||
response = client.post( |
|||
"/v1-to-v2/item-filter", |
|||
json={ |
|||
"title": "Filtered Item", |
|||
"size": 50, |
|||
"sub": {"name": "Sub"}, |
|||
"multi": [{"name": "Multi1"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert result["new_title"] == "Filtered Item" |
|||
assert result["new_size"] == 50 |
|||
assert result["new_sub"]["new_sub_name"] == "Sub" |
|||
assert result["new_multi"][0]["new_sub_name"] == "Multi1" |
|||
# Verify secret fields are filtered out |
|||
assert "secret" not in result |
|||
assert "new_sub_secret" not in result["new_sub"] |
|||
assert "new_sub_secret" not in result["new_multi"][0] |
|||
|
|||
|
|||
def test_v1_to_v2_item_filter_returns_none(): |
|||
response = client.post( |
|||
"/v1-to-v2/item-filter", |
|||
json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() is None |
|||
|
|||
|
|||
def test_v2_to_v1_item_success(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "New Item", |
|||
"new_size": 200, |
|||
"new_description": "V2 description", |
|||
"new_sub": {"new_sub_name": "V2 Sub"}, |
|||
"new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "New Item", |
|||
"size": 200, |
|||
"description": "V2 description", |
|||
"sub": {"name": "V2 Sub"}, |
|||
"multi": [{"name": "N1"}, {"name": "N2"}], |
|||
} |
|||
|
|||
|
|||
def test_v2_to_v1_item_returns_none(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "Invalid New", |
|||
"new_size": -5, |
|||
"new_sub": {"new_sub_name": "NewSub"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() is None |
|||
|
|||
|
|||
def test_v2_to_v1_item_minimal(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "MinimalNew", |
|||
"new_size": 75, |
|||
"new_sub": {"new_sub_name": "MinNewSub"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"title": "MinimalNew", |
|||
"size": 75, |
|||
"description": None, |
|||
"sub": {"name": "MinNewSub"}, |
|||
"multi": [], |
|||
} |
|||
|
|||
|
|||
def test_v2_to_v1_item_filter_success(): |
|||
response = client.post( |
|||
"/v2-to-v1/item-filter", |
|||
json={ |
|||
"new_title": "Filtered New", |
|||
"new_size": 75, |
|||
"new_sub": {"new_sub_name": "NewSub"}, |
|||
"new_multi": [], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
result = response.json() |
|||
assert result["title"] == "Filtered New" |
|||
assert result["size"] == 75 |
|||
assert result["sub"]["name"] == "NewSub" |
|||
# Verify secret fields are filtered out |
|||
assert "secret" not in result |
|||
assert "sub_secret" not in result["sub"] |
|||
|
|||
|
|||
def test_v2_to_v1_item_filter_returns_none(): |
|||
response = client.post( |
|||
"/v2-to-v1/item-filter", |
|||
json={ |
|||
"new_title": "Invalid Filtered", |
|||
"new_size": -100, |
|||
"new_sub": {"new_sub_name": "Sub"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() is None |
|||
|
|||
|
|||
def test_v1_to_v2_validation_error(): |
|||
response = client.post("/v1-to-v2/", json={"title": "Missing fields"}) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body", "size"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
}, |
|||
{ |
|||
"loc": ["body", "sub"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_v1_to_v2_nested_validation_error(): |
|||
response = client.post( |
|||
"/v1-to-v2/", |
|||
json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
error_detail = response.json()["detail"] |
|||
assert len(error_detail) == 1 |
|||
assert error_detail[0]["loc"] == ["body", "sub", "name"] |
|||
|
|||
|
|||
def test_v1_to_v2_type_validation_error(): |
|||
response = client.post( |
|||
"/v1-to-v2/", |
|||
json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
error_detail = response.json()["detail"] |
|||
assert len(error_detail) == 1 |
|||
assert error_detail[0]["loc"] == ["body", "size"] |
|||
|
|||
|
|||
def test_v2_to_v1_validation_error(): |
|||
response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"}) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["body", "new_size"], |
|||
"msg": "Field required", |
|||
"input": {"new_title": "Missing fields"}, |
|||
}, |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["body", "new_sub"], |
|||
"msg": "Field required", |
|||
"input": {"new_title": "Missing fields"}, |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_v2_to_v1_nested_validation_error(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "Bad sub", |
|||
"new_size": 200, |
|||
"new_sub": {"wrong_field": "value"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["body", "new_sub", "new_sub_name"], |
|||
"msg": "Field required", |
|||
"input": {"wrong_field": "value"}, |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_v2_to_v1_type_validation_error(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "Bad type", |
|||
"new_size": "not_a_number", |
|||
"new_sub": {"new_sub_name": "Sub"}, |
|||
}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "int_parsing", |
|||
"loc": ["body", "new_size"], |
|||
"msg": "Input should be a valid integer, unable to parse string as an integer", |
|||
"input": "not_a_number", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_v1_to_v2_with_multi_items(): |
|||
response = client.post( |
|||
"/v1-to-v2/", |
|||
json={ |
|||
"title": "Complex Item", |
|||
"size": 300, |
|||
"description": "Item with multiple sub-items", |
|||
"sub": {"name": "Main Sub"}, |
|||
"multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"new_title": "Complex Item", |
|||
"new_size": 300, |
|||
"new_description": "Item with multiple sub-items", |
|||
"new_sub": {"new_sub_name": "Main Sub"}, |
|||
"new_multi": [ |
|||
{"new_sub_name": "Sub1"}, |
|||
{"new_sub_name": "Sub2"}, |
|||
{"new_sub_name": "Sub3"}, |
|||
], |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_v2_to_v1_with_multi_items(): |
|||
response = client.post( |
|||
"/v2-to-v1/item", |
|||
json={ |
|||
"new_title": "Complex New Item", |
|||
"new_size": 400, |
|||
"new_description": "New item with multiple sub-items", |
|||
"new_sub": {"new_sub_name": "Main New Sub"}, |
|||
"new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}], |
|||
}, |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"title": "Complex New Item", |
|||
"size": 400, |
|||
"description": "New item with multiple sub-items", |
|||
"sub": {"name": "Main New Sub"}, |
|||
"multi": [{"name": "NewSub1"}, {"name": "NewSub2"}], |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/v1-to-v2/": { |
|||
"post": { |
|||
"summary": "Handle V1 Item To V2", |
|||
"operationId": "handle_v1_item_to_v2_v1_to_v2__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"anyOf": [ |
|||
{ |
|||
"$ref": "#/components/schemas/NewItem" |
|||
}, |
|||
{"type": "null"}, |
|||
], |
|||
"title": "Response Handle V1 Item To V2 V1 To V2 Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v1-to-v2/item-filter": { |
|||
"post": { |
|||
"summary": "Handle V1 Item To V2 Filter", |
|||
"operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"anyOf": [ |
|||
{ |
|||
"$ref": "#/components/schemas/NewItem" |
|||
}, |
|||
{"type": "null"}, |
|||
], |
|||
"title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/item": { |
|||
"post": { |
|||
"summary": "Handle V2 Item To V1", |
|||
"operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/NewItem"} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
"/v2-to-v1/item-filter": { |
|||
"post": { |
|||
"summary": "Handle V2 Item To V1 Filter", |
|||
"operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/NewItem"} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
}, |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"title": {"type": "string", "title": "Title"}, |
|||
"size": {"type": "integer", "title": "Size"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"sub": {"$ref": "#/components/schemas/SubItem"}, |
|||
"multi": { |
|||
"items": {"$ref": "#/components/schemas/SubItem"}, |
|||
"type": "array", |
|||
"title": "Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["title", "size", "sub"], |
|||
"title": "Item", |
|||
}, |
|||
"NewItem": { |
|||
"properties": { |
|||
"new_title": {"type": "string", "title": "New Title"}, |
|||
"new_size": {"type": "integer", "title": "New Size"}, |
|||
"new_description": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "New Description", |
|||
}, |
|||
"new_sub": {"$ref": "#/components/schemas/NewSubItem"}, |
|||
"new_multi": { |
|||
"items": {"$ref": "#/components/schemas/NewSubItem"}, |
|||
"type": "array", |
|||
"title": "New Multi", |
|||
"default": [], |
|||
}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["new_title", "new_size", "new_sub"], |
|||
"title": "NewItem", |
|||
}, |
|||
"NewSubItem": { |
|||
"properties": { |
|||
"new_sub_name": {"type": "string", "title": "New Sub Name"} |
|||
}, |
|||
"type": "object", |
|||
"required": ["new_sub_name"], |
|||
"title": "NewSubItem", |
|||
}, |
|||
"SubItem": { |
|||
"properties": {"name": {"type": "string", "title": "Name"}}, |
|||
"type": "object", |
|||
"required": ["name"], |
|||
"title": "SubItem", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,115 +0,0 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ...utils import needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial007_pv1_py39"), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module( |
|||
f"docs_src.path_operation_advanced_configuration.{request.param}" |
|||
) |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_post(client: TestClient): |
|||
yaml_data = """ |
|||
name: Deadpoolio |
|||
tags: |
|||
- x-force |
|||
- x-men |
|||
- x-avengers |
|||
""" |
|||
response = client.post("/items/", content=yaml_data) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "Deadpoolio", |
|||
"tags": ["x-force", "x-men", "x-avengers"], |
|||
} |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_post_broken_yaml(client: TestClient): |
|||
yaml_data = """ |
|||
name: Deadpoolio |
|||
tags: |
|||
x - x-force |
|||
x - x-men |
|||
x - x-avengers |
|||
""" |
|||
response = client.post("/items/", content=yaml_data) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == {"detail": "Invalid YAML"} |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_post_invalid(client: TestClient): |
|||
yaml_data = """ |
|||
name: Deadpoolio |
|||
tags: |
|||
- x-force |
|||
- x-men |
|||
- x-avengers |
|||
- sneaky: object |
|||
""" |
|||
response = client.post("/items/", content=yaml_data) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"} |
|||
] |
|||
} |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"post": { |
|||
"summary": "Create Item", |
|||
"operationId": "create_item_items__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/x-yaml": { |
|||
"schema": { |
|||
"title": "Item", |
|||
"required": ["name", "tags"], |
|||
"type": "object", |
|||
"properties": { |
|||
"name": {"title": "Name", "type": "string"}, |
|||
"tags": { |
|||
"title": "Tags", |
|||
"type": "array", |
|||
"items": {"type": "string"}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
import sys |
|||
from typing import Any |
|||
|
|||
import pytest |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
|
|||
import importlib |
|||
|
|||
from ...utils import needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="mod", |
|||
params=[ |
|||
"tutorial001_an_py39", |
|||
pytest.param("tutorial001_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_mod(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") |
|||
return mod |
|||
|
|||
|
|||
def test_model(mod: Any): |
|||
item = mod.Item(name="Foo", size=3.4) |
|||
assert item.dict() == {"name": "Foo", "description": None, "size": 3.4} |
|||
@ -1,143 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
|
|||
import importlib |
|||
|
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ...utils import needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial002_an_py39", |
|||
pytest.param("tutorial002_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
with warnings.catch_warnings(record=True): |
|||
warnings.filterwarnings( |
|||
"ignore", |
|||
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", |
|||
category=FastAPIDeprecationWarning, |
|||
) |
|||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") |
|||
|
|||
c = TestClient(mod.app) |
|||
return c |
|||
|
|||
|
|||
def test_call(client: TestClient): |
|||
response = client.post("/items/", json={"name": "Foo", "size": 3.4}) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "Foo", |
|||
"description": None, |
|||
"size": 3.4, |
|||
} |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"post": { |
|||
"summary": "Create Item", |
|||
"operationId": "create_item_items__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Item", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"size": {"type": "number", "title": "Size"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "size"], |
|||
"title": "Item", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,158 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
|
|||
import importlib |
|||
|
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ...utils import needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial003_an_py39", |
|||
pytest.param("tutorial003_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
with warnings.catch_warnings(record=True): |
|||
warnings.filterwarnings( |
|||
"ignore", |
|||
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", |
|||
category=FastAPIDeprecationWarning, |
|||
) |
|||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") |
|||
|
|||
c = TestClient(mod.app) |
|||
return c |
|||
|
|||
|
|||
def test_call(client: TestClient): |
|||
response = client.post("/items/", json={"name": "Foo", "size": 3.4}) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "Foo", |
|||
"description": None, |
|||
"size": 3.4, |
|||
} |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"post": { |
|||
"summary": "Create Item", |
|||
"operationId": "create_item_items__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
"title": "Item", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/ItemV2" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"size": {"type": "number", "title": "Size"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "size"], |
|||
"title": "Item", |
|||
}, |
|||
"ItemV2": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"description": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Description", |
|||
}, |
|||
"size": {"type": "number", "title": "Size"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "size"], |
|||
"title": "ItemV2", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,156 +0,0 @@ |
|||
import sys |
|||
import warnings |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import skip_module_if_py_gte_314 |
|||
|
|||
if sys.version_info >= (3, 14): |
|||
skip_module_if_py_gte_314() |
|||
|
|||
|
|||
import importlib |
|||
|
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ...utils import needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial004_an_py39"), |
|||
pytest.param("tutorial004_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
with warnings.catch_warnings(record=True): |
|||
warnings.filterwarnings( |
|||
"ignore", |
|||
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", |
|||
category=FastAPIDeprecationWarning, |
|||
) |
|||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") |
|||
|
|||
c = TestClient(mod.app) |
|||
return c |
|||
|
|||
|
|||
def test_call(client: TestClient): |
|||
response = client.post("/items/", json={"item": {"name": "Foo", "size": 3.4}}) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "Foo", |
|||
"description": None, |
|||
"size": 3.4, |
|||
} |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"post": { |
|||
"summary": "Create Item", |
|||
"operationId": "create_item_items__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"allOf": [ |
|||
{ |
|||
"$ref": "#/components/schemas/Body_create_item_items__post" |
|||
} |
|||
], |
|||
"title": "Body", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {"$ref": "#/components/schemas/Item"} |
|||
} |
|||
}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"Body_create_item_items__post": { |
|||
"properties": { |
|||
"item": { |
|||
"allOf": [{"$ref": "#/components/schemas/Item"}], |
|||
"title": "Item", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"required": ["item"], |
|||
"title": "Body_create_item_items__post", |
|||
}, |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"size": {"type": "number", "title": "Size"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "size"], |
|||
"title": "Item", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -1,128 +0,0 @@ |
|||
import importlib |
|||
import warnings |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ...utils import needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial002_pv1_py39", |
|||
"tutorial002_pv1_an_py39", |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
with warnings.catch_warnings(record=True): |
|||
warnings.filterwarnings( |
|||
"ignore", |
|||
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", |
|||
category=FastAPIDeprecationWarning, |
|||
) |
|||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_form(client: TestClient): |
|||
response = client.post("/login/", data={"username": "Foo", "password": "secret"}) |
|||
assert response.status_code == 200 |
|||
assert response.json() == {"username": "Foo", "password": "secret"} |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_extra_form(client: TestClient): |
|||
response = client.post( |
|||
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"} |
|||
) |
|||
assert response.status_code == 422 |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.extra", |
|||
"loc": ["body", "extra"], |
|||
"msg": "extra fields not permitted", |
|||
} |
|||
] |
|||
} |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_form_no_password(client: TestClient): |
|||
response = client.post("/login/", data={"username": "Foo"}) |
|||
assert response.status_code == 422 |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "password"], |
|||
"msg": "field required", |
|||
} |
|||
] |
|||
} |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_form_no_username(client: TestClient): |
|||
response = client.post("/login/", data={"password": "secret"}) |
|||
assert response.status_code == 422 |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "username"], |
|||
"msg": "field required", |
|||
} |
|||
] |
|||
} |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_form_no_data(client: TestClient): |
|||
response = client.post("/login/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "username"], |
|||
"msg": "field required", |
|||
}, |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "password"], |
|||
"msg": "field required", |
|||
}, |
|||
] |
|||
} |
|||
|
|||
|
|||
# TODO: remove when deprecating Pydantic v1 |
|||
@needs_pydanticv1 |
|||
def test_post_body_json(client: TestClient): |
|||
response = client.post("/login/", json={"username": "Foo", "password": "secret"}) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == { |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "username"], |
|||
"msg": "field required", |
|||
}, |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["body", "password"], |
|||
"msg": "field required", |
|||
}, |
|||
] |
|||
} |
|||
@ -1,152 +0,0 @@ |
|||
import importlib |
|||
import warnings |
|||
|
|||
import pytest |
|||
from fastapi.exceptions import FastAPIDeprecationWarning |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from ...utils import needs_py310, needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial001_pv1_py39"), |
|||
pytest.param("tutorial001_pv1_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
with warnings.catch_warnings(record=True): |
|||
warnings.filterwarnings( |
|||
"ignore", |
|||
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", |
|||
category=FastAPIDeprecationWarning, |
|||
) |
|||
mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_post_body_example(client: TestClient): |
|||
response = client.put( |
|||
"/items/5", |
|||
json={ |
|||
"name": "Foo", |
|||
"description": "A very nice Item", |
|||
"price": 35.4, |
|||
"tax": 3.2, |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/{item_id}": { |
|||
"put": { |
|||
"summary": "Update Item", |
|||
"operationId": "update_item_items__item_id__put", |
|||
"parameters": [ |
|||
{ |
|||
"required": True, |
|||
"schema": {"type": "integer", "title": "Item Id"}, |
|||
"name": "item_id", |
|||
"in": "path", |
|||
} |
|||
], |
|||
"requestBody": { |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"title": "Item", |
|||
"allOf": [ |
|||
{"$ref": "#/components/schemas/Item"} |
|||
], |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"Item": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"description": {"type": "string", "title": "Description"}, |
|||
"price": {"type": "number", "title": "Price"}, |
|||
"tax": {"type": "number", "title": "Tax"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "price"], |
|||
"title": "Item", |
|||
"examples": [ |
|||
{ |
|||
"name": "Foo", |
|||
"description": "A very nice Item", |
|||
"price": 35.4, |
|||
"tax": 3.2, |
|||
} |
|||
], |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
@ -5,8 +5,6 @@ import pytest |
|||
from fastapi.testclient import TestClient |
|||
from pytest import MonkeyPatch |
|||
|
|||
from ...utils import needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="mod_path", |
|||
@ -34,16 +32,6 @@ def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): |
|||
assert settings.items_per_user == 50 |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_settings_pv1(mod_path: str, monkeypatch: MonkeyPatch): |
|||
monkeypatch.setenv("ADMIN_EMAIL", "[email protected]") |
|||
config_mod = importlib.import_module(f"{mod_path}.config_pv1") |
|||
settings = config_mod.Settings() |
|||
assert settings.app_name == "Awesome API" |
|||
assert settings.admin_email == "[email protected]" |
|||
assert settings.items_per_user == 50 |
|||
|
|||
|
|||
def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch): |
|||
monkeypatch.setenv("ADMIN_EMAIL", "[email protected]") |
|||
client = TestClient(main_mod.app) |
|||
|
|||
@ -4,16 +4,8 @@ import pytest |
|||
from fastapi.testclient import TestClient |
|||
from pytest import MonkeyPatch |
|||
|
|||
from ...utils import needs_pydanticv1 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="app", |
|||
params=[ |
|||
pytest.param("tutorial001_py39"), |
|||
pytest.param("tutorial001_pv1_py39", marks=needs_pydanticv1), |
|||
], |
|||
) |
|||
@pytest.fixture(name="app", params=[pytest.param("tutorial001_py39")]) |
|||
def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch): |
|||
monkeypatch.setenv("ADMIN_EMAIL", "[email protected]") |
|||
mod = importlib.import_module(f"docs_src.settings.{request.param}") |
|||
|
|||
Loading…
Reference in new issue