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/applications.py
CHANGED
|
@@ -15,12 +15,12 @@ from typing import (
|
|
|
15
15
|
|
|
16
16
|
from fastapi import routing
|
|
17
17
|
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
18
|
-
from fastapi.encoders import DictIntStrAny, SetIntStr
|
|
19
18
|
from fastapi.exception_handlers import (
|
|
20
19
|
http_exception_handler,
|
|
21
20
|
request_validation_exception_handler,
|
|
21
|
+
websocket_request_validation_exception_handler,
|
|
22
22
|
)
|
|
23
|
-
from fastapi.exceptions import RequestValidationError
|
|
23
|
+
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
|
24
24
|
from fastapi.logger import logger
|
|
25
25
|
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
|
|
26
26
|
from fastapi.openapi.docs import (
|
|
@@ -30,7 +30,7 @@ from fastapi.openapi.docs import (
|
|
|
30
30
|
)
|
|
31
31
|
from fastapi.openapi.utils import get_openapi
|
|
32
32
|
from fastapi.params import Depends
|
|
33
|
-
from fastapi.types import DecoratedCallable
|
|
33
|
+
from fastapi.types import DecoratedCallable, IncEx
|
|
34
34
|
from fastapi.utils import generate_unique_id
|
|
35
35
|
from starlette.applications import Starlette
|
|
36
36
|
from starlette.datastructures import State
|
|
@@ -145,6 +145,11 @@ class FastAPI(Starlette):
|
|
|
145
145
|
self.exception_handlers.setdefault(
|
|
146
146
|
RequestValidationError, request_validation_exception_handler
|
|
147
147
|
)
|
|
148
|
+
self.exception_handlers.setdefault(
|
|
149
|
+
WebSocketRequestValidationError,
|
|
150
|
+
# Starlette still has incorrect type specification for the handlers
|
|
151
|
+
websocket_request_validation_exception_handler, # type: ignore
|
|
152
|
+
)
|
|
148
153
|
|
|
149
154
|
self.user_middleware: List[Middleware] = (
|
|
150
155
|
[] if middleware is None else list(middleware)
|
|
@@ -291,8 +296,8 @@ class FastAPI(Starlette):
|
|
|
291
296
|
deprecated: Optional[bool] = None,
|
|
292
297
|
methods: Optional[List[str]] = None,
|
|
293
298
|
operation_id: Optional[str] = None,
|
|
294
|
-
response_model_include: Optional[
|
|
295
|
-
response_model_exclude: Optional[
|
|
299
|
+
response_model_include: Optional[IncEx] = None,
|
|
300
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
296
301
|
response_model_by_alias: bool = True,
|
|
297
302
|
response_model_exclude_unset: bool = False,
|
|
298
303
|
response_model_exclude_defaults: bool = False,
|
|
@@ -349,8 +354,8 @@ class FastAPI(Starlette):
|
|
|
349
354
|
deprecated: Optional[bool] = None,
|
|
350
355
|
methods: Optional[List[str]] = None,
|
|
351
356
|
operation_id: Optional[str] = None,
|
|
352
|
-
response_model_include: Optional[
|
|
353
|
-
response_model_exclude: Optional[
|
|
357
|
+
response_model_include: Optional[IncEx] = None,
|
|
358
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
354
359
|
response_model_by_alias: bool = True,
|
|
355
360
|
response_model_exclude_unset: bool = False,
|
|
356
361
|
response_model_exclude_defaults: bool = False,
|
|
@@ -395,15 +400,34 @@ class FastAPI(Starlette):
|
|
|
395
400
|
return decorator
|
|
396
401
|
|
|
397
402
|
def add_api_websocket_route(
|
|
398
|
-
self,
|
|
403
|
+
self,
|
|
404
|
+
path: str,
|
|
405
|
+
endpoint: Callable[..., Any],
|
|
406
|
+
name: Optional[str] = None,
|
|
407
|
+
*,
|
|
408
|
+
dependencies: Optional[Sequence[Depends]] = None,
|
|
399
409
|
) -> None:
|
|
400
|
-
self.router.add_api_websocket_route(
|
|
410
|
+
self.router.add_api_websocket_route(
|
|
411
|
+
path,
|
|
412
|
+
endpoint,
|
|
413
|
+
name=name,
|
|
414
|
+
dependencies=dependencies,
|
|
415
|
+
)
|
|
401
416
|
|
|
402
417
|
def websocket(
|
|
403
|
-
self,
|
|
418
|
+
self,
|
|
419
|
+
path: str,
|
|
420
|
+
name: Optional[str] = None,
|
|
421
|
+
*,
|
|
422
|
+
dependencies: Optional[Sequence[Depends]] = None,
|
|
404
423
|
) -> Callable[[DecoratedCallable], DecoratedCallable]:
|
|
405
424
|
def decorator(func: DecoratedCallable) -> DecoratedCallable:
|
|
406
|
-
self.add_api_websocket_route(
|
|
425
|
+
self.add_api_websocket_route(
|
|
426
|
+
path,
|
|
427
|
+
func,
|
|
428
|
+
name=name,
|
|
429
|
+
dependencies=dependencies,
|
|
430
|
+
)
|
|
407
431
|
return func
|
|
408
432
|
|
|
409
433
|
return decorator
|
|
@@ -451,8 +475,8 @@ class FastAPI(Starlette):
|
|
|
451
475
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
452
476
|
deprecated: Optional[bool] = None,
|
|
453
477
|
operation_id: Optional[str] = None,
|
|
454
|
-
response_model_include: Optional[
|
|
455
|
-
response_model_exclude: Optional[
|
|
478
|
+
response_model_include: Optional[IncEx] = None,
|
|
479
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
456
480
|
response_model_by_alias: bool = True,
|
|
457
481
|
response_model_exclude_unset: bool = False,
|
|
458
482
|
response_model_exclude_defaults: bool = False,
|
|
@@ -506,8 +530,8 @@ class FastAPI(Starlette):
|
|
|
506
530
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
507
531
|
deprecated: Optional[bool] = None,
|
|
508
532
|
operation_id: Optional[str] = None,
|
|
509
|
-
response_model_include: Optional[
|
|
510
|
-
response_model_exclude: Optional[
|
|
533
|
+
response_model_include: Optional[IncEx] = None,
|
|
534
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
511
535
|
response_model_by_alias: bool = True,
|
|
512
536
|
response_model_exclude_unset: bool = False,
|
|
513
537
|
response_model_exclude_defaults: bool = False,
|
|
@@ -561,8 +585,8 @@ class FastAPI(Starlette):
|
|
|
561
585
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
562
586
|
deprecated: Optional[bool] = None,
|
|
563
587
|
operation_id: Optional[str] = None,
|
|
564
|
-
response_model_include: Optional[
|
|
565
|
-
response_model_exclude: Optional[
|
|
588
|
+
response_model_include: Optional[IncEx] = None,
|
|
589
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
566
590
|
response_model_by_alias: bool = True,
|
|
567
591
|
response_model_exclude_unset: bool = False,
|
|
568
592
|
response_model_exclude_defaults: bool = False,
|
|
@@ -616,8 +640,8 @@ class FastAPI(Starlette):
|
|
|
616
640
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
617
641
|
deprecated: Optional[bool] = None,
|
|
618
642
|
operation_id: Optional[str] = None,
|
|
619
|
-
response_model_include: Optional[
|
|
620
|
-
response_model_exclude: Optional[
|
|
643
|
+
response_model_include: Optional[IncEx] = None,
|
|
644
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
621
645
|
response_model_by_alias: bool = True,
|
|
622
646
|
response_model_exclude_unset: bool = False,
|
|
623
647
|
response_model_exclude_defaults: bool = False,
|
|
@@ -671,8 +695,8 @@ class FastAPI(Starlette):
|
|
|
671
695
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
672
696
|
deprecated: Optional[bool] = None,
|
|
673
697
|
operation_id: Optional[str] = None,
|
|
674
|
-
response_model_include: Optional[
|
|
675
|
-
response_model_exclude: Optional[
|
|
698
|
+
response_model_include: Optional[IncEx] = None,
|
|
699
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
676
700
|
response_model_by_alias: bool = True,
|
|
677
701
|
response_model_exclude_unset: bool = False,
|
|
678
702
|
response_model_exclude_defaults: bool = False,
|
|
@@ -726,8 +750,8 @@ class FastAPI(Starlette):
|
|
|
726
750
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
727
751
|
deprecated: Optional[bool] = None,
|
|
728
752
|
operation_id: Optional[str] = None,
|
|
729
|
-
response_model_include: Optional[
|
|
730
|
-
response_model_exclude: Optional[
|
|
753
|
+
response_model_include: Optional[IncEx] = None,
|
|
754
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
731
755
|
response_model_by_alias: bool = True,
|
|
732
756
|
response_model_exclude_unset: bool = False,
|
|
733
757
|
response_model_exclude_defaults: bool = False,
|
|
@@ -781,8 +805,8 @@ class FastAPI(Starlette):
|
|
|
781
805
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
782
806
|
deprecated: Optional[bool] = None,
|
|
783
807
|
operation_id: Optional[str] = None,
|
|
784
|
-
response_model_include: Optional[
|
|
785
|
-
response_model_exclude: Optional[
|
|
808
|
+
response_model_include: Optional[IncEx] = None,
|
|
809
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
786
810
|
response_model_by_alias: bool = True,
|
|
787
811
|
response_model_exclude_unset: bool = False,
|
|
788
812
|
response_model_exclude_defaults: bool = False,
|
|
@@ -836,8 +860,8 @@ class FastAPI(Starlette):
|
|
|
836
860
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
837
861
|
deprecated: Optional[bool] = None,
|
|
838
862
|
operation_id: Optional[str] = None,
|
|
839
|
-
response_model_include: Optional[
|
|
840
|
-
response_model_exclude: Optional[
|
|
863
|
+
response_model_include: Optional[IncEx] = None,
|
|
864
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
841
865
|
response_model_by_alias: bool = True,
|
|
842
866
|
response_model_exclude_unset: bool = False,
|
|
843
867
|
response_model_exclude_defaults: bool = False,
|
fastapi/datastructures.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
from typing import Any, Callable, Dict, Iterable, Type, TypeVar
|
|
2
|
-
|
|
1
|
+
from typing import Any, Callable, Dict, Iterable, Type, TypeVar, cast
|
|
2
|
+
|
|
3
|
+
from fastapi._compat import (
|
|
4
|
+
PYDANTIC_V2,
|
|
5
|
+
CoreSchema,
|
|
6
|
+
GetJsonSchemaHandler,
|
|
7
|
+
JsonSchemaValue,
|
|
8
|
+
general_plain_validator_function,
|
|
9
|
+
)
|
|
3
10
|
from starlette.datastructures import URL as URL # noqa: F401
|
|
4
11
|
from starlette.datastructures import Address as Address # noqa: F401
|
|
5
12
|
from starlette.datastructures import FormData as FormData # noqa: F401
|
|
@@ -21,8 +28,28 @@ class UploadFile(StarletteUploadFile):
|
|
|
21
28
|
return v
|
|
22
29
|
|
|
23
30
|
@classmethod
|
|
24
|
-
def
|
|
25
|
-
|
|
31
|
+
def _validate(cls, __input_value: Any, _: Any) -> "UploadFile":
|
|
32
|
+
if not isinstance(__input_value, StarletteUploadFile):
|
|
33
|
+
raise ValueError(f"Expected UploadFile, received: {type(__input_value)}")
|
|
34
|
+
return cast(UploadFile, __input_value)
|
|
35
|
+
|
|
36
|
+
if not PYDANTIC_V2:
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
|
|
40
|
+
field_schema.update({"type": "string", "format": "binary"})
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def __get_pydantic_json_schema__(
|
|
44
|
+
cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
|
|
45
|
+
) -> JsonSchemaValue:
|
|
46
|
+
return {"type": "string", "format": "binary"}
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def __get_pydantic_core_schema__(
|
|
50
|
+
cls, source: Type[Any], handler: Callable[[Any], CoreSchema]
|
|
51
|
+
) -> CoreSchema:
|
|
52
|
+
return general_plain_validator_function(cls._validate)
|
|
26
53
|
|
|
27
54
|
|
|
28
55
|
class DefaultPlaceholder:
|
fastapi/dependencies/models.py
CHANGED
fastapi/dependencies/utils.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import dataclasses
|
|
2
1
|
import inspect
|
|
3
2
|
from contextlib import contextmanager
|
|
4
|
-
from copy import
|
|
3
|
+
from copy import deepcopy
|
|
5
4
|
from typing import (
|
|
6
5
|
Any,
|
|
7
6
|
Callable,
|
|
@@ -20,6 +19,31 @@ from typing import (
|
|
|
20
19
|
|
|
21
20
|
import anyio
|
|
22
21
|
from fastapi import params
|
|
22
|
+
from fastapi._compat import (
|
|
23
|
+
PYDANTIC_V2,
|
|
24
|
+
ErrorWrapper,
|
|
25
|
+
ModelField,
|
|
26
|
+
Required,
|
|
27
|
+
Undefined,
|
|
28
|
+
_regenerate_error_with_loc,
|
|
29
|
+
copy_field_info,
|
|
30
|
+
create_body_model,
|
|
31
|
+
evaluate_forwardref,
|
|
32
|
+
field_annotation_is_scalar,
|
|
33
|
+
get_annotation_from_field_info,
|
|
34
|
+
get_missing_field_error,
|
|
35
|
+
is_bytes_field,
|
|
36
|
+
is_bytes_sequence_field,
|
|
37
|
+
is_scalar_field,
|
|
38
|
+
is_scalar_sequence_field,
|
|
39
|
+
is_sequence_field,
|
|
40
|
+
is_uploadfile_or_nonable_uploadfile_annotation,
|
|
41
|
+
is_uploadfile_sequence_annotation,
|
|
42
|
+
lenient_issubclass,
|
|
43
|
+
sequence_types,
|
|
44
|
+
serialize_sequence_value,
|
|
45
|
+
value_is_sequence,
|
|
46
|
+
)
|
|
23
47
|
from fastapi.concurrency import (
|
|
24
48
|
AsyncExitStack,
|
|
25
49
|
asynccontextmanager,
|
|
@@ -31,50 +55,14 @@ from fastapi.security.base import SecurityBase
|
|
|
31
55
|
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
|
32
56
|
from fastapi.security.open_id_connect_url import OpenIdConnect
|
|
33
57
|
from fastapi.utils import create_response_field, get_path_param_names
|
|
34
|
-
from pydantic import
|
|
35
|
-
from pydantic.error_wrappers import ErrorWrapper
|
|
36
|
-
from pydantic.errors import MissingError
|
|
37
|
-
from pydantic.fields import (
|
|
38
|
-
SHAPE_FROZENSET,
|
|
39
|
-
SHAPE_LIST,
|
|
40
|
-
SHAPE_SEQUENCE,
|
|
41
|
-
SHAPE_SET,
|
|
42
|
-
SHAPE_SINGLETON,
|
|
43
|
-
SHAPE_TUPLE,
|
|
44
|
-
SHAPE_TUPLE_ELLIPSIS,
|
|
45
|
-
FieldInfo,
|
|
46
|
-
ModelField,
|
|
47
|
-
Required,
|
|
48
|
-
Undefined,
|
|
49
|
-
)
|
|
50
|
-
from pydantic.schema import get_annotation_from_field_info
|
|
51
|
-
from pydantic.typing import evaluate_forwardref, get_args, get_origin
|
|
52
|
-
from pydantic.utils import lenient_issubclass
|
|
58
|
+
from pydantic.fields import FieldInfo
|
|
53
59
|
from starlette.background import BackgroundTasks
|
|
54
60
|
from starlette.concurrency import run_in_threadpool
|
|
55
61
|
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
|
|
56
62
|
from starlette.requests import HTTPConnection, Request
|
|
57
63
|
from starlette.responses import Response
|
|
58
64
|
from starlette.websockets import WebSocket
|
|
59
|
-
from typing_extensions import Annotated
|
|
60
|
-
|
|
61
|
-
sequence_shapes = {
|
|
62
|
-
SHAPE_LIST,
|
|
63
|
-
SHAPE_SET,
|
|
64
|
-
SHAPE_FROZENSET,
|
|
65
|
-
SHAPE_TUPLE,
|
|
66
|
-
SHAPE_SEQUENCE,
|
|
67
|
-
SHAPE_TUPLE_ELLIPSIS,
|
|
68
|
-
}
|
|
69
|
-
sequence_types = (list, set, tuple)
|
|
70
|
-
sequence_shape_to_type = {
|
|
71
|
-
SHAPE_LIST: list,
|
|
72
|
-
SHAPE_SET: set,
|
|
73
|
-
SHAPE_TUPLE: tuple,
|
|
74
|
-
SHAPE_SEQUENCE: list,
|
|
75
|
-
SHAPE_TUPLE_ELLIPSIS: list,
|
|
76
|
-
}
|
|
77
|
-
|
|
65
|
+
from typing_extensions import Annotated, get_args, get_origin
|
|
78
66
|
|
|
79
67
|
multipart_not_installed_error = (
|
|
80
68
|
'Form data requires "python-multipart" to be installed. \n'
|
|
@@ -216,36 +204,6 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
|
|
|
216
204
|
)
|
|
217
205
|
|
|
218
206
|
|
|
219
|
-
def is_scalar_field(field: ModelField) -> bool:
|
|
220
|
-
field_info = field.field_info
|
|
221
|
-
if not (
|
|
222
|
-
field.shape == SHAPE_SINGLETON
|
|
223
|
-
and not lenient_issubclass(field.type_, BaseModel)
|
|
224
|
-
and not lenient_issubclass(field.type_, sequence_types + (dict,))
|
|
225
|
-
and not dataclasses.is_dataclass(field.type_)
|
|
226
|
-
and not isinstance(field_info, params.Body)
|
|
227
|
-
):
|
|
228
|
-
return False
|
|
229
|
-
if field.sub_fields:
|
|
230
|
-
if not all(is_scalar_field(f) for f in field.sub_fields):
|
|
231
|
-
return False
|
|
232
|
-
return True
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def is_scalar_sequence_field(field: ModelField) -> bool:
|
|
236
|
-
if (field.shape in sequence_shapes) and not lenient_issubclass(
|
|
237
|
-
field.type_, BaseModel
|
|
238
|
-
):
|
|
239
|
-
if field.sub_fields is not None:
|
|
240
|
-
for sub_field in field.sub_fields:
|
|
241
|
-
if not is_scalar_field(sub_field):
|
|
242
|
-
return False
|
|
243
|
-
return True
|
|
244
|
-
if lenient_issubclass(field.type_, sequence_types):
|
|
245
|
-
return True
|
|
246
|
-
return False
|
|
247
|
-
|
|
248
|
-
|
|
249
207
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
|
250
208
|
signature = inspect.signature(call)
|
|
251
209
|
globalns = getattr(call, "__globals__", {})
|
|
@@ -364,12 +322,11 @@ def analyze_param(
|
|
|
364
322
|
is_path_param: bool,
|
|
365
323
|
) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
|
|
366
324
|
field_info = None
|
|
367
|
-
used_default_field_info = False
|
|
368
325
|
depends = None
|
|
369
326
|
type_annotation: Any = Any
|
|
370
327
|
if (
|
|
371
328
|
annotation is not inspect.Signature.empty
|
|
372
|
-
and get_origin(annotation) is Annotated
|
|
329
|
+
and get_origin(annotation) is Annotated
|
|
373
330
|
):
|
|
374
331
|
annotated_args = get_args(annotation)
|
|
375
332
|
type_annotation = annotated_args[0]
|
|
@@ -384,7 +341,9 @@ def analyze_param(
|
|
|
384
341
|
fastapi_annotation = next(iter(fastapi_annotations), None)
|
|
385
342
|
if isinstance(fastapi_annotation, FieldInfo):
|
|
386
343
|
# Copy `field_info` because we mutate `field_info.default` below.
|
|
387
|
-
field_info =
|
|
344
|
+
field_info = copy_field_info(
|
|
345
|
+
field_info=fastapi_annotation, annotation=annotation
|
|
346
|
+
)
|
|
388
347
|
assert field_info.default is Undefined or field_info.default is Required, (
|
|
389
348
|
f"`{field_info.__class__.__name__}` default value cannot be set in"
|
|
390
349
|
f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
|
|
@@ -415,6 +374,8 @@ def analyze_param(
|
|
|
415
374
|
f" together for {param_name!r}"
|
|
416
375
|
)
|
|
417
376
|
field_info = value
|
|
377
|
+
if PYDANTIC_V2:
|
|
378
|
+
field_info.annotation = type_annotation
|
|
418
379
|
|
|
419
380
|
if depends is not None and depends.dependency is None:
|
|
420
381
|
depends.dependency = type_annotation
|
|
@@ -433,10 +394,15 @@ def analyze_param(
|
|
|
433
394
|
# We might check here that `default_value is Required`, but the fact is that the same
|
|
434
395
|
# parameter might sometimes be a path parameter and sometimes not. See
|
|
435
396
|
# `tests/test_infer_param_optionality.py` for an example.
|
|
436
|
-
field_info = params.Path()
|
|
397
|
+
field_info = params.Path(annotation=type_annotation)
|
|
398
|
+
elif is_uploadfile_or_nonable_uploadfile_annotation(
|
|
399
|
+
type_annotation
|
|
400
|
+
) or is_uploadfile_sequence_annotation(type_annotation):
|
|
401
|
+
field_info = params.File(annotation=type_annotation, default=default_value)
|
|
402
|
+
elif not field_annotation_is_scalar(annotation=type_annotation):
|
|
403
|
+
field_info = params.Body(annotation=type_annotation, default=default_value)
|
|
437
404
|
else:
|
|
438
|
-
field_info = params.Query(default=default_value)
|
|
439
|
-
used_default_field_info = True
|
|
405
|
+
field_info = params.Query(annotation=type_annotation, default=default_value)
|
|
440
406
|
|
|
441
407
|
field = None
|
|
442
408
|
if field_info is not None:
|
|
@@ -450,8 +416,8 @@ def analyze_param(
|
|
|
450
416
|
and getattr(field_info, "in_", None) is None
|
|
451
417
|
):
|
|
452
418
|
field_info.in_ = params.ParamTypes.query
|
|
453
|
-
|
|
454
|
-
|
|
419
|
+
use_annotation = get_annotation_from_field_info(
|
|
420
|
+
type_annotation,
|
|
455
421
|
field_info,
|
|
456
422
|
param_name,
|
|
457
423
|
)
|
|
@@ -459,19 +425,15 @@ def analyze_param(
|
|
|
459
425
|
alias = param_name.replace("_", "-")
|
|
460
426
|
else:
|
|
461
427
|
alias = field_info.alias or param_name
|
|
428
|
+
field_info.alias = alias
|
|
462
429
|
field = create_response_field(
|
|
463
430
|
name=param_name,
|
|
464
|
-
type_=
|
|
431
|
+
type_=use_annotation,
|
|
465
432
|
default=field_info.default,
|
|
466
433
|
alias=alias,
|
|
467
434
|
required=field_info.default in (Required, Undefined),
|
|
468
435
|
field_info=field_info,
|
|
469
436
|
)
|
|
470
|
-
if used_default_field_info:
|
|
471
|
-
if lenient_issubclass(field.type_, UploadFile):
|
|
472
|
-
field.field_info = params.File(field_info.default)
|
|
473
|
-
elif not is_scalar_field(field=field):
|
|
474
|
-
field.field_info = params.Body(field_info.default)
|
|
475
437
|
|
|
476
438
|
return type_annotation, depends, field
|
|
477
439
|
|
|
@@ -554,13 +516,13 @@ async def solve_dependencies(
|
|
|
554
516
|
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
|
|
555
517
|
) -> Tuple[
|
|
556
518
|
Dict[str, Any],
|
|
557
|
-
List[
|
|
519
|
+
List[Any],
|
|
558
520
|
Optional[BackgroundTasks],
|
|
559
521
|
Response,
|
|
560
522
|
Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
|
|
561
523
|
]:
|
|
562
524
|
values: Dict[str, Any] = {}
|
|
563
|
-
errors: List[
|
|
525
|
+
errors: List[Any] = []
|
|
564
526
|
if response is None:
|
|
565
527
|
response = Response()
|
|
566
528
|
del response.headers["content-length"]
|
|
@@ -674,7 +636,7 @@ async def solve_dependencies(
|
|
|
674
636
|
def request_params_to_args(
|
|
675
637
|
required_params: Sequence[ModelField],
|
|
676
638
|
received_params: Union[Mapping[str, Any], QueryParams, Headers],
|
|
677
|
-
) -> Tuple[Dict[str, Any], List[
|
|
639
|
+
) -> Tuple[Dict[str, Any], List[Any]]:
|
|
678
640
|
values = {}
|
|
679
641
|
errors = []
|
|
680
642
|
for field in required_params:
|
|
@@ -688,23 +650,19 @@ def request_params_to_args(
|
|
|
688
650
|
assert isinstance(
|
|
689
651
|
field_info, params.Param
|
|
690
652
|
), "Params must be subclasses of Param"
|
|
653
|
+
loc = (field_info.in_.value, field.alias)
|
|
691
654
|
if value is None:
|
|
692
655
|
if field.required:
|
|
693
|
-
errors.append(
|
|
694
|
-
ErrorWrapper(
|
|
695
|
-
MissingError(), loc=(field_info.in_.value, field.alias)
|
|
696
|
-
)
|
|
697
|
-
)
|
|
656
|
+
errors.append(get_missing_field_error(loc=loc))
|
|
698
657
|
else:
|
|
699
658
|
values[field.name] = deepcopy(field.default)
|
|
700
659
|
continue
|
|
701
|
-
v_, errors_ = field.validate(
|
|
702
|
-
value, values, loc=(field_info.in_.value, field.alias)
|
|
703
|
-
)
|
|
660
|
+
v_, errors_ = field.validate(value, values, loc=loc)
|
|
704
661
|
if isinstance(errors_, ErrorWrapper):
|
|
705
662
|
errors.append(errors_)
|
|
706
663
|
elif isinstance(errors_, list):
|
|
707
|
-
errors
|
|
664
|
+
new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
|
|
665
|
+
errors.extend(new_errors)
|
|
708
666
|
else:
|
|
709
667
|
values[field.name] = v_
|
|
710
668
|
return values, errors
|
|
@@ -713,9 +671,9 @@ def request_params_to_args(
|
|
|
713
671
|
async def request_body_to_args(
|
|
714
672
|
required_params: List[ModelField],
|
|
715
673
|
received_body: Optional[Union[Dict[str, Any], FormData]],
|
|
716
|
-
) -> Tuple[Dict[str, Any], List[
|
|
674
|
+
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
|
|
717
675
|
values = {}
|
|
718
|
-
errors = []
|
|
676
|
+
errors: List[Dict[str, Any]] = []
|
|
719
677
|
if required_params:
|
|
720
678
|
field = required_params[0]
|
|
721
679
|
field_info = field.field_info
|
|
@@ -733,9 +691,7 @@ async def request_body_to_args(
|
|
|
733
691
|
|
|
734
692
|
value: Optional[Any] = None
|
|
735
693
|
if received_body is not None:
|
|
736
|
-
if (
|
|
737
|
-
field.shape in sequence_shapes or field.type_ in sequence_types
|
|
738
|
-
) and isinstance(received_body, FormData):
|
|
694
|
+
if (is_sequence_field(field)) and isinstance(received_body, FormData):
|
|
739
695
|
value = received_body.getlist(field.alias)
|
|
740
696
|
else:
|
|
741
697
|
try:
|
|
@@ -748,7 +704,7 @@ async def request_body_to_args(
|
|
|
748
704
|
or (isinstance(field_info, params.Form) and value == "")
|
|
749
705
|
or (
|
|
750
706
|
isinstance(field_info, params.Form)
|
|
751
|
-
and field
|
|
707
|
+
and is_sequence_field(field)
|
|
752
708
|
and len(value) == 0
|
|
753
709
|
)
|
|
754
710
|
):
|
|
@@ -759,16 +715,17 @@ async def request_body_to_args(
|
|
|
759
715
|
continue
|
|
760
716
|
if (
|
|
761
717
|
isinstance(field_info, params.File)
|
|
762
|
-
and
|
|
718
|
+
and is_bytes_field(field)
|
|
763
719
|
and isinstance(value, UploadFile)
|
|
764
720
|
):
|
|
765
721
|
value = await value.read()
|
|
766
722
|
elif (
|
|
767
|
-
field
|
|
723
|
+
is_bytes_sequence_field(field)
|
|
768
724
|
and isinstance(field_info, params.File)
|
|
769
|
-
and
|
|
770
|
-
and isinstance(value, sequence_types)
|
|
725
|
+
and value_is_sequence(value)
|
|
771
726
|
):
|
|
727
|
+
# For types
|
|
728
|
+
assert isinstance(value, sequence_types) # type: ignore[arg-type]
|
|
772
729
|
results: List[Union[bytes, str]] = []
|
|
773
730
|
|
|
774
731
|
async def process_fn(
|
|
@@ -780,24 +737,19 @@ async def request_body_to_args(
|
|
|
780
737
|
async with anyio.create_task_group() as tg:
|
|
781
738
|
for sub_value in value:
|
|
782
739
|
tg.start_soon(process_fn, sub_value.read)
|
|
783
|
-
value =
|
|
740
|
+
value = serialize_sequence_value(field=field, value=results)
|
|
784
741
|
|
|
785
742
|
v_, errors_ = field.validate(value, values, loc=loc)
|
|
786
743
|
|
|
787
|
-
if isinstance(errors_,
|
|
788
|
-
errors.append(errors_)
|
|
789
|
-
elif isinstance(errors_, list):
|
|
744
|
+
if isinstance(errors_, list):
|
|
790
745
|
errors.extend(errors_)
|
|
746
|
+
elif errors_:
|
|
747
|
+
errors.append(errors_)
|
|
791
748
|
else:
|
|
792
749
|
values[field.name] = v_
|
|
793
750
|
return values, errors
|
|
794
751
|
|
|
795
752
|
|
|
796
|
-
def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
|
|
797
|
-
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
|
|
798
|
-
return missing_field_error
|
|
799
|
-
|
|
800
|
-
|
|
801
753
|
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
|
802
754
|
flat_dependant = get_flat_dependant(dependant)
|
|
803
755
|
if not flat_dependant.body_params:
|
|
@@ -815,12 +767,16 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
|
|
815
767
|
for param in flat_dependant.body_params:
|
|
816
768
|
setattr(param.field_info, "embed", True) # noqa: B010
|
|
817
769
|
model_name = "Body_" + name
|
|
818
|
-
BodyModel
|
|
819
|
-
|
|
820
|
-
|
|
770
|
+
BodyModel = create_body_model(
|
|
771
|
+
fields=flat_dependant.body_params, model_name=model_name
|
|
772
|
+
)
|
|
821
773
|
required = any(True for f in flat_dependant.body_params if f.required)
|
|
822
|
-
|
|
823
|
-
|
|
774
|
+
BodyFieldInfo_kwargs: Dict[str, Any] = {
|
|
775
|
+
"annotation": BodyModel,
|
|
776
|
+
"alias": "body",
|
|
777
|
+
}
|
|
778
|
+
if not required:
|
|
779
|
+
BodyFieldInfo_kwargs["default"] = None
|
|
824
780
|
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
|
|
825
781
|
BodyFieldInfo: Type[params.Body] = params.File
|
|
826
782
|
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
|