From 49def556c9e3a12cb08a9c36ecaed9fba8e35e63 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Fri, 22 May 2026 15:12:10 -0400 Subject: [PATCH 1/2] Update unary-get query paramter to match spec Signed-off-by: Oliver Sun --- src/connectrpc/_client_shared.py | 12 +++++------ test/test_client_shared.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 test/test_client_shared.py diff --git a/src/connectrpc/_client_shared.py b/src/connectrpc/_client_shared.py index d0df033..7aa4201 100644 --- a/src/connectrpc/_client_shared.py +++ b/src/connectrpc/_client_shared.py @@ -22,14 +22,14 @@ def prepare_get_params( codec: Codec, request_data: bytes, headers: HTTPHeaders ) -> dict[str, str]: - params = { - "connect": f"v{CONNECT_PROTOCOL_VERSION}", - "message": base64.urlsafe_b64encode(request_data).decode("ascii"), - "base64": "1", - "encoding": codec.name(), - } + # Insertion order is the Query-Get order from the Connect spec; urlencode + # iterates dict items in insertion order, which has been guaranteed since + # Python 3.7 (we require >= 3.10). + params: dict[str, str] = {"connect": f"v{CONNECT_PROTOCOL_VERSION}", "base64": "1"} if "content-encoding" in headers: params["compression"] = headers.pop("content-encoding") + params["encoding"] = codec.name() + params["message"] = base64.urlsafe_b64encode(request_data).decode("ascii") return params diff --git a/test/test_client_shared.py b/test/test_client_shared.py new file mode 100644 index 0000000..ee86b4d --- /dev/null +++ b/test/test_client_shared.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from urllib.parse import urlencode + +from pyqwest import Headers as HTTPHeaders + +from connectrpc._client_shared import prepare_get_params +from connectrpc.codec import proto_binary_codec + + +def test_prepare_get_params_order_without_compression() -> None: + params = prepare_get_params(proto_binary_codec(), b"hello", HTTPHeaders([])) + assert list(params.keys()) == ["connect", "base64", "encoding", "message"] + + +def test_prepare_get_params_order_with_compression() -> None: + headers = HTTPHeaders([("content-encoding", "gzip")]) + params = prepare_get_params(proto_binary_codec(), b"hello", headers) + assert list(params.keys()) == [ + "connect", + "base64", + "compression", + "encoding", + "message", + ] + assert "content-encoding" not in headers + + +def test_prepare_get_params_urlencode_order() -> None: + headers = HTTPHeaders([("content-encoding", "gzip")]) + params = prepare_get_params(proto_binary_codec(), b"hello", headers) + query = urlencode(params) + expected_prefix = "connect=v1&base64=1&compression=gzip&encoding=proto&message=" + assert query.startswith(expected_prefix) From e742bc4fe96de81dd115ffc8d6a2dba26ec1a814 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Mon, 25 May 2026 10:00:40 +0900 Subject: [PATCH 2/2] Cleanup Signed-off-by: Anuraag Agrawal --- src/connectrpc/_client_shared.py | 4 +--- test/test_client_shared.py | 17 ++++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/connectrpc/_client_shared.py b/src/connectrpc/_client_shared.py index 7aa4201..bfd3e49 100644 --- a/src/connectrpc/_client_shared.py +++ b/src/connectrpc/_client_shared.py @@ -22,9 +22,7 @@ def prepare_get_params( codec: Codec, request_data: bytes, headers: HTTPHeaders ) -> dict[str, str]: - # Insertion order is the Query-Get order from the Connect spec; urlencode - # iterates dict items in insertion order, which has been guaranteed since - # Python 3.7 (we require >= 3.10). + # Follow order in spec: https://connectrpc.com/docs/protocol/#unary-get-request params: dict[str, str] = {"connect": f"v{CONNECT_PROTOCOL_VERSION}", "base64": "1"} if "content-encoding" in headers: params["compression"] = headers.pop("content-encoding") diff --git a/test/test_client_shared.py b/test/test_client_shared.py index ee86b4d..179ecf5 100644 --- a/test/test_client_shared.py +++ b/test/test_client_shared.py @@ -8,12 +8,7 @@ from connectrpc.codec import proto_binary_codec -def test_prepare_get_params_order_without_compression() -> None: - params = prepare_get_params(proto_binary_codec(), b"hello", HTTPHeaders([])) - assert list(params.keys()) == ["connect", "base64", "encoding", "message"] - - -def test_prepare_get_params_order_with_compression() -> None: +def test_prepare_get_params_order() -> None: headers = HTTPHeaders([("content-encoding", "gzip")]) params = prepare_get_params(proto_binary_codec(), b"hello", headers) assert list(params.keys()) == [ @@ -24,11 +19,11 @@ def test_prepare_get_params_order_with_compression() -> None: "message", ] assert "content-encoding" not in headers - - -def test_prepare_get_params_urlencode_order() -> None: - headers = HTTPHeaders([("content-encoding", "gzip")]) - params = prepare_get_params(proto_binary_codec(), b"hello", headers) query = urlencode(params) expected_prefix = "connect=v1&base64=1&compression=gzip&encoding=proto&message=" assert query.startswith(expected_prefix) + + +def test_prepare_get_params_order_without_compression() -> None: + params = prepare_get_params(proto_binary_codec(), b"hello", HTTPHeaders([])) + assert list(params.keys()) == ["connect", "base64", "encoding", "message"]