From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Tue, 2 Dec 2025 04:57:19 +0000 (+0100) Subject: 🐛 Fix parsing extra non-body parameter list (#14356) X-Git-Tag: 0.123.2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=de5bec637c6af5126382c1e604514964ba9e5093;p=thirdparty%2Ffastapi%2Ffastapi.git 🐛 Fix parsing extra non-body parameter list (#14356) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 2b2e6c5af9..20cb2c88c8 100644 --- 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 diff --git a/tests/test_query_cookie_header_model_extra_params.py b/tests/test_query_cookie_header_model_extra_params.py new file mode 100644 index 0000000000..f4ebefb3f3 --- /dev/null +++ b/tests/test_query_cookie_header_model_extra_params.py @@ -0,0 +1,111 @@ +from fastapi import Cookie, FastAPI, Header, Query +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() + + +class Model(BaseModel): + param: str + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + else: + + class Config: + extra = "allow" + + +@app.get("/query") +async def query_model_with_extra(data: Model = Query()): + return data + + +@app.get("/header") +async def header_model_with_extra(data: Model = Header()): + return data + + +@app.get("/cookie") +async def cookies_model_with_extra(data: Model = Cookie()): + return data + + +def test_query_pass_extra_list(): + client = TestClient(app) + resp = client.get( + "/query", + 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"], + } + + +def test_query_pass_extra_single(): + client = TestClient(app) + resp = client.get( + "/query", + params={ + "param": "123", + "param2": "456", + }, + ) + assert resp.status_code == 200 + assert resp.json() == { + "param": "123", + "param2": "456", + } + + +def test_header_pass_extra_list(): + client = TestClient(app) + + resp = client.get( + "/header", + headers=[ + ("param", "123"), + ("param2", "456"), # Pass a list of values as extra parameter + ("param2", "789"), + ], + ) + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == ["456", "789"] + + +def test_header_pass_extra_single(): + client = TestClient(app) + + resp = client.get( + "/header", + headers=[ + ("param", "123"), + ("param2", "456"), + ], + ) + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == "456" + + +def test_cookie_pass_extra_list(): + client = TestClient(app) + client.cookies = [ + ("param", "123"), + ("param2", "456"), # Pass a list of values as extra parameter + ("param2", "789"), + ] + resp = client.get("/cookie") + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == "789" # Cookies only keep the last value diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py index 60940e1da2..554a48d2e8 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -77,7 +77,7 @@ def test_header_param_model_no_underscore(client: TestClient): "user-agent": "testclient", "save-data": "true", "if-modified-since": "yesterday", - "x-tag": "two", + "x-tag": ["one", "two"], }, } )