From eed1b75cc3ec3d7b4126506cf4d59df7da911e33 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 17 Mar 2026 10:42:02 -0400 Subject: [PATCH 1/4] Update docstrings for consistency in rendered API Just a first pass at improving the rendering of some of these, based on the review I gave. Effectively: * Add periods consistently * Add some links * Mention generated code for endpoints (this maybe could be extended further?) * Make a couple docstrings more consistent with others I also noticed while iterating on this locally that `zensical serve` does not seem to consistently cache-bust when changing these docstrings; for now, I'm just wiping the cache as a part of the justfile target, and am planning to report upstream. Ref: https://github.com/connectrpc/connect-python/pull/172#pullrequestreview-3960543487 Signed-off-by: Stefan VanBuren --- justfile | 4 ++++ src/connectrpc/_client_async.py | 10 +++++----- src/connectrpc/_client_sync.py | 10 +++++----- src/connectrpc/_interceptor_async.py | 2 +- src/connectrpc/_interceptor_sync.py | 2 +- src/connectrpc/_server_async.py | 4 +++- src/connectrpc/_server_sync.py | 9 ++++----- src/connectrpc/compression/__init__.py | 8 +++++--- src/connectrpc/protocol.py | 8 ++++---- src/connectrpc/request.py | 13 ++----------- 10 files changed, 34 insertions(+), 36 deletions(-) diff --git a/justfile b/justfile index 2b7a566..7b824de 100644 --- a/justfile +++ b/justfile @@ -67,4 +67,8 @@ bump semver: uv version --bump={{ semver }} --directory protoc-gen-connect-python serve-docs: + # It seems like when iterating on docstrings in src/, re-runs of `zensical + # serve` do not properly cache-bust, so for now, remove the .cache + # directory before running. + rm -rf .cache uv run --group docs zensical serve diff --git a/src/connectrpc/_client_async.py b/src/connectrpc/_client_async.py index c334da9..91f3168 100644 --- a/src/connectrpc/_client_async.py +++ b/src/connectrpc/_client_async.py @@ -105,15 +105,15 @@ def __init__( Args: address: The address of the server to connect to, including scheme. - proto_json: Whether to use JSON for the protocol + proto_json: Whether to use JSON for the protocol. accept_compression: Compression algorithms to accept from the server. If unset, defaults to gzip. If set to empty, disables response compression. send_compression: Compression algorithm to use for sending requests. If unset, defaults to gzip. If set to None, disables request compression. - timeout_ms: The timeout for requests in milliseconds - read_max_bytes: The maximum number of bytes to read from the response - interceptors: A list of interceptors to apply to requests - http_client: A pyqwest Client to use for requests + timeout_ms: The timeout for requests in milliseconds. + read_max_bytes: The maximum number of bytes to read from the response. + interceptors: A list of interceptors to apply to requests. + http_client: A pyqwest Client to use for requests. """ self._address = address self._codec = get_proto_json_codec() if proto_json else get_proto_binary_codec() diff --git a/src/connectrpc/_client_sync.py b/src/connectrpc/_client_sync.py index 728c418..79ea161 100644 --- a/src/connectrpc/_client_sync.py +++ b/src/connectrpc/_client_sync.py @@ -95,15 +95,15 @@ def __init__( Args: address: The address of the server to connect to, including scheme. - proto_json: Whether to use JSON for the protocol + proto_json: Whether to use JSON for the protocol. accept_compression: Compression algorithms to accept from the server. If unset, defaults to gzip. If set to empty, disables response compression. send_compression: Compression algorithm to use for sending requests. If unset, defaults to gzip. If set to None, disables request compression. - timeout_ms: The timeout for requests in milliseconds - read_max_bytes: The maximum number of bytes to read from the response - interceptors: A list of interceptors to apply to requests - http_client: A pyqwest SyncClient to use for requests + timeout_ms: The timeout for requests in milliseconds. + read_max_bytes: The maximum number of bytes to read from the response. + interceptors: A list of interceptors to apply to requests. + http_client: A pyqwest SyncClient to use for requests. """ self._address = address self._codec = get_proto_json_codec() if proto_json else get_proto_binary_codec() diff --git a/src/connectrpc/_interceptor_async.py b/src/connectrpc/_interceptor_async.py index e641c73..29a7ea0 100644 --- a/src/connectrpc/_interceptor_async.py +++ b/src/connectrpc/_interceptor_async.py @@ -126,7 +126,7 @@ class MetadataInterceptor(Protocol[T]): access to metadata such as headers and trailers. To access request and response bodies of a method, instead use an interceptor - corresponding to the type of method such as UnaryInterceptor. + corresponding to the type of method such as [UnaryInterceptor][]. """ async def on_start(self, ctx: RequestContext) -> T: diff --git a/src/connectrpc/_interceptor_sync.py b/src/connectrpc/_interceptor_sync.py index ad86229..bbd5cf4 100644 --- a/src/connectrpc/_interceptor_sync.py +++ b/src/connectrpc/_interceptor_sync.py @@ -126,7 +126,7 @@ class MetadataInterceptorSync(Protocol[T]): access to metadata such as headers and trailers. To access request and response bodies of a method, instead use an interceptor - corresponding to the type of method such as UnaryInterceptorSync. + corresponding to the type of method such as [UnaryInterceptorSync][]. """ def on_start_sync(self, ctx: RequestContext) -> T: diff --git a/src/connectrpc/_server_async.py b/src/connectrpc/_server_async.py index ece862a..0e59a36 100644 --- a/src/connectrpc/_server_async.py +++ b/src/connectrpc/_server_async.py @@ -95,7 +95,9 @@ def __init__( Args: service: The service instance or async generator that yields the service during lifespan. - endpoints: A mapping of URL paths to endpoints resolved from service. + endpoints: A callable that takes the service instance and returns a mapping of URL + paths to endpoints. Typically provided directly by generated code from the + Connect Python plugin. interceptors: A sequence of interceptors to apply to the endpoints. read_max_bytes: Maximum size of request messages. compressions: Supported compression algorithms. If unset, defaults to gzip. diff --git a/src/connectrpc/_server_sync.py b/src/connectrpc/_server_sync.py index 8e3e1ba..df27c1c 100644 --- a/src/connectrpc/_server_sync.py +++ b/src/connectrpc/_server_sync.py @@ -98,15 +98,14 @@ def _process_headers(headers: dict) -> Headers: def prepare_response_headers( base_headers: dict[str, list[str]], selected_encoding: str ) -> dict[str, list[str]]: - """Prepare response headers and determine if compression should be used. + """Prepare response headers with the selected compression encoding. Args: - base_headers: Base response headers - selected_encoding: Selected compression encoding - compressed_size: Size of compressed content (if compression was attempted) + base_headers: Base response headers. + selected_encoding: Selected compression encoding. Returns: - tuple[dict, bool]: Final headers and whether to use compression + The final response headers with content-encoding set. """ headers = base_headers.copy() diff --git a/src/connectrpc/compression/__init__.py b/src/connectrpc/compression/__init__.py index 5a2e6cf..7e5902b 100644 --- a/src/connectrpc/compression/__init__.py +++ b/src/connectrpc/compression/__init__.py @@ -11,10 +11,12 @@ class Compression(Protocol): By default, gzip compression is used. Other compression methods can be used by specifying implementations of this protocol. We provide standard - implementations for + implementations for: - - br (connectrpc.compression.brotli.BrotliCompression) - requires the `brotli` dependency - - zstd (connectrpc.compression.zstd.ZstdCompression) - requires the `zstandard` dependency + - br ([BrotliCompression][connectrpc.compression.brotli.BrotliCompression]) - + requires the `brotli` dependency. + - zstd ([ZstdCompression][connectrpc.compression.zstd.ZstdCompression]) - + requires the `zstandard` dependency. """ def name(self) -> str: diff --git a/src/connectrpc/protocol.py b/src/connectrpc/protocol.py index 4fc1edd..23352cb 100644 --- a/src/connectrpc/protocol.py +++ b/src/connectrpc/protocol.py @@ -4,13 +4,13 @@ class ProtocolType(Enum): - """A protocol supported by Connect""" + """A protocol supported by Connect.""" CONNECT = 1 - """The Connect protocol""" + """The Connect protocol.""" GRPC = 2 - """The gRPC protocol""" + """The gRPC protocol.""" GRPC_WEB = 3 - """The gRPC-Web protocol""" + """The gRPC-Web protocol.""" diff --git a/src/connectrpc/request.py b/src/connectrpc/request.py index a8feded..d5afd72 100644 --- a/src/connectrpc/request.py +++ b/src/connectrpc/request.py @@ -63,11 +63,7 @@ def http_method(self) -> str: return self._http_method def request_headers(self) -> Headers: - """ - Returns the request headers associated with the context. - - :return: A mapping of header keys to lists of header values. - """ + """Returns the request headers associated with the context.""" return self._request_headers def response_headers(self) -> Headers: @@ -87,12 +83,7 @@ def response_trailers(self) -> Headers: return self._response_trailers def timeout_ms(self) -> float | None: - """ - Returns the remaining time until the timeout. - - Returns: - float | None: The remaining time in milliseconds, or None if no timeout is set. - """ + """Returns the remaining time until the timeout in milliseconds, or None if no timeout is set.""" if self._end_time is None: return None return (self._end_time - time.monotonic()) * 1000.0 From 5a4bb70cf7867f72ac8d59f09eba7c61f367ba9b Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 17 Mar 2026 11:12:58 -0400 Subject: [PATCH 2/4] Add several more links Signed-off-by: Stefan VanBuren --- src/connectrpc/_interceptor_async.py | 4 ++-- src/connectrpc/_interceptor_sync.py | 6 +++--- src/connectrpc/errors.py | 2 +- src/connectrpc/method.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/connectrpc/_interceptor_async.py b/src/connectrpc/_interceptor_async.py index 29a7ea0..34c8f61 100644 --- a/src/connectrpc/_interceptor_async.py +++ b/src/connectrpc/_interceptor_async.py @@ -130,9 +130,9 @@ class MetadataInterceptor(Protocol[T]): """ async def on_start(self, ctx: RequestContext) -> T: - """Called when the RPC starts. The return value will be passed to on_end as-is. + """Called when the RPC starts. The return value will be passed to [on_end][] as-is. For example, if measuring RPC invocation time, on_start may return the current - time. If a return value isn't needed or on_end won't be used, return None. + time. If a return value isn't needed or [on_end][] won't be used, return None. """ ... diff --git a/src/connectrpc/_interceptor_sync.py b/src/connectrpc/_interceptor_sync.py index bbd5cf4..8a2e8fb 100644 --- a/src/connectrpc/_interceptor_sync.py +++ b/src/connectrpc/_interceptor_sync.py @@ -130,9 +130,9 @@ class MetadataInterceptorSync(Protocol[T]): """ def on_start_sync(self, ctx: RequestContext) -> T: - """Called when the RPC starts. The return value will be passed to on_end as-is. - For example, if measuring RPC invocation time, on_start may return the current - time. If a return value isn't needed or on_end won't be used, return None. + """Called when the RPC starts. The return value will be passed to [on_end_sync][] as-is. + For example, if measuring RPC invocation time, on_start_sync may return the current + time. If a return value isn't needed or [on_end_sync][] won't be used, return None. """ ... diff --git a/src/connectrpc/errors.py b/src/connectrpc/errors.py index ae12caf..e8b32d0 100644 --- a/src/connectrpc/errors.py +++ b/src/connectrpc/errors.py @@ -21,7 +21,7 @@ class ConnectError(Exception): If a server raises a ConnectError, the same exception content will be raised on the client as well. Errors surfacing on the client side such as timeouts will also be raised as a ConnectError with an appropriate - code. + [Code][]. """ def __init__( diff --git a/src/connectrpc/method.py b/src/connectrpc/method.py index fcbca6e..0c33d52 100644 --- a/src/connectrpc/method.py +++ b/src/connectrpc/method.py @@ -70,4 +70,4 @@ class MethodInfo(Generic[REQ, RES]): """The output message type of the method.""" idempotency_level: IdempotencyLevel - """The idempotency level of the method.""" + """The [IdempotencyLevel][] of the method.""" From d83ea8d26f414fa2488b40f8ede29d597ec28d12 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 17 Mar 2026 11:20:15 -0400 Subject: [PATCH 3/4] Last round of fixes Signed-off-by: Stefan VanBuren --- src/connectrpc/_client_async.py | 1 + src/connectrpc/_client_sync.py | 1 + src/connectrpc/_server_shared.py | 4 ++-- src/connectrpc/_server_sync.py | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/connectrpc/_client_async.py b/src/connectrpc/_client_async.py index 91f3168..17caa3b 100644 --- a/src/connectrpc/_client_async.py +++ b/src/connectrpc/_client_async.py @@ -106,6 +106,7 @@ def __init__( Args: address: The address of the server to connect to, including scheme. proto_json: Whether to use JSON for the protocol. + protocol: The [ProtocolType][] to use for requests. accept_compression: Compression algorithms to accept from the server. If unset, defaults to gzip. If set to empty, disables response compression. send_compression: Compression algorithm to use for sending requests. If unset, diff --git a/src/connectrpc/_client_sync.py b/src/connectrpc/_client_sync.py index 79ea161..7fa373e 100644 --- a/src/connectrpc/_client_sync.py +++ b/src/connectrpc/_client_sync.py @@ -96,6 +96,7 @@ def __init__( Args: address: The address of the server to connect to, including scheme. proto_json: Whether to use JSON for the protocol. + protocol: The [ProtocolType][] to use for requests. accept_compression: Compression algorithms to accept from the server. If unset, defaults to gzip. If set to empty, disables response compression. send_compression: Compression algorithm to use for sending requests. If unset, diff --git a/src/connectrpc/_server_shared.py b/src/connectrpc/_server_shared.py index 18f8f83..512c2a2 100644 --- a/src/connectrpc/_server_shared.py +++ b/src/connectrpc/_server_shared.py @@ -21,7 +21,7 @@ class Endpoint(Generic[REQ, RES]): Represents an endpoint in a service. Attributes: - method: The method to map the the RPC function. + method: The method to map to the RPC function. """ method: MethodInfo[REQ, RES] @@ -83,7 +83,7 @@ class EndpointSync(Generic[REQ, RES]): Represents a sync endpoint in a service. Attributes: - method: The method to map the RPC function. + method: The method to map to the RPC function. """ method: MethodInfo[REQ, RES] diff --git a/src/connectrpc/_server_sync.py b/src/connectrpc/_server_sync.py index df27c1c..3182b1e 100644 --- a/src/connectrpc/_server_sync.py +++ b/src/connectrpc/_server_sync.py @@ -172,7 +172,8 @@ def __init__( """Initialize the WSGI application. Args: - endpoints: A mapping of URL paths to service endpoints. + endpoints: A mapping of URL paths to endpoints. Typically provided directly + by generated code from the Connect Python plugin. interceptors: A sequence of interceptors to apply to the endpoints. read_max_bytes: Maximum size of request messages. compressions: Supported compression algorithms. If unset, defaults to gzip. From df29dccc9071f41e7f9eaa8efa77529999c5580c Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 17 Mar 2026 11:50:52 -0400 Subject: [PATCH 4/4] Fix zensical paths to refer to just `src` directory Also improves a type hint for `_default_compressions` that was firing in my $EDITOR. Ref: https://github.com/zensical/zensical/issues/451#issuecomment-4075985352 Signed-off-by: Stefan VanBuren --- justfile | 4 ---- src/connectrpc/_compression.py | 2 +- zensical.toml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/justfile b/justfile index 7b824de..2b7a566 100644 --- a/justfile +++ b/justfile @@ -67,8 +67,4 @@ bump semver: uv version --bump={{ semver }} --directory protoc-gen-connect-python serve-docs: - # It seems like when iterating on docstrings in src/, re-runs of `zensical - # serve` do not properly cache-bust, so for now, remove the .cache - # directory before running. - rm -rf .cache uv run --group docs zensical serve diff --git a/src/connectrpc/_compression.py b/src/connectrpc/_compression.py index b4d806a..eb462f8 100644 --- a/src/connectrpc/_compression.py +++ b/src/connectrpc/_compression.py @@ -26,7 +26,7 @@ def decompress(self, data: bytes | bytearray | memoryview) -> bytes: _identity = IdentityCompression() _gzip = GzipCompression() -_default_compressions = {"gzip": _gzip, "identity": _identity} +_default_compressions: dict[str, Compression] = {"gzip": _gzip, "identity": _identity} def resolve_compressions( diff --git a/zensical.toml b/zensical.toml index 51a60ee..bad4943 100644 --- a/zensical.toml +++ b/zensical.toml @@ -28,7 +28,7 @@ alternate_style = true [project.plugins.mkdocstrings.handlers.python] inventories = ["https://docs.python.org/3/objects.inv"] -paths = ["."] +paths = ["src"] [project.plugins.mkdocstrings.handlers.python.options] docstring_options = { ignore_init_summary = true }