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
|
## 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
|
```protobuf
|
||||||
syntax = "proto2";
|
syntax = "proto3";
|
||||||
|
|
||||||
message EchoMsg {
|
service Greeter {
|
||||||
required string value = 1;
|
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 {
|
message HelloRequest {
|
||||||
rpc Echo (EchoMsg) returns (EchoMsg) {}
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HelloReply {
|
||||||
|
string message = 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -32,19 +39,65 @@ Then generate the services and stubs with `hrpc`.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ pip install hrpc
|
$ 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
|
```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
|
```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.
|
And run them in separate terminals to see the output.
|
||||||
@ -54,6 +107,17 @@ $ python server.py # terminal 1
|
|||||||
$ python client.py # terminal 2
|
$ 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).
|
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)
|
![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__":
|
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__":
|
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