Help us understand the problem. What is going on with this article?

PythonでシンプルなgRPC

More than 1 year has passed since last update.

概要

  • PythonでgRPCのシンプルな処理を作成する。
  • RESTのように何らかの値をリクエストして、結果を返す処理を作ってみる。

全体の流れ

  1. grpcio-toolsのインストール
  2. .protoファイルを作成して、コンパイルする。
  3. 自動生成されたソースを使ってserverとclientのソースも作る。
  4. テスト

1. grpcio-toolsのインストール

pipでインストール
$ pip install grpcio-tools

2. protoファイルの作成

simple.proto
syntax = "proto3";

package simple;

// request
message SimpleRequest{
    string name = 1;
    string msg = 2;
}

// response
message SimpleResponse{
    string reply_msg = 1;
}

// interface
service SimpleService{
    rpc SimpleSend (SimpleRequest) returns (SimpleResponse) {}
}

以下のソースを作って実行する。(コンパイル)
同じフォルダにsimple_pb2.pysimple_pb2_grpc.pyが作成される。

codegen.py
from grpc.tools import protoc
protoc.main(
    (
        '',
        '-I.',
        '--python_out=.',
        '--grpc_python_out=.',
        'simple.proto',
    )
)
実行
$ python codegen.py

3. serverとclientのソース作成

サーバ側

server.py
import grpc
import time
import simple_pb2
import simple_pb2_grpc
from concurrent import futures


class SimpleServiceServicer(simple_pb2_grpc.SimpleServiceServicer):
    def __init__(self):
        pass

    def SimpleSend(self, request, context):
        print('logging: name {}, msg {}'.format(request.name, request.msg))
        # protoファイルのResponseと一致させる
        return simple_pb2.SimpleResponse(
            reply_msg = 'Hello! ' + request.name + '. Your message is ' + request.msg
        )


# start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
simple_pb2_grpc.add_SimpleServiceServicer_to_server(SimpleServiceServicer(), server)
server.add_insecure_port('[::]:5051')
server.start()
print('run server')

# wait
try:
    while True:
        time.sleep(3600)
except KeyboardInterrupt:
    # stop server
    server.stop(0)

  • ソース解説
    • クラス名はprotoファイルのservice(SimpleServie)+Servicerと合わせる。
    • 関数名(SimpleSend)はrpc と合わせる。関数名なのでPythonならスネークケース(simple_send)が良いかも
    • simple_pb2SimpleResponseが定義されるため、そこからresponseを返す

クライアント側

client.py
import grpc
import simple_pb2
import simple_pb2_grpc


with grpc.insecure_channel('localhost:5051') as channel:
    stub = simple_pb2_grpc.SimpleServiceStub(channel)
    name = "Tom"
    msg = "Test"
    response = stub.SimpleSend(simple_pb2.SimpleRequest(name=name, msg=msg))

print('Reply: ', response.reply_msg)

  • ソース解説
    • SimpleRequestは引数としてnameとmsgを取る。それに渡してクラスを生成してるイメージ
    • stubのSimpleSend関数を呼び出して、それに引数として生成したクラスを渡してる
    • responseに含まれるreply_msgを出力

4. テスト

serverの起動
$ python server.py
run server
クライアントの実行
$ python client.py
Reply:  Hello! Tom. Your message is Test

Process finished with exit code 0

作業フォルダ(最終形)

 C:\Users\grpc のディレクトリ

2020/01/21  17:47    <DIR>          .
2020/01/21  17:47    <DIR>          ..
2020/01/21  17:45               327 client.py
2020/01/21  14:31               173 codegen.py
2020/01/21  17:47               851 server.py
2020/01/21  17:43               300 simple.proto
2020/01/21  17:43             4,212 simple_pb2.py
2020/01/21  17:43             1,342 simple_pb2_grpc.py

リストやdictを使う場合

リストの値を返したい場合

.protoファイル
syntax = "proto3";

package simple;

// request
message SimpleRequest{
}

// response 
message SimpleResponse{
    repeated string msgs = 1;  // repeatedを使う
}

// interface
service SimpleService{
    rpc simple_send (SimpleRequest) returns (SimpleResponse) {}
}

サーバ側

リストを返すサーバ
import grpc
import time
import simple_iter_pb2
import simple_iter_pb2_grpc
from concurrent import futures


class SimpleServiceServicer(simple_iter_pb2_grpc.SimpleServiceServicer):
    def __init__(self):
        pass

    def simple_send(self, request, context):
        sample_data = ['message1', 'message2']
        return simple_iter_pb2.SimpleResponse(msgs=sample_data)  # リスト名を忘れずに


# start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
simple_iter_pb2_grpc.add_SimpleServiceServicer_to_server(SimpleServiceServicer(), server)
server.add_insecure_port('[::]:5051')
server.start()
print('run server')

# wait
try:
    while True:
        time.sleep(3600)
except KeyboardInterrupt:
    # stop server
    server.stop(0)

dictの値を返したい場合

.protoファイル
syntax = "proto3";

package simple;

// request
message SimpleRequest{
}

// response
message SimpleResponse{
    map<string, string> msg = 1; // mapを使う
}

// interface
service SimpleService{
    rpc simple_send (SimpleRequest) returns (SimpleResponse) {}
}

サーバは省略

参考

以下の記事が大変参考になりました。より詳しい記載があります。

ny7760
SIerでPM/システム設計等やってます。エンジニアになるため勉強中。Python/AWS/機械学習/サーバーレス/React等
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away