Add a complete greeter example

This commit is contained in:
Luke Murphy 2020-08-09 03:35:01 +02:00
parent c85a0bfe85
commit da82547264
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
8 changed files with 429 additions and 25 deletions

View File

@ -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)

28
example/README.md Normal file
View File

@ -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
```

View File

@ -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")

16
example/greeter.proto Normal file
View File

@ -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;
}

99
example/greeter_grpc.py Normal file
View File

@ -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,
)
)

166
example/greeter_pb2.py Normal file
View File

@ -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)

View File

@ -1,9 +0,0 @@
syntax = "proto2";
message EchoMsg {
required string value = 1;
}
service Example {
rpc Echo (EchoMsg) returns (EchoMsg) {}
}

View File

@ -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__":
server = Server(50055)
server.add_service(Greeter().service)
try:
server.serve(backend="trio")
except KeyboardInterrupt:
pass