fastapi 0.118.2__py3-none-any.whl → 0.119.0__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/__init__.py +50 -0
- fastapi/_compat/main.py +305 -0
- fastapi/_compat/model_field.py +53 -0
- fastapi/_compat/shared.py +209 -0
- fastapi/_compat/v1.py +334 -0
- fastapi/_compat/v2.py +459 -0
- fastapi/datastructures.py +6 -7
- fastapi/dependencies/utils.py +72 -33
- fastapi/encoders.py +11 -4
- fastapi/openapi/utils.py +5 -18
- fastapi/routing.py +4 -2
- fastapi/temp_pydantic_v1_params.py +724 -0
- fastapi/utils.py +62 -36
- {fastapi-0.118.2.dist-info → fastapi-0.119.0.dist-info}/METADATA +2 -1
- {fastapi-0.118.2.dist-info → fastapi-0.119.0.dist-info}/RECORD +19 -13
- fastapi/_compat.py +0 -680
- {fastapi-0.118.2.dist-info → fastapi-0.119.0.dist-info}/WHEEL +0 -0
- {fastapi-0.118.2.dist-info → fastapi-0.119.0.dist-info}/entry_points.txt +0 -0
- {fastapi-0.118.2.dist-info → fastapi-0.119.0.dist-info}/licenses/LICENSE +0 -0
fastapi/__init__.py
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from .main import BaseConfig as BaseConfig
|
|
2
|
+
from .main import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
|
3
|
+
from .main import RequiredParam as RequiredParam
|
|
4
|
+
from .main import Undefined as Undefined
|
|
5
|
+
from .main import UndefinedType as UndefinedType
|
|
6
|
+
from .main import Url as Url
|
|
7
|
+
from .main import Validator as Validator
|
|
8
|
+
from .main import _get_model_config as _get_model_config
|
|
9
|
+
from .main import _is_error_wrapper as _is_error_wrapper
|
|
10
|
+
from .main import _is_model_class as _is_model_class
|
|
11
|
+
from .main import _is_model_field as _is_model_field
|
|
12
|
+
from .main import _is_undefined as _is_undefined
|
|
13
|
+
from .main import _model_dump as _model_dump
|
|
14
|
+
from .main import _model_rebuild as _model_rebuild
|
|
15
|
+
from .main import copy_field_info as copy_field_info
|
|
16
|
+
from .main import create_body_model as create_body_model
|
|
17
|
+
from .main import evaluate_forwardref as evaluate_forwardref
|
|
18
|
+
from .main import get_annotation_from_field_info as get_annotation_from_field_info
|
|
19
|
+
from .main import get_cached_model_fields as get_cached_model_fields
|
|
20
|
+
from .main import get_compat_model_name_map as get_compat_model_name_map
|
|
21
|
+
from .main import get_definitions as get_definitions
|
|
22
|
+
from .main import get_missing_field_error as get_missing_field_error
|
|
23
|
+
from .main import get_schema_from_model_field as get_schema_from_model_field
|
|
24
|
+
from .main import is_bytes_field as is_bytes_field
|
|
25
|
+
from .main import is_bytes_sequence_field as is_bytes_sequence_field
|
|
26
|
+
from .main import is_scalar_field as is_scalar_field
|
|
27
|
+
from .main import is_scalar_sequence_field as is_scalar_sequence_field
|
|
28
|
+
from .main import is_sequence_field as is_sequence_field
|
|
29
|
+
from .main import serialize_sequence_value as serialize_sequence_value
|
|
30
|
+
from .main import (
|
|
31
|
+
with_info_plain_validator_function as with_info_plain_validator_function,
|
|
32
|
+
)
|
|
33
|
+
from .model_field import ModelField as ModelField
|
|
34
|
+
from .shared import PYDANTIC_V2 as PYDANTIC_V2
|
|
35
|
+
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
|
|
36
|
+
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
|
|
37
|
+
from .shared import field_annotation_is_scalar as field_annotation_is_scalar
|
|
38
|
+
from .shared import (
|
|
39
|
+
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
|
|
40
|
+
)
|
|
41
|
+
from .shared import (
|
|
42
|
+
is_uploadfile_sequence_annotation as is_uploadfile_sequence_annotation,
|
|
43
|
+
)
|
|
44
|
+
from .shared import lenient_issubclass as lenient_issubclass
|
|
45
|
+
from .shared import sequence_types as sequence_types
|
|
46
|
+
from .shared import value_is_sequence as value_is_sequence
|
|
47
|
+
from .v1 import CoreSchema as CoreSchema
|
|
48
|
+
from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
|
|
49
|
+
from .v1 import JsonSchemaValue as JsonSchemaValue
|
|
50
|
+
from .v1 import _normalize_errors as _normalize_errors
|
fastapi/_compat/main.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
Dict,
|
|
5
|
+
List,
|
|
6
|
+
Sequence,
|
|
7
|
+
Tuple,
|
|
8
|
+
Type,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from fastapi._compat import v1
|
|
12
|
+
from fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass
|
|
13
|
+
from fastapi.types import ModelNameMap
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
from typing_extensions import Literal
|
|
16
|
+
|
|
17
|
+
from .model_field import ModelField
|
|
18
|
+
|
|
19
|
+
if PYDANTIC_V2:
|
|
20
|
+
from .v2 import BaseConfig as BaseConfig
|
|
21
|
+
from .v2 import FieldInfo as FieldInfo
|
|
22
|
+
from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
|
23
|
+
from .v2 import RequiredParam as RequiredParam
|
|
24
|
+
from .v2 import Undefined as Undefined
|
|
25
|
+
from .v2 import UndefinedType as UndefinedType
|
|
26
|
+
from .v2 import Url as Url
|
|
27
|
+
from .v2 import Validator as Validator
|
|
28
|
+
from .v2 import evaluate_forwardref as evaluate_forwardref
|
|
29
|
+
from .v2 import get_missing_field_error as get_missing_field_error
|
|
30
|
+
from .v2 import (
|
|
31
|
+
with_info_plain_validator_function as with_info_plain_validator_function,
|
|
32
|
+
)
|
|
33
|
+
else:
|
|
34
|
+
from .v1 import BaseConfig as BaseConfig # type: ignore[assignment]
|
|
35
|
+
from .v1 import FieldInfo as FieldInfo
|
|
36
|
+
from .v1 import ( # type: ignore[assignment]
|
|
37
|
+
PydanticSchemaGenerationError as PydanticSchemaGenerationError,
|
|
38
|
+
)
|
|
39
|
+
from .v1 import RequiredParam as RequiredParam
|
|
40
|
+
from .v1 import Undefined as Undefined
|
|
41
|
+
from .v1 import UndefinedType as UndefinedType
|
|
42
|
+
from .v1 import Url as Url # type: ignore[assignment]
|
|
43
|
+
from .v1 import Validator as Validator
|
|
44
|
+
from .v1 import evaluate_forwardref as evaluate_forwardref
|
|
45
|
+
from .v1 import get_missing_field_error as get_missing_field_error
|
|
46
|
+
from .v1 import ( # type: ignore[assignment]
|
|
47
|
+
with_info_plain_validator_function as with_info_plain_validator_function,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@lru_cache
|
|
52
|
+
def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
|
53
|
+
if lenient_issubclass(model, v1.BaseModel):
|
|
54
|
+
return v1.get_model_fields(model)
|
|
55
|
+
else:
|
|
56
|
+
from . import v2
|
|
57
|
+
|
|
58
|
+
return v2.get_model_fields(model) # type: ignore[return-value]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _is_undefined(value: object) -> bool:
|
|
62
|
+
if isinstance(value, v1.UndefinedType):
|
|
63
|
+
return True
|
|
64
|
+
elif PYDANTIC_V2:
|
|
65
|
+
from . import v2
|
|
66
|
+
|
|
67
|
+
return isinstance(value, v2.UndefinedType)
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_model_config(model: BaseModel) -> Any:
|
|
72
|
+
if isinstance(model, v1.BaseModel):
|
|
73
|
+
return v1._get_model_config(model)
|
|
74
|
+
elif PYDANTIC_V2:
|
|
75
|
+
from . import v2
|
|
76
|
+
|
|
77
|
+
return v2._get_model_config(model)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _model_dump(
|
|
81
|
+
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
|
82
|
+
) -> Any:
|
|
83
|
+
if isinstance(model, v1.BaseModel):
|
|
84
|
+
return v1._model_dump(model, mode=mode, **kwargs)
|
|
85
|
+
elif PYDANTIC_V2:
|
|
86
|
+
from . import v2
|
|
87
|
+
|
|
88
|
+
return v2._model_dump(model, mode=mode, **kwargs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _is_error_wrapper(exc: Exception) -> bool:
|
|
92
|
+
if isinstance(exc, v1.ErrorWrapper):
|
|
93
|
+
return True
|
|
94
|
+
elif PYDANTIC_V2:
|
|
95
|
+
from . import v2
|
|
96
|
+
|
|
97
|
+
return isinstance(exc, v2.ErrorWrapper)
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
|
102
|
+
if isinstance(field_info, v1.FieldInfo):
|
|
103
|
+
return v1.copy_field_info(field_info=field_info, annotation=annotation)
|
|
104
|
+
else:
|
|
105
|
+
assert PYDANTIC_V2
|
|
106
|
+
from . import v2
|
|
107
|
+
|
|
108
|
+
return v2.copy_field_info(field_info=field_info, annotation=annotation)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def create_body_model(
|
|
112
|
+
*, fields: Sequence[ModelField], model_name: str
|
|
113
|
+
) -> Type[BaseModel]:
|
|
114
|
+
if fields and isinstance(fields[0], v1.ModelField):
|
|
115
|
+
return v1.create_body_model(fields=fields, model_name=model_name)
|
|
116
|
+
else:
|
|
117
|
+
assert PYDANTIC_V2
|
|
118
|
+
from . import v2
|
|
119
|
+
|
|
120
|
+
return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_annotation_from_field_info(
|
|
124
|
+
annotation: Any, field_info: FieldInfo, field_name: str
|
|
125
|
+
) -> Any:
|
|
126
|
+
if isinstance(field_info, v1.FieldInfo):
|
|
127
|
+
return v1.get_annotation_from_field_info(
|
|
128
|
+
annotation=annotation, field_info=field_info, field_name=field_name
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
assert PYDANTIC_V2
|
|
132
|
+
from . import v2
|
|
133
|
+
|
|
134
|
+
return v2.get_annotation_from_field_info(
|
|
135
|
+
annotation=annotation, field_info=field_info, field_name=field_name
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def is_bytes_field(field: ModelField) -> bool:
|
|
140
|
+
if isinstance(field, v1.ModelField):
|
|
141
|
+
return v1.is_bytes_field(field)
|
|
142
|
+
else:
|
|
143
|
+
assert PYDANTIC_V2
|
|
144
|
+
from . import v2
|
|
145
|
+
|
|
146
|
+
return v2.is_bytes_field(field) # type: ignore[arg-type]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def is_bytes_sequence_field(field: ModelField) -> bool:
|
|
150
|
+
if isinstance(field, v1.ModelField):
|
|
151
|
+
return v1.is_bytes_sequence_field(field)
|
|
152
|
+
else:
|
|
153
|
+
assert PYDANTIC_V2
|
|
154
|
+
from . import v2
|
|
155
|
+
|
|
156
|
+
return v2.is_bytes_sequence_field(field) # type: ignore[arg-type]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def is_scalar_field(field: ModelField) -> bool:
|
|
160
|
+
if isinstance(field, v1.ModelField):
|
|
161
|
+
return v1.is_scalar_field(field)
|
|
162
|
+
else:
|
|
163
|
+
assert PYDANTIC_V2
|
|
164
|
+
from . import v2
|
|
165
|
+
|
|
166
|
+
return v2.is_scalar_field(field) # type: ignore[arg-type]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def is_scalar_sequence_field(field: ModelField) -> bool:
|
|
170
|
+
if isinstance(field, v1.ModelField):
|
|
171
|
+
return v1.is_scalar_sequence_field(field)
|
|
172
|
+
else:
|
|
173
|
+
assert PYDANTIC_V2
|
|
174
|
+
from . import v2
|
|
175
|
+
|
|
176
|
+
return v2.is_scalar_sequence_field(field) # type: ignore[arg-type]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def is_sequence_field(field: ModelField) -> bool:
|
|
180
|
+
if isinstance(field, v1.ModelField):
|
|
181
|
+
return v1.is_sequence_field(field)
|
|
182
|
+
else:
|
|
183
|
+
assert PYDANTIC_V2
|
|
184
|
+
from . import v2
|
|
185
|
+
|
|
186
|
+
return v2.is_sequence_field(field) # type: ignore[arg-type]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
|
190
|
+
if isinstance(field, v1.ModelField):
|
|
191
|
+
return v1.serialize_sequence_value(field=field, value=value)
|
|
192
|
+
else:
|
|
193
|
+
assert PYDANTIC_V2
|
|
194
|
+
from . import v2
|
|
195
|
+
|
|
196
|
+
return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _model_rebuild(model: Type[BaseModel]) -> None:
|
|
200
|
+
if lenient_issubclass(model, v1.BaseModel):
|
|
201
|
+
v1._model_rebuild(model)
|
|
202
|
+
elif PYDANTIC_V2:
|
|
203
|
+
from . import v2
|
|
204
|
+
|
|
205
|
+
v2._model_rebuild(model)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
|
|
209
|
+
v1_model_fields = [field for field in fields if isinstance(field, v1.ModelField)]
|
|
210
|
+
v1_flat_models = v1.get_flat_models_from_fields(v1_model_fields, known_models=set()) # type: ignore[attr-defined]
|
|
211
|
+
all_flat_models = v1_flat_models
|
|
212
|
+
if PYDANTIC_V2:
|
|
213
|
+
from . import v2
|
|
214
|
+
|
|
215
|
+
v2_model_fields = [
|
|
216
|
+
field for field in fields if isinstance(field, v2.ModelField)
|
|
217
|
+
]
|
|
218
|
+
v2_flat_models = v2.get_flat_models_from_fields(
|
|
219
|
+
v2_model_fields, known_models=set()
|
|
220
|
+
)
|
|
221
|
+
all_flat_models = all_flat_models.union(v2_flat_models)
|
|
222
|
+
|
|
223
|
+
model_name_map = v2.get_model_name_map(all_flat_models)
|
|
224
|
+
return model_name_map
|
|
225
|
+
model_name_map = v1.get_model_name_map(all_flat_models)
|
|
226
|
+
return model_name_map
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_definitions(
|
|
230
|
+
*,
|
|
231
|
+
fields: List[ModelField],
|
|
232
|
+
model_name_map: ModelNameMap,
|
|
233
|
+
separate_input_output_schemas: bool = True,
|
|
234
|
+
) -> Tuple[
|
|
235
|
+
Dict[Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue],
|
|
236
|
+
Dict[str, Dict[str, Any]],
|
|
237
|
+
]:
|
|
238
|
+
v1_fields = [field for field in fields if isinstance(field, v1.ModelField)]
|
|
239
|
+
v1_field_maps, v1_definitions = v1.get_definitions(
|
|
240
|
+
fields=v1_fields,
|
|
241
|
+
model_name_map=model_name_map,
|
|
242
|
+
separate_input_output_schemas=separate_input_output_schemas,
|
|
243
|
+
)
|
|
244
|
+
if not PYDANTIC_V2:
|
|
245
|
+
return v1_field_maps, v1_definitions
|
|
246
|
+
else:
|
|
247
|
+
from . import v2
|
|
248
|
+
|
|
249
|
+
v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
|
|
250
|
+
v2_field_maps, v2_definitions = v2.get_definitions(
|
|
251
|
+
fields=v2_fields,
|
|
252
|
+
model_name_map=model_name_map,
|
|
253
|
+
separate_input_output_schemas=separate_input_output_schemas,
|
|
254
|
+
)
|
|
255
|
+
all_definitions = {**v1_definitions, **v2_definitions}
|
|
256
|
+
all_field_maps = {**v1_field_maps, **v2_field_maps}
|
|
257
|
+
return all_field_maps, all_definitions
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_schema_from_model_field(
|
|
261
|
+
*,
|
|
262
|
+
field: ModelField,
|
|
263
|
+
model_name_map: ModelNameMap,
|
|
264
|
+
field_mapping: Dict[
|
|
265
|
+
Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue
|
|
266
|
+
],
|
|
267
|
+
separate_input_output_schemas: bool = True,
|
|
268
|
+
) -> Dict[str, Any]:
|
|
269
|
+
if isinstance(field, v1.ModelField):
|
|
270
|
+
return v1.get_schema_from_model_field(
|
|
271
|
+
field=field,
|
|
272
|
+
model_name_map=model_name_map,
|
|
273
|
+
field_mapping=field_mapping,
|
|
274
|
+
separate_input_output_schemas=separate_input_output_schemas,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
assert PYDANTIC_V2
|
|
278
|
+
from . import v2
|
|
279
|
+
|
|
280
|
+
return v2.get_schema_from_model_field(
|
|
281
|
+
field=field, # type: ignore[arg-type]
|
|
282
|
+
model_name_map=model_name_map,
|
|
283
|
+
field_mapping=field_mapping, # type: ignore[arg-type]
|
|
284
|
+
separate_input_output_schemas=separate_input_output_schemas,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _is_model_field(value: Any) -> bool:
|
|
289
|
+
if isinstance(value, v1.ModelField):
|
|
290
|
+
return True
|
|
291
|
+
elif PYDANTIC_V2:
|
|
292
|
+
from . import v2
|
|
293
|
+
|
|
294
|
+
return isinstance(value, v2.ModelField)
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _is_model_class(value: Any) -> bool:
|
|
299
|
+
if lenient_issubclass(value, v1.BaseModel):
|
|
300
|
+
return True
|
|
301
|
+
elif PYDANTIC_V2:
|
|
302
|
+
from . import v2
|
|
303
|
+
|
|
304
|
+
return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined]
|
|
305
|
+
return False
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Any,
|
|
3
|
+
Dict,
|
|
4
|
+
List,
|
|
5
|
+
Tuple,
|
|
6
|
+
Union,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from fastapi.types import IncEx
|
|
10
|
+
from pydantic.fields import FieldInfo
|
|
11
|
+
from typing_extensions import Literal, Protocol
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ModelField(Protocol):
|
|
15
|
+
field_info: "FieldInfo"
|
|
16
|
+
name: str
|
|
17
|
+
mode: Literal["validation", "serialization"] = "validation"
|
|
18
|
+
_version: Literal["v1", "v2"] = "v1"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def alias(self) -> str: ...
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def required(self) -> bool: ...
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def default(self) -> Any: ...
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def type_(self) -> Any: ...
|
|
31
|
+
|
|
32
|
+
def get_default(self) -> Any: ...
|
|
33
|
+
|
|
34
|
+
def validate(
|
|
35
|
+
self,
|
|
36
|
+
value: Any,
|
|
37
|
+
values: Dict[str, Any] = {}, # noqa: B006
|
|
38
|
+
*,
|
|
39
|
+
loc: Tuple[Union[int, str], ...] = (),
|
|
40
|
+
) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: ...
|
|
41
|
+
|
|
42
|
+
def serialize(
|
|
43
|
+
self,
|
|
44
|
+
value: Any,
|
|
45
|
+
*,
|
|
46
|
+
mode: Literal["json", "python"] = "json",
|
|
47
|
+
include: Union[IncEx, None] = None,
|
|
48
|
+
exclude: Union[IncEx, None] = None,
|
|
49
|
+
by_alias: bool = True,
|
|
50
|
+
exclude_unset: bool = False,
|
|
51
|
+
exclude_defaults: bool = False,
|
|
52
|
+
exclude_none: bool = False,
|
|
53
|
+
) -> Any: ...
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import types
|
|
3
|
+
import typing
|
|
4
|
+
from collections import deque
|
|
5
|
+
from dataclasses import is_dataclass
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Deque,
|
|
9
|
+
FrozenSet,
|
|
10
|
+
List,
|
|
11
|
+
Mapping,
|
|
12
|
+
Sequence,
|
|
13
|
+
Set,
|
|
14
|
+
Tuple,
|
|
15
|
+
Type,
|
|
16
|
+
Union,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from fastapi._compat import v1
|
|
20
|
+
from fastapi.types import UnionType
|
|
21
|
+
from pydantic import BaseModel
|
|
22
|
+
from pydantic.version import VERSION as PYDANTIC_VERSION
|
|
23
|
+
from starlette.datastructures import UploadFile
|
|
24
|
+
from typing_extensions import Annotated, get_args, get_origin
|
|
25
|
+
|
|
26
|
+
# Copy from Pydantic v2, compatible with v1
|
|
27
|
+
if sys.version_info < (3, 9):
|
|
28
|
+
# Pydantic no longer supports Python 3.8, this might be incorrect, but the code
|
|
29
|
+
# this is used for is also never reached in this codebase, as it's a copy of
|
|
30
|
+
# Pydantic's lenient_issubclass, just for compatibility with v1
|
|
31
|
+
# TODO: remove when dropping support for Python 3.8
|
|
32
|
+
WithArgsTypes: Tuple[Any, ...] = ()
|
|
33
|
+
elif sys.version_info < (3, 10):
|
|
34
|
+
WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined]
|
|
35
|
+
else:
|
|
36
|
+
WithArgsTypes: tuple[Any, ...] = (
|
|
37
|
+
typing._GenericAlias, # type: ignore[attr-defined]
|
|
38
|
+
types.GenericAlias,
|
|
39
|
+
types.UnionType,
|
|
40
|
+
) # pyright: ignore[reportAttributeAccessIssue]
|
|
41
|
+
|
|
42
|
+
PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
|
|
43
|
+
PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
sequence_annotation_to_type = {
|
|
47
|
+
Sequence: list,
|
|
48
|
+
List: list,
|
|
49
|
+
list: list,
|
|
50
|
+
Tuple: tuple,
|
|
51
|
+
tuple: tuple,
|
|
52
|
+
Set: set,
|
|
53
|
+
set: set,
|
|
54
|
+
FrozenSet: frozenset,
|
|
55
|
+
frozenset: frozenset,
|
|
56
|
+
Deque: deque,
|
|
57
|
+
deque: deque,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
sequence_types = tuple(sequence_annotation_to_type.keys())
|
|
61
|
+
|
|
62
|
+
Url: Type[Any]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Copy of Pydantic v2, compatible with v1
|
|
66
|
+
def lenient_issubclass(
|
|
67
|
+
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]
|
|
68
|
+
) -> bool:
|
|
69
|
+
try:
|
|
70
|
+
return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type]
|
|
71
|
+
except TypeError: # pragma: no cover
|
|
72
|
+
if isinstance(cls, WithArgsTypes):
|
|
73
|
+
return False
|
|
74
|
+
raise # pragma: no cover
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
78
|
+
if lenient_issubclass(annotation, (str, bytes)):
|
|
79
|
+
return False
|
|
80
|
+
return lenient_issubclass(annotation, sequence_types) # type: ignore[arg-type]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
84
|
+
origin = get_origin(annotation)
|
|
85
|
+
if origin is Union or origin is UnionType:
|
|
86
|
+
for arg in get_args(annotation):
|
|
87
|
+
if field_annotation_is_sequence(arg):
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
90
|
+
return _annotation_is_sequence(annotation) or _annotation_is_sequence(
|
|
91
|
+
get_origin(annotation)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def value_is_sequence(value: Any) -> bool:
|
|
96
|
+
return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
|
100
|
+
return (
|
|
101
|
+
lenient_issubclass(annotation, (BaseModel, v1.BaseModel, Mapping, UploadFile))
|
|
102
|
+
or _annotation_is_sequence(annotation)
|
|
103
|
+
or is_dataclass(annotation)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
|
108
|
+
origin = get_origin(annotation)
|
|
109
|
+
if origin is Union or origin is UnionType:
|
|
110
|
+
return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
|
|
111
|
+
|
|
112
|
+
if origin is Annotated:
|
|
113
|
+
return field_annotation_is_complex(get_args(annotation)[0])
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
_annotation_is_complex(annotation)
|
|
117
|
+
or _annotation_is_complex(origin)
|
|
118
|
+
or hasattr(origin, "__pydantic_core_schema__")
|
|
119
|
+
or hasattr(origin, "__get_pydantic_core_schema__")
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def field_annotation_is_scalar(annotation: Any) -> bool:
|
|
124
|
+
# handle Ellipsis here to make tuple[int, ...] work nicely
|
|
125
|
+
return annotation is Ellipsis or not field_annotation_is_complex(annotation)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
129
|
+
origin = get_origin(annotation)
|
|
130
|
+
if origin is Union or origin is UnionType:
|
|
131
|
+
at_least_one_scalar_sequence = False
|
|
132
|
+
for arg in get_args(annotation):
|
|
133
|
+
if field_annotation_is_scalar_sequence(arg):
|
|
134
|
+
at_least_one_scalar_sequence = True
|
|
135
|
+
continue
|
|
136
|
+
elif not field_annotation_is_scalar(arg):
|
|
137
|
+
return False
|
|
138
|
+
return at_least_one_scalar_sequence
|
|
139
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
140
|
+
field_annotation_is_scalar(sub_annotation)
|
|
141
|
+
for sub_annotation in get_args(annotation)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool:
|
|
146
|
+
if lenient_issubclass(annotation, bytes):
|
|
147
|
+
return True
|
|
148
|
+
origin = get_origin(annotation)
|
|
149
|
+
if origin is Union or origin is UnionType:
|
|
150
|
+
for arg in get_args(annotation):
|
|
151
|
+
if lenient_issubclass(arg, bytes):
|
|
152
|
+
return True
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:
|
|
157
|
+
if lenient_issubclass(annotation, UploadFile):
|
|
158
|
+
return True
|
|
159
|
+
origin = get_origin(annotation)
|
|
160
|
+
if origin is Union or origin is UnionType:
|
|
161
|
+
for arg in get_args(annotation):
|
|
162
|
+
if lenient_issubclass(arg, UploadFile):
|
|
163
|
+
return True
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_bytes_sequence_annotation(annotation: Any) -> bool:
|
|
168
|
+
origin = get_origin(annotation)
|
|
169
|
+
if origin is Union or origin is UnionType:
|
|
170
|
+
at_least_one = False
|
|
171
|
+
for arg in get_args(annotation):
|
|
172
|
+
if is_bytes_sequence_annotation(arg):
|
|
173
|
+
at_least_one = True
|
|
174
|
+
continue
|
|
175
|
+
return at_least_one
|
|
176
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
177
|
+
is_bytes_or_nonable_bytes_annotation(sub_annotation)
|
|
178
|
+
for sub_annotation in get_args(annotation)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
|
|
183
|
+
origin = get_origin(annotation)
|
|
184
|
+
if origin is Union or origin is UnionType:
|
|
185
|
+
at_least_one = False
|
|
186
|
+
for arg in get_args(annotation):
|
|
187
|
+
if is_uploadfile_sequence_annotation(arg):
|
|
188
|
+
at_least_one = True
|
|
189
|
+
continue
|
|
190
|
+
return at_least_one
|
|
191
|
+
return field_annotation_is_sequence(annotation) and all(
|
|
192
|
+
is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
|
|
193
|
+
for sub_annotation in get_args(annotation)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def annotation_is_pydantic_v1(annotation: Any) -> bool:
|
|
198
|
+
if lenient_issubclass(annotation, v1.BaseModel):
|
|
199
|
+
return True
|
|
200
|
+
origin = get_origin(annotation)
|
|
201
|
+
if origin is Union or origin is UnionType:
|
|
202
|
+
for arg in get_args(annotation):
|
|
203
|
+
if lenient_issubclass(arg, v1.BaseModel):
|
|
204
|
+
return True
|
|
205
|
+
if field_annotation_is_sequence(annotation):
|
|
206
|
+
for sub_annotation in get_args(annotation):
|
|
207
|
+
if annotation_is_pydantic_v1(sub_annotation):
|
|
208
|
+
return True
|
|
209
|
+
return False
|