From 50f30a2c1f9bcb0555b0904dbefc849a50922483 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 17 Jul 2026 14:23:00 +0900 Subject: [PATCH] Render docstrings and expose service descriptor Signed-off-by: Anuraag Agrawal --- .../conformance/v1/service_connect.py | 506 ++++++++++++++++ .../grpc/reflection/v1/reflection_connect.py | 30 + .../reflection/v1alpha/reflection_connect.py | 30 + .../connectrpc/example/haberdasher_connect.py | 564 ++++++++++++++++++ .../test/test_grpcreflect.py | 4 +- .../gen/connectrpc/eliza/v1/eliza_connect.py | 90 +++ poe_tasks.toml | 10 +- .../protoc_gen_connectrpc/__init__.py | 60 +- pyproject.toml | 6 + test/buf.gen.yaml | 2 + .../connectrpc/example/haberdasher_connect.py | 86 +++ .../connectrpc/example/haberdasher_connect.py | 76 +++ 12 files changed, 1460 insertions(+), 4 deletions(-) create mode 100644 connectrpc-grpcreflect/test/connectrpc/example/haberdasher_connect.py diff --git a/conformance/test/gen/connectrpc/conformance/v1/service_connect.py b/conformance/test/gen/connectrpc/conformance/v1/service_connect.py index 5f0eb04..f4c0df7 100644 --- a/conformance/test/gen/connectrpc/conformance/v1/service_connect.py +++ b/conformance/test/gen/connectrpc/conformance/v1/service_connect.py @@ -13,7 +13,9 @@ from connectrpc.errors import ConnectError from connectrpc.method import IdempotencyLevel, MethodInfo from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService +from . import service_pb from .service_pb import BidiStreamRequest, BidiStreamResponse, ClientStreamRequest, ClientStreamResponse, IdempotentUnaryRequest, IdempotentUnaryResponse, ServerStreamRequest, ServerStreamResponse, UnaryRequest, UnaryResponse, UnimplementedRequest, UnimplementedResponse if TYPE_CHECKING: @@ -26,24 +28,152 @@ class ConformanceService(Protocol): + """ + The service implemented by conformance test servers. This is implemented by + the reference servers, used to test clients, and is expected to be implemented + by test servers, since this is the service used by reference clients. + + Test servers must implement the service as described. + """ async def unary(self, request: UnaryRequest, ctx: RequestContext[UnaryRequest, UnaryResponse]) -> UnaryResponse: + """ + A unary operation. The request indicates the response headers and trailers + and also indicates either a response message or an error to send back. + + Response message data is specified as bytes. The service should echo back + request properties in the ConformancePayload and then include the message + data in the data field. + + If the response_delay_ms duration is specified, the server should wait the + given duration after reading the request before sending the corresponding + response. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def server_stream(self, request: ServerStreamRequest, ctx: RequestContext[ServerStreamRequest, ServerStreamResponse]) -> AsyncIterator[ServerStreamResponse]: + """ + A server-streaming operation. The request indicates the response headers, + response messages, trailers, and an optional error to send back. The + response data should be sent in the order indicated, and the server should + wait between sending response messages as indicated. + + Response message data is specified as bytes. The service should echo back + request properties in the first ConformancePayload, and then include the + message data in the data field. Subsequent messages after the first one + should contain only the data field. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. When there + are no responses to send, servers should throw an error if one is provided + and return without error if one is not. Stream headers and trailers should + still be set on the stream if provided regardless of whether a response is + sent or an error is thrown. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def client_stream(self, request: AsyncIterator[ClientStreamRequest], ctx: RequestContext[ClientStreamRequest, ClientStreamResponse]) -> ClientStreamResponse: + """ + A client-streaming operation. The first request indicates the response + headers and trailers and also indicates either a response message or an + error to send back. + + Response message data is specified as bytes. The service should echo back + request properties, including all request messages in the order they were + received, in the ConformancePayload and then include the message data in + the data field. + + If the input stream is empty, the server's response will include no data, + only the request properties (headers, timeout). + + Servers should only read the response definition from the first message in + the stream and should ignore any definition set in subsequent messages. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def bidi_stream(self, request: AsyncIterator[BidiStreamRequest], ctx: RequestContext[BidiStreamRequest, BidiStreamResponse]) -> AsyncIterator[BidiStreamResponse]: + """ + A bidirectional-streaming operation. The first request indicates the response + headers, response messages, trailers, and an optional error to send back. + The response data should be sent in the order indicated, and the server + should wait between sending response messages as indicated. + + Response message data is specified as bytes and should be included in the + data field of the ConformancePayload in each response. + + Servers should send responses indicated according to the rules of half duplex + vs. full duplex streams. Once all responses are sent, the server should either + return an error if specified or close the stream without error. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. Stream + headers and trailers should always be set on the stream if provided + regardless of whether a response is sent or an error is thrown. + + If the full_duplex field is true: + - the handler should read one request and then send back one response, and + then alternate, reading another request and then sending back another response, etc. + + - if the server receives a request and has no responses to send, it + should throw the error specified in the request. + + - the service should echo back all request properties in the first response + including the last received request. Subsequent responses should only + echo back the last received request. + + - if the response_delay_ms duration is specified, the server should wait the given + duration after reading the request before sending the corresponding + response. + + If the full_duplex field is false: + - the handler should read all requests until the client is done sending. + Once all requests are read, the server should then send back any responses + specified in the response definition. + + - the server should echo back all request properties, including all request + messages in the order they were received, in the first response. Subsequent + responses should only include the message data in the data field. + + - if the response_delay_ms duration is specified, the server should wait that + long in between sending each response message. + + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def unimplemented(self, request: UnimplementedRequest, ctx: RequestContext[UnimplementedRequest, UnimplementedResponse]) -> UnimplementedResponse: + """ + A unary endpoint that the server should not implement and should instead + return an unimplemented error when invoked. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def idempotent_unary(self, request: IdempotentUnaryRequest, ctx: RequestContext[IdempotentUnaryRequest, IdempotentUnaryResponse]) -> IdempotentUnaryResponse: + """ + A unary endpoint denoted as having no side effects (i.e. idempotent). + Implementations should use an HTTP GET when invoking this endpoint and + leverage query parameters to send data. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in service_pb.desc().services if s.type_name == 'connectrpc.conformance.v1.ConformanceService') class ConformanceServiceASGIApplication(ConnectASGIApplication[ConformanceService]): def __init__( @@ -132,6 +262,13 @@ def path(self) -> str: class ConformanceServiceClient(ConnectClient): + """ + The service implemented by conformance test servers. This is implemented by + the reference servers, used to test clients, and is expected to be implemented + by test servers, since this is the service used by reference clients. + + Test servers must implement the service as described. + """ async def unary( self, request: UnaryRequest, @@ -139,6 +276,22 @@ async def unary( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> UnaryResponse: + """ + A unary operation. The request indicates the response headers and trailers + and also indicates either a response message or an error to send back. + + Response message data is specified as bytes. The service should echo back + request properties in the ConformancePayload and then include the message + data in the data field. + + If the response_delay_ms duration is specified, the server should wait the + given duration after reading the request before sending the corresponding + response. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -159,6 +312,28 @@ def server_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[ServerStreamResponse]: + """ + A server-streaming operation. The request indicates the response headers, + response messages, trailers, and an optional error to send back. The + response data should be sent in the order indicated, and the server should + wait between sending response messages as indicated. + + Response message data is specified as bytes. The service should echo back + request properties in the first ConformancePayload, and then include the + message data in the data field. Subsequent messages after the first one + should contain only the data field. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. When there + are no responses to send, servers should throw an error if one is provided + and return without error if one is not. Stream headers and trailers should + still be set on the stream if provided regardless of whether a response is + sent or an error is thrown. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -179,6 +354,26 @@ async def client_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> ClientStreamResponse: + """ + A client-streaming operation. The first request indicates the response + headers and trailers and also indicates either a response message or an + error to send back. + + Response message data is specified as bytes. The service should echo back + request properties, including all request messages in the order they were + received, in the ConformancePayload and then include the message data in + the data field. + + If the input stream is empty, the server's response will include no data, + only the request properties (headers, timeout). + + Servers should only read the response definition from the first message in + the stream and should ignore any definition set in subsequent messages. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ return await self.execute_client_stream( request=request, method=MethodInfo( @@ -199,6 +394,56 @@ def bidi_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[BidiStreamResponse]: + """ + A bidirectional-streaming operation. The first request indicates the response + headers, response messages, trailers, and an optional error to send back. + The response data should be sent in the order indicated, and the server + should wait between sending response messages as indicated. + + Response message data is specified as bytes and should be included in the + data field of the ConformancePayload in each response. + + Servers should send responses indicated according to the rules of half duplex + vs. full duplex streams. Once all responses are sent, the server should either + return an error if specified or close the stream without error. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. Stream + headers and trailers should always be set on the stream if provided + regardless of whether a response is sent or an error is thrown. + + If the full_duplex field is true: + - the handler should read one request and then send back one response, and + then alternate, reading another request and then sending back another response, etc. + + - if the server receives a request and has no responses to send, it + should throw the error specified in the request. + + - the service should echo back all request properties in the first response + including the last received request. Subsequent responses should only + echo back the last received request. + + - if the response_delay_ms duration is specified, the server should wait the given + duration after reading the request before sending the corresponding + response. + + If the full_duplex field is false: + - the handler should read all requests until the client is done sending. + Once all requests are read, the server should then send back any responses + specified in the response definition. + + - the server should echo back all request properties, including all request + messages in the order they were received, in the first response. Subsequent + responses should only include the message data in the data field. + + - if the response_delay_ms duration is specified, the server should wait that + long in between sending each response message. + + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -219,6 +464,10 @@ async def unimplemented( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> UnimplementedResponse: + """ + A unary endpoint that the server should not implement and should instead + return an unimplemented error when invoked. + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -240,6 +489,11 @@ async def idempotent_unary( timeout_ms: int | None = None, use_get: bool = False, ) -> IdempotentUnaryResponse: + """ + A unary endpoint denoted as having no side effects (i.e. idempotent). + Implementations should use an HTTP GET when invoking this endpoint and + leverage query parameters to send data. + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -255,24 +509,152 @@ async def idempotent_unary( ) class ConformanceServiceSync(Protocol): + """ + The service implemented by conformance test servers. This is implemented by + the reference servers, used to test clients, and is expected to be implemented + by test servers, since this is the service used by reference clients. + + Test servers must implement the service as described. + """ def unary(self, request: UnaryRequest, ctx: RequestContext[UnaryRequest, UnaryResponse]) -> UnaryResponse: + """ + A unary operation. The request indicates the response headers and trailers + and also indicates either a response message or an error to send back. + + Response message data is specified as bytes. The service should echo back + request properties in the ConformancePayload and then include the message + data in the data field. + + If the response_delay_ms duration is specified, the server should wait the + given duration after reading the request before sending the corresponding + response. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def server_stream(self, request: ServerStreamRequest, ctx: RequestContext[ServerStreamRequest, ServerStreamResponse]) -> Iterator[ServerStreamResponse]: + """ + A server-streaming operation. The request indicates the response headers, + response messages, trailers, and an optional error to send back. The + response data should be sent in the order indicated, and the server should + wait between sending response messages as indicated. + + Response message data is specified as bytes. The service should echo back + request properties in the first ConformancePayload, and then include the + message data in the data field. Subsequent messages after the first one + should contain only the data field. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. When there + are no responses to send, servers should throw an error if one is provided + and return without error if one is not. Stream headers and trailers should + still be set on the stream if provided regardless of whether a response is + sent or an error is thrown. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def client_stream(self, request: Iterator[ClientStreamRequest], ctx: RequestContext[ClientStreamRequest, ClientStreamResponse]) -> ClientStreamResponse: + """ + A client-streaming operation. The first request indicates the response + headers and trailers and also indicates either a response message or an + error to send back. + + Response message data is specified as bytes. The service should echo back + request properties, including all request messages in the order they were + received, in the ConformancePayload and then include the message data in + the data field. + + If the input stream is empty, the server's response will include no data, + only the request properties (headers, timeout). + + Servers should only read the response definition from the first message in + the stream and should ignore any definition set in subsequent messages. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def bidi_stream(self, request: Iterator[BidiStreamRequest], ctx: RequestContext[BidiStreamRequest, BidiStreamResponse]) -> Iterator[BidiStreamResponse]: + """ + A bidirectional-streaming operation. The first request indicates the response + headers, response messages, trailers, and an optional error to send back. + The response data should be sent in the order indicated, and the server + should wait between sending response messages as indicated. + + Response message data is specified as bytes and should be included in the + data field of the ConformancePayload in each response. + + Servers should send responses indicated according to the rules of half duplex + vs. full duplex streams. Once all responses are sent, the server should either + return an error if specified or close the stream without error. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. Stream + headers and trailers should always be set on the stream if provided + regardless of whether a response is sent or an error is thrown. + + If the full_duplex field is true: + - the handler should read one request and then send back one response, and + then alternate, reading another request and then sending back another response, etc. + + - if the server receives a request and has no responses to send, it + should throw the error specified in the request. + + - the service should echo back all request properties in the first response + including the last received request. Subsequent responses should only + echo back the last received request. + + - if the response_delay_ms duration is specified, the server should wait the given + duration after reading the request before sending the corresponding + response. + + If the full_duplex field is false: + - the handler should read all requests until the client is done sending. + Once all requests are read, the server should then send back any responses + specified in the response definition. + + - the server should echo back all request properties, including all request + messages in the order they were received, in the first response. Subsequent + responses should only include the message data in the data field. + + - if the response_delay_ms duration is specified, the server should wait that + long in between sending each response message. + + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def unimplemented(self, request: UnimplementedRequest, ctx: RequestContext[UnimplementedRequest, UnimplementedResponse]) -> UnimplementedResponse: + """ + A unary endpoint that the server should not implement and should instead + return an unimplemented error when invoked. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def idempotent_unary(self, request: IdempotentUnaryRequest, ctx: RequestContext[IdempotentUnaryRequest, IdempotentUnaryResponse]) -> IdempotentUnaryResponse: + """ + A unary endpoint denoted as having no side effects (i.e. idempotent). + Implementations should use an HTTP GET when invoking this endpoint and + leverage query parameters to send data. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in service_pb.desc().services if s.type_name == 'connectrpc.conformance.v1.ConformanceService') class ConformanceServiceWSGIApplication(ConnectWSGIApplication): def __init__( @@ -359,6 +741,13 @@ def path(self) -> str: class ConformanceServiceClientSync(ConnectClientSync): + """ + The service implemented by conformance test servers. This is implemented by + the reference servers, used to test clients, and is expected to be implemented + by test servers, since this is the service used by reference clients. + + Test servers must implement the service as described. + """ def unary( self, request: UnaryRequest, @@ -366,6 +755,22 @@ def unary( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> UnaryResponse: + """ + A unary operation. The request indicates the response headers and trailers + and also indicates either a response message or an error to send back. + + Response message data is specified as bytes. The service should echo back + request properties in the ConformancePayload and then include the message + data in the data field. + + If the response_delay_ms duration is specified, the server should wait the + given duration after reading the request before sending the corresponding + response. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ return self.execute_unary( request=request, method=MethodInfo( @@ -385,6 +790,28 @@ def server_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[ServerStreamResponse]: + """ + A server-streaming operation. The request indicates the response headers, + response messages, trailers, and an optional error to send back. The + response data should be sent in the order indicated, and the server should + wait between sending response messages as indicated. + + Response message data is specified as bytes. The service should echo back + request properties in the first ConformancePayload, and then include the + message data in the data field. Subsequent messages after the first one + should contain only the data field. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. When there + are no responses to send, servers should throw an error if one is provided + and return without error if one is not. Stream headers and trailers should + still be set on the stream if provided regardless of whether a response is + sent or an error is thrown. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -404,6 +831,26 @@ def client_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> ClientStreamResponse: + """ + A client-streaming operation. The first request indicates the response + headers and trailers and also indicates either a response message or an + error to send back. + + Response message data is specified as bytes. The service should echo back + request properties, including all request messages in the order they were + received, in the ConformancePayload and then include the message data in + the data field. + + If the input stream is empty, the server's response will include no data, + only the request properties (headers, timeout). + + Servers should only read the response definition from the first message in + the stream and should ignore any definition set in subsequent messages. + + Servers should allow the response definition to be unset in the request and + if it is, set no response headers or trailers and return no response data. + The returned payload should only contain the request info. + """ return self.execute_client_stream( request=request, method=MethodInfo( @@ -423,6 +870,56 @@ def bidi_stream( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[BidiStreamResponse]: + """ + A bidirectional-streaming operation. The first request indicates the response + headers, response messages, trailers, and an optional error to send back. + The response data should be sent in the order indicated, and the server + should wait between sending response messages as indicated. + + Response message data is specified as bytes and should be included in the + data field of the ConformancePayload in each response. + + Servers should send responses indicated according to the rules of half duplex + vs. full duplex streams. Once all responses are sent, the server should either + return an error if specified or close the stream without error. + + Servers should immediately send response headers on the stream before sleeping + for any specified response delay and/or sending the first message so that + clients can be unblocked reading response headers. + + If a response definition is not specified OR is specified, but response data + is empty, the server should skip sending anything on the stream. Stream + headers and trailers should always be set on the stream if provided + regardless of whether a response is sent or an error is thrown. + + If the full_duplex field is true: + - the handler should read one request and then send back one response, and + then alternate, reading another request and then sending back another response, etc. + + - if the server receives a request and has no responses to send, it + should throw the error specified in the request. + + - the service should echo back all request properties in the first response + including the last received request. Subsequent responses should only + echo back the last received request. + + - if the response_delay_ms duration is specified, the server should wait the given + duration after reading the request before sending the corresponding + response. + + If the full_duplex field is false: + - the handler should read all requests until the client is done sending. + Once all requests are read, the server should then send back any responses + specified in the response definition. + + - the server should echo back all request properties, including all request + messages in the order they were received, in the first response. Subsequent + responses should only include the message data in the data field. + + - if the response_delay_ms duration is specified, the server should wait that + long in between sending each response message. + + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -442,6 +939,10 @@ def unimplemented( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> UnimplementedResponse: + """ + A unary endpoint that the server should not implement and should instead + return an unimplemented error when invoked. + """ return self.execute_unary( request=request, method=MethodInfo( @@ -462,6 +963,11 @@ def idempotent_unary( timeout_ms: int | None = None, use_get: bool = False, ) -> IdempotentUnaryResponse: + """ + A unary endpoint denoted as having no side effects (i.e. idempotent). + Implementations should use an HTTP GET when invoking this endpoint and + leverage query parameters to send data. + """ return self.execute_unary( request=request, method=MethodInfo( diff --git a/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1/reflection_connect.py b/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1/reflection_connect.py index 3e5ea45..f3586d3 100644 --- a/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1/reflection_connect.py +++ b/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1/reflection_connect.py @@ -13,7 +13,9 @@ from connectrpc.errors import ConnectError from connectrpc.method import IdempotencyLevel, MethodInfo from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService +from . import reflection_pb from .reflection_pb import ServerReflectionRequest, ServerReflectionResponse if TYPE_CHECKING: @@ -26,9 +28,18 @@ class ServerReflection(Protocol): + """""" def server_reflection_info(self, request: AsyncIterator[ServerReflectionRequest], ctx: RequestContext[ServerReflectionRequest, ServerReflectionResponse]) -> AsyncIterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in reflection_pb.desc().services if s.type_name == 'grpc.reflection.v1.ServerReflection') class ServerReflectionASGIApplication(ConnectASGIApplication[ServerReflection]): def __init__( @@ -67,6 +78,7 @@ def path(self) -> str: class ServerReflectionClient(ConnectClient): + """""" def server_reflection_info( self, request: AsyncIterator[ServerReflectionRequest], @@ -74,6 +86,10 @@ def server_reflection_info( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -88,9 +104,18 @@ def server_reflection_info( ) class ServerReflectionSync(Protocol): + """""" def server_reflection_info(self, request: Iterator[ServerReflectionRequest], ctx: RequestContext[ServerReflectionRequest, ServerReflectionResponse]) -> Iterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in reflection_pb.desc().services if s.type_name == 'grpc.reflection.v1.ServerReflection') class ServerReflectionWSGIApplication(ConnectWSGIApplication): def __init__( @@ -127,6 +152,7 @@ def path(self) -> str: class ServerReflectionClientSync(ConnectClientSync): + """""" def server_reflection_info( self, request: Iterator[ServerReflectionRequest], @@ -134,6 +160,10 @@ def server_reflection_info( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( diff --git a/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1alpha/reflection_connect.py b/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1alpha/reflection_connect.py index d940d19..8ea89d3 100644 --- a/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1alpha/reflection_connect.py +++ b/connectrpc-grpcreflect/connectrpc_grpcreflect/_gen/grpc/reflection/v1alpha/reflection_connect.py @@ -13,7 +13,9 @@ from connectrpc.errors import ConnectError from connectrpc.method import IdempotencyLevel, MethodInfo from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService +from . import reflection_pb from .reflection_pb import ServerReflectionRequest, ServerReflectionResponse if TYPE_CHECKING: @@ -26,9 +28,18 @@ class ServerReflection(Protocol): + """""" def server_reflection_info(self, request: AsyncIterator[ServerReflectionRequest], ctx: RequestContext[ServerReflectionRequest, ServerReflectionResponse]) -> AsyncIterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in reflection_pb.desc().services if s.type_name == 'grpc.reflection.v1alpha.ServerReflection') class ServerReflectionASGIApplication(ConnectASGIApplication[ServerReflection]): def __init__( @@ -67,6 +78,7 @@ def path(self) -> str: class ServerReflectionClient(ConnectClient): + """""" def server_reflection_info( self, request: AsyncIterator[ServerReflectionRequest], @@ -74,6 +86,10 @@ def server_reflection_info( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -88,9 +104,18 @@ def server_reflection_info( ) class ServerReflectionSync(Protocol): + """""" def server_reflection_info(self, request: Iterator[ServerReflectionRequest], ctx: RequestContext[ServerReflectionRequest, ServerReflectionResponse]) -> Iterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in reflection_pb.desc().services if s.type_name == 'grpc.reflection.v1alpha.ServerReflection') class ServerReflectionWSGIApplication(ConnectWSGIApplication): def __init__( @@ -127,6 +152,7 @@ def path(self) -> str: class ServerReflectionClientSync(ConnectClientSync): + """""" def server_reflection_info( self, request: Iterator[ServerReflectionRequest], @@ -134,6 +160,10 @@ def server_reflection_info( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[ServerReflectionResponse]: + """ + The reflection service is structured as a bidirectional stream, ensuring + all related requests go to a single server. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( diff --git a/connectrpc-grpcreflect/test/connectrpc/example/haberdasher_connect.py b/connectrpc-grpcreflect/test/connectrpc/example/haberdasher_connect.py new file mode 100644 index 0000000..65110bf --- /dev/null +++ b/connectrpc-grpcreflect/test/connectrpc/example/haberdasher_connect.py @@ -0,0 +1,564 @@ +# Generated from connectrpc/example/haberdasher.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.11.1 with parameter "". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService +from protobuf.wkt import Empty + +from . import haberdasher_pb +from .haberdasher_pb import Hat, Size + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext + + +class Haberdasher(Protocol): + """ + A Haberdasher makes hats for clients. + """ + async def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def make_flexible_hat(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def make_various_hats(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in haberdasher_pb.desc().services if s.type_name == 'connectrpc.example.Haberdasher') + +class HaberdasherASGIApplication(ConnectASGIApplication[Haberdasher]): + def __init__( + self, + service: Haberdasher | AsyncGenerator[Haberdasher], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = None, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/connectrpc.example.Haberdasher/MakeHat": Endpoint.unary( + method=MethodInfo( + name="MakeHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + function=svc.make_hat, + ), + "/connectrpc.example.Haberdasher/MakeFlexibleHat": Endpoint.client_stream( + method=MethodInfo( + name="MakeFlexibleHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.make_flexible_hat, + ), + "/connectrpc.example.Haberdasher/MakeSimilarHats": Endpoint.server_stream( + method=MethodInfo( + name="MakeSimilarHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + function=svc.make_similar_hats, + ), + "/connectrpc.example.Haberdasher/MakeVariousHats": Endpoint.bidi_stream( + method=MethodInfo( + name="MakeVariousHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.make_various_hats, + ), + "/connectrpc.example.Haberdasher/ListParts": Endpoint.server_stream( + method=MethodInfo( + name="ListParts", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Hat.Part, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.list_parts, + ), + "/connectrpc.example.Haberdasher/DoNothing": Endpoint.unary( + method=MethodInfo( + name="DoNothing", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.do_nothing, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/connectrpc.example.Haberdasher" + + +class HaberdasherClient(ConnectClient): + """ + A Haberdasher makes hats for clients. + """ + async def make_hat( + self, + request: Size, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + use_get: bool = False, + ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="MakeHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + headers=headers, + timeout_ms=timeout_ms, + use_get=use_get, + ) + + async def make_flexible_hat( + self, + request: AsyncIterator[Size], + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ + return await self.execute_client_stream( + request=request, + method=MethodInfo( + name="MakeFlexibleHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def make_similar_hats( + self, + request: Size, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="MakeSimilarHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def make_various_hats( + self, + request: AsyncIterator[Size], + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ + return self.execute_bidi_stream( + request=request, + method=MethodInfo( + name="MakeVariousHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def list_parts( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="ListParts", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Hat.Part, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def do_nothing( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Empty: + """""" + return await self.execute_unary( + request=request, + method=MethodInfo( + name="DoNothing", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + +class HaberdasherSync(Protocol): + """ + A Haberdasher makes hats for clients. + """ + def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def make_flexible_hat(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def make_various_hats(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in haberdasher_pb.desc().services if s.type_name == 'connectrpc.example.Haberdasher') + +class HaberdasherWSGIApplication(ConnectWSGIApplication): + def __init__( + self, + service: HaberdasherSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = None, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + endpoints={ + "/connectrpc.example.Haberdasher/MakeHat": EndpointSync.unary( + method=MethodInfo( + name="MakeHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + function=service.make_hat, + ), + "/connectrpc.example.Haberdasher/MakeFlexibleHat": EndpointSync.client_stream( + method=MethodInfo( + name="MakeFlexibleHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.make_flexible_hat, + ), + "/connectrpc.example.Haberdasher/MakeSimilarHats": EndpointSync.server_stream( + method=MethodInfo( + name="MakeSimilarHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + function=service.make_similar_hats, + ), + "/connectrpc.example.Haberdasher/MakeVariousHats": EndpointSync.bidi_stream( + method=MethodInfo( + name="MakeVariousHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.make_various_hats, + ), + "/connectrpc.example.Haberdasher/ListParts": EndpointSync.server_stream( + method=MethodInfo( + name="ListParts", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Hat.Part, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.list_parts, + ), + "/connectrpc.example.Haberdasher/DoNothing": EndpointSync.unary( + method=MethodInfo( + name="DoNothing", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.do_nothing, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/connectrpc.example.Haberdasher" + + +class HaberdasherClientSync(ConnectClientSync): + """ + A Haberdasher makes hats for clients. + """ + def make_hat( + self, + request: Size, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + use_get: bool = False, + ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="MakeHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + headers=headers, + timeout_ms=timeout_ms, + use_get=use_get, + ) + def make_flexible_hat( + self, + request: Iterator[Size], + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ + return self.execute_client_stream( + request=request, + method=MethodInfo( + name="MakeFlexibleHat", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def make_similar_hats( + self, + request: Size, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="MakeSimilarHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.NO_SIDE_EFFECTS, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def make_various_hats( + self, + request: Iterator[Size], + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ + return self.execute_bidi_stream( + request=request, + method=MethodInfo( + name="MakeVariousHats", + service_name="connectrpc.example.Haberdasher", + input=Size, + output=Hat, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def list_parts( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="ListParts", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Hat.Part, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def do_nothing( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Empty: + """""" + return self.execute_unary( + request=request, + method=MethodInfo( + name="DoNothing", + service_name="connectrpc.example.Haberdasher", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/connectrpc-grpcreflect/test/test_grpcreflect.py b/connectrpc-grpcreflect/test/test_grpcreflect.py index 7cd4dd6..de63c6d 100644 --- a/connectrpc-grpcreflect/test/test_grpcreflect.py +++ b/connectrpc-grpcreflect/test/test_grpcreflect.py @@ -47,7 +47,7 @@ ServerReflectionRequest as ServerReflectionAlphaRequest, ) -from .connectrpc.example import haberdasher_pb +from .connectrpc.example import haberdasher_connect, haberdasher_pb if TYPE_CHECKING: from collections.abc import AsyncIterator @@ -123,7 +123,7 @@ async def test_list_services(reflection_client: ReflectionClient) -> None: def test_list_services_service_descriptor() -> None: app = ServerReflectionWSGIApplication( - ServerReflectionServiceSync(haberdasher_pb.desc().services[0]) + ServerReflectionServiceSync(haberdasher_connect.Haberdasher.desc()) ) with ServerReflectionClientSync( diff --git a/example/example/gen/connectrpc/eliza/v1/eliza_connect.py b/example/example/gen/connectrpc/eliza/v1/eliza_connect.py index b7dde0e..e698f66 100644 --- a/example/example/gen/connectrpc/eliza/v1/eliza_connect.py +++ b/example/example/gen/connectrpc/eliza/v1/eliza_connect.py @@ -13,7 +13,9 @@ from connectrpc.errors import ConnectError from connectrpc.method import IdempotencyLevel, MethodInfo from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService +from . import eliza_pb from .eliza_pb import ConverseRequest, ConverseResponse, IntroduceRequest, IntroduceResponse, SayRequest, SayResponse if TYPE_CHECKING: @@ -26,15 +28,39 @@ class ElizaService(Protocol): + """ + ElizaService provides a way to talk to Eliza, a port of the DOCTOR script + for Joseph Weizenbaum's original ELIZA program. Created in the mid-1960s at + the MIT Artificial Intelligence Laboratory, ELIZA demonstrates the + superficiality of human-computer communication. DOCTOR simulates a + psychotherapist, and is commonly found as an Easter egg in emacs + distributions. + """ async def say(self, request: SayRequest, ctx: RequestContext[SayRequest, SayResponse]) -> SayResponse: + """ + Say is a unary RPC. Eliza responds to the prompt with a single sentence. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def converse(self, request: AsyncIterator[ConverseRequest], ctx: RequestContext[ConverseRequest, ConverseResponse]) -> AsyncIterator[ConverseResponse]: + """ + Converse is a bidirectional RPC. The caller may exchange multiple + back-and-forth messages with Eliza over a long-lived connection. Eliza + responds to each ConverseRequest with a ConverseResponse. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def introduce(self, request: IntroduceRequest, ctx: RequestContext[IntroduceRequest, IntroduceResponse]) -> AsyncIterator[IntroduceResponse]: + """ + Introduce is a server streaming RPC. Given the caller's name, Eliza + returns a stream of sentences to introduce itself. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in eliza_pb.desc().services if s.type_name == 'connectrpc.eliza.v1.ElizaService') class ElizaServiceASGIApplication(ConnectASGIApplication[ElizaService]): def __init__( @@ -93,6 +119,14 @@ def path(self) -> str: class ElizaServiceClient(ConnectClient): + """ + ElizaService provides a way to talk to Eliza, a port of the DOCTOR script + for Joseph Weizenbaum's original ELIZA program. Created in the mid-1960s at + the MIT Artificial Intelligence Laboratory, ELIZA demonstrates the + superficiality of human-computer communication. DOCTOR simulates a + psychotherapist, and is commonly found as an Easter egg in emacs + distributions. + """ async def say( self, request: SayRequest, @@ -101,6 +135,9 @@ async def say( timeout_ms: int | None = None, use_get: bool = False, ) -> SayResponse: + """ + Say is a unary RPC. Eliza responds to the prompt with a single sentence. + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -122,6 +159,11 @@ def converse( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[ConverseResponse]: + """ + Converse is a bidirectional RPC. The caller may exchange multiple + back-and-forth messages with Eliza over a long-lived connection. Eliza + responds to each ConverseRequest with a ConverseResponse. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -142,6 +184,10 @@ def introduce( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[IntroduceResponse]: + """ + Introduce is a server streaming RPC. Given the caller's name, Eliza + returns a stream of sentences to introduce itself. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -156,15 +202,39 @@ def introduce( ) class ElizaServiceSync(Protocol): + """ + ElizaService provides a way to talk to Eliza, a port of the DOCTOR script + for Joseph Weizenbaum's original ELIZA program. Created in the mid-1960s at + the MIT Artificial Intelligence Laboratory, ELIZA demonstrates the + superficiality of human-computer communication. DOCTOR simulates a + psychotherapist, and is commonly found as an Easter egg in emacs + distributions. + """ def say(self, request: SayRequest, ctx: RequestContext[SayRequest, SayResponse]) -> SayResponse: + """ + Say is a unary RPC. Eliza responds to the prompt with a single sentence. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def converse(self, request: Iterator[ConverseRequest], ctx: RequestContext[ConverseRequest, ConverseResponse]) -> Iterator[ConverseResponse]: + """ + Converse is a bidirectional RPC. The caller may exchange multiple + back-and-forth messages with Eliza over a long-lived connection. Eliza + responds to each ConverseRequest with a ConverseResponse. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def introduce(self, request: IntroduceRequest, ctx: RequestContext[IntroduceRequest, IntroduceResponse]) -> Iterator[IntroduceResponse]: + """ + Introduce is a server streaming RPC. Given the caller's name, Eliza + returns a stream of sentences to introduce itself. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in eliza_pb.desc().services if s.type_name == 'connectrpc.eliza.v1.ElizaService') class ElizaServiceWSGIApplication(ConnectWSGIApplication): def __init__( @@ -221,6 +291,14 @@ def path(self) -> str: class ElizaServiceClientSync(ConnectClientSync): + """ + ElizaService provides a way to talk to Eliza, a port of the DOCTOR script + for Joseph Weizenbaum's original ELIZA program. Created in the mid-1960s at + the MIT Artificial Intelligence Laboratory, ELIZA demonstrates the + superficiality of human-computer communication. DOCTOR simulates a + psychotherapist, and is commonly found as an Easter egg in emacs + distributions. + """ def say( self, request: SayRequest, @@ -229,6 +307,9 @@ def say( timeout_ms: int | None = None, use_get: bool = False, ) -> SayResponse: + """ + Say is a unary RPC. Eliza responds to the prompt with a single sentence. + """ return self.execute_unary( request=request, method=MethodInfo( @@ -249,6 +330,11 @@ def converse( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[ConverseResponse]: + """ + Converse is a bidirectional RPC. The caller may exchange multiple + back-and-forth messages with Eliza over a long-lived connection. Eliza + responds to each ConverseRequest with a ConverseResponse. + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -268,6 +354,10 @@ def introduce( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[IntroduceResponse]: + """ + Introduce is a server streaming RPC. Given the caller's name, Eliza + returns a stream of sentences to introduce itself. + """ return self.execute_server_stream( request=request, method=MethodInfo( diff --git a/poe_tasks.toml b/poe_tasks.toml index 18790d5..5379ba9 100644 --- a/poe_tasks.toml +++ b/poe_tasks.toml @@ -97,7 +97,15 @@ cmd = "tombi lint" [tasks.lint-types] help = "Apply type checking to Python files" -cmd = "ty check ." +# Workaround lack of monorepo support by running in each project explicitly. +# https://docs.astral.sh/ty/reference/typing-faq/#does-ty-support-monorepos +# https://github.com/astral-sh/ty/issues/819 +sequence = [ + { cmd = "ty check --project connectrpc-grpcreflect" }, + { cmd = "ty check --project connectrpc-otel" }, + { cmd = "ty check --project example" }, + { cmd = "ty check" }, +] [tasks.test] help = "Run unit tests" diff --git a/protoc-gen-connectrpc/protoc_gen_connectrpc/__init__.py b/protoc-gen-connectrpc/protoc_gen_connectrpc/__init__.py index 1894c9d..79d1cb9 100644 --- a/protoc-gen-connectrpc/protoc_gen_connectrpc/__init__.py +++ b/protoc-gen-connectrpc/protoc_gen_connectrpc/__init__.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from protobuf._sanitization import escape_identifier -from protobuf.plugin import File, Ident, Module, Schema, run +from protobuf.plugin import File, Ident, Module, Schema, get_comments, run from protobuf.wkt import MethodOptions if TYPE_CHECKING: @@ -22,6 +22,9 @@ _TYPING = Module("typing") _PROTOCOL = _TYPING.ident("Protocol") +_PROTOBUF = Module("protobuf") +_DESC_SERVICE = _PROTOBUF.ident("DescService") + _PYQWEST = Module("pyqwest") _PYQWEST_CLIENT = _PYQWEST.ident("Client") _PYQWEST_SYNC_CLIENT = _PYQWEST.ident("SyncClient") @@ -117,6 +120,8 @@ def _generate_async_stubs(f: File, service: DescService, options: Options) -> No "_DEFAULT_CODECS" if options.protobuf == _ProtobufOption.GOOGLE else "None" ) with f.scope("class ", service_name, "(", _PROTOCOL, "):"): + with f.doc(): + _generate_docstring(f, service) for method in service.methods: def_prefix, request_type, response_type = _async_signature(method, options) with f.scope( @@ -135,6 +140,8 @@ def _generate_async_stubs(f: File, service: DescService, options: Options) -> No *response_type, ":", ): + with f.doc(): + _generate_docstring(f, method) f.print( "raise ", _CONNECT_ERROR, @@ -143,6 +150,8 @@ def _generate_async_stubs(f: File, service: DescService, options: Options) -> No ".UNIMPLEMENTED, 'Not implemented')", ) f.print() + _generate_desc_method(f, service, options) + f.print() with f.scope( "class ", @@ -229,6 +238,9 @@ def _generate_async_stubs(f: File, service: DescService, options: Options) -> No f.print() f.print() with f.scope("class ", service_name, "Client(", _CONNECT_CLIENT, "):"): + with f.doc(): + _generate_docstring(f, service) + if options.protobuf == _ProtobufOption.GOOGLE: _print_google_compat_client_init(f, _INTERCEPTOR, _PYQWEST_CLIENT) @@ -251,6 +263,8 @@ def _generate_async_stubs(f: File, service: DescService, options: Options) -> No if _supports_get(method): f.print("use_get: bool = False,") with f.scope(") -> ", *response_type, ":"): + with f.doc(): + _generate_docstring(f, method) await_return = ( "await " if method.method_kind in ("unary", "client_streaming") @@ -297,6 +311,8 @@ def _generate_sync_stubs(f: File, service: DescService, options: Options) -> Non "_DEFAULT_CODECS" if options.protobuf == _ProtobufOption.GOOGLE else "None" ) with f.scope("class ", service_name, "Sync(", _PROTOCOL, "):"): + with f.doc(): + _generate_docstring(f, service) for method in service.methods: request_type, response_type = _sync_signature(method, options) with f.scope( @@ -314,6 +330,8 @@ def _generate_sync_stubs(f: File, service: DescService, options: Options) -> Non *response_type, ":", ): + with f.doc(): + _generate_docstring(f, method) f.print( "raise ", _CONNECT_ERROR, @@ -322,6 +340,8 @@ def _generate_sync_stubs(f: File, service: DescService, options: Options) -> Non ".UNIMPLEMENTED, 'Not implemented')", ) f.print() + _generate_desc_method(f, service, options) + f.print() with f.scope( "class ", @@ -392,6 +412,9 @@ def _generate_sync_stubs(f: File, service: DescService, options: Options) -> Non f.print() f.print() with f.scope("class ", service_name, "ClientSync(", _CONNECT_CLIENT_SYNC, "):"): + with f.doc(): + _generate_docstring(f, service) + if options.protobuf == _ProtobufOption.GOOGLE: _print_google_compat_client_init(f, _INTERCEPTOR_SYNC, _PYQWEST_SYNC_CLIENT) @@ -414,6 +437,8 @@ def _generate_sync_stubs(f: File, service: DescService, options: Options) -> Non if _supports_get(method): f.print("use_get: bool = False,") with f.scope(") -> ", *response_type, ":"): + with f.doc(): + _generate_docstring(f, method) with f.scope("return ", "self.", _client_execute_method(method), "("): f.print("request=request,") with f.scope("method=", _METHOD_INFO, "("): @@ -509,6 +534,39 @@ def _message_ident(method: DescMethod, message: DescMessage, options: Options) - return mod.ident(name) +def _generate_docstring(f: File, desc: DescService | DescMethod) -> None: + comments = get_comments(desc) + text = "" + if comments.leading: + text += comments.leading.removesuffix("\n") + if comments.trailing: + if len(text) > 0: + text += "\n\n" + text += comments.trailing.removesuffix("\n") + if len(text) > 0: + text += "\n" + + for line in text.splitlines(): + # Comments in protobuf often start with a space, this + # leads to weird indentation in the generated docstring, so we remove it. + f.print(line.removeprefix(" ")) + + +def _generate_desc_method(f: File, service: DescService, options: Options) -> None: + if options.protobuf == _ProtobufOption.PY: + f.print("@classmethod") + with f.scope("def desc(cls) -> ", _DESC_SERVICE, ":"): + with f.doc("Returns the descriptor for this service."): + pass + f.print( + "return next(s for s in ", + service.file, + ".desc().services if s.type_name == '", + service.type_name, + "')", + ) + + # https://github.com/grpc/grpc/blob/0dd1b2cad21d89984f9a1b3c6249d649381eeb65/src/compiler/python_generator_helpers.h#L67 def _module_name(filename: str) -> str: filename = filename.removesuffix(".proto") diff --git a/pyproject.toml b/pyproject.toml index 04bff31..6fea5d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -246,6 +246,12 @@ exclude = [ # gRPC generated code references `grpc.experimental`, which is not exposed by the # `grpc` package's public API. See https://github.com/grpc/grpc/issues/39555. "**/*_pb2_grpc.py", + # Workaround lack of monorepo support by excluding subproject code from top-level project. + # https://docs.astral.sh/ty/reference/typing-faq/#does-ty-support-monorepos + # https://github.com/astral-sh/ty/issues/819 + "connectrpc-grpcreflect", + "connectrpc-otel", + "example", ] [tool.ty.environment] diff --git a/test/buf.gen.yaml b/test/buf.gen.yaml index f01d015..cfe3b96 100644 --- a/test/buf.gen.yaml +++ b/test/buf.gen.yaml @@ -7,6 +7,8 @@ plugins: # TODO: Consider extracting a testing package to share haberdasher among packages. - local: protoc-gen-py out: ../connectrpc-grpcreflect/test/ + - local: protoc-gen-connectrpc + out: ../connectrpc-grpcreflect/test/ # NOTE: v26.0 is the earliest version supporting protobuf==5. - remote: buf.build/protocolbuffers/python:v26.0 diff --git a/test/connectrpc/example/haberdasher_connect.py b/test/connectrpc/example/haberdasher_connect.py index fa426e3..65110bf 100644 --- a/test/connectrpc/example/haberdasher_connect.py +++ b/test/connectrpc/example/haberdasher_connect.py @@ -13,8 +13,10 @@ from connectrpc.errors import ConnectError from connectrpc.method import IdempotencyLevel, MethodInfo from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +from protobuf import DescService from protobuf.wkt import Empty +from . import haberdasher_pb from .haberdasher_pb import Hat, Size if TYPE_CHECKING: @@ -27,24 +29,47 @@ class Haberdasher(Protocol): + """ + A Haberdasher makes hats for clients. + """ async def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def make_flexible_hat(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_various_hats(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in haberdasher_pb.desc().services if s.type_name == 'connectrpc.example.Haberdasher') class HaberdasherASGIApplication(ConnectASGIApplication[Haberdasher]): def __init__( @@ -133,6 +158,9 @@ def path(self) -> str: class HaberdasherClient(ConnectClient): + """ + A Haberdasher makes hats for clients. + """ async def make_hat( self, request: Size, @@ -141,6 +169,9 @@ async def make_hat( timeout_ms: int | None = None, use_get: bool = False, ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -162,6 +193,9 @@ async def make_flexible_hat( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ return await self.execute_client_stream( request=request, method=MethodInfo( @@ -182,6 +216,9 @@ def make_similar_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -202,6 +239,9 @@ def make_various_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -222,6 +262,9 @@ def list_parts( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -242,6 +285,7 @@ async def do_nothing( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Empty: + """""" return await self.execute_unary( request=request, method=MethodInfo( @@ -256,24 +300,47 @@ async def do_nothing( ) class HaberdasherSync(Protocol): + """ + A Haberdasher makes hats for clients. + """ def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_flexible_hat(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_various_hats(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in haberdasher_pb.desc().services if s.type_name == 'connectrpc.example.Haberdasher') class HaberdasherWSGIApplication(ConnectWSGIApplication): def __init__( @@ -360,6 +427,9 @@ def path(self) -> str: class HaberdasherClientSync(ConnectClientSync): + """ + A Haberdasher makes hats for clients. + """ def make_hat( self, request: Size, @@ -368,6 +438,9 @@ def make_hat( timeout_ms: int | None = None, use_get: bool = False, ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ return self.execute_unary( request=request, method=MethodInfo( @@ -388,6 +461,9 @@ def make_flexible_hat( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ return self.execute_client_stream( request=request, method=MethodInfo( @@ -407,6 +483,9 @@ def make_similar_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -426,6 +505,9 @@ def make_various_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -445,6 +527,9 @@ def list_parts( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -464,6 +549,7 @@ def do_nothing( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Empty: + """""" return self.execute_unary( request=request, method=MethodInfo( diff --git a/test/google_compat/connectrpc/example/haberdasher_connect.py b/test/google_compat/connectrpc/example/haberdasher_connect.py index 25d8d04..705a1bf 100644 --- a/test/google_compat/connectrpc/example/haberdasher_connect.py +++ b/test/google_compat/connectrpc/example/haberdasher_connect.py @@ -36,22 +36,41 @@ _GZIP_COMPRESSION = GzipCompression() class Haberdasher(Protocol): + """ + A Haberdasher makes hats for clients. + """ async def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def make_flexible_hat(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_various_hats(self, request: AsyncIterator[Size], ctx: RequestContext[Size, Hat]) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') async def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') @@ -142,6 +161,9 @@ def path(self) -> str: class HaberdasherClient(ConnectClient): + """ + A Haberdasher makes hats for clients. + """ def __init__( self, address: str, @@ -174,6 +196,9 @@ async def make_hat( timeout_ms: int | None = None, use_get: bool = False, ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ return await self.execute_unary( request=request, method=MethodInfo( @@ -195,6 +220,9 @@ async def make_flexible_hat( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ return await self.execute_client_stream( request=request, method=MethodInfo( @@ -215,6 +243,9 @@ def make_similar_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -235,6 +266,9 @@ def make_various_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -255,6 +289,9 @@ def list_parts( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> AsyncIterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -275,6 +312,7 @@ async def do_nothing( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Empty: + """""" return await self.execute_unary( request=request, method=MethodInfo( @@ -289,22 +327,41 @@ async def do_nothing( ) class HaberdasherSync(Protocol): + """ + A Haberdasher makes hats for clients. + """ def make_hat(self, request: Size, ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_flexible_hat(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_similar_hats(self, request: Size, ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def make_various_hats(self, request: Iterator[Size], ctx: RequestContext[Size, Hat]) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def list_parts(self, request: Empty, ctx: RequestContext[Empty, Hat.Part]) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') def do_nothing(self, request: Empty, ctx: RequestContext[Empty, Empty]) -> Empty: + """""" raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') @@ -393,6 +450,9 @@ def path(self) -> str: class HaberdasherClientSync(ConnectClientSync): + """ + A Haberdasher makes hats for clients. + """ def __init__( self, address: str, @@ -425,6 +485,9 @@ def make_hat( timeout_ms: int | None = None, use_get: bool = False, ) -> Hat: + """ + MakeHat produces a hat of mysterious, randomly-selected color! + """ return self.execute_unary( request=request, method=MethodInfo( @@ -445,6 +508,9 @@ def make_flexible_hat( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Hat: + """ + MakeFlexibleHats produces a single hat adhering to many sizes. + """ return self.execute_client_stream( request=request, method=MethodInfo( @@ -464,6 +530,9 @@ def make_similar_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat]: + """ + MakeSimilarHats produces hats of mysterious, randomly-selected color following a single order! + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -483,6 +552,9 @@ def make_various_hats( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat]: + """ + MakeVariousHats produces hats of mysterious, randomly-selected color following many orders! + """ return self.execute_bidi_stream( request=request, method=MethodInfo( @@ -502,6 +574,9 @@ def list_parts( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Iterator[Hat.Part]: + """ + ListParts lists available parts for making a hat. + """ return self.execute_server_stream( request=request, method=MethodInfo( @@ -521,6 +596,7 @@ def do_nothing( headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, ) -> Empty: + """""" return self.execute_unary( request=request, method=MethodInfo(