From a47f7fed4c391a20f62ac4c329592f39149ab5bc Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Fri, 29 May 2026 21:43:39 +0100 Subject: [PATCH] Return MultiParams from request.post_vars() to preserve multi-valued form fields Form submissions with multiple values for the same key (e.g. ```` fields). For multipart forms or file uploads, use ``request.form()`` instead. ``await request.json()`` - Any Returns the parsed JSON body of a request submitted by ``POST``. diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index c89ad0a3..fc1e6df1 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -237,7 +237,8 @@ def register_routes(): if request.method == "GET": return Response.html(request.scope["csrftoken"]()) else: - return Response.json(await request.post_vars()) + # post_vars() returns a MultiParams; convert to a plain dict for JSON + return Response.json(dict(await request.post_vars())) async def csrftoken_form(request, datasette): return Response.html( diff --git a/tests/test_internals_request.py b/tests/test_internals_request.py index 6d2dc70a..8b5ebfab 100644 --- a/tests/test_internals_request.py +++ b/tests/test_internals_request.py @@ -33,28 +33,44 @@ def _receive_chunks(chunks): return receive +def _form_request(body: bytes) -> Request: + return Request( + _post_scope(headers=[[b"content-type", b"application/x-www-form-urlencoded"]]), + _receive_chunks([body]), + ) + + @pytest.mark.asyncio async def test_request_post_vars(): - scope = { - "http_version": "1.1", - "method": "POST", - "path": "/", - "raw_path": b"/", - "query_string": b"", - "scheme": "http", - "type": "http", - "headers": [[b"content-type", b"application/x-www-form-urlencoded"]], - } + request = _form_request(b"foo=bar&baz=1&empty=") + post_vars = await request.post_vars() + assert post_vars["foo"] == "bar" + assert post_vars["baz"] == "1" + assert post_vars["empty"] == "" + assert post_vars.get("missing") is None + assert set(post_vars.keys()) == {"foo", "baz", "empty"} + assert dict(post_vars.items()) == {"foo": "bar", "baz": "1", "empty": ""} - async def receive(): - return { - "type": "http.request", - "body": b"foo=bar&baz=1&empty=", - "more_body": False, - } - request = Request(scope, receive) - assert {"foo": "bar", "baz": "1", "empty": ""} == await request.post_vars() +@pytest.mark.asyncio +async def test_request_post_vars_multi(): + # post_vars() returns a MultiParams so multiple values for the same key are + # preserved, matching the behaviour of request.args. See issue #2425. + request = _form_request(b"multi=1&multi=2&single=3") + post_vars = await request.post_vars() + assert post_vars.get("multi") == "1" + assert post_vars.get("single") == "3" + assert post_vars["multi"] == "1" + assert post_vars["single"] == "3" + assert post_vars.getlist("multi") == ["1", "2"] + assert post_vars.getlist("single") == ["3"] + assert post_vars.getlist("missing") == [] + assert "multi" in post_vars + assert "missing" not in post_vars + assert list(post_vars.keys()) == ["multi", "single"] + assert len(post_vars) == 2 + with pytest.raises(KeyError): + post_vars["missing"] @pytest.mark.asyncio