← oracle_refix

fastapi_14356

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

Task input

🐛 Fix parsing extra non-body parameter list

Currently if we declare Query or Header parameters as model with `extra="allow"` and then pass extra parameter with multiple values, only last value of such parameter will be passed to model validator:

```py
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()


class Model(BaseModel):
    param: str

    model_config = {"extra": "allow"}


@app.get("/")
async def model_with_extra(data: Model = Query()):
    return data


def test_pass_extra_list():
    client = TestClient(app)
    resp = client.get(
        "/",
        params={
            "param": "123",
            "param2": ["456", "789"],  # Pass a list of values as extra parameter
        },
    )
    assert resp.status_code == 200
    assert resp.json() == {
        "param": "123",
        "param2": ["456", "789"],
    }

# AssertionError: assert
# {'param': '123', 'param2': '789'} == {'param': '123', 'param2': ['456', '789']}
```

The reason is the same as with Form parameters. See #14303
There is also a note about implementation described in #14303. It's also relevant to this PR.

**As for Cookies:**
As far as I understand the spec, you can't pass multiple Cookie parameters with the same name (only last value will be used). The only way to pass a list of values is to pass them comma-separated, but it's not currently supported by Starlette

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -791,9 +791,16 @@ def request_params_to_args(
         processed_keys.add(alias or field.alias)
         processed_keys.add(field.name)
 
-    for key, value in received_params.items():
+    for key in received_params.keys():
         if key not in processed_keys:
-            params_to_process[key] = value
+            if hasattr(received_params, "getlist"):
+                value = received_params.getlist(key)
+                if isinstance(value, list) and (len(value) == 1):
+                    params_to_process[key] = value[0]
+                else:
+                    params_to_process[key] = value
+            else:
+                params_to_process[key] = received_params.get(key)
 
     if single_not_embedded_field:
         field_info = first_field.field_info

Test output

show
.....E
==================================== ERRORS ====================================
____________ ERROR at setup of test_header_param_model[tutorial003] ____________

request = <SubRequest 'client' for <Function test_header_param_model[tutorial003]>>

    @pytest.fixture(
        name="client",
        params=[
            "tutorial003",
            pytest.param("tutorial003_py39", marks=needs_py39),
            pytest.param("tutorial003_py310", marks=needs_py310),
            "tutorial003_an",
            pytest.param("tutorial003_an_py39", marks=needs_py39),
            pytest.param("tutorial003_an_py310", marks=needs_py310),
        ],
    )
    def get_client(request: pytest.FixtureRequest):
>       mod = importlib.import_module(f"docs_src.header_param_models.{request.param}")
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_tutorial/test_header_param_models/test_tutorial003.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1395: in _gcd_import
    ???
<frozen importlib._bootstrap>:1360: in _find_and_load
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

name = 'docs_src.header_param_models.tutorial003'
import_ = <function _gcd_import at 0x1034e44a0>

>   ???
E   ModuleNotFoundError: No module named 'docs_src.header_param_models.tutorial003'

<frozen importlib._bootstrap>:1324: ModuleNotFoundError
=============================== 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 !!!!!!!!!!!!!!!!!!!!!!!!!!!
5 passed, 1 warning, 1 error in 0.63s