@ -18,7 +18,7 @@ from typing import (
)
)
from fastapi . exceptions import RequestErrorModel
from fastapi . exceptions import RequestErrorModel
from fastapi . types import IncEx , ModelNameMap , UnionType
from fastapi . types import IncEx , ModelNameMap , UnionType , FFQuery
from pydantic import BaseModel , create_model
from pydantic import BaseModel , create_model
from pydantic . version import VERSION as PYDANTIC_VERSION
from pydantic . version import VERSION as PYDANTIC_VERSION
from starlette . datastructures import UploadFile
from starlette . datastructures import UploadFile
@ -43,6 +43,13 @@ sequence_annotation_to_type = {
sequence_types = tuple ( sequence_annotation_to_type . keys ( ) )
sequence_types = tuple ( sequence_annotation_to_type . keys ( ) )
mapping_annotation_to_type = {
FFQuery : list ,
}
mapping_types = tuple ( mapping_annotation_to_type . keys ( ) )
if PYDANTIC_V2 :
if PYDANTIC_V2 :
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
from pydantic import TypeAdapter
from pydantic import TypeAdapter
@ -242,6 +249,12 @@ if PYDANTIC_V2:
def is_scalar_sequence_field ( field : ModelField ) - > bool :
def is_scalar_sequence_field ( field : ModelField ) - > bool :
return field_annotation_is_scalar_sequence ( field . field_info . annotation )
return field_annotation_is_scalar_sequence ( field . field_info . annotation )
def is_scalar_sequence_mapping_field ( field : ModelField ) - > bool :
return field_annotation_is_scalar_sequence_mapping ( field . field_info . annotation )
def is_scalar_mapping_field ( field : ModelField ) - > bool :
return field_annotation_is_scalar_mapping ( field . field_info . annotation )
def is_bytes_field ( field : ModelField ) - > bool :
def is_bytes_field ( field : ModelField ) - > bool :
return is_bytes_or_nonable_bytes_annotation ( field . type_ )
return is_bytes_or_nonable_bytes_annotation ( field . type_ )
@ -294,6 +307,7 @@ else:
from pydantic . fields import ( # type: ignore[attr-defined]
from pydantic . fields import ( # type: ignore[attr-defined]
SHAPE_FROZENSET ,
SHAPE_FROZENSET ,
SHAPE_LIST ,
SHAPE_LIST ,
SHAPE_MAPPING ,
SHAPE_SEQUENCE ,
SHAPE_SEQUENCE ,
SHAPE_SET ,
SHAPE_SET ,
SHAPE_SINGLETON ,
SHAPE_SINGLETON ,
@ -341,6 +355,7 @@ else:
SHAPE_SEQUENCE ,
SHAPE_SEQUENCE ,
SHAPE_TUPLE_ELLIPSIS ,
SHAPE_TUPLE_ELLIPSIS ,
}
}
sequence_shape_to_type = {
sequence_shape_to_type = {
SHAPE_LIST : list ,
SHAPE_LIST : list ,
SHAPE_SET : set ,
SHAPE_SET : set ,
@ -349,6 +364,11 @@ else:
SHAPE_TUPLE_ELLIPSIS : list ,
SHAPE_TUPLE_ELLIPSIS : list ,
}
}
mapping_shapes = {
SHAPE_MAPPING ,
}
mapping_shapes_to_type = { SHAPE_MAPPING : FFQuery }
@dataclass
@dataclass
class GenerateJsonSchema : # type: ignore[no-redef]
class GenerateJsonSchema : # type: ignore[no-redef]
ref_template : str
ref_template : str
@ -416,6 +436,28 @@ else:
return True
return True
return False
return False
def is_pv1_scalar_mapping_field ( field : ModelField ) - > bool :
if ( field . shape in mapping_shapes ) and not lenient_issubclass ( # type: ignore[attr-defined]
field . type_ , BaseModel
) :
if field . sub_fields is not None : # type: ignore[attr-defined]
for sub_field in field . sub_fields : # type: ignore[attr-defined]
if not is_scalar_field ( sub_field ) :
return False
return True
return False
def is_pv1_scalar_sequence_mapping_field ( field : ModelField ) - > bool :
if ( field . shape in mapping_shapes ) and not lenient_issubclass ( # type: ignore[attr-defined]
field . type_ , BaseModel
) :
if field . sub_fields is not None : # type: ignore[attr-defined]
for sub_field in field . sub_fields : # type: ignore[attr-defined]
if not is_scalar_sequence_field ( sub_field ) :
return False
return True
return False
def _normalize_errors ( errors : Sequence [ Any ] ) - > List [ Dict [ str , Any ] ] :
def _normalize_errors ( errors : Sequence [ Any ] ) - > List [ Dict [ str , Any ] ] :
use_errors : List [ Any ] = [ ]
use_errors : List [ Any ] = [ ]
for error in errors :
for error in errors :
@ -486,6 +528,12 @@ else:
def is_scalar_sequence_field ( field : ModelField ) - > bool :
def is_scalar_sequence_field ( field : ModelField ) - > bool :
return is_pv1_scalar_sequence_field ( field )
return is_pv1_scalar_sequence_field ( field )
def is_scalar_sequence_mapping_field ( field : ModelField ) - > bool :
return is_pv1_scalar_sequence_mapping_field ( field )
def is_scalar_mapping_field ( field : ModelField ) - > bool :
return is_pv1_scalar_mapping_field ( field )
def is_bytes_field ( field : ModelField ) - > bool :
def is_bytes_field ( field : ModelField ) - > bool :
return lenient_issubclass ( field . type_ , bytes )
return lenient_issubclass ( field . type_ , bytes )
@ -535,14 +583,27 @@ def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
)
)
def _annotation_is_mapping ( annotation : Union [ Type [ Any ] , None ] ) - > bool :
if lenient_issubclass ( annotation , ( str , bytes ) ) :
return False
return lenient_issubclass ( annotation , mapping_types )
def field_annotation_is_mapping ( annotation : Union [ Type [ Any ] , None ] ) - > bool :
return _annotation_is_mapping ( annotation ) or _annotation_is_mapping (
get_origin ( annotation )
)
def value_is_sequence ( value : Any ) - > bool :
def value_is_sequence ( value : Any ) - > bool :
return isinstance ( value , sequence_types ) and not isinstance ( value , ( str , bytes ) ) # type: ignore[arg-type]
return isinstance ( value , sequence_types ) and not isinstance ( value , ( str , bytes ) ) # type: ignore[arg-type]
def _annotation_is_complex ( annotation : Union [ Type [ Any ] , None ] ) - > bool :
def _annotation_is_complex ( annotation : Union [ Type [ Any ] , None ] ) - > bool :
return (
return (
lenient_issubclass ( annotation , ( BaseModel , Mapping , UploadFile ) )
lenient_issubclass ( annotation , ( BaseModel , UploadFile ) )
or _annotation_is_sequence ( annotation )
or _annotation_is_sequence ( annotation )
or _annotation_is_mapping ( annotation )
or is_dataclass ( annotation )
or is_dataclass ( annotation )
)
)
@ -573,8 +634,6 @@ def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> b
if field_annotation_is_scalar_sequence ( arg ) :
if field_annotation_is_scalar_sequence ( arg ) :
at_least_one_scalar_sequence = True
at_least_one_scalar_sequence = True
continue
continue
elif not field_annotation_is_scalar ( arg ) :
return False
return at_least_one_scalar_sequence
return at_least_one_scalar_sequence
return field_annotation_is_sequence ( annotation ) and all (
return field_annotation_is_sequence ( annotation ) and all (
field_annotation_is_scalar ( sub_annotation )
field_annotation_is_scalar ( sub_annotation )
@ -582,6 +641,22 @@ def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> b
)
)
def field_annotation_is_scalar_mapping ( annotation : Union [ Type [ Any ] , None ] ) - > bool :
return field_annotation_is_mapping ( annotation ) and all (
field_annotation_is_scalar ( sub_annotation )
for sub_annotation in get_args ( annotation )
)
def field_annotation_is_scalar_sequence_mapping (
annotation : Union [ Type [ Any ] , None ]
) - > bool :
return field_annotation_is_mapping ( annotation ) and all (
field_annotation_is_scalar_sequence ( sub_annotation )
for sub_annotation in get_args ( annotation ) [ 1 : ]
)
def is_bytes_or_nonable_bytes_annotation ( annotation : Any ) - > bool :
def is_bytes_or_nonable_bytes_annotation ( annotation : Any ) - > bool :
if lenient_issubclass ( annotation , bytes ) :
if lenient_issubclass ( annotation , bytes ) :
return True
return True