← oracle_full

fastapi_14371

failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(None) · None tool calls · 0 s · fastapi/fastapi

Task input

🐛 Fix parameter aliases

This PR fixes the rest of failing tests in #14358.

---

**Note:** for `validation_aliases` only string value is currently supported. We can later think of supporting `AliasPath` and `AliasChoices`


**Will also fix issues:**

<details>

* https://github.com/fastapi/fastapi/discussions/8535
* https://github.com/fastapi/fastapi/discussions/10014

</details>

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -110,6 +110,18 @@ def alias(self) -> str:
         a = self.field_info.alias
         return a if a is not None else self.name
 
+    @property
+    def validation_alias(self) -> Union[str, None]:
+        va = self.field_info.validation_alias
+        if isinstance(va, str) and va:
+            return va
+        return None
+
+    @property
+    def serialization_alias(self) -> Union[str, None]:
+        sa = self.field_info.serialization_alias
+        return sa or None
+
     @property
     def required(self) -> bool:
         return self.field_info.is_required()
@@ -243,12 +255,18 @@ def get_schema_from_model_field(
         if (separate_input_output_schemas or _has_computed_fields(field))
         else "validation"
     )
+    field_alias = (
+        (field.validation_alias or field.alias)
+        if field.mode == "validation"
+        else (field.serialization_alias or field.alias)
+    )
+
     # This expects that GenerateJsonSchema was already used to generate the definitions
     json_schema = field_mapping[(field, override_mode or field.mode)]
     if "$ref" not in json_schema:
         # TODO remove when deprecating Pydantic v1
         # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
-        json_schema["title"] = field.field_info.title or field.alias.title().replace(
+        json_schema["title"] = field.field_info.title or field_alias.title().replace(
             "_", " "
         )
     return json_schema
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -752,7 +752,7 @@ def _validate_value_with_model_field(
 def _get_multidict_value(
     field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None
 ) -> Any:
-    alias = alias or field.alias
+    alias = alias or get_validation_alias(field)
     if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
         value = values.getlist(alias)
     else:
@@ -809,15 +809,13 @@ def request_params_to_args(
                 field.field_info, "convert_underscores", default_convert_underscores
             )
             if convert_underscores:
-                alias = (
-                    field.alias
-                    if field.alias != field.name
-                    else field.name.replace("_", "-")
-                )
+                alias = get_validation_alias(field)
+                if alias == field.name:
+                    alias = alias.replace("_", "-")
         value = _get_multidict_value(field, received_params, alias=alias)
         if value is not None:
-            params_to_process[field.alias] = value
-        processed_keys.add(alias or field.alias)
+            params_to_process[get_validation_alias(field)] = value
+        processed_keys.add(alias or get_validation_alias(field))
 
     for key in received_params.keys():
         if key not in processed_keys:
@@ -847,7 +845,7 @@ def request_params_to_args(
         assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), (
             "Params must be subclasses of Param"
         )
-        loc = (field_info.in_.value, field.alias)
+        loc = (field_info.in_.value, get_validation_alias(field))
         v_, errors_ = _validate_value_with_model_field(
             field=field, value=value, values=values, loc=loc
         )
@@ -936,8 +934,8 @@ async def process_fn(
                     tg.start_soon(process_fn, sub_value.read)
             value = serialize_sequence_value(field=field, value=results)
         if value is not None:
-            values[field.alias] = value
-    field_aliases = {field.alias for field in body_fields}
+            values[get_validation_alias(field)] = value
+    field_aliases = {get_validation_alias(field) for field in body_fields}
     for key in received_body.keys():
         if key not in field_aliases:
             param_values = received_body.getlist(key)
@@ -979,11 +977,11 @@ async def request_body_to_args(
         )
         return {first_field.name: v_}, errors_
     for field in body_fields:
-        loc = ("body", field.alias)
+        loc = ("body", get_validation_alias(field))
         value: Optional[Any] = None
         if body_to_process is not None:
             try:
-                value = body_to_process.get(field.alias)
+                value = body_to_process.get(get_validation_alias(field))
             # If the received body is a list, not a dict
             except AttributeError:
                 errors.append(get_missing_field_error(loc))
@@ -1062,3 +1060,8 @@ def get_body_field(
         field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
     )
     return final_field
+
+
+def get_validation_alias(field: ModelField) -> str:
+    va = getattr(field, "validation_alias", None)
+    return va or field.alias
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -19,6 +19,7 @@
     _get_flat_fields_from_params,
     get_flat_dependant,
     get_flat_params,
+    get_validation_alias,
 )
 from fastapi.encoders import jsonable_encoder
 from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
@@ -141,15 +142,15 @@ def _get_openapi_operation_parameters(
                 field_mapping=field_mapping,
                 separate_input_output_schemas=separate_input_output_schemas,
             )
-            name = param.alias
+            name = get_validation_alias(param)
             convert_underscores = getattr(
                 param.field_info,
                 "convert_underscores",
                 default_convert_underscores,
             )
             if (
                 param_type == ParamTypes.header
-                and param.alias == param.name
+                and name == param.name
                 and convert_underscores
             ):
                 name = param.name.replace("_", "-")
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -115,6 +115,10 @@ def __init__(
         else:
             kwargs["deprecated"] = deprecated
         if PYDANTIC_V2:
+            if serialization_alias in (_Unset, None) and isinstance(alias, str):
+                serialization_alias = alias
+            if validation_alias in (_Unset, None):
+                validation_alias = alias
             kwargs.update(
                 {
                     "annotation": annotation,
@@ -571,6 +575,10 @@ def __init__(
         else:
             kwargs["deprecated"] = deprecated
         if PYDANTIC_V2:
+            if serialization_alias in (_Unset, None) and isinstance(alias, str):
+                serialization_alias = alias
+            if validation_alias in (_Unset, None):
+                validation_alias = alias
             kwargs.update(
                 {
                     "annotation": annotation,

Test output

show
==================================== ERRORS ====================================
______ ERROR collecting tests/test_request_params/test_file/test_list.py _______
tests/test_request_params/test_file/test_list.py:19: in <module>
    @app.post("/list-bytes", operation_id="list_bytes")
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/routing.py:1128: in decorator
    self.add_api_route(
fastapi/routing.py:1067: in add_api_route
    route = route_class(
fastapi/routing.py:686: in __init__
    self.dependant = get_dependant(
fastapi/dependencies/utils.py:288: in get_dependant
    param_details = analyze_param(
fastapi/dependencies/utils.py:525: in analyze_param
    ensure_multipart_is_installed()
fastapi/dependencies/utils.py:121: in ensure_multipart_is_installed
    raise RuntimeError(multipart_not_installed_error) from None
E   RuntimeError: Form data requires "python-multipart" to be installed. 
E   You can install "python-multipart" with: 
E   
E   pip install python-multipart
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.18s