Skip to content

Commit 9f7c3a4

Browse files
authored
Merge branch 'fastapi:master' into master
2 parents 09d2f0e + b203d7a commit 9f7c3a4

4 files changed

Lines changed: 38 additions & 32 deletions

File tree

docs/en/docs/release-notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ hide:
99

1010
### Refactors
1111

12+
* ♻️ Refactor internal `check_file_field()`, rename to `ensure_multipart_is_installed()` to clarify its purpose. PR [#12106](https://github.com/fastapi/fastapi/pull/12106) by [@tiangolo](https://github.com/tiangolo).
13+
* ♻️ Rename internal `create_response_field()` to `create_model_field()` as it's used for more than response models. PR [#12103](https://github.com/fastapi/fastapi/pull/12103) by [@tiangolo](https://github.com/tiangolo).
1214
* ♻️ Refactor and simplify internal data from `solve_dependencies()` using dataclasses. PR [#12100](https://github.com/fastapi/fastapi/pull/12100) by [@tiangolo](https://github.com/tiangolo).
1315
* ♻️ Refactor and simplify internal `analyze_param()` to structure data with dataclasses instead of tuple. PR [#12099](https://github.com/fastapi/fastapi/pull/12099) by [@tiangolo](https://github.com/tiangolo).
1416
* ♻️ Refactor and simplify dependencies data structures with dataclasses. PR [#12098](https://github.com/fastapi/fastapi/pull/12098) by [@tiangolo](https://github.com/tiangolo).

fastapi/dependencies/utils.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from fastapi.security.base import SecurityBase
5656
from fastapi.security.oauth2 import OAuth2, SecurityScopes
5757
from fastapi.security.open_id_connect_url import OpenIdConnect
58-
from fastapi.utils import create_response_field, get_path_param_names
58+
from fastapi.utils import create_model_field, get_path_param_names
5959
from pydantic.fields import FieldInfo
6060
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
6161
from starlette.concurrency import run_in_threadpool
@@ -80,25 +80,23 @@
8080
)
8181

8282

83-
def check_file_field(field: ModelField) -> None:
84-
field_info = field.field_info
85-
if isinstance(field_info, params.Form):
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__
8689
try:
87-
# __version__ is available in both multiparts, and can be mocked
88-
from multipart import __version__ # type: ignore
89-
90-
assert __version__
91-
try:
92-
# parse_options_header is only available in the right multipart
93-
from multipart.multipart import parse_options_header # type: ignore
94-
95-
assert parse_options_header
96-
except ImportError:
97-
logger.error(multipart_incorrect_install_error)
98-
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
9994
except ImportError:
100-
logger.error(multipart_not_installed_error)
101-
raise RuntimeError(multipart_not_installed_error) from None
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
102100

103101

104102
def get_param_sub_dependant(
@@ -336,6 +334,7 @@ def analyze_param(
336334
if annotation is not inspect.Signature.empty:
337335
use_annotation = annotation
338336
type_annotation = annotation
337+
# Extract Annotated info
339338
if get_origin(use_annotation) is Annotated:
340339
annotated_args = get_args(annotation)
341340
type_annotation = annotated_args[0]
@@ -355,6 +354,7 @@ def analyze_param(
355354
)
356355
else:
357356
fastapi_annotation = None
357+
# Set default for Annotated FieldInfo
358358
if isinstance(fastapi_annotation, FieldInfo):
359359
# Copy `field_info` because we mutate `field_info.default` below.
360360
field_info = copy_field_info(
@@ -369,9 +369,10 @@ def analyze_param(
369369
field_info.default = value
370370
else:
371371
field_info.default = Required
372+
# Get Annotated Depends
372373
elif isinstance(fastapi_annotation, params.Depends):
373374
depends = fastapi_annotation
374-
375+
# Get Depends from default value
375376
if isinstance(value, params.Depends):
376377
assert depends is None, (
377378
"Cannot specify `Depends` in `Annotated` and default value"
@@ -382,6 +383,7 @@ def analyze_param(
382383
f" default value together for {param_name!r}"
383384
)
384385
depends = value
386+
# Get FieldInfo from default value
385387
elif isinstance(value, FieldInfo):
386388
assert field_info is None, (
387389
"Cannot specify FastAPI annotations in `Annotated` and default value"
@@ -391,11 +393,13 @@ def analyze_param(
391393
if PYDANTIC_V2:
392394
field_info.annotation = type_annotation
393395

396+
# Get Depends from type annotation
394397
if depends is not None and depends.dependency is None:
395398
# Copy `depends` before mutating it
396399
depends = copy(depends)
397400
depends.dependency = type_annotation
398401

402+
# Handle non-param type annotations like Request
399403
if lenient_issubclass(
400404
type_annotation,
401405
(
@@ -411,6 +415,7 @@ def analyze_param(
411415
assert (
412416
field_info is None
413417
), 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
414419
elif field_info is None and depends is None:
415420
default_value = value if value is not inspect.Signature.empty else Required
416421
if is_path_param:
@@ -428,7 +433,9 @@ def analyze_param(
428433
field_info = params.Query(annotation=use_annotation, default=default_value)
429434

430435
field = None
436+
# It's a field_info, not a dependency
431437
if field_info is not None:
438+
# Handle field_info.in_
432439
if is_path_param:
433440
assert isinstance(field_info, params.Path), (
434441
f"Cannot use `{field_info.__class__.__name__}` for path param"
@@ -444,12 +451,14 @@ def analyze_param(
444451
field_info,
445452
param_name,
446453
)
454+
if isinstance(field_info, params.Form):
455+
ensure_multipart_is_installed()
447456
if not field_info.alias and getattr(field_info, "convert_underscores", None):
448457
alias = param_name.replace("_", "-")
449458
else:
450459
alias = field_info.alias or param_name
451460
field_info.alias = alias
452-
field = create_response_field(
461+
field = create_model_field(
453462
name=param_name,
454463
type_=use_annotation_from_field_info,
455464
default=field_info.default,
@@ -786,7 +795,6 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
786795
embed = getattr(field_info, "embed", None)
787796
body_param_names_set = {param.name for param in flat_dependant.body_params}
788797
if len(body_param_names_set) == 1 and not embed:
789-
check_file_field(first_param)
790798
return first_param
791799
# If one field requires to embed, all have to be embedded
792800
# in case a sub-dependency is evaluated with a single unique body field
@@ -818,12 +826,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
818826
]
819827
if len(set(body_param_media_types)) == 1:
820828
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
821-
final_field = create_response_field(
829+
final_field = create_model_field(
822830
name="body",
823831
type_=BodyModel,
824832
required=required,
825833
alias="body",
826834
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
827835
)
828-
check_file_field(final_field)
829836
return final_field

fastapi/routing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from fastapi.types import DecoratedCallable, IncEx
5050
from fastapi.utils import (
5151
create_cloned_field,
52-
create_response_field,
52+
create_model_field,
5353
generate_unique_id,
5454
get_value_or_default,
5555
is_body_allowed_for_status_code,
@@ -497,7 +497,7 @@ def __init__(
497497
status_code
498498
), f"Status code {status_code} must not have a response body"
499499
response_name = "Response_" + self.unique_id
500-
self.response_field = create_response_field(
500+
self.response_field = create_model_field(
501501
name=response_name,
502502
type_=self.response_model,
503503
mode="serialization",
@@ -530,7 +530,7 @@ def __init__(
530530
additional_status_code
531531
), f"Status code {additional_status_code} must not have a response body"
532532
response_name = f"Response_{additional_status_code}_{self.unique_id}"
533-
response_field = create_response_field(name=response_name, type_=model)
533+
response_field = create_model_field(name=response_name, type_=model)
534534
response_fields[additional_status_code] = response_field
535535
if response_fields:
536536
self.response_fields: Dict[Union[int, str], ModelField] = response_fields

fastapi/utils.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ def get_path_param_names(path: str) -> Set[str]:
6060
return set(re.findall("{(.*?)}", path))
6161

6262

63-
def create_response_field(
63+
def create_model_field(
6464
name: str,
65-
type_: Type[Any],
65+
type_: Any,
6666
class_validators: Optional[Dict[str, Validator]] = None,
6767
default: Optional[Any] = Undefined,
6868
required: Union[bool, UndefinedType] = Undefined,
@@ -71,9 +71,6 @@ def create_response_field(
7171
alias: Optional[str] = None,
7272
mode: Literal["validation", "serialization"] = "validation",
7373
) -> ModelField:
74-
"""
75-
Create a new response field. Raises if type_ is invalid.
76-
"""
7774
class_validators = class_validators or {}
7875
if PYDANTIC_V2:
7976
field_info = field_info or FieldInfo(
@@ -135,7 +132,7 @@ def create_cloned_field(
135132
use_type.__fields__[f.name] = create_cloned_field(
136133
f, cloned_types=cloned_types
137134
)
138-
new_field = create_response_field(name=field.name, type_=use_type)
135+
new_field = create_model_field(name=field.name, type_=use_type)
139136
new_field.has_alias = field.has_alias # type: ignore[attr-defined]
140137
new_field.alias = field.alias # type: ignore[misc]
141138
new_field.class_validators = field.class_validators # type: ignore[attr-defined]

0 commit comments

Comments
 (0)