From a6287aea6b0f6853f018f25087c45aa428e8d0bc Mon Sep 17 00:00:00 2001 From: dongjiang Date: Sat, 11 Jul 2026 13:44:28 +0800 Subject: [PATCH 1/2] perf: avoid unnecessary copies in IdentityCompression and reuse protocol singletons - IdentityCompression.compress/decompress now return bytes input as-is instead of always calling bytes(data), eliminating a redundant copy on the hot path when no compression is used. - negotiate_server_protocol returns module-level singletons instead of creating new stateless protocol instances on every request. - Add unit tests for both optimizations. Signed-off-by: dongjiang --- src/connectrpc/_compression.py | 4 +- src/connectrpc/_protocol_server.py | 11 +++-- test/test_compression.py | 59 +++++++++++++++++++++++++++ test/test_protocol_server.py | 65 ++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 test/test_protocol_server.py diff --git a/src/connectrpc/_compression.py b/src/connectrpc/_compression.py index eb462f81..ef04b281 100644 --- a/src/connectrpc/_compression.py +++ b/src/connectrpc/_compression.py @@ -16,11 +16,11 @@ def name(self) -> str: def compress(self, data: bytes | bytearray | memoryview) -> bytes: """Return data as-is without compression.""" - return bytes(data) + return data if isinstance(data, bytes) else bytes(data) def decompress(self, data: bytes | bytearray | memoryview) -> bytes: """Return data as-is without decompression.""" - return bytes(data) + return data if isinstance(data, bytes) else bytes(data) _identity = IdentityCompression() diff --git a/src/connectrpc/_protocol_server.py b/src/connectrpc/_protocol_server.py index 62897e89..1b7a58e5 100644 --- a/src/connectrpc/_protocol_server.py +++ b/src/connectrpc/_protocol_server.py @@ -16,13 +16,18 @@ from ._protocol import ServerProtocol +_CONNECT_PROTOCOL = ConnectServerProtocol() +_GRPC_PROTOCOL = GRPCServerProtocol() +_GRPC_WEB_PROTOCOL = GRPCWebServerProtocol() + + def negotiate_server_protocol(content_type: str) -> ServerProtocol: if content_type == GRPC_CONTENT_TYPE_DEFAULT or content_type.startswith( GRPC_CONTENT_TYPE_PREFIX ): - return GRPCServerProtocol() + return _GRPC_PROTOCOL if content_type == GRPC_WEB_CONTENT_TYPE_DEFAULT or content_type.startswith( GRPC_WEB_CONTENT_TYPE_PREFIX ): - return GRPCWebServerProtocol() - return ConnectServerProtocol() + return _GRPC_WEB_PROTOCOL + return _CONNECT_PROTOCOL diff --git a/test/test_compression.py b/test/test_compression.py index c14e56ad..e123827d 100644 --- a/test/test_compression.py +++ b/test/test_compression.py @@ -4,6 +4,8 @@ from pyqwest import Client, SyncClient from pyqwest.testing import ASGITransport, WSGITransport +from connectrpc._compression import IdentityCompression + from connectrpc.client import ResponseMetadata from connectrpc.compression.brotli import BrotliCompression from connectrpc.compression.gzip import GzipCompression @@ -88,3 +90,60 @@ def make_hat(self, request, ctx): assert res.size == 10 assert res.color == "blue" assert meta.headers.get("content-encoding") == encoding + + +class TestIdentityCompression: + def setup_method(self): + self.ic = IdentityCompression() + + def test_name(self): + assert self.ic.name() == "identity" + + def test_compress_bytes_returns_same_object(self): + """bytes input must be returned as-is without copying.""" + data = b"hello" + result = self.ic.compress(data) + assert result is data + + def test_decompress_bytes_returns_same_object(self): + """bytes input must be returned as-is without copying.""" + data = b"hello" + result = self.ic.decompress(data) + assert result is data + + def test_compress_bytearray_copies(self): + """bytearray input must be copied into a new bytes object.""" + data = bytearray(b"hello") + result = self.ic.compress(data) + assert result == b"hello" + assert isinstance(result, bytes) + assert not isinstance(result, bytearray) + + def test_decompress_bytearray_copies(self): + """bytearray input must be copied into a new bytes object.""" + data = bytearray(b"hello") + result = self.ic.decompress(data) + assert result == b"hello" + assert isinstance(result, bytes) + + def test_compress_memoryview_copies(self): + """memoryview input must be copied into a new bytes object.""" + data = memoryview(b"hello") + result = self.ic.compress(data) + assert result == b"hello" + assert isinstance(result, bytes) + + def test_decompress_memoryview_copies(self): + """memoryview input must be copied into a new bytes object.""" + data = memoryview(b"hello") + result = self.ic.decompress(data) + assert result == b"hello" + assert isinstance(result, bytes) + + def test_compress_empty_bytes(self): + data = b"" + assert self.ic.compress(data) is data + + def test_decompress_empty_bytes(self): + data = b"" + assert self.ic.decompress(data) is data diff --git a/test/test_protocol_server.py b/test/test_protocol_server.py new file mode 100644 index 00000000..feac0741 --- /dev/null +++ b/test/test_protocol_server.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from connectrpc._protocol_connect import ConnectServerProtocol +from connectrpc._protocol_grpc import ( + GRPCServerProtocol, + GRPCWebServerProtocol, +) +from connectrpc._protocol_server import ( + _CONNECT_PROTOCOL, + _GRPC_PROTOCOL, + _GRPC_WEB_PROTOCOL, + negotiate_server_protocol, +) + + +class TestNegotiateServerProtocol: + """Tests for negotiate_server_protocol singleton reuse.""" + + def test_grpc_content_type_default_returns_singleton(self): + protocol = negotiate_server_protocol("application/grpc") + assert protocol is _GRPC_PROTOCOL + + def test_grpc_content_type_with_suffix_returns_singleton(self): + protocol = negotiate_server_protocol("application/grpc+proto") + assert protocol is _GRPC_PROTOCOL + + def test_grpc_web_content_type_default_returns_singleton(self): + protocol = negotiate_server_protocol("application/grpc-web") + assert protocol is _GRPC_WEB_PROTOCOL + + def test_grpc_web_content_type_with_suffix_returns_singleton(self): + protocol = negotiate_server_protocol("application/grpc-web+proto") + assert protocol is _GRPC_WEB_PROTOCOL + + def test_connect_content_type_json_returns_singleton(self): + protocol = negotiate_server_protocol("application/json") + assert protocol is _CONNECT_PROTOCOL + + def test_connect_content_type_proto_returns_singleton(self): + protocol = negotiate_server_protocol("application/proto") + assert protocol is _CONNECT_PROTOCOL + + def test_unknown_content_type_returns_connect_singleton(self): + protocol = negotiate_server_protocol("text/plain") + assert protocol is _CONNECT_PROTOCOL + + def test_repeated_calls_return_same_grpc_instance(self): + p1 = negotiate_server_protocol("application/grpc") + p2 = negotiate_server_protocol("application/grpc+proto") + assert p1 is p2 + + def test_repeated_calls_return_same_grpc_web_instance(self): + p1 = negotiate_server_protocol("application/grpc-web") + p2 = negotiate_server_protocol("application/grpc-web+proto") + assert p1 is p2 + + def test_singleton_types_are_correct(self): + assert isinstance(_CONNECT_PROTOCOL, ConnectServerProtocol) + assert isinstance(_GRPC_PROTOCOL, GRPCServerProtocol) + assert isinstance(_GRPC_WEB_PROTOCOL, GRPCWebServerProtocol) + + def test_singletons_are_distinct(self): + assert _CONNECT_PROTOCOL is not _GRPC_PROTOCOL + assert _CONNECT_PROTOCOL is not _GRPC_WEB_PROTOCOL + assert _GRPC_PROTOCOL is not _GRPC_WEB_PROTOCOL From 6b8070dec628937665756f52d08aaf7a40176fc4 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Mon, 13 Jul 2026 10:52:18 +0900 Subject: [PATCH 2/2] Cleanup Signed-off-by: Anuraag Agrawal --- test/test_compression.py | 70 ++++++++++-------------------------- test/test_protocol_server.py | 65 --------------------------------- 2 files changed, 18 insertions(+), 117 deletions(-) delete mode 100644 test/test_protocol_server.py diff --git a/test/test_compression.py b/test/test_compression.py index e123827d..e0eafcef 100644 --- a/test/test_compression.py +++ b/test/test_compression.py @@ -5,7 +5,6 @@ from pyqwest.testing import ASGITransport, WSGITransport from connectrpc._compression import IdentityCompression - from connectrpc.client import ResponseMetadata from connectrpc.compression.brotli import BrotliCompression from connectrpc.compression.gzip import GzipCompression @@ -93,57 +92,24 @@ def make_hat(self, request, ctx): class TestIdentityCompression: - def setup_method(self): - self.ic = IdentityCompression() - def test_name(self): - assert self.ic.name() == "identity" + assert IdentityCompression().name() == "identity" - def test_compress_bytes_returns_same_object(self): - """bytes input must be returned as-is without copying.""" + def test_bytes(self): data = b"hello" - result = self.ic.compress(data) - assert result is data - - def test_decompress_bytes_returns_same_object(self): - """bytes input must be returned as-is without copying.""" - data = b"hello" - result = self.ic.decompress(data) - assert result is data - - def test_compress_bytearray_copies(self): - """bytearray input must be copied into a new bytes object.""" - data = bytearray(b"hello") - result = self.ic.compress(data) - assert result == b"hello" - assert isinstance(result, bytes) - assert not isinstance(result, bytearray) - - def test_decompress_bytearray_copies(self): - """bytearray input must be copied into a new bytes object.""" - data = bytearray(b"hello") - result = self.ic.decompress(data) - assert result == b"hello" - assert isinstance(result, bytes) - - def test_compress_memoryview_copies(self): - """memoryview input must be copied into a new bytes object.""" - data = memoryview(b"hello") - result = self.ic.compress(data) - assert result == b"hello" - assert isinstance(result, bytes) - - def test_decompress_memoryview_copies(self): - """memoryview input must be copied into a new bytes object.""" - data = memoryview(b"hello") - result = self.ic.decompress(data) - assert result == b"hello" - assert isinstance(result, bytes) - - def test_compress_empty_bytes(self): - data = b"" - assert self.ic.compress(data) is data - - def test_decompress_empty_bytes(self): - data = b"" - assert self.ic.decompress(data) is data + compression = IdentityCompression() + compressed = compression.compress(data) + assert compressed is data + decompressed = compression.decompress(compressed) + assert decompressed is data + + @pytest.mark.parametrize("ctor", [bytearray, memoryview]) + def test_not_bytes(self, ctor: type[bytearray | memoryview]) -> None: + data = ctor(b"hello") + compression = IdentityCompression() + compressed = compression.compress(data) + assert compressed == b"hello" + assert isinstance(compressed, bytes) + decompressed = compression.decompress(compressed) + assert decompressed == b"hello" + assert isinstance(decompressed, bytes) diff --git a/test/test_protocol_server.py b/test/test_protocol_server.py deleted file mode 100644 index feac0741..00000000 --- a/test/test_protocol_server.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations - -from connectrpc._protocol_connect import ConnectServerProtocol -from connectrpc._protocol_grpc import ( - GRPCServerProtocol, - GRPCWebServerProtocol, -) -from connectrpc._protocol_server import ( - _CONNECT_PROTOCOL, - _GRPC_PROTOCOL, - _GRPC_WEB_PROTOCOL, - negotiate_server_protocol, -) - - -class TestNegotiateServerProtocol: - """Tests for negotiate_server_protocol singleton reuse.""" - - def test_grpc_content_type_default_returns_singleton(self): - protocol = negotiate_server_protocol("application/grpc") - assert protocol is _GRPC_PROTOCOL - - def test_grpc_content_type_with_suffix_returns_singleton(self): - protocol = negotiate_server_protocol("application/grpc+proto") - assert protocol is _GRPC_PROTOCOL - - def test_grpc_web_content_type_default_returns_singleton(self): - protocol = negotiate_server_protocol("application/grpc-web") - assert protocol is _GRPC_WEB_PROTOCOL - - def test_grpc_web_content_type_with_suffix_returns_singleton(self): - protocol = negotiate_server_protocol("application/grpc-web+proto") - assert protocol is _GRPC_WEB_PROTOCOL - - def test_connect_content_type_json_returns_singleton(self): - protocol = negotiate_server_protocol("application/json") - assert protocol is _CONNECT_PROTOCOL - - def test_connect_content_type_proto_returns_singleton(self): - protocol = negotiate_server_protocol("application/proto") - assert protocol is _CONNECT_PROTOCOL - - def test_unknown_content_type_returns_connect_singleton(self): - protocol = negotiate_server_protocol("text/plain") - assert protocol is _CONNECT_PROTOCOL - - def test_repeated_calls_return_same_grpc_instance(self): - p1 = negotiate_server_protocol("application/grpc") - p2 = negotiate_server_protocol("application/grpc+proto") - assert p1 is p2 - - def test_repeated_calls_return_same_grpc_web_instance(self): - p1 = negotiate_server_protocol("application/grpc-web") - p2 = negotiate_server_protocol("application/grpc-web+proto") - assert p1 is p2 - - def test_singleton_types_are_correct(self): - assert isinstance(_CONNECT_PROTOCOL, ConnectServerProtocol) - assert isinstance(_GRPC_PROTOCOL, GRPCServerProtocol) - assert isinstance(_GRPC_WEB_PROTOCOL, GRPCWebServerProtocol) - - def test_singletons_are_distinct(self): - assert _CONNECT_PROTOCOL is not _GRPC_PROTOCOL - assert _CONNECT_PROTOCOL is not _GRPC_WEB_PROTOCOL - assert _GRPC_PROTOCOL is not _GRPC_WEB_PROTOCOL