From da82547264d13440002a272084a7fd303809faee Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sun, 9 Aug 2020 03:35:01 +0200 Subject: [PATCH] Add a complete greeter example --- README.md | 88 ++++++++++++++++++--- example/README.md | 28 +++++++ example/client.py | 25 +++++- example/greeter.proto | 16 ++++ example/greeter_grpc.py | 99 ++++++++++++++++++++++++ example/greeter_pb2.py | 166 ++++++++++++++++++++++++++++++++++++++++ example/schema.proto | 9 --- example/server.py | 23 +++++- 8 files changed, 429 insertions(+), 25 deletions(-) create mode 100644 example/README.md create mode 100644 example/greeter.proto create mode 100644 example/greeter_grpc.py create mode 100644 example/greeter_pb2.py delete mode 100644 example/schema.proto diff --git a/README.md b/README.md index 9b5baf2..77612fd 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,26 @@ $ pip install hrpc ## Example -> **TLDR; See the [example](./example/) directory** +> **TLDR; See the [example](./example) directory** -Define an RPC service in a `schema.proto`. +Define an RPC service in a `greeter.proto`. ```protobuf -syntax = "proto2"; +syntax = "proto3"; -message EchoMsg { - required string value = 1; +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayHelloGoodbye (HelloRequest) returns (stream HelloReply) {} + rpc SayHelloToMany (stream HelloRequest) returns (stream HelloReply) {} + rpc SayHelloToManyAtOnce (stream HelloRequest) returns (HelloReply) {} } -service Example { - rpc Echo (EchoMsg) returns (EchoMsg) {} +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; } ``` @@ -32,19 +39,65 @@ Then generate the services and stubs with `hrpc`. ```sh $ pip install hrpc -$ hrpc schema.proto +$ hrpc greeter.proto ``` -This creates `schema_gprc.py` (services) and `schema_pb2.py` (stubs) files. +This creates `greeter_gprc.py` (services) and `greeter_pb2.py` (stubs) files. -You can then write a async-ready server and client like so. +You can then write a async-ready server. ```python -# server.py +"""Greeter server.""" + +from greeter_grpc import GreeterServicer +from greeter_pb2 import HelloReply, HelloRequest +from purerpc import Server + + +class Greeter(GreeterServicer): + async def SayHello(self, message): + return HelloReply(message="Hello, " + message.name) + + async def SayHelloToMany(self, input_messages): + async for message in input_messages: + yield HelloReply(message=f"Hello, {message.name}") + + +if __name__ == "__main__": + server = Server(50055) + server.add_service(Greeter().service) + server.serve(backend="trio") + ``` +And a client. + ```python -# client.py +"""Greeter client.""" + +import anyio +import purerpc +from greeter_grpc import GreeterStub +from greeter_pb2 import HelloReply, HelloRequest + + +async def gen(): + for i in range(5): + yield HelloRequest(name=str(i)) + + +async def main(): + async with purerpc.insecure_channel("localhost", 50055) as channel: + stub = GreeterStub(channel) + reply = await stub.SayHello(HelloRequest(name="World")) + print(reply.message) + + async for reply in stub.SayHelloToMany(gen()): + print(reply.message) + + +if __name__ == "__main__": + anyio.run(main, backend="trio") ``` And run them in separate terminals to see the output. @@ -54,6 +107,17 @@ $ python server.py # terminal 1 $ python client.py # terminal 2 ``` +Output: + +``` +Hello, World +Hello, 0 +Hello, 1 +Hello, 2 +Hello, 3 +Hello, 4 +``` + Go forth and [Remote Procedure Call](https://en.wikipedia.org/wiki/Remote_procedure_call). ![The person who invented the term RPC](https://upload.wikimedia.org/wikipedia/en/9/90/BruceJayNelson.JPG) diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..b48bff3 --- /dev/null +++ b/example/README.md @@ -0,0 +1,28 @@ +# example + +## What Is What + +- **greeter.proto**: human-written protobuf definition +- **greeter_grpc.py**: machine-generated service code +- **greeter_pb2.py**: machine-generated stub code +- **server.py**: human-written server +- **client.py**: human-written client code + +## Run It + +```sh +$ pip install hrpc trio +$ python server.py # terminal 1 +$ python client.py # terminal 2 +``` + +Output: + +``` +Hello, World +Hello, 0 +Hello, 1 +Hello, 2 +Hello, 3 +Hello, 4 +``` diff --git a/example/client.py b/example/client.py index 1e7dc84..aac718a 100644 --- a/example/client.py +++ b/example/client.py @@ -1,4 +1,25 @@ -"""Echo client.""" +"""Greeter client.""" + +import anyio +import purerpc +from greeter_grpc import GreeterStub +from greeter_pb2 import HelloReply, HelloRequest + + +async def gen(): + for i in range(5): + yield HelloRequest(name=str(i)) + + +async def main(): + async with purerpc.insecure_channel("localhost", 50055) as channel: + stub = GreeterStub(channel) + reply = await stub.SayHello(HelloRequest(name="World")) + print(reply.message) + + async for reply in stub.SayHelloToMany(gen()): + print(reply.message) + if __name__ == "__main__": - pass + anyio.run(main, backend="trio") diff --git a/example/greeter.proto b/example/greeter.proto new file mode 100644 index 0000000..ca1e932 --- /dev/null +++ b/example/greeter.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayHelloGoodbye (HelloRequest) returns (stream HelloReply) {} + rpc SayHelloToMany (stream HelloRequest) returns (stream HelloReply) {} + rpc SayHelloToManyAtOnce (stream HelloRequest) returns (HelloReply) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} diff --git a/example/greeter_grpc.py b/example/greeter_grpc.py new file mode 100644 index 0000000..dab8a28 --- /dev/null +++ b/example/greeter_grpc.py @@ -0,0 +1,99 @@ +import purerpc +import greeter_pb2 as greeter__pb2 + + +class GreeterServicer(purerpc.Servicer): + async def SayHello(self, input_message): + raise NotImplementedError() + + async def SayHelloGoodbye(self, input_message): + raise NotImplementedError() + + async def SayHelloToMany(self, input_messages): + raise NotImplementedError() + + async def SayHelloToManyAtOnce(self, input_messages): + raise NotImplementedError() + + @property + def service(self) -> purerpc.Service: + service_obj = purerpc.Service( + "Greeter" + ) + service_obj.add_method( + "SayHello", + self.SayHello, + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_UNARY, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + service_obj.add_method( + "SayHelloGoodbye", + self.SayHelloGoodbye, + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_STREAM, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + service_obj.add_method( + "SayHelloToMany", + self.SayHelloToMany, + purerpc.RPCSignature( + purerpc.Cardinality.STREAM_STREAM, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + service_obj.add_method( + "SayHelloToManyAtOnce", + self.SayHelloToManyAtOnce, + purerpc.RPCSignature( + purerpc.Cardinality.STREAM_UNARY, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + return service_obj + + +class GreeterStub: + def __init__(self, channel): + self._client = purerpc.Client( + "Greeter", + channel + ) + self.SayHello = self._client.get_method_stub( + "SayHello", + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_UNARY, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + self.SayHelloGoodbye = self._client.get_method_stub( + "SayHelloGoodbye", + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_STREAM, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + self.SayHelloToMany = self._client.get_method_stub( + "SayHelloToMany", + purerpc.RPCSignature( + purerpc.Cardinality.STREAM_STREAM, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) + self.SayHelloToManyAtOnce = self._client.get_method_stub( + "SayHelloToManyAtOnce", + purerpc.RPCSignature( + purerpc.Cardinality.STREAM_UNARY, + greeter__pb2.HelloRequest, + greeter__pb2.HelloReply, + ) + ) \ No newline at end of file diff --git a/example/greeter_pb2.py b/example/greeter_pb2.py new file mode 100644 index 0000000..8223423 --- /dev/null +++ b/example/greeter_pb2.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: greeter.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='greeter.proto', + package='', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\rgreeter.proto\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xd2\x01\n\x07Greeter\x12(\n\x08SayHello\x12\r.HelloRequest\x1a\x0b.HelloReply\"\x00\x12\x31\n\x0fSayHelloGoodbye\x12\r.HelloRequest\x1a\x0b.HelloReply\"\x00\x30\x01\x12\x32\n\x0eSayHelloToMany\x12\r.HelloRequest\x1a\x0b.HelloReply\"\x00(\x01\x30\x01\x12\x36\n\x14SayHelloToManyAtOnce\x12\r.HelloRequest\x1a\x0b.HelloReply\"\x00(\x01\x62\x06proto3' +) + + + + +_HELLOREQUEST = _descriptor.Descriptor( + name='HelloRequest', + full_name='HelloRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='HelloRequest.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=17, + serialized_end=45, +) + + +_HELLOREPLY = _descriptor.Descriptor( + name='HelloReply', + full_name='HelloReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message', full_name='HelloReply.message', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=47, + serialized_end=76, +) + +DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST +DESCRIPTOR.message_types_by_name['HelloReply'] = _HELLOREPLY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), { + 'DESCRIPTOR' : _HELLOREQUEST, + '__module__' : 'greeter_pb2' + # @@protoc_insertion_point(class_scope:HelloRequest) + }) +_sym_db.RegisterMessage(HelloRequest) + +HelloReply = _reflection.GeneratedProtocolMessageType('HelloReply', (_message.Message,), { + 'DESCRIPTOR' : _HELLOREPLY, + '__module__' : 'greeter_pb2' + # @@protoc_insertion_point(class_scope:HelloReply) + }) +_sym_db.RegisterMessage(HelloReply) + + + +_GREETER = _descriptor.ServiceDescriptor( + name='Greeter', + full_name='Greeter', + file=DESCRIPTOR, + index=0, + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_start=79, + serialized_end=289, + methods=[ + _descriptor.MethodDescriptor( + name='SayHello', + full_name='Greeter.SayHello', + index=0, + containing_service=None, + input_type=_HELLOREQUEST, + output_type=_HELLOREPLY, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), + _descriptor.MethodDescriptor( + name='SayHelloGoodbye', + full_name='Greeter.SayHelloGoodbye', + index=1, + containing_service=None, + input_type=_HELLOREQUEST, + output_type=_HELLOREPLY, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), + _descriptor.MethodDescriptor( + name='SayHelloToMany', + full_name='Greeter.SayHelloToMany', + index=2, + containing_service=None, + input_type=_HELLOREQUEST, + output_type=_HELLOREPLY, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), + _descriptor.MethodDescriptor( + name='SayHelloToManyAtOnce', + full_name='Greeter.SayHelloToManyAtOnce', + index=3, + containing_service=None, + input_type=_HELLOREQUEST, + output_type=_HELLOREPLY, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), +]) +_sym_db.RegisterServiceDescriptor(_GREETER) + +DESCRIPTOR.services_by_name['Greeter'] = _GREETER + +# @@protoc_insertion_point(module_scope) diff --git a/example/schema.proto b/example/schema.proto deleted file mode 100644 index 3247e86..0000000 --- a/example/schema.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto2"; - -message EchoMsg { - required string value = 1; -} - -service Example { - rpc Echo (EchoMsg) returns (EchoMsg) {} -} diff --git a/example/server.py b/example/server.py index 1921bbe..b315aa6 100644 --- a/example/server.py +++ b/example/server.py @@ -1,4 +1,23 @@ -"""Echo server.""" +"""Greeter server.""" + +from greeter_grpc import GreeterServicer +from greeter_pb2 import HelloReply, HelloRequest +from purerpc import Server + + +class Greeter(GreeterServicer): + async def SayHello(self, message): + return HelloReply(message="Hello, " + message.name) + + async def SayHelloToMany(self, input_messages): + async for message in input_messages: + yield HelloReply(message=f"Hello, {message.name}") + if __name__ == "__main__": - pass + server = Server(50055) + server.add_service(Greeter().service) + try: + server.serve(backend="trio") + except KeyboardInterrupt: + pass