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/routing.py
CHANGED
|
@@ -20,6 +20,14 @@ from typing import (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from fastapi import params
|
|
23
|
+
from fastapi._compat import (
|
|
24
|
+
ModelField,
|
|
25
|
+
Undefined,
|
|
26
|
+
_get_model_config,
|
|
27
|
+
_model_dump,
|
|
28
|
+
_normalize_errors,
|
|
29
|
+
lenient_issubclass,
|
|
30
|
+
)
|
|
23
31
|
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
24
32
|
from fastapi.dependencies.models import Dependant
|
|
25
33
|
from fastapi.dependencies.utils import (
|
|
@@ -29,9 +37,14 @@ from fastapi.dependencies.utils import (
|
|
|
29
37
|
get_typed_return_annotation,
|
|
30
38
|
solve_dependencies,
|
|
31
39
|
)
|
|
32
|
-
from fastapi.encoders import
|
|
33
|
-
from fastapi.exceptions import
|
|
34
|
-
|
|
40
|
+
from fastapi.encoders import jsonable_encoder
|
|
41
|
+
from fastapi.exceptions import (
|
|
42
|
+
FastAPIError,
|
|
43
|
+
RequestValidationError,
|
|
44
|
+
ResponseValidationError,
|
|
45
|
+
WebSocketRequestValidationError,
|
|
46
|
+
)
|
|
47
|
+
from fastapi.types import DecoratedCallable, IncEx
|
|
35
48
|
from fastapi.utils import (
|
|
36
49
|
create_cloned_field,
|
|
37
50
|
create_response_field,
|
|
@@ -40,23 +53,20 @@ from fastapi.utils import (
|
|
|
40
53
|
is_body_allowed_for_status_code,
|
|
41
54
|
)
|
|
42
55
|
from pydantic import BaseModel
|
|
43
|
-
from pydantic.error_wrappers import ErrorWrapper, ValidationError
|
|
44
|
-
from pydantic.fields import ModelField, Undefined
|
|
45
|
-
from pydantic.utils import lenient_issubclass
|
|
46
56
|
from starlette import routing
|
|
47
57
|
from starlette.concurrency import run_in_threadpool
|
|
48
58
|
from starlette.exceptions import HTTPException
|
|
49
59
|
from starlette.requests import Request
|
|
50
60
|
from starlette.responses import JSONResponse, Response
|
|
51
|
-
from starlette.routing import BaseRoute, Match
|
|
52
|
-
from starlette.routing import Mount as Mount # noqa
|
|
53
61
|
from starlette.routing import (
|
|
62
|
+
BaseRoute,
|
|
63
|
+
Match,
|
|
54
64
|
compile_path,
|
|
55
65
|
get_name,
|
|
56
66
|
request_response,
|
|
57
67
|
websocket_session,
|
|
58
68
|
)
|
|
59
|
-
from starlette.
|
|
69
|
+
from starlette.routing import Mount as Mount # noqa
|
|
60
70
|
from starlette.types import ASGIApp, Lifespan, Scope
|
|
61
71
|
from starlette.websockets import WebSocket
|
|
62
72
|
|
|
@@ -69,14 +79,15 @@ def _prepare_response_content(
|
|
|
69
79
|
exclude_none: bool = False,
|
|
70
80
|
) -> Any:
|
|
71
81
|
if isinstance(res, BaseModel):
|
|
72
|
-
read_with_orm_mode = getattr(res
|
|
82
|
+
read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None)
|
|
73
83
|
if read_with_orm_mode:
|
|
74
84
|
# Let from_orm extract the data from this model instead of converting
|
|
75
85
|
# it now to a dict.
|
|
76
86
|
# Otherwise there's no way to extract lazy data that requires attribute
|
|
77
87
|
# access instead of dict iteration, e.g. lazy relationships.
|
|
78
88
|
return res
|
|
79
|
-
return
|
|
89
|
+
return _model_dump(
|
|
90
|
+
res,
|
|
80
91
|
by_alias=True,
|
|
81
92
|
exclude_unset=exclude_unset,
|
|
82
93
|
exclude_defaults=exclude_defaults,
|
|
@@ -111,8 +122,8 @@ async def serialize_response(
|
|
|
111
122
|
*,
|
|
112
123
|
field: Optional[ModelField] = None,
|
|
113
124
|
response_content: Any,
|
|
114
|
-
include: Optional[
|
|
115
|
-
exclude: Optional[
|
|
125
|
+
include: Optional[IncEx] = None,
|
|
126
|
+
exclude: Optional[IncEx] = None,
|
|
116
127
|
by_alias: bool = True,
|
|
117
128
|
exclude_unset: bool = False,
|
|
118
129
|
exclude_defaults: bool = False,
|
|
@@ -121,24 +132,40 @@ async def serialize_response(
|
|
|
121
132
|
) -> Any:
|
|
122
133
|
if field:
|
|
123
134
|
errors = []
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
if not hasattr(field, "serialize"):
|
|
136
|
+
# pydantic v1
|
|
137
|
+
response_content = _prepare_response_content(
|
|
138
|
+
response_content,
|
|
139
|
+
exclude_unset=exclude_unset,
|
|
140
|
+
exclude_defaults=exclude_defaults,
|
|
141
|
+
exclude_none=exclude_none,
|
|
142
|
+
)
|
|
130
143
|
if is_coroutine:
|
|
131
144
|
value, errors_ = field.validate(response_content, {}, loc=("response",))
|
|
132
145
|
else:
|
|
133
146
|
value, errors_ = await run_in_threadpool(
|
|
134
147
|
field.validate, response_content, {}, loc=("response",)
|
|
135
148
|
)
|
|
136
|
-
if isinstance(errors_,
|
|
137
|
-
errors.append(errors_)
|
|
138
|
-
elif isinstance(errors_, list):
|
|
149
|
+
if isinstance(errors_, list):
|
|
139
150
|
errors.extend(errors_)
|
|
151
|
+
elif errors_:
|
|
152
|
+
errors.append(errors_)
|
|
140
153
|
if errors:
|
|
141
|
-
raise
|
|
154
|
+
raise ResponseValidationError(
|
|
155
|
+
errors=_normalize_errors(errors), body=response_content
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if hasattr(field, "serialize"):
|
|
159
|
+
return field.serialize(
|
|
160
|
+
value,
|
|
161
|
+
include=include,
|
|
162
|
+
exclude=exclude,
|
|
163
|
+
by_alias=by_alias,
|
|
164
|
+
exclude_unset=exclude_unset,
|
|
165
|
+
exclude_defaults=exclude_defaults,
|
|
166
|
+
exclude_none=exclude_none,
|
|
167
|
+
)
|
|
168
|
+
|
|
142
169
|
return jsonable_encoder(
|
|
143
170
|
value,
|
|
144
171
|
include=include,
|
|
@@ -171,8 +198,8 @@ def get_request_handler(
|
|
|
171
198
|
status_code: Optional[int] = None,
|
|
172
199
|
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
|
|
173
200
|
response_field: Optional[ModelField] = None,
|
|
174
|
-
response_model_include: Optional[
|
|
175
|
-
response_model_exclude: Optional[
|
|
201
|
+
response_model_include: Optional[IncEx] = None,
|
|
202
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
176
203
|
response_model_by_alias: bool = True,
|
|
177
204
|
response_model_exclude_unset: bool = False,
|
|
178
205
|
response_model_exclude_defaults: bool = False,
|
|
@@ -216,7 +243,16 @@ def get_request_handler(
|
|
|
216
243
|
body = body_bytes
|
|
217
244
|
except json.JSONDecodeError as e:
|
|
218
245
|
raise RequestValidationError(
|
|
219
|
-
[
|
|
246
|
+
[
|
|
247
|
+
{
|
|
248
|
+
"type": "json_invalid",
|
|
249
|
+
"loc": ("body", e.pos),
|
|
250
|
+
"msg": "JSON decode error",
|
|
251
|
+
"input": {},
|
|
252
|
+
"ctx": {"error": e.msg},
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
body=e.doc,
|
|
220
256
|
) from e
|
|
221
257
|
except HTTPException:
|
|
222
258
|
raise
|
|
@@ -232,7 +268,7 @@ def get_request_handler(
|
|
|
232
268
|
)
|
|
233
269
|
values, errors, background_tasks, sub_response, _ = solved_result
|
|
234
270
|
if errors:
|
|
235
|
-
raise RequestValidationError(errors, body=body)
|
|
271
|
+
raise RequestValidationError(_normalize_errors(errors), body=body)
|
|
236
272
|
else:
|
|
237
273
|
raw_response = await run_endpoint_function(
|
|
238
274
|
dependant=dependant, values=values, is_coroutine=is_coroutine
|
|
@@ -283,8 +319,7 @@ def get_websocket_app(
|
|
|
283
319
|
)
|
|
284
320
|
values, errors, _, _2, _3 = solved_result
|
|
285
321
|
if errors:
|
|
286
|
-
|
|
287
|
-
raise WebSocketRequestValidationError(errors)
|
|
322
|
+
raise WebSocketRequestValidationError(_normalize_errors(errors))
|
|
288
323
|
assert dependant.call is not None, "dependant.call must be a function"
|
|
289
324
|
await dependant.call(**values)
|
|
290
325
|
|
|
@@ -298,13 +333,21 @@ class APIWebSocketRoute(routing.WebSocketRoute):
|
|
|
298
333
|
endpoint: Callable[..., Any],
|
|
299
334
|
*,
|
|
300
335
|
name: Optional[str] = None,
|
|
336
|
+
dependencies: Optional[Sequence[params.Depends]] = None,
|
|
301
337
|
dependency_overrides_provider: Optional[Any] = None,
|
|
302
338
|
) -> None:
|
|
303
339
|
self.path = path
|
|
304
340
|
self.endpoint = endpoint
|
|
305
341
|
self.name = get_name(endpoint) if name is None else name
|
|
342
|
+
self.dependencies = list(dependencies or [])
|
|
306
343
|
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
|
307
344
|
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
|
|
345
|
+
for depends in self.dependencies[::-1]:
|
|
346
|
+
self.dependant.dependencies.insert(
|
|
347
|
+
0,
|
|
348
|
+
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
|
349
|
+
)
|
|
350
|
+
|
|
308
351
|
self.app = websocket_session(
|
|
309
352
|
get_websocket_app(
|
|
310
353
|
dependant=self.dependant,
|
|
@@ -337,8 +380,8 @@ class APIRoute(routing.Route):
|
|
|
337
380
|
name: Optional[str] = None,
|
|
338
381
|
methods: Optional[Union[Set[str], List[str]]] = None,
|
|
339
382
|
operation_id: Optional[str] = None,
|
|
340
|
-
response_model_include: Optional[
|
|
341
|
-
response_model_exclude: Optional[
|
|
383
|
+
response_model_include: Optional[IncEx] = None,
|
|
384
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
342
385
|
response_model_by_alias: bool = True,
|
|
343
386
|
response_model_exclude_unset: bool = False,
|
|
344
387
|
response_model_exclude_defaults: bool = False,
|
|
@@ -412,16 +455,14 @@ class APIRoute(routing.Route):
|
|
|
412
455
|
# would pass the validation and be returned as is.
|
|
413
456
|
# By being a new field, no inheritance will be passed as is. A new model
|
|
414
457
|
# will be always created.
|
|
458
|
+
# TODO: remove when deprecating Pydantic v1
|
|
415
459
|
self.secure_cloned_response_field: Optional[
|
|
416
460
|
ModelField
|
|
417
461
|
] = create_cloned_field(self.response_field)
|
|
418
462
|
else:
|
|
419
463
|
self.response_field = None # type: ignore
|
|
420
464
|
self.secure_cloned_response_field = None
|
|
421
|
-
|
|
422
|
-
self.dependencies = list(dependencies)
|
|
423
|
-
else:
|
|
424
|
-
self.dependencies = []
|
|
465
|
+
self.dependencies = list(dependencies or [])
|
|
425
466
|
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
|
426
467
|
# if a "form feed" character (page break) is found in the description text,
|
|
427
468
|
# truncate description text to the content preceding the first "form feed"
|
|
@@ -516,7 +557,7 @@ class APIRouter(routing.Router):
|
|
|
516
557
|
), "A path prefix must not end with '/', as the routes will start with '/'"
|
|
517
558
|
self.prefix = prefix
|
|
518
559
|
self.tags: List[Union[str, Enum]] = tags or []
|
|
519
|
-
self.dependencies = list(dependencies or [])
|
|
560
|
+
self.dependencies = list(dependencies or [])
|
|
520
561
|
self.deprecated = deprecated
|
|
521
562
|
self.include_in_schema = include_in_schema
|
|
522
563
|
self.responses = responses or {}
|
|
@@ -561,8 +602,8 @@ class APIRouter(routing.Router):
|
|
|
561
602
|
deprecated: Optional[bool] = None,
|
|
562
603
|
methods: Optional[Union[Set[str], List[str]]] = None,
|
|
563
604
|
operation_id: Optional[str] = None,
|
|
564
|
-
response_model_include: Optional[
|
|
565
|
-
response_model_exclude: Optional[
|
|
605
|
+
response_model_include: Optional[IncEx] = None,
|
|
606
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
566
607
|
response_model_by_alias: bool = True,
|
|
567
608
|
response_model_exclude_unset: bool = False,
|
|
568
609
|
response_model_exclude_defaults: bool = False,
|
|
@@ -642,8 +683,8 @@ class APIRouter(routing.Router):
|
|
|
642
683
|
deprecated: Optional[bool] = None,
|
|
643
684
|
methods: Optional[List[str]] = None,
|
|
644
685
|
operation_id: Optional[str] = None,
|
|
645
|
-
response_model_include: Optional[
|
|
646
|
-
response_model_exclude: Optional[
|
|
686
|
+
response_model_include: Optional[IncEx] = None,
|
|
687
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
647
688
|
response_model_by_alias: bool = True,
|
|
648
689
|
response_model_exclude_unset: bool = False,
|
|
649
690
|
response_model_exclude_defaults: bool = False,
|
|
@@ -690,21 +731,37 @@ class APIRouter(routing.Router):
|
|
|
690
731
|
return decorator
|
|
691
732
|
|
|
692
733
|
def add_api_websocket_route(
|
|
693
|
-
self,
|
|
734
|
+
self,
|
|
735
|
+
path: str,
|
|
736
|
+
endpoint: Callable[..., Any],
|
|
737
|
+
name: Optional[str] = None,
|
|
738
|
+
*,
|
|
739
|
+
dependencies: Optional[Sequence[params.Depends]] = None,
|
|
694
740
|
) -> None:
|
|
741
|
+
current_dependencies = self.dependencies.copy()
|
|
742
|
+
if dependencies:
|
|
743
|
+
current_dependencies.extend(dependencies)
|
|
744
|
+
|
|
695
745
|
route = APIWebSocketRoute(
|
|
696
746
|
self.prefix + path,
|
|
697
747
|
endpoint=endpoint,
|
|
698
748
|
name=name,
|
|
749
|
+
dependencies=current_dependencies,
|
|
699
750
|
dependency_overrides_provider=self.dependency_overrides_provider,
|
|
700
751
|
)
|
|
701
752
|
self.routes.append(route)
|
|
702
753
|
|
|
703
754
|
def websocket(
|
|
704
|
-
self,
|
|
755
|
+
self,
|
|
756
|
+
path: str,
|
|
757
|
+
name: Optional[str] = None,
|
|
758
|
+
*,
|
|
759
|
+
dependencies: Optional[Sequence[params.Depends]] = None,
|
|
705
760
|
) -> Callable[[DecoratedCallable], DecoratedCallable]:
|
|
706
761
|
def decorator(func: DecoratedCallable) -> DecoratedCallable:
|
|
707
|
-
self.add_api_websocket_route(
|
|
762
|
+
self.add_api_websocket_route(
|
|
763
|
+
path, func, name=name, dependencies=dependencies
|
|
764
|
+
)
|
|
708
765
|
return func
|
|
709
766
|
|
|
710
767
|
return decorator
|
|
@@ -744,7 +801,7 @@ class APIRouter(routing.Router):
|
|
|
744
801
|
path = getattr(r, "path") # noqa: B009
|
|
745
802
|
name = getattr(r, "name", "unknown")
|
|
746
803
|
if path is not None and not path:
|
|
747
|
-
raise
|
|
804
|
+
raise FastAPIError(
|
|
748
805
|
f"Prefix and path cannot be both empty (path operation: {name})"
|
|
749
806
|
)
|
|
750
807
|
if responses is None:
|
|
@@ -819,8 +876,16 @@ class APIRouter(routing.Router):
|
|
|
819
876
|
name=route.name,
|
|
820
877
|
)
|
|
821
878
|
elif isinstance(route, APIWebSocketRoute):
|
|
879
|
+
current_dependencies = []
|
|
880
|
+
if dependencies:
|
|
881
|
+
current_dependencies.extend(dependencies)
|
|
882
|
+
if route.dependencies:
|
|
883
|
+
current_dependencies.extend(route.dependencies)
|
|
822
884
|
self.add_api_websocket_route(
|
|
823
|
-
prefix + route.path,
|
|
885
|
+
prefix + route.path,
|
|
886
|
+
route.endpoint,
|
|
887
|
+
dependencies=current_dependencies,
|
|
888
|
+
name=route.name,
|
|
824
889
|
)
|
|
825
890
|
elif isinstance(route, routing.WebSocketRoute):
|
|
826
891
|
self.add_websocket_route(
|
|
@@ -845,8 +910,8 @@ class APIRouter(routing.Router):
|
|
|
845
910
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
846
911
|
deprecated: Optional[bool] = None,
|
|
847
912
|
operation_id: Optional[str] = None,
|
|
848
|
-
response_model_include: Optional[
|
|
849
|
-
response_model_exclude: Optional[
|
|
913
|
+
response_model_include: Optional[IncEx] = None,
|
|
914
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
850
915
|
response_model_by_alias: bool = True,
|
|
851
916
|
response_model_exclude_unset: bool = False,
|
|
852
917
|
response_model_exclude_defaults: bool = False,
|
|
@@ -901,8 +966,8 @@ class APIRouter(routing.Router):
|
|
|
901
966
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
902
967
|
deprecated: Optional[bool] = None,
|
|
903
968
|
operation_id: Optional[str] = None,
|
|
904
|
-
response_model_include: Optional[
|
|
905
|
-
response_model_exclude: Optional[
|
|
969
|
+
response_model_include: Optional[IncEx] = None,
|
|
970
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
906
971
|
response_model_by_alias: bool = True,
|
|
907
972
|
response_model_exclude_unset: bool = False,
|
|
908
973
|
response_model_exclude_defaults: bool = False,
|
|
@@ -957,8 +1022,8 @@ class APIRouter(routing.Router):
|
|
|
957
1022
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
958
1023
|
deprecated: Optional[bool] = None,
|
|
959
1024
|
operation_id: Optional[str] = None,
|
|
960
|
-
response_model_include: Optional[
|
|
961
|
-
response_model_exclude: Optional[
|
|
1025
|
+
response_model_include: Optional[IncEx] = None,
|
|
1026
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
962
1027
|
response_model_by_alias: bool = True,
|
|
963
1028
|
response_model_exclude_unset: bool = False,
|
|
964
1029
|
response_model_exclude_defaults: bool = False,
|
|
@@ -1013,8 +1078,8 @@ class APIRouter(routing.Router):
|
|
|
1013
1078
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
1014
1079
|
deprecated: Optional[bool] = None,
|
|
1015
1080
|
operation_id: Optional[str] = None,
|
|
1016
|
-
response_model_include: Optional[
|
|
1017
|
-
response_model_exclude: Optional[
|
|
1081
|
+
response_model_include: Optional[IncEx] = None,
|
|
1082
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
1018
1083
|
response_model_by_alias: bool = True,
|
|
1019
1084
|
response_model_exclude_unset: bool = False,
|
|
1020
1085
|
response_model_exclude_defaults: bool = False,
|
|
@@ -1069,8 +1134,8 @@ class APIRouter(routing.Router):
|
|
|
1069
1134
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
1070
1135
|
deprecated: Optional[bool] = None,
|
|
1071
1136
|
operation_id: Optional[str] = None,
|
|
1072
|
-
response_model_include: Optional[
|
|
1073
|
-
response_model_exclude: Optional[
|
|
1137
|
+
response_model_include: Optional[IncEx] = None,
|
|
1138
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
1074
1139
|
response_model_by_alias: bool = True,
|
|
1075
1140
|
response_model_exclude_unset: bool = False,
|
|
1076
1141
|
response_model_exclude_defaults: bool = False,
|
|
@@ -1125,8 +1190,8 @@ class APIRouter(routing.Router):
|
|
|
1125
1190
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
1126
1191
|
deprecated: Optional[bool] = None,
|
|
1127
1192
|
operation_id: Optional[str] = None,
|
|
1128
|
-
response_model_include: Optional[
|
|
1129
|
-
response_model_exclude: Optional[
|
|
1193
|
+
response_model_include: Optional[IncEx] = None,
|
|
1194
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
1130
1195
|
response_model_by_alias: bool = True,
|
|
1131
1196
|
response_model_exclude_unset: bool = False,
|
|
1132
1197
|
response_model_exclude_defaults: bool = False,
|
|
@@ -1181,8 +1246,8 @@ class APIRouter(routing.Router):
|
|
|
1181
1246
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
1182
1247
|
deprecated: Optional[bool] = None,
|
|
1183
1248
|
operation_id: Optional[str] = None,
|
|
1184
|
-
response_model_include: Optional[
|
|
1185
|
-
response_model_exclude: Optional[
|
|
1249
|
+
response_model_include: Optional[IncEx] = None,
|
|
1250
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
1186
1251
|
response_model_by_alias: bool = True,
|
|
1187
1252
|
response_model_exclude_unset: bool = False,
|
|
1188
1253
|
response_model_exclude_defaults: bool = False,
|
|
@@ -1237,8 +1302,8 @@ class APIRouter(routing.Router):
|
|
|
1237
1302
|
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
|
1238
1303
|
deprecated: Optional[bool] = None,
|
|
1239
1304
|
operation_id: Optional[str] = None,
|
|
1240
|
-
response_model_include: Optional[
|
|
1241
|
-
response_model_exclude: Optional[
|
|
1305
|
+
response_model_include: Optional[IncEx] = None,
|
|
1306
|
+
response_model_exclude: Optional[IncEx] = None,
|
|
1242
1307
|
response_model_by_alias: bool = True,
|
|
1243
1308
|
response_model_exclude_unset: bool = False,
|
|
1244
1309
|
response_model_exclude_defaults: bool = False,
|
fastapi/security/api_key.py
CHANGED
|
@@ -21,7 +21,9 @@ class APIKeyQuery(APIKeyBase):
|
|
|
21
21
|
auto_error: bool = True,
|
|
22
22
|
):
|
|
23
23
|
self.model: APIKey = APIKey(
|
|
24
|
-
**{"in": APIKeyIn.query},
|
|
24
|
+
**{"in": APIKeyIn.query}, # type: ignore[arg-type]
|
|
25
|
+
name=name,
|
|
26
|
+
description=description,
|
|
25
27
|
)
|
|
26
28
|
self.scheme_name = scheme_name or self.__class__.__name__
|
|
27
29
|
self.auto_error = auto_error
|
|
@@ -48,7 +50,9 @@ class APIKeyHeader(APIKeyBase):
|
|
|
48
50
|
auto_error: bool = True,
|
|
49
51
|
):
|
|
50
52
|
self.model: APIKey = APIKey(
|
|
51
|
-
**{"in": APIKeyIn.header},
|
|
53
|
+
**{"in": APIKeyIn.header}, # type: ignore[arg-type]
|
|
54
|
+
name=name,
|
|
55
|
+
description=description,
|
|
52
56
|
)
|
|
53
57
|
self.scheme_name = scheme_name or self.__class__.__name__
|
|
54
58
|
self.auto_error = auto_error
|
|
@@ -75,7 +79,9 @@ class APIKeyCookie(APIKeyBase):
|
|
|
75
79
|
auto_error: bool = True,
|
|
76
80
|
):
|
|
77
81
|
self.model: APIKey = APIKey(
|
|
78
|
-
**{"in": APIKeyIn.cookie},
|
|
82
|
+
**{"in": APIKeyIn.cookie}, # type: ignore[arg-type]
|
|
83
|
+
name=name,
|
|
84
|
+
description=description,
|
|
79
85
|
)
|
|
80
86
|
self.scheme_name = scheme_name or self.__class__.__name__
|
|
81
87
|
self.auto_error = auto_error
|
fastapi/security/oauth2.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Union
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union, cast
|
|
2
2
|
|
|
3
3
|
from fastapi.exceptions import HTTPException
|
|
4
4
|
from fastapi.openapi.models import OAuth2 as OAuth2Model
|
|
@@ -9,6 +9,9 @@ from fastapi.security.utils import get_authorization_scheme_param
|
|
|
9
9
|
from starlette.requests import Request
|
|
10
10
|
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
|
11
11
|
|
|
12
|
+
# TODO: import from typing when deprecating Python 3.9
|
|
13
|
+
from typing_extensions import Annotated
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
class OAuth2PasswordRequestForm:
|
|
14
17
|
"""
|
|
@@ -45,12 +48,13 @@ class OAuth2PasswordRequestForm:
|
|
|
45
48
|
|
|
46
49
|
def __init__(
|
|
47
50
|
self,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
*,
|
|
52
|
+
grant_type: Annotated[Union[str, None], Form(pattern="password")] = None,
|
|
53
|
+
username: Annotated[str, Form()],
|
|
54
|
+
password: Annotated[str, Form()],
|
|
55
|
+
scope: Annotated[str, Form()] = "",
|
|
56
|
+
client_id: Annotated[Union[str, None], Form()] = None,
|
|
57
|
+
client_secret: Annotated[Union[str, None], Form()] = None,
|
|
54
58
|
):
|
|
55
59
|
self.grant_type = grant_type
|
|
56
60
|
self.username = username
|
|
@@ -95,12 +99,12 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
|
|
95
99
|
|
|
96
100
|
def __init__(
|
|
97
101
|
self,
|
|
98
|
-
grant_type: str
|
|
99
|
-
username: str
|
|
100
|
-
password: str
|
|
101
|
-
scope: str
|
|
102
|
-
client_id:
|
|
103
|
-
client_secret:
|
|
102
|
+
grant_type: Annotated[str, Form(pattern="password")],
|
|
103
|
+
username: Annotated[str, Form()],
|
|
104
|
+
password: Annotated[str, Form()],
|
|
105
|
+
scope: Annotated[str, Form()] = "",
|
|
106
|
+
client_id: Annotated[Union[str, None], Form()] = None,
|
|
107
|
+
client_secret: Annotated[Union[str, None], Form()] = None,
|
|
104
108
|
):
|
|
105
109
|
super().__init__(
|
|
106
110
|
grant_type=grant_type,
|
|
@@ -121,7 +125,9 @@ class OAuth2(SecurityBase):
|
|
|
121
125
|
description: Optional[str] = None,
|
|
122
126
|
auto_error: bool = True,
|
|
123
127
|
):
|
|
124
|
-
self.model = OAuth2Model(
|
|
128
|
+
self.model = OAuth2Model(
|
|
129
|
+
flows=cast(OAuthFlowsModel, flows), description=description
|
|
130
|
+
)
|
|
125
131
|
self.scheme_name = scheme_name or self.__class__.__name__
|
|
126
132
|
self.auto_error = auto_error
|
|
127
133
|
|
|
@@ -148,7 +154,9 @@ class OAuth2PasswordBearer(OAuth2):
|
|
|
148
154
|
):
|
|
149
155
|
if not scopes:
|
|
150
156
|
scopes = {}
|
|
151
|
-
flows = OAuthFlowsModel(
|
|
157
|
+
flows = OAuthFlowsModel(
|
|
158
|
+
password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes})
|
|
159
|
+
)
|
|
152
160
|
super().__init__(
|
|
153
161
|
flows=flows,
|
|
154
162
|
scheme_name=scheme_name,
|
|
@@ -185,12 +193,15 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
|
|
185
193
|
if not scopes:
|
|
186
194
|
scopes = {}
|
|
187
195
|
flows = OAuthFlowsModel(
|
|
188
|
-
authorizationCode=
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
authorizationCode=cast(
|
|
197
|
+
Any,
|
|
198
|
+
{
|
|
199
|
+
"authorizationUrl": authorizationUrl,
|
|
200
|
+
"tokenUrl": tokenUrl,
|
|
201
|
+
"refreshUrl": refreshUrl,
|
|
202
|
+
"scopes": scopes,
|
|
203
|
+
},
|
|
204
|
+
)
|
|
194
205
|
)
|
|
195
206
|
super().__init__(
|
|
196
207
|
flows=flows,
|
fastapi/types.py
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import types
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Callable, Dict, Set, Type, TypeVar, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
2
6
|
|
|
3
7
|
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
|
|
8
|
+
UnionType = getattr(types, "UnionType", Union)
|
|
9
|
+
NoneType = getattr(types, "UnionType", None)
|
|
10
|
+
ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str]
|
|
11
|
+
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]]
|