fastapi 0.96.1__py3-none-any.whl → 0.100.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fastapi might be problematic. Click here for more details.
- fastapi/__init__.py +1 -1
- fastapi/_compat.py +597 -0
- fastapi/applications.py +51 -27
- fastapi/datastructures.py +31 -4
- fastapi/dependencies/models.py +1 -1
- fastapi/dependencies/utils.py +76 -120
- fastapi/encoders.py +91 -13
- fastapi/exception_handlers.py +11 -2
- fastapi/exceptions.py +20 -8
- fastapi/middleware/asyncexitstack.py +13 -16
- fastapi/openapi/constants.py +1 -0
- fastapi/openapi/models.py +203 -57
- fastapi/openapi/utils.py +65 -44
- fastapi/param_functions.py +15 -1
- fastapi/params.py +57 -8
- fastapi/routing.py +126 -61
- fastapi/security/api_key.py +9 -3
- fastapi/security/oauth2.py +32 -21
- fastapi/types.py +9 -1
- fastapi/utils.py +62 -60
- {fastapi-0.96.1.dist-info → fastapi-0.100.0b1.dist-info}/METADATA +3 -37
- fastapi-0.100.0b1.dist-info/RECORD +48 -0
- fastapi-0.96.1.dist-info/RECORD +0 -47
- {fastapi-0.96.1.dist-info → fastapi-0.100.0b1.dist-info}/WHEEL +0 -0
- {fastapi-0.96.1.dist-info → fastapi-0.100.0b1.dist-info}/licenses/LICENSE +0 -0
fastapi/__init__.py
CHANGED
fastapi/_compat.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from copy import copy
|
|
3
|
+
from dataclasses import dataclass, is_dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Deque,
|
|
9
|
+
Dict,
|
|
10
|
+
FrozenSet,
|
|
11
|
+
List,
|
|
12
|
+
Mapping,
|
|
13
|
+
Sequence,
|
|
14
|
+
Set,
|
|
15
|
+
Tuple,
|
|
16
|
+
Type,
|
|
17
|
+
Union,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from fastapi.exceptions import RequestErrorModel
|
|
21
|
+
from fastapi.types import IncEx, ModelNameMap, UnionType
|
|
22
|
+
from pydantic import BaseModel, create_model
|
|
23
|
+
from pydantic.version import VERSION as PYDANTIC_VERSION
|
|
24
|
+
from starlette.datastructures import UploadFile
|
|
25
|
+
from typing_extensions import Annotated, Literal, get_args, get_origin
|
|
26
|
+
|
|
27
|
+
PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
sequence_annotation_to_type = {
|
|
31
|
+
Sequence: list,
|
|
32
|
+
List: list,
|
|
33
|
+
list: list,
|
|
34
|
+
Tuple: tuple,
|
|
35
|
+
tuple: tuple,
|
|
36
|
+
Set: set,
|
|
37
|
+
set: set,
|
|
38
|
+
FrozenSet: frozenset,
|
|
39
|
+
frozenset: frozenset,
|
|
40
|
+
Deque: deque,
|
|
41
|
+
deque: deque,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
sequence_types = tuple(sequence_annotation_to_type.keys())
|
|
45
|
+
|
|
46
|
+
if PYDANTIC_V2:
|
|
47
|
+
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
|
48
|
+
from pydantic import TypeAdapter
|
|
49
|
+
from pydantic import ValidationError as ValidationError
|
|
50
|
+
from pydantic._internal._fields import Undefined as Undefined
|
|
51
|
+
from pydantic._internal._fields import _UndefinedType
|
|
52
|
+
from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined]
|
|
53
|
+
GetJsonSchemaHandler as GetJsonSchemaHandler,
|
|
54
|
+
)
|
|
55
|
+
from pydantic._internal._typing_extra import eval_type_lenient
|
|
56
|
+
from pydantic._internal._utils import lenient_issubclass as lenient_issubclass
|
|
57
|
+
from pydantic.fields import FieldInfo
|
|
58
|
+
from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema
|
|
59
|
+
from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
|
|
60
|
+
from pydantic_core import CoreSchema as CoreSchema
|
|
61
|
+
from pydantic_core import ErrorDetails
|
|
62
|
+
from pydantic_core import MultiHostUrl as MultiHostUrl
|
|
63
|
+
from pydantic_core import Url as Url
|
|
64
|
+
from pydantic_core.core_schema import (
|
|
65
|
+
general_plain_validator_function as general_plain_validator_function,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
Required = Undefined
|
|
69
|
+
UndefinedType = _UndefinedType
|
|
70
|
+
evaluate_forwardref = eval_type_lenient
|
|
71
|
+
Validator = Any
|
|
72
|
+
|
|
73
|
+
class BaseConfig:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
class ErrorWrapper(Exception):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ModelField:
|
|
81
|
+
field_info: FieldInfo
|
|
82
|
+
name: str
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def alias(self) -> str:
|
|
86
|
+
a = self.field_info.alias
|
|
87
|
+
return a if a is not None else self.name
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def required(self) -> bool:
|
|
91
|
+
return self.field_info.is_required()
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def default(self) -> Any:
|
|
95
|
+
return self.get_default()
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def type_(self) -> Any:
|
|
99
|
+
return self.field_info.annotation
|
|
100
|
+
|
|
101
|
+
def __post_init__(self) -> None:
|
|
102
|
+
self._type_adapter: TypeAdapter[Any] = TypeAdapter(
|
|
103
|
+
Annotated[self.field_info.annotation, self.field_info]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def get_default(self) -> Any:
|
|
107
|
+
if self.field_info.is_required():
|
|
108
|
+
return Undefined
|
|
109
|
+
return self.field_info.get_default(call_default_factory=True)
|
|
110
|
+
|
|
111
|
+
def validate(
|
|
112
|
+
self,
|
|
113
|
+
value: Any,
|
|
114
|
+
values: Dict[str, Any] = {}, # noqa: B006
|
|
115
|
+
*,
|
|
116
|
+
loc: Tuple[Union[int, str], ...] = (),
|
|
117
|
+
) -> Tuple[Any, Union[List[Dict[str, Any]], None]]:
|
|
118
|
+
try:
|
|
119
|
+
return (
|
|
120
|
+
self._type_adapter.validate_python(value, from_attributes=True),
|
|
121
|
+
None,
|
|
122
|
+
)
|
|
123
|
+
except ValidationError as exc:
|
|
124
|
+
return None, _regenerate_error_with_loc(
|
|
125
|
+
errors=exc.errors(), loc_prefix=loc
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def serialize(
|
|
129
|
+
self,
|
|
130
|
+
value: Any,
|
|
131
|
+
*,
|
|
132
|
+
mode: Literal["json", "python"] = "json",
|
|
133
|
+
include: Union[IncEx, None] = None,
|
|
134
|
+
exclude: Union[IncEx, None] = None,
|
|
135
|
+
by_alias: bool = True,
|
|
136
|
+
exclude_unset: bool = False,
|
|
137
|
+
exclude_defaults: bool = False,
|
|
138
|
+
exclude_none: bool = False,
|
|
139
|
+
) -> Any:
|
|
140
|
+
# What calls this code passes a value that already called
|
|
141
|
+
# self._type_adapter.validate_python(value)
|
|
142
|
+
return self._type_adapter.dump_python(
|
|
143
|
+
value,
|
|
144
|
+
mode=mode,
|
|
145
|
+
include=include,
|
|
146
|
+
exclude=exclude,
|
|
147
|
+
by_alias=by_alias,
|
|
148
|
+
exclude_unset=exclude_unset,
|
|
149
|
+
exclude_defaults=exclude_defaults,
|
|
150
|
+
exclude_none=exclude_none,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def __hash__(self) -> int:
|
|
154
|
+
# Each ModelField is unique for our purposes, to allow making a dict from
|
|
155
|
+
# ModelField to its JSON Schema.
|
|
156
|
+
return id(self)
|
|
157
|
+
|
|
158
|
+
def get_annotation_from_field_info(
|
|
159
|
+
annotation: Any, field_info: FieldInfo, field_name: str
|
|
160
|
+
) -> Any:
|
|
161
|
+
return annotation
|
|
162
|
+
|
|
163
|
+
def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
|
|
164
|
+
return errors # type: ignore[return-value]
|
|
165
|
+
|
|
166
|
+
def _model_rebuild(model: Type[BaseModel]) -> None:
|
|
167
|
+
model.model_rebuild()
|
|
168
|
+
|
|
169
|
+
def _model_dump(
|
|
170
|
+
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
|
171
|
+
) -> Any:
|
|
172
|
+
return model.model_dump(mode=mode, **kwargs)
|
|
173
|
+
|
|
174
|
+
def _get_model_config(model: BaseModel) -> Any:
|
|
175
|
+
return model.model_config
|
|
176
|
+
|
|
177
|
+
def get_schema_from_model_field(
|
|
178
|
+
*,
|
|
179
|
+
field: ModelField,
|
|
180
|
+
schema_generator: GenerateJsonSchema,
|
|
181
|
+
model_name_map: ModelNameMap,
|
|
182
|
+
) -> Dict[str, Any]:
|
|
183
|
+
# This expects that GenerateJsonSchema was already used to generate the definitions
|
|
184
|
+
json_schema = schema_generator.generate_inner(field._type_adapter.core_schema)
|
|
185
|
+
if "$ref" not in json_schema:
|
|
186
|
+
# TODO remove when deprecating Pydantic v1
|
|
187
|
+
# Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
|
|
188
|
+
json_schema[
|
|
189
|
+
"title"
|
|
190
|
+
] = field.field_info.title or field.alias.title().replace("_", " ")
|
|
191
|
+
return json_schema
|
|
192
|
+
|
|
193
|
+
def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
|
|
194
|
+
return {}
|
|
195
|
+
|
|
196
|
+
def get_definitions(
|
|
197
|
+
*,
|
|
198
|
+
fields: List[ModelField],
|
|
199
|
+
schema_generator: GenerateJsonSchema,
|
|
200
|
+
model_name_map: ModelNameMap,
|
|
201
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
202
|
+
inputs = [
|
|
203
|
+
(field, "validation", field._type_adapter.core_schema) for field in fields
|
|
204
|
+
]
|
|
205
|
+
_, definitions = schema_generator.generate_definitions(inputs=inputs) # type: ignore[arg-type]
|
|
206
|
+
return definitions # type: ignore[return-value]
|
|
207
|
+
|
|
208
|
+
def is_scalar_field(field: ModelField) -> bool:
|
|
209
|
+
from fastapi import params
|
|
210
|
+
|
|
211
|
+
return field_annotation_is_scalar(
|
|
212
|
+
field.field_info.annotation
|
|
213
|
+
) and not isinstance(field.field_info, params.Body)
|
|
214
|
+
|
|
215
|
+
def is_sequence_field(field: ModelField) -> bool:
|
|
216
|
+
return field_annotation_is_sequence(field.field_info.annotation)
|
|
217
|
+
|
|
218
|
+
def is_scalar_sequence_field(field: ModelField) -> bool:
|
|
219
|
+
return field_annotation_is_scalar_sequence(field.field_info.annotation)
|
|
220
|
+
|
|
221
|
+
def is_bytes_field(field: ModelField) -> bool:
|
|
222
|
+
return is_bytes_or_nonable_bytes_annotation(field.type_)
|
|
223
|
+
|
|
224
|
+
def is_bytes_sequence_field(field: ModelField) -> bool:
|
|
225
|
+
return is_bytes_sequence_annotation(field.type_)
|
|
226
|
+
|
|
227
|
+
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
|
228
|
+
return type(field_info).from_annotation(annotation)
|
|
229
|
+
|
|
230
|
+
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
|
231
|
+
origin_type = (
|
|
232
|
+
get_origin(field.field_info.annotation) or field.field_info.annotation
|
|
233
|
+
)
|
|
234
|
+
assert issubclass(origin_type, sequence_types) # type: ignore[arg-type]
|
|
235
|
+
return sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return]
|
|
236
|
+
|
|
237
|
+
def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:
|
|
238
|
+
error = ValidationError.from_exception_data(
|
|
239
|
+
"Field required", [{"type": "missing", "loc": loc, "input": {}}]
|
|
240
|
+
).errors()[0]
|
|
241
|
+
error["input"] = None
|
|
242
|
+
return error # type: ignore[return-value]
|
|
243
|
+
|
|
244
|
+
def create_body_model(
|
|
245
|
+
*, fields: Sequence[ModelField], model_name: str
|
|
246
|
+
) -> Type[BaseModel]:
|
|
247
|
+
field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields}
|
|
248
|
+
BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload]
|
|
249
|
+
return BodyModel
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
|
|
253
|
+
from pydantic import AnyUrl as Url # noqa: F401
|
|
254
|
+
from pydantic import ( # type: ignore[assignment]
|
|
255
|
+
BaseConfig as BaseConfig, # noqa: F401
|
|
256
|
+
)
|
|
257
|
+
from pydantic import ValidationError as ValidationError # noqa: F401
|
|
258
|
+
from pydantic.class_validators import ( # type: ignore[no-redef]
|
|
259
|
+
Validator as Validator, # noqa: F401
|
|
260
|
+
)
|
|
261
|
+
from pydantic.error_wrappers import ( # type: ignore[no-redef]
|
|
262
|
+
ErrorWrapper as ErrorWrapper, # noqa: F401
|
|
263
|
+
)
|
|
264
|
+
from pydantic.errors import MissingError
|
|
265
|
+
from pydantic.fields import ( # type: ignore[attr-defined]
|
|
266
|
+
SHAPE_FROZENSET,
|
|
267
|
+
SHAPE_LIST,
|
|
268
|
+
SHAPE_SEQUENCE,
|
|
269
|
+
SHAPE_SET,
|
|
270
|
+
SHAPE_SINGLETON,
|
|
271
|
+
SHAPE_TUPLE,
|
|
272
|
+
SHAPE_TUPLE_ELLIPSIS,
|
|
273
|
+
)
|
|
274
|
+
from pydantic.fields import FieldInfo as FieldInfo
|
|
275
|
+
from pydantic.fields import ( # type: ignore[no-redef,attr-defined]
|
|
276
|
+
ModelField as ModelField, # noqa: F401
|
|
277
|
+
)
|
|
278
|
+
from pydantic.fields import ( # type: ignore[no-redef,attr-defined]
|
|
279
|
+
Required as Required, # noqa: F401
|
|
280
|
+
)
|
|
281
|
+
from pydantic.fields import ( # type: ignore[no-redef,attr-defined]
|
|
282
|
+
Undefined as Undefined,
|
|
283
|
+
)
|
|
284
|
+
from pydantic.fields import ( # type: ignore[no-redef, attr-defined]
|
|
285
|
+
UndefinedType as UndefinedType, # noqa: F401
|
|
286
|
+
)
|
|
287
|
+
from pydantic.networks import ( # type: ignore[no-redef]
|
|
288
|
+
MultiHostDsn as MultiHostUrl, # noqa: F401
|
|
289
|
+
)
|
|
290
|
+
from pydantic.schema import (
|
|
291
|
+
field_schema,
|
|
292
|
+
get_flat_models_from_fields,
|
|
293
|
+
get_model_name_map,
|
|
294
|
+
model_process_schema,
|
|
295
|
+
)
|
|
296
|
+
from pydantic.schema import ( # type: ignore[no-redef] # noqa: F401
|
|
297
|
+
get_annotation_from_field_info as get_annotation_from_field_info,
|
|
298
|
+
)
|
|
299
|
+
from pydantic.typing import ( # type: ignore[no-redef]
|
|
300
|
+
evaluate_forwardref as evaluate_forwardref, # noqa: F401
|
|
301
|
+
)
|
|
302
|
+
from pydantic.utils import ( # type: ignore[no-redef]
|
|
303
|
+
lenient_issubclass as lenient_issubclass, # noqa: F401
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
ErrorDetails = Dict[str, Any] # type: ignore[assignment,misc]
|
|
307
|
+
GetJsonSchemaHandler = Any # type: ignore[assignment,misc]
|
|
308
|
+
JsonSchemaValue = Dict[str, Any] # type: ignore[misc]
|
|
309
|
+
CoreSchema = Any # type: ignore[assignment,misc]
|
|
310
|
+
|
|
311
|
+
sequence_shapes = {
|
|
312
|
+
SHAPE_LIST,
|
|
313
|
+
SHAPE_SET,
|
|
314
|
+
SHAPE_FROZENSET,
|
|
315
|
+
SHAPE_TUPLE,
|
|
316
|
+
SHAPE_SEQUENCE,
|
|
317
|
+
SHAPE_TUPLE_ELLIPSIS,
|
|
318
|
+
}
|
|
319
|
+
sequence_shape_to_type = {
|
|
320
|
+
SHAPE_LIST: list,
|
|
321
|
+
SHAPE_SET: set,
|
|
322
|
+
SHAPE_TUPLE: tuple,
|
|
323
|
+
SHAPE_SEQUENCE: list,
|
|
324
|
+
SHAPE_TUPLE_ELLIPSIS: list,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@dataclass
|
|
328
|
+
class GenerateJsonSchema: # type: ignore[no-redef]
|
|
329
|
+
ref_template: str
|
|
330
|
+
|
|
331
|
+
class PydanticSchemaGenerationError(Exception): # type: ignore[no-redef]
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def general_plain_validator_function( # type: ignore[misc]
|
|
335
|
+
function: Callable[..., Any],
|
|
336
|
+
*,
|
|
337
|
+
ref: Union[str, None] = None,
|
|
338
|
+
metadata: Any = None,
|
|
339
|
+
serialization: Any = None,
|
|
340
|
+
) -> Any:
|
|
341
|
+
return {}
|
|
342
|
+
|
|
343
|
+
def get_model_definitions(
|
|
344
|
+
*,
|
|
345
|
+
flat_models: Set[Union[Type[BaseModel], Type[Enum]]],
|
|
346
|
+
model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
|
|
347
|
+
) -> Dict[str, Any]:
|
|
348
|
+
definitions: Dict[str, Dict[str, Any]] = {}
|
|
349
|
+
for model in flat_models:
|
|
350
|
+
m_schema, m_definitions, m_nested_models = model_process_schema(
|
|
351
|
+
model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
|
352
|
+
)
|
|
353
|
+
definitions.update(m_definitions)
|
|
354
|
+
model_name = model_name_map[model]
|
|
355
|
+
if "description" in m_schema:
|
|
356
|
+
m_schema["description"] = m_schema["description"].split("\f")[0]
|
|
357
|
+
definitions[model_name] = m_schema
|
|
358
|
+
return definitions
|
|
359
|
+
|
|
360
|
+
def is_pv1_scalar_field(field: ModelField) -> bool:
|
|
361
|
+
from fastapi import params
|
|
362
|
+
|
|
363
|
+
field_info = field.field_info
|
|
364
|
+
if not (
|
|
365
|
+
field.shape == SHAPE_SINGLETON # type: ignore[attr-defined]
|
|
366
|
+
and not lenient_issubclass(field.type_, BaseModel)
|
|
367
|
+
and not lenient_issubclass(field.type_, dict)
|
|
368
|
+
and not field_annotation_is_sequence(field.type_)
|
|
369
|
+
and not is_dataclass(field.type_)
|
|
370
|
+
and not isinstance(field_info, params.Body)
|
|
371
|
+
):
|
|
372
|
+
return False
|
|
373
|
+
if field.sub_fields: # type: ignore[attr-defined]
|
|
374
|
+
if not all(
|
|
375
|
+
is_pv1_scalar_field(f)
|
|
376
|
+
for f in field.sub_fields # type: ignore[attr-defined]
|
|
377
|
+
):
|
|
378
|
+
return False
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
def is_pv1_scalar_sequence_field(field: ModelField) -> bool:
|
|
382
|
+
if (field.shape in sequence_shapes) and not lenient_issubclass( # type: ignore[attr-defined]
|
|
383
|
+
field.type_, BaseModel
|
|
384
|
+
):
|
|
385
|
+
if field.sub_fields is not None: # type: ignore[attr-defined]
|
|
386
|
+
for sub_field in field.sub_fields: # type: ignore[attr-defined]
|
|
387
|
+
if not is_pv1_scalar_field(sub_field):
|
|
388
|
+
return False
|
|
389
|
+
return True
|
|
390
|
+
if _annotation_is_sequence(field.type_):
|
|
391
|
+
return True
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
|
|
395
|
+
use_errors: List[Any] = []
|
|
396
|
+
for error in errors:
|
|
397
|
+
if isinstance(error, ErrorWrapper):
|
|
398
|
+
new_errors = ValidationError( # type: ignore[call-arg]
|
|
399
|
+
errors=[error], model=RequestErrorModel
|
|
400
|
+
).errors()
|
|
401
|
+
use_errors.extend(new_errors)
|
|
402
|
+
elif isinstance(error, list):
|
|
403
|
+
use_errors.extend(_normalize_errors(error))
|
|
404
|
+
else:
|
|
405
|
+
use_errors.append(error)
|
|
406
|
+
return use_errors
|
|
407
|
+
|
|
408
|
+
def _model_rebuild(model: Type[BaseModel]) -> None:
|
|
409
|
+
model.update_forward_refs()
|
|
410
|
+
|
|
411
|
+
def _model_dump(
|
|
412
|
+
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
|
413
|
+
) -> Any:
|
|
414
|
+
return model.dict(**kwargs)
|
|
415
|
+
|
|
416
|
+
def _get_model_config(model: BaseModel) -> Any:
|
|
417
|
+
return model.__config__ # type: ignore[attr-defined]
|
|
418
|
+
|
|
419
|
+
def get_schema_from_model_field(
|
|
420
|
+
*,
|
|
421
|
+
field: ModelField,
|
|
422
|
+
schema_generator: GenerateJsonSchema,
|
|
423
|
+
model_name_map: ModelNameMap,
|
|
424
|
+
) -> Dict[str, Any]:
|
|
425
|
+
# This expects that GenerateJsonSchema was already used to generate the definitions
|
|
426
|
+
return field_schema( # type: ignore[no-any-return]
|
|
427
|
+
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
|
428
|
+
)[0]
|
|
429
|
+
|
|
430
|
+
def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
|
|
431
|
+
models = get_flat_models_from_fields(fields, known_models=set())
|
|
432
|
+
return get_model_name_map(models) # type: ignore[no-any-return]
|
|
433
|
+
|
|
434
|
+
def get_definitions(
|
|
435
|
+
*,
|
|
436
|
+
fields: List[ModelField],
|
|
437
|
+
schema_generator: GenerateJsonSchema,
|
|
438
|
+
model_name_map: ModelNameMap,
|
|
439
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
440
|
+
models = get_flat_models_from_fields(fields, known_models=set())
|
|
441
|
+
return get_model_definitions(flat_models=models, model_name_map=model_name_map)
|
|
442
|
+
|
|
443
|
+
def is_scalar_field(field: ModelField) -> bool:
|
|
444
|
+
return is_pv1_scalar_field(field)
|
|
445
|
+
|
|
446
|
+
def is_sequence_field(field: ModelField) -> bool:
|
|
447
|
+
return field.shape in sequence_shapes or _annotation_is_sequence(field.type_) # type: ignore[attr-defined]
|
|
448
|
+
|
|
449
|
+
def is_scalar_sequence_field(field: ModelField) -> bool:
|
|
450
|
+
return is_pv1_scalar_sequence_field(field)
|
|
451
|
+
|
|
452
|
+
def is_bytes_field(field: ModelField) -> bool:
|
|
453
|
+
return lenient_issubclass(field.type_, bytes)
|
|
454
|
+
|
|
455
|
+
def is_bytes_sequence_field(field: ModelField) -> bool:
|
|
456
|
+
return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) # type: ignore[attr-defined]
|
|
457
|
+
|
|
458
|
+
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
|
459
|
+
return copy(field_info)
|
|
460
|
+
|
|
461
|
+
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
|
462
|
+
return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return,attr-defined]
|
|
463
|
+
|
|
464
|
+
def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:
|
|
465
|
+
missing_field_error = ErrorWrapper(MissingError(), loc=loc) # type: ignore[call-arg]
|
|
466
|
+
new_error = ValidationError([missing_field_error], RequestErrorModel)
|
|
467
|
+
return new_error.errors()[0] # type: ignore[return-value]
|
|
468
|
+
|
|
469
|
+
def create_body_model(
|
|
470
|
+
*, fields: Sequence[ModelField], model_name: str
|
|
471
|
+
) -> Type[BaseModel]:
|
|
472
|
+
BodyModel = create_model(model_name)
|
|
473
|
+
for f in fields:
|
|
474
|
+
BodyModel.__fields__[f.name] = f # type: ignore[index]
|
|
475
|
+
return BodyModel
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _regenerate_error_with_loc(
|
|
479
|
+
*, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
|
|
480
|
+
) -> List[Dict[str, Any]]:
|
|
481
|
+
updated_loc_errors: List[Any] = [
|
|
482
|
+
{**err, "loc": loc_prefix + err.get("loc", ())}
|
|
483
|
+
for err in _normalize_errors(errors)
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
return updated_loc_errors
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
490
|
+
if lenient_issubclass(annotation, (str, bytes)):
|
|
491
|
+
return False
|
|
492
|
+
return lenient_issubclass(annotation, sequence_types)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
496
|
+
return _annotation_is_sequence(annotation) or _annotation_is_sequence(
|
|
497
|
+
get_origin(annotation)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def value_is_sequence(value: Any) -> bool:
|
|
502
|
+
return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type]
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
|
506
|
+
return (
|
|
507
|
+
lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
|
|
508
|
+
or _annotation_is_sequence(annotation)
|
|
509
|
+
or is_dataclass(annotation)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
|
514
|
+
origin = get_origin(annotation)
|
|
515
|
+
if origin is Union or origin is UnionType:
|
|
516
|
+
return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
_annotation_is_complex(annotation)
|
|
520
|
+
or _annotation_is_complex(origin)
|
|
521
|
+
or hasattr(origin, "__pydantic_core_schema__")
|
|
522
|
+
or hasattr(origin, "__get_pydantic_core_schema__")
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def field_annotation_is_scalar(annotation: Any) -> bool:
|
|
527
|
+
# handle Ellipsis here to make tuple[int, ...] work nicely
|
|
528
|
+
return annotation is Ellipsis or not field_annotation_is_complex(annotation)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
532
|
+
origin = get_origin(annotation)
|
|
533
|
+
if origin is Union or origin is UnionType:
|
|
534
|
+
at_least_one_scalar_sequence = False
|
|
535
|
+
for arg in get_args(annotation):
|
|
536
|
+
if field_annotation_is_scalar_sequence(arg):
|
|
537
|
+
at_least_one_scalar_sequence = True
|
|
538
|
+
continue
|
|
539
|
+
elif not field_annotation_is_scalar(arg):
|
|
540
|
+
return False
|
|
541
|
+
return at_least_one_scalar_sequence
|
|
542
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
543
|
+
field_annotation_is_scalar(sub_annotation)
|
|
544
|
+
for sub_annotation in get_args(annotation)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool:
|
|
549
|
+
if lenient_issubclass(annotation, bytes):
|
|
550
|
+
return True
|
|
551
|
+
origin = get_origin(annotation)
|
|
552
|
+
if origin is Union or origin is UnionType:
|
|
553
|
+
for arg in get_args(annotation):
|
|
554
|
+
if lenient_issubclass(arg, bytes):
|
|
555
|
+
return True
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:
|
|
560
|
+
if lenient_issubclass(annotation, UploadFile):
|
|
561
|
+
return True
|
|
562
|
+
origin = get_origin(annotation)
|
|
563
|
+
if origin is Union or origin is UnionType:
|
|
564
|
+
for arg in get_args(annotation):
|
|
565
|
+
if lenient_issubclass(arg, UploadFile):
|
|
566
|
+
return True
|
|
567
|
+
return False
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def is_bytes_sequence_annotation(annotation: Any) -> bool:
|
|
571
|
+
origin = get_origin(annotation)
|
|
572
|
+
if origin is Union or origin is UnionType:
|
|
573
|
+
at_least_one = False
|
|
574
|
+
for arg in get_args(annotation):
|
|
575
|
+
if is_bytes_sequence_annotation(arg):
|
|
576
|
+
at_least_one = True
|
|
577
|
+
continue
|
|
578
|
+
return at_least_one
|
|
579
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
580
|
+
is_bytes_or_nonable_bytes_annotation(sub_annotation)
|
|
581
|
+
for sub_annotation in get_args(annotation)
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
|
|
586
|
+
origin = get_origin(annotation)
|
|
587
|
+
if origin is Union or origin is UnionType:
|
|
588
|
+
at_least_one = False
|
|
589
|
+
for arg in get_args(annotation):
|
|
590
|
+
if is_uploadfile_sequence_annotation(arg):
|
|
591
|
+
at_least_one = True
|
|
592
|
+
continue
|
|
593
|
+
return at_least_one
|
|
594
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
595
|
+
is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
|
|
596
|
+
for sub_annotation in get_args(annotation)
|
|
597
|
+
)
|