Add a complete greeter example
This commit is contained in:
parent
c85a0bfe85
commit
da82547264
88
README.md
88
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)
|
||||
|
28
example/README.md
Normal file
28
example/README.md
Normal 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
|
||||
```
|
@ -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
16
example/greeter.proto
Normal 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
99
example/greeter_grpc.py
Normal 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
166
example/greeter_pb2.py
Normal 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)
|
@ -1,9 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message EchoMsg {
|
||||
required string value = 1;
|
||||
}
|
||||
|
||||
service Example {
|
||||
rpc Echo (EchoMsg) returns (EchoMsg) {}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user