Browse Source

🎨 Auto format

pull/15584/head
pre-commit-ci-lite[bot] 4 weeks ago
committed by GitHub
parent
commit
30ef0e833a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 70
      fastapi/_compat/v2.py
  2. 26
      fastapi/dependencies/utils.py
  3. 2
      fastapi/encoders.py
  4. 9
      fastapi/routing.py

70
fastapi/_compat/v2.py

@ -516,43 +516,43 @@ def _regenerate_error_with_loc(
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
updated_loc_errors: list[Any] = [] updated_loc_errors: list[Any] = []
for err in errors: for err in errors:
if decoded_value is not Undefined: if decoded_value is not Undefined:
new_err = {**err, "loc": loc_prefix + err.get("loc", ())} new_err = {**err, "loc": loc_prefix + err.get("loc", ())}
# If we are validating a Body with multiple fields, Pydantic's # If we are validating a Body with multiple fields, Pydantic's
# "input" might be just the value of one field. # "input" might be just the value of one field.
# But when we decode the whole body, we might need to drill down. # But when we decode the whole body, we might need to drill down.
# However, for tutorials, usually "input" matches the decoded value # However, for tutorials, usually "input" matches the decoded value
# if the error is at the top level of the body. # if the error is at the top level of the body.
# Let's try to match Pydantic's behavior more closely but with # Let's try to match Pydantic's behavior more closely but with
# decoded values. # decoded values.
curr_input = decoded_value curr_input = decoded_value
# If the error is inside the body, try to find the specific input # If the error is inside the body, try to find the specific input
# that caused it, based on the relative location from the body root. # that caused it, based on the relative location from the body root.
# loc_prefix is usually ('body',) # loc_prefix is usually ('body',)
rel_loc = err.get("loc", ()) rel_loc = err.get("loc", ())
for path_item in rel_loc: for path_item in rel_loc:
if path_item == "[key]": if path_item == "[key]":
# For dict key errors, Pydantic includes "[key]" in the loc. # For dict key errors, Pydantic includes "[key]" in the loc.
# The "input" should be the key itself, which was the previous # The "input" should be the key itself, which was the previous
# path_item. # path_item.
break break
try: try:
if isinstance(curr_input, (dict, list)): if isinstance(curr_input, (dict, list)):
curr_input = curr_input[path_item] # type: ignore[index] curr_input = curr_input[path_item] # type: ignore[index]
else: else:
break
except (KeyError, IndexError, TypeError):
break break
except (KeyError, IndexError, TypeError):
break
# If it's a key error, the input is the key which is the last path item before "[key]" # If it's a key error, the input is the key which is the last path item before "[key]"
if rel_loc and rel_loc[-1] == "[key]": if rel_loc and rel_loc[-1] == "[key]":
new_err["input"] = rel_loc[-2] new_err["input"] = rel_loc[-2]
else:
new_err["input"] = curr_input
if new_err.get("msg") == "Input should be a valid array":
new_err["msg"] = "Input should be a valid list"
else: else:
new_err = {**err, "loc": loc_prefix + err.get("loc", ())} new_err["input"] = curr_input
updated_loc_errors.append(new_err) if new_err.get("msg") == "Input should be a valid array":
new_err["msg"] = "Input should be a valid list"
else:
new_err = {**err, "loc": loc_prefix + err.get("loc", ())}
updated_loc_errors.append(new_err)
return updated_loc_errors return updated_loc_errors

26
fastapi/dependencies/utils.py

@ -15,6 +15,7 @@ from collections.abc import (
from contextlib import AsyncExitStack, contextmanager from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy from copy import copy, deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from functools import lru_cache
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
@ -64,11 +65,10 @@ from fastapi.utils import (
create_model_field, create_model_field,
get_path_param_names, get_path_param_names,
) )
from pydantic import BaseModel, Json, TypeAdapter, create_model, ValidationError from pydantic import BaseModel, Json, TypeAdapter, ValidationError, create_model
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.background import BackgroundTasks as StarletteBackgroundTasks
from starlette.concurrency import run_in_threadpool from starlette.concurrency import run_in_threadpool
from functools import lru_cache
from starlette.datastructures import ( from starlette.datastructures import (
FormData, FormData,
Headers, Headers,
@ -757,12 +757,15 @@ def _validate_value_with_model_field(
if isinstance(value, FastAPIOptimizedJsonBytes): if isinstance(value, FastAPIOptimizedJsonBytes):
return field.validate_json(value, values, loc=loc) return field.validate_json(value, values, loc=loc)
return field.validate(value, values, loc=loc) return field.validate(value, values, loc=loc)
# If it's a scalar and we have bytes, we MUST decode it first because Pydantic's # If it's a scalar and we have bytes, we MUST decode it first because Pydantic's
# validate_python doesn't handle JSON-encoded scalar bytes (like b'"-1"') # validate_python doesn't handle JSON-encoded scalar bytes (like b'"-1"')
if isinstance(value, bytes) and field_annotation_is_scalar(field.field_info.annotation): if isinstance(value, bytes) and field_annotation_is_scalar(
field.field_info.annotation
):
try: try:
import json import json
value = json.loads(value) value = json.loads(value)
except json.JSONDecodeError: except json.JSONDecodeError:
pass pass
@ -888,10 +891,10 @@ def request_params_to_args(
if grouped_adapter is not None: if grouped_adapter is not None:
try: try:
# We use from_attributes=True because the request might be an object? # We use from_attributes=True because the request might be an object?
# No, params_to_process is a dict, but from_attributes doesn't hurt. # No, params_to_process is a dict, but from_attributes doesn't hurt.
validated_data = grouped_adapter.validate_python(params_to_process) validated_data = grouped_adapter.validate_python(params_to_process)
# validated_data is a dict (or BaseModel? TypeAdapter(BaseModel) returns BaseModel) # validated_data is a dict (or BaseModel? TypeAdapter(BaseModel) returns BaseModel)
# We need to extract to values dict. # We need to extract to values dict.
if hasattr(validated_data, "model_dump"): if hasattr(validated_data, "model_dump"):
@ -899,22 +902,22 @@ def request_params_to_args(
# model_dump might convert inner models, which we don't want (FastAPI keeps them as objects) # model_dump might convert inner models, which we don't want (FastAPI keeps them as objects)
values.update(validated_data.__dict__) values.update(validated_data.__dict__)
else: else:
values.update(validated_data) # type: ignore values.update(validated_data) # type: ignore
except ValidationError as exc: except ValidationError as exc:
field_in = fields[0].field_info.in_.value field_in = fields[0].field_info.in_.value
# Map f.name to f.alias in case Pydantic returned the internal name # Map f.name to f.alias in case Pydantic returned the internal name
name_to_alias = {f.name: get_validation_alias(f) for f in fields} name_to_alias = {f.name: get_validation_alias(f) for f in fields}
for err in exc.errors(include_url=False): for err in exc.errors(include_url=False):
err_loc = list(err["loc"]) err_loc = list(err["loc"])
if err_loc and err_loc[0] in name_to_alias: if err_loc and err_loc[0] in name_to_alias:
err_loc[0] = name_to_alias[err_loc[0]] # type: ignore err_loc[0] = name_to_alias[err_loc[0]] # type: ignore
err["loc"] = (field_in, *err_loc) # type: ignore err["loc"] = (field_in, *err_loc) # type: ignore
if err["type"] == "missing": if err["type"] == "missing":
err["input"] = None err["input"] = None
errors.append(err) # type: ignore errors.append(err) # type: ignore
return values, errors return values, errors
@ -1051,6 +1054,7 @@ async def request_body_to_args(
if isinstance(received_body, bytes): if isinstance(received_body, bytes):
try: try:
import json import json
body_to_process = json.loads(received_body) body_to_process = json.loads(received_body)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
return values, [ return values, [

2
fastapi/encoders.py

@ -242,7 +242,7 @@ def jsonable_encoder(
include = set(include) # type: ignore[assignment] # ty: ignore[invalid-assignment] include = set(include) # type: ignore[assignment] # ty: ignore[invalid-assignment]
if exclude is not None and not isinstance(exclude, (set, dict)): if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude) # type: ignore[assignment] # ty: ignore[invalid-assignment] exclude = set(exclude) # type: ignore[assignment] # ty: ignore[invalid-assignment]
if not custom_encoder and not sqlalchemy_safe: if not custom_encoder and not sqlalchemy_safe:
try: try:
return _any_type_adapter.dump_python( return _any_type_adapter.dump_python(

9
fastapi/routing.py

@ -35,7 +35,6 @@ from anyio.abc import ObjectReceiveStream
from fastapi import params from fastapi import params
from fastapi._compat import ( from fastapi._compat import (
ModelField, ModelField,
Undefined,
lenient_issubclass, lenient_issubclass,
) )
from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.datastructures import Default, DefaultPlaceholder
@ -73,6 +72,7 @@ from fastapi.utils import (
get_value_or_default, get_value_or_default,
is_body_allowed_for_status_code, is_body_allowed_for_status_code,
) )
from pydantic import TypeAdapter
from starlette import routing from starlette import routing
from starlette._exception_handler import wrap_app_handling_exceptions from starlette._exception_handler import wrap_app_handling_exceptions
from starlette._utils import is_async_callable from starlette._utils import is_async_callable
@ -91,7 +91,6 @@ from starlette.routing import Mount as Mount # noqa
from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
from typing_extensions import deprecated from typing_extensions import deprecated
from pydantic import TypeAdapter
_any_type_adapter = TypeAdapter(Any) _any_type_adapter = TypeAdapter(Any)
@ -424,7 +423,7 @@ def get_request_handler(
subtype = message.get_content_subtype() subtype = message.get_content_subtype()
if subtype == "json" or subtype.endswith("+json"): if subtype == "json" or subtype.endswith("+json"):
is_json_content = True is_json_content = True
if is_json_content: if is_json_content:
body = FastAPIOptimizedJsonBytes(body_bytes) body = FastAPIOptimizedJsonBytes(body_bytes)
else: else:
@ -515,7 +514,9 @@ def get_request_handler(
if hasattr(item.data, "model_dump_json"): if hasattr(item.data, "model_dump_json"):
data_str = item.data.model_dump_json() data_str = item.data.model_dump_json()
else: else:
data_str = _any_type_adapter.dump_json(item.data).decode("utf-8") data_str = _any_type_adapter.dump_json(
item.data
).decode("utf-8")
else: else:
data_str = None data_str = None
return format_sse_event( return format_sse_event(

Loading…
Cancel
Save