fastapi 0.112.2__py3-none-any.whl → 0.112.3__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/dependencies/models.py +27 -48
- fastapi/dependencies/utils.py +74 -56
- fastapi/routing.py +24 -15
- fastapi/utils.py +3 -6
- {fastapi-0.112.2.dist-info → fastapi-0.112.3.dist-info}/METADATA +3 -4
- {fastapi-0.112.2.dist-info → fastapi-0.112.3.dist-info}/RECORD +10 -10
- {fastapi-0.112.2.dist-info → fastapi-0.112.3.dist-info}/WHEEL +0 -0
- {fastapi-0.112.2.dist-info → fastapi-0.112.3.dist-info}/entry_points.txt +0 -0
- {fastapi-0.112.2.dist-info → fastapi-0.112.3.dist-info}/licenses/LICENSE +0 -0
fastapi/__init__.py
CHANGED
fastapi/dependencies/models.py
CHANGED
|
@@ -1,58 +1,37 @@
|
|
|
1
|
-
from
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Callable, List, Optional, Sequence, Tuple
|
|
2
3
|
|
|
3
4
|
from fastapi._compat import ModelField
|
|
4
5
|
from fastapi.security.base import SecurityBase
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
@dataclass
|
|
7
9
|
class SecurityRequirement:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
):
|
|
11
|
-
self.security_scheme = security_scheme
|
|
12
|
-
self.scopes = scopes
|
|
10
|
+
security_scheme: SecurityBase
|
|
11
|
+
scopes: Optional[Sequence[str]] = None
|
|
13
12
|
|
|
14
13
|
|
|
14
|
+
@dataclass
|
|
15
15
|
class Dependant:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
) -> None:
|
|
38
|
-
self.path_params = path_params or []
|
|
39
|
-
self.query_params = query_params or []
|
|
40
|
-
self.header_params = header_params or []
|
|
41
|
-
self.cookie_params = cookie_params or []
|
|
42
|
-
self.body_params = body_params or []
|
|
43
|
-
self.dependencies = dependencies or []
|
|
44
|
-
self.security_requirements = security_schemes or []
|
|
45
|
-
self.request_param_name = request_param_name
|
|
46
|
-
self.websocket_param_name = websocket_param_name
|
|
47
|
-
self.http_connection_param_name = http_connection_param_name
|
|
48
|
-
self.response_param_name = response_param_name
|
|
49
|
-
self.background_tasks_param_name = background_tasks_param_name
|
|
50
|
-
self.security_scopes = security_scopes
|
|
51
|
-
self.security_scopes_param_name = security_scopes_param_name
|
|
52
|
-
self.name = name
|
|
53
|
-
self.call = call
|
|
54
|
-
self.use_cache = use_cache
|
|
55
|
-
# Store the path to be able to re-generate a dependable from it in overrides
|
|
56
|
-
self.path = path
|
|
57
|
-
# Save the cache key at creation to optimize performance
|
|
16
|
+
path_params: List[ModelField] = field(default_factory=list)
|
|
17
|
+
query_params: List[ModelField] = field(default_factory=list)
|
|
18
|
+
header_params: List[ModelField] = field(default_factory=list)
|
|
19
|
+
cookie_params: List[ModelField] = field(default_factory=list)
|
|
20
|
+
body_params: List[ModelField] = field(default_factory=list)
|
|
21
|
+
dependencies: List["Dependant"] = field(default_factory=list)
|
|
22
|
+
security_requirements: List[SecurityRequirement] = field(default_factory=list)
|
|
23
|
+
name: Optional[str] = None
|
|
24
|
+
call: Optional[Callable[..., Any]] = None
|
|
25
|
+
request_param_name: Optional[str] = None
|
|
26
|
+
websocket_param_name: Optional[str] = None
|
|
27
|
+
http_connection_param_name: Optional[str] = None
|
|
28
|
+
response_param_name: Optional[str] = None
|
|
29
|
+
background_tasks_param_name: Optional[str] = None
|
|
30
|
+
security_scopes_param_name: Optional[str] = None
|
|
31
|
+
security_scopes: Optional[List[str]] = None
|
|
32
|
+
use_cache: bool = True
|
|
33
|
+
path: Optional[str] = None
|
|
34
|
+
cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False)
|
|
35
|
+
|
|
36
|
+
def __post_init__(self) -> None:
|
|
58
37
|
self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or []))))
|
fastapi/dependencies/utils.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from contextlib import AsyncExitStack, contextmanager
|
|
3
3
|
from copy import copy, deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from typing import (
|
|
5
6
|
Any,
|
|
6
7
|
Callable,
|
|
@@ -54,7 +55,7 @@ from fastapi.logger import logger
|
|
|
54
55
|
from fastapi.security.base import SecurityBase
|
|
55
56
|
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
|
56
57
|
from fastapi.security.open_id_connect_url import OpenIdConnect
|
|
57
|
-
from fastapi.utils import
|
|
58
|
+
from fastapi.utils import create_model_field, get_path_param_names
|
|
58
59
|
from pydantic.fields import FieldInfo
|
|
59
60
|
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
|
|
60
61
|
from starlette.concurrency import run_in_threadpool
|
|
@@ -79,25 +80,23 @@ multipart_incorrect_install_error = (
|
|
|
79
80
|
)
|
|
80
81
|
|
|
81
82
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
def ensure_multipart_is_installed() -> None:
|
|
84
|
+
try:
|
|
85
|
+
# __version__ is available in both multiparts, and can be mocked
|
|
86
|
+
from multipart import __version__ # type: ignore
|
|
87
|
+
|
|
88
|
+
assert __version__
|
|
85
89
|
try:
|
|
86
|
-
#
|
|
87
|
-
from multipart import
|
|
88
|
-
|
|
89
|
-
assert
|
|
90
|
-
try:
|
|
91
|
-
# parse_options_header is only available in the right multipart
|
|
92
|
-
from multipart.multipart import parse_options_header # type: ignore
|
|
93
|
-
|
|
94
|
-
assert parse_options_header
|
|
95
|
-
except ImportError:
|
|
96
|
-
logger.error(multipart_incorrect_install_error)
|
|
97
|
-
raise RuntimeError(multipart_incorrect_install_error) from None
|
|
90
|
+
# parse_options_header is only available in the right multipart
|
|
91
|
+
from multipart.multipart import parse_options_header # type: ignore
|
|
92
|
+
|
|
93
|
+
assert parse_options_header
|
|
98
94
|
except ImportError:
|
|
99
|
-
logger.error(
|
|
100
|
-
raise RuntimeError(
|
|
95
|
+
logger.error(multipart_incorrect_install_error)
|
|
96
|
+
raise RuntimeError(multipart_incorrect_install_error) from None
|
|
97
|
+
except ImportError:
|
|
98
|
+
logger.error(multipart_not_installed_error)
|
|
99
|
+
raise RuntimeError(multipart_not_installed_error) from None
|
|
101
100
|
|
|
102
101
|
|
|
103
102
|
def get_param_sub_dependant(
|
|
@@ -175,7 +174,7 @@ def get_flat_dependant(
|
|
|
175
174
|
header_params=dependant.header_params.copy(),
|
|
176
175
|
cookie_params=dependant.cookie_params.copy(),
|
|
177
176
|
body_params=dependant.body_params.copy(),
|
|
178
|
-
|
|
177
|
+
security_requirements=dependant.security_requirements.copy(),
|
|
179
178
|
use_cache=dependant.use_cache,
|
|
180
179
|
path=dependant.path,
|
|
181
180
|
)
|
|
@@ -258,16 +257,16 @@ def get_dependant(
|
|
|
258
257
|
)
|
|
259
258
|
for param_name, param in signature_params.items():
|
|
260
259
|
is_path_param = param_name in path_param_names
|
|
261
|
-
|
|
260
|
+
param_details = analyze_param(
|
|
262
261
|
param_name=param_name,
|
|
263
262
|
annotation=param.annotation,
|
|
264
263
|
value=param.default,
|
|
265
264
|
is_path_param=is_path_param,
|
|
266
265
|
)
|
|
267
|
-
if depends is not None:
|
|
266
|
+
if param_details.depends is not None:
|
|
268
267
|
sub_dependant = get_param_sub_dependant(
|
|
269
268
|
param_name=param_name,
|
|
270
|
-
depends=depends,
|
|
269
|
+
depends=param_details.depends,
|
|
271
270
|
path=path,
|
|
272
271
|
security_scopes=security_scopes,
|
|
273
272
|
)
|
|
@@ -275,18 +274,18 @@ def get_dependant(
|
|
|
275
274
|
continue
|
|
276
275
|
if add_non_field_param_to_dependency(
|
|
277
276
|
param_name=param_name,
|
|
278
|
-
type_annotation=type_annotation,
|
|
277
|
+
type_annotation=param_details.type_annotation,
|
|
279
278
|
dependant=dependant,
|
|
280
279
|
):
|
|
281
280
|
assert (
|
|
282
|
-
|
|
281
|
+
param_details.field is None
|
|
283
282
|
), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
|
|
284
283
|
continue
|
|
285
|
-
assert
|
|
286
|
-
if is_body_param(param_field=
|
|
287
|
-
dependant.body_params.append(
|
|
284
|
+
assert param_details.field is not None
|
|
285
|
+
if is_body_param(param_field=param_details.field, is_path_param=is_path_param):
|
|
286
|
+
dependant.body_params.append(param_details.field)
|
|
288
287
|
else:
|
|
289
|
-
add_param_to_fields(field=
|
|
288
|
+
add_param_to_fields(field=param_details.field, dependant=dependant)
|
|
290
289
|
return dependant
|
|
291
290
|
|
|
292
291
|
|
|
@@ -314,13 +313,20 @@ def add_non_field_param_to_dependency(
|
|
|
314
313
|
return None
|
|
315
314
|
|
|
316
315
|
|
|
316
|
+
@dataclass
|
|
317
|
+
class ParamDetails:
|
|
318
|
+
type_annotation: Any
|
|
319
|
+
depends: Optional[params.Depends]
|
|
320
|
+
field: Optional[ModelField]
|
|
321
|
+
|
|
322
|
+
|
|
317
323
|
def analyze_param(
|
|
318
324
|
*,
|
|
319
325
|
param_name: str,
|
|
320
326
|
annotation: Any,
|
|
321
327
|
value: Any,
|
|
322
328
|
is_path_param: bool,
|
|
323
|
-
) ->
|
|
329
|
+
) -> ParamDetails:
|
|
324
330
|
field_info = None
|
|
325
331
|
depends = None
|
|
326
332
|
type_annotation: Any = Any
|
|
@@ -328,6 +334,7 @@ def analyze_param(
|
|
|
328
334
|
if annotation is not inspect.Signature.empty:
|
|
329
335
|
use_annotation = annotation
|
|
330
336
|
type_annotation = annotation
|
|
337
|
+
# Extract Annotated info
|
|
331
338
|
if get_origin(use_annotation) is Annotated:
|
|
332
339
|
annotated_args = get_args(annotation)
|
|
333
340
|
type_annotation = annotated_args[0]
|
|
@@ -347,6 +354,7 @@ def analyze_param(
|
|
|
347
354
|
)
|
|
348
355
|
else:
|
|
349
356
|
fastapi_annotation = None
|
|
357
|
+
# Set default for Annotated FieldInfo
|
|
350
358
|
if isinstance(fastapi_annotation, FieldInfo):
|
|
351
359
|
# Copy `field_info` because we mutate `field_info.default` below.
|
|
352
360
|
field_info = copy_field_info(
|
|
@@ -361,9 +369,10 @@ def analyze_param(
|
|
|
361
369
|
field_info.default = value
|
|
362
370
|
else:
|
|
363
371
|
field_info.default = Required
|
|
372
|
+
# Get Annotated Depends
|
|
364
373
|
elif isinstance(fastapi_annotation, params.Depends):
|
|
365
374
|
depends = fastapi_annotation
|
|
366
|
-
|
|
375
|
+
# Get Depends from default value
|
|
367
376
|
if isinstance(value, params.Depends):
|
|
368
377
|
assert depends is None, (
|
|
369
378
|
"Cannot specify `Depends` in `Annotated` and default value"
|
|
@@ -374,6 +383,7 @@ def analyze_param(
|
|
|
374
383
|
f" default value together for {param_name!r}"
|
|
375
384
|
)
|
|
376
385
|
depends = value
|
|
386
|
+
# Get FieldInfo from default value
|
|
377
387
|
elif isinstance(value, FieldInfo):
|
|
378
388
|
assert field_info is None, (
|
|
379
389
|
"Cannot specify FastAPI annotations in `Annotated` and default value"
|
|
@@ -383,11 +393,13 @@ def analyze_param(
|
|
|
383
393
|
if PYDANTIC_V2:
|
|
384
394
|
field_info.annotation = type_annotation
|
|
385
395
|
|
|
396
|
+
# Get Depends from type annotation
|
|
386
397
|
if depends is not None and depends.dependency is None:
|
|
387
398
|
# Copy `depends` before mutating it
|
|
388
399
|
depends = copy(depends)
|
|
389
400
|
depends.dependency = type_annotation
|
|
390
401
|
|
|
402
|
+
# Handle non-param type annotations like Request
|
|
391
403
|
if lenient_issubclass(
|
|
392
404
|
type_annotation,
|
|
393
405
|
(
|
|
@@ -403,6 +415,7 @@ def analyze_param(
|
|
|
403
415
|
assert (
|
|
404
416
|
field_info is None
|
|
405
417
|
), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
|
|
418
|
+
# Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
|
|
406
419
|
elif field_info is None and depends is None:
|
|
407
420
|
default_value = value if value is not inspect.Signature.empty else Required
|
|
408
421
|
if is_path_param:
|
|
@@ -420,7 +433,9 @@ def analyze_param(
|
|
|
420
433
|
field_info = params.Query(annotation=use_annotation, default=default_value)
|
|
421
434
|
|
|
422
435
|
field = None
|
|
436
|
+
# It's a field_info, not a dependency
|
|
423
437
|
if field_info is not None:
|
|
438
|
+
# Handle field_info.in_
|
|
424
439
|
if is_path_param:
|
|
425
440
|
assert isinstance(field_info, params.Path), (
|
|
426
441
|
f"Cannot use `{field_info.__class__.__name__}` for path param"
|
|
@@ -436,12 +451,14 @@ def analyze_param(
|
|
|
436
451
|
field_info,
|
|
437
452
|
param_name,
|
|
438
453
|
)
|
|
454
|
+
if isinstance(field_info, params.Form):
|
|
455
|
+
ensure_multipart_is_installed()
|
|
439
456
|
if not field_info.alias and getattr(field_info, "convert_underscores", None):
|
|
440
457
|
alias = param_name.replace("_", "-")
|
|
441
458
|
else:
|
|
442
459
|
alias = field_info.alias or param_name
|
|
443
460
|
field_info.alias = alias
|
|
444
|
-
field =
|
|
461
|
+
field = create_model_field(
|
|
445
462
|
name=param_name,
|
|
446
463
|
type_=use_annotation_from_field_info,
|
|
447
464
|
default=field_info.default,
|
|
@@ -450,7 +467,7 @@ def analyze_param(
|
|
|
450
467
|
field_info=field_info,
|
|
451
468
|
)
|
|
452
469
|
|
|
453
|
-
return type_annotation, depends, field
|
|
470
|
+
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
|
|
454
471
|
|
|
455
472
|
|
|
456
473
|
def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
|
|
@@ -521,6 +538,15 @@ async def solve_generator(
|
|
|
521
538
|
return await stack.enter_async_context(cm)
|
|
522
539
|
|
|
523
540
|
|
|
541
|
+
@dataclass
|
|
542
|
+
class SolvedDependency:
|
|
543
|
+
values: Dict[str, Any]
|
|
544
|
+
errors: List[Any]
|
|
545
|
+
background_tasks: Optional[StarletteBackgroundTasks]
|
|
546
|
+
response: Response
|
|
547
|
+
dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any]
|
|
548
|
+
|
|
549
|
+
|
|
524
550
|
async def solve_dependencies(
|
|
525
551
|
*,
|
|
526
552
|
request: Union[Request, WebSocket],
|
|
@@ -531,13 +557,7 @@ async def solve_dependencies(
|
|
|
531
557
|
dependency_overrides_provider: Optional[Any] = None,
|
|
532
558
|
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
|
|
533
559
|
async_exit_stack: AsyncExitStack,
|
|
534
|
-
) ->
|
|
535
|
-
Dict[str, Any],
|
|
536
|
-
List[Any],
|
|
537
|
-
Optional[StarletteBackgroundTasks],
|
|
538
|
-
Response,
|
|
539
|
-
Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
|
|
540
|
-
]:
|
|
560
|
+
) -> SolvedDependency:
|
|
541
561
|
values: Dict[str, Any] = {}
|
|
542
562
|
errors: List[Any] = []
|
|
543
563
|
if response is None:
|
|
@@ -579,27 +599,21 @@ async def solve_dependencies(
|
|
|
579
599
|
dependency_cache=dependency_cache,
|
|
580
600
|
async_exit_stack=async_exit_stack,
|
|
581
601
|
)
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
_, # the subdependency returns the same response we have
|
|
587
|
-
sub_dependency_cache,
|
|
588
|
-
) = solved_result
|
|
589
|
-
dependency_cache.update(sub_dependency_cache)
|
|
590
|
-
if sub_errors:
|
|
591
|
-
errors.extend(sub_errors)
|
|
602
|
+
background_tasks = solved_result.background_tasks
|
|
603
|
+
dependency_cache.update(solved_result.dependency_cache)
|
|
604
|
+
if solved_result.errors:
|
|
605
|
+
errors.extend(solved_result.errors)
|
|
592
606
|
continue
|
|
593
607
|
if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
|
|
594
608
|
solved = dependency_cache[sub_dependant.cache_key]
|
|
595
609
|
elif is_gen_callable(call) or is_async_gen_callable(call):
|
|
596
610
|
solved = await solve_generator(
|
|
597
|
-
call=call, stack=async_exit_stack, sub_values=
|
|
611
|
+
call=call, stack=async_exit_stack, sub_values=solved_result.values
|
|
598
612
|
)
|
|
599
613
|
elif is_coroutine_callable(call):
|
|
600
|
-
solved = await call(**
|
|
614
|
+
solved = await call(**solved_result.values)
|
|
601
615
|
else:
|
|
602
|
-
solved = await run_in_threadpool(call, **
|
|
616
|
+
solved = await run_in_threadpool(call, **solved_result.values)
|
|
603
617
|
if sub_dependant.name is not None:
|
|
604
618
|
values[sub_dependant.name] = solved
|
|
605
619
|
if sub_dependant.cache_key not in dependency_cache:
|
|
@@ -646,7 +660,13 @@ async def solve_dependencies(
|
|
|
646
660
|
values[dependant.security_scopes_param_name] = SecurityScopes(
|
|
647
661
|
scopes=dependant.security_scopes
|
|
648
662
|
)
|
|
649
|
-
return
|
|
663
|
+
return SolvedDependency(
|
|
664
|
+
values=values,
|
|
665
|
+
errors=errors,
|
|
666
|
+
background_tasks=background_tasks,
|
|
667
|
+
response=response,
|
|
668
|
+
dependency_cache=dependency_cache,
|
|
669
|
+
)
|
|
650
670
|
|
|
651
671
|
|
|
652
672
|
def request_params_to_args(
|
|
@@ -775,7 +795,6 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
|
|
775
795
|
embed = getattr(field_info, "embed", None)
|
|
776
796
|
body_param_names_set = {param.name for param in flat_dependant.body_params}
|
|
777
797
|
if len(body_param_names_set) == 1 and not embed:
|
|
778
|
-
check_file_field(first_param)
|
|
779
798
|
return first_param
|
|
780
799
|
# If one field requires to embed, all have to be embedded
|
|
781
800
|
# in case a sub-dependency is evaluated with a single unique body field
|
|
@@ -807,12 +826,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
|
|
807
826
|
]
|
|
808
827
|
if len(set(body_param_media_types)) == 1:
|
|
809
828
|
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
|
|
810
|
-
final_field =
|
|
829
|
+
final_field = create_model_field(
|
|
811
830
|
name="body",
|
|
812
831
|
type_=BodyModel,
|
|
813
832
|
required=required,
|
|
814
833
|
alias="body",
|
|
815
834
|
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
|
816
835
|
)
|
|
817
|
-
check_file_field(final_field)
|
|
818
836
|
return final_field
|
fastapi/routing.py
CHANGED
|
@@ -49,7 +49,7 @@ from fastapi.exceptions import (
|
|
|
49
49
|
from fastapi.types import DecoratedCallable, IncEx
|
|
50
50
|
from fastapi.utils import (
|
|
51
51
|
create_cloned_field,
|
|
52
|
-
|
|
52
|
+
create_model_field,
|
|
53
53
|
generate_unique_id,
|
|
54
54
|
get_value_or_default,
|
|
55
55
|
is_body_allowed_for_status_code,
|
|
@@ -292,26 +292,34 @@ def get_request_handler(
|
|
|
292
292
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
293
293
|
async_exit_stack=async_exit_stack,
|
|
294
294
|
)
|
|
295
|
-
|
|
295
|
+
errors = solved_result.errors
|
|
296
296
|
if not errors:
|
|
297
297
|
raw_response = await run_endpoint_function(
|
|
298
|
-
dependant=dependant,
|
|
298
|
+
dependant=dependant,
|
|
299
|
+
values=solved_result.values,
|
|
300
|
+
is_coroutine=is_coroutine,
|
|
299
301
|
)
|
|
300
302
|
if isinstance(raw_response, Response):
|
|
301
303
|
if raw_response.background is None:
|
|
302
|
-
raw_response.background = background_tasks
|
|
304
|
+
raw_response.background = solved_result.background_tasks
|
|
303
305
|
response = raw_response
|
|
304
306
|
else:
|
|
305
|
-
response_args: Dict[str, Any] = {
|
|
307
|
+
response_args: Dict[str, Any] = {
|
|
308
|
+
"background": solved_result.background_tasks
|
|
309
|
+
}
|
|
306
310
|
# If status_code was set, use it, otherwise use the default from the
|
|
307
311
|
# response class, in the case of redirect it's 307
|
|
308
312
|
current_status_code = (
|
|
309
|
-
status_code
|
|
313
|
+
status_code
|
|
314
|
+
if status_code
|
|
315
|
+
else solved_result.response.status_code
|
|
310
316
|
)
|
|
311
317
|
if current_status_code is not None:
|
|
312
318
|
response_args["status_code"] = current_status_code
|
|
313
|
-
if
|
|
314
|
-
response_args["status_code"] =
|
|
319
|
+
if solved_result.response.status_code:
|
|
320
|
+
response_args["status_code"] = (
|
|
321
|
+
solved_result.response.status_code
|
|
322
|
+
)
|
|
315
323
|
content = await serialize_response(
|
|
316
324
|
field=response_field,
|
|
317
325
|
response_content=raw_response,
|
|
@@ -326,7 +334,7 @@ def get_request_handler(
|
|
|
326
334
|
response = actual_response_class(content, **response_args)
|
|
327
335
|
if not is_body_allowed_for_status_code(response.status_code):
|
|
328
336
|
response.body = b""
|
|
329
|
-
response.headers.raw.extend(
|
|
337
|
+
response.headers.raw.extend(solved_result.response.headers.raw)
|
|
330
338
|
if errors:
|
|
331
339
|
validation_error = RequestValidationError(
|
|
332
340
|
_normalize_errors(errors), body=body
|
|
@@ -360,11 +368,12 @@ def get_websocket_app(
|
|
|
360
368
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
361
369
|
async_exit_stack=async_exit_stack,
|
|
362
370
|
)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
371
|
+
if solved_result.errors:
|
|
372
|
+
raise WebSocketRequestValidationError(
|
|
373
|
+
_normalize_errors(solved_result.errors)
|
|
374
|
+
)
|
|
366
375
|
assert dependant.call is not None, "dependant.call must be a function"
|
|
367
|
-
await dependant.call(**values)
|
|
376
|
+
await dependant.call(**solved_result.values)
|
|
368
377
|
|
|
369
378
|
return app
|
|
370
379
|
|
|
@@ -488,7 +497,7 @@ class APIRoute(routing.Route):
|
|
|
488
497
|
status_code
|
|
489
498
|
), f"Status code {status_code} must not have a response body"
|
|
490
499
|
response_name = "Response_" + self.unique_id
|
|
491
|
-
self.response_field =
|
|
500
|
+
self.response_field = create_model_field(
|
|
492
501
|
name=response_name,
|
|
493
502
|
type_=self.response_model,
|
|
494
503
|
mode="serialization",
|
|
@@ -521,7 +530,7 @@ class APIRoute(routing.Route):
|
|
|
521
530
|
additional_status_code
|
|
522
531
|
), f"Status code {additional_status_code} must not have a response body"
|
|
523
532
|
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
|
524
|
-
response_field =
|
|
533
|
+
response_field = create_model_field(name=response_name, type_=model)
|
|
525
534
|
response_fields[additional_status_code] = response_field
|
|
526
535
|
if response_fields:
|
|
527
536
|
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
|
fastapi/utils.py
CHANGED
|
@@ -60,9 +60,9 @@ def get_path_param_names(path: str) -> Set[str]:
|
|
|
60
60
|
return set(re.findall("{(.*?)}", path))
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def
|
|
63
|
+
def create_model_field(
|
|
64
64
|
name: str,
|
|
65
|
-
type_:
|
|
65
|
+
type_: Any,
|
|
66
66
|
class_validators: Optional[Dict[str, Validator]] = None,
|
|
67
67
|
default: Optional[Any] = Undefined,
|
|
68
68
|
required: Union[bool, UndefinedType] = Undefined,
|
|
@@ -71,9 +71,6 @@ def create_response_field(
|
|
|
71
71
|
alias: Optional[str] = None,
|
|
72
72
|
mode: Literal["validation", "serialization"] = "validation",
|
|
73
73
|
) -> ModelField:
|
|
74
|
-
"""
|
|
75
|
-
Create a new response field. Raises if type_ is invalid.
|
|
76
|
-
"""
|
|
77
74
|
class_validators = class_validators or {}
|
|
78
75
|
if PYDANTIC_V2:
|
|
79
76
|
field_info = field_info or FieldInfo(
|
|
@@ -135,7 +132,7 @@ def create_cloned_field(
|
|
|
135
132
|
use_type.__fields__[f.name] = create_cloned_field(
|
|
136
133
|
f, cloned_types=cloned_types
|
|
137
134
|
)
|
|
138
|
-
new_field =
|
|
135
|
+
new_field = create_model_field(name=field.name, type_=use_type)
|
|
139
136
|
new_field.has_alias = field.has_alias # type: ignore[attr-defined]
|
|
140
137
|
new_field.alias = field.alias # type: ignore[misc]
|
|
141
138
|
new_field.class_validators = field.class_validators # type: ignore[attr-defined]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi
|
|
3
|
-
Version: 0.112.
|
|
3
|
+
Version: 0.112.3
|
|
4
4
|
Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
|
|
5
5
|
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
|
6
6
|
Classifier: Intended Audience :: Information Technology
|
|
@@ -115,9 +115,8 @@ The key features are:
|
|
|
115
115
|
<a href="https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor" target="_blank" title="Automate FastAPI documentation generation with Bump.sh"><img src="https://fastapi.tiangolo.com/img/sponsors/bump-sh.svg"></a>
|
|
116
116
|
<a href="https://github.com/scalar/scalar/?utm_source=fastapi&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Scalar: Beautiful Open-Source API References from Swagger/OpenAPI files"><img src="https://fastapi.tiangolo.com/img/sponsors/scalar.svg"></a>
|
|
117
117
|
<a href="https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge" target="_blank" title="Auth, user management and more for your B2B product"><img src="https://fastapi.tiangolo.com/img/sponsors/propelauth.png"></a>
|
|
118
|
-
<a href="https://docs.withcoherence.com/
|
|
118
|
+
<a href="https://docs.withcoherence.com/coherence-templates/full-stack-template/#fastapi?utm_medium=advertising&utm_source=fastapi&utm_campaign=docs" target="_blank" title="Coherence"><img src="https://fastapi.tiangolo.com/img/sponsors/coherence.png"></a>
|
|
119
119
|
<a href="https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/?utm_campaign=fastapi_framework&utm_source=fastapi_sponsorship&utm_medium=web_referral" target="_blank" title="Simplify Full Stack Development with FastAPI & MongoDB"><img src="https://fastapi.tiangolo.com/img/sponsors/mongodb.png"></a>
|
|
120
|
-
<a href="https://konghq.com/products/kong-konnect?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=fast-api" target="_blank" title="Kong Konnect - API management platform"><img src="https://fastapi.tiangolo.com/img/sponsors/kong.png"></a>
|
|
121
120
|
<a href="https://zuplo.link/fastapi-gh" target="_blank" title="Zuplo: Scale, Protect, Document, and Monetize your FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/zuplo.png"></a>
|
|
122
121
|
<a href="https://fine.dev?ref=fastapibadge" target="_blank" title="Fine's AI FastAPI Workflow: Effortlessly Deploy and Integrate FastAPI into Your Project"><img src="https://fastapi.tiangolo.com/img/sponsors/fine.png"></a>
|
|
123
122
|
<a href="https://liblab.com?utm_source=fastapi" target="_blank" title="liblab - Generate SDKs from FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/liblab.png"></a>
|
|
@@ -457,7 +456,7 @@ Coming back to the previous code example, **FastAPI** will:
|
|
|
457
456
|
* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests.
|
|
458
457
|
* As the `q` parameter is declared with `= None`, it is optional.
|
|
459
458
|
* Without the `None` it would be required (as is the body in the case with `PUT`).
|
|
460
|
-
* For `PUT` requests to `/items/{item_id}`,
|
|
459
|
+
* For `PUT` requests to `/items/{item_id}`, read the body as JSON:
|
|
461
460
|
* Check that it has a required attribute `name` that should be a `str`.
|
|
462
461
|
* Check that it has a required attribute `price` that has to be a `float`.
|
|
463
462
|
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
fastapi-0.112.
|
|
2
|
-
fastapi-0.112.
|
|
3
|
-
fastapi-0.112.
|
|
4
|
-
fastapi-0.112.
|
|
5
|
-
fastapi/__init__.py,sha256=
|
|
1
|
+
fastapi-0.112.3.dist-info/METADATA,sha256=w1QfelBarqYSCtz6ly70TBP_VdihTpwEujW_K2sRg9E,27273
|
|
2
|
+
fastapi-0.112.3.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
|
3
|
+
fastapi-0.112.3.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
|
|
4
|
+
fastapi-0.112.3.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
|
|
5
|
+
fastapi/__init__.py,sha256=o7tP2gKcsHMiiJNCKrN-GA3wqWjGR54B379fYdZ3_TA,1081
|
|
6
6
|
fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
|
|
7
7
|
fastapi/_compat.py,sha256=OjE3FUZ0IPXqIJWKhoWKDNCHv4so-FQ-rfN8ngQZeFE,23134
|
|
8
8
|
fastapi/applications.py,sha256=Ix-o9pQAWhEDf9J0Q1hZ0nBB1uP72c-Y3oiYzvrwqiM,176316
|
|
@@ -11,8 +11,8 @@ fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418
|
|
|
11
11
|
fastapi/concurrency.py,sha256=AYLnS4judDUmXsNRICtoKSP0prfYDcS8ehBtYW9JhQQ,1403
|
|
12
12
|
fastapi/datastructures.py,sha256=b2PEz77XGq-u3Ur1Inwk0AGjOsQZO49yF9C7IPJ15cY,5766
|
|
13
13
|
fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
fastapi/dependencies/models.py,sha256
|
|
15
|
-
fastapi/dependencies/utils.py,sha256=
|
|
14
|
+
fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507
|
|
15
|
+
fastapi/dependencies/utils.py,sha256=2J36LBZMJTZ6jadjANaG-voTfzgs8JpROJCRtoLoSFA,30922
|
|
16
16
|
fastapi/encoders.py,sha256=LvwYmFeOz4tVwvgBoC5rvZnbr7hZr73KGrU8O7zSptU,11068
|
|
17
17
|
fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332
|
|
18
18
|
fastapi/exceptions.py,sha256=taNixuFEXb67lI1bnX1ubq8y8TseJ4yoPlWjyP0fTzk,4969
|
|
@@ -33,7 +33,7 @@ fastapi/params.py,sha256=CWumi-CkfxWSg4I2KpPxIvQyKdeW_Lnct9reHnrTwW8,28199
|
|
|
33
33
|
fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142
|
|
35
35
|
fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761
|
|
36
|
-
fastapi/routing.py,sha256=
|
|
36
|
+
fastapi/routing.py,sha256=e_H0k8q4HQl88mgi-Jdf-9hBfn2gTvDjixXrufDN3U0,175326
|
|
37
37
|
fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881
|
|
38
38
|
fastapi/security/api_key.py,sha256=_OqUUjEHG5_MT1IPAhXIGJRCPldTBdSww_DegFy_W8Y,9368
|
|
39
39
|
fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141
|
|
@@ -45,6 +45,6 @@ fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69
|
|
|
45
45
|
fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76
|
|
46
46
|
fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
|
|
47
47
|
fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383
|
|
48
|
-
fastapi/utils.py,sha256=
|
|
48
|
+
fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948
|
|
49
49
|
fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
|
|
50
|
-
fastapi-0.112.
|
|
50
|
+
fastapi-0.112.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|