pythonでgRPC チュートリアルチャレンジ


goでgRPCはやったことあったけどpythonではどうやるんだろうーと思ったので、チュートリアルにチャレンジin mac
goのクイックスタートをやってる方もいてとても分かりやすくまとまっています
https://budougumi0617.github.io/2018/01/01/hello-grpc-go/
間違いあったら指摘いただけると助かります

インストール

# grpcのインストール
pip install --upgrade pip
sudo python -m pip install grpcio
sudo python -m pip install grpcio-tools
# サンプルコードのインストール
git clone -b v1.18.0 https://github.com/grpc/grpc

動くことを確認

cd grpc/examples/python/helloworld
python greeter_server.py
# 別ターミナルで
python greeter_client.py

Greeter client received: Hello, you!
# 出たね

protoファイルからスクリプトを生成

protoファイルを作る

// vim my_if.proto

// なにかをするservice(xx_pb2_proto.pyのコメントとして使用される)
service MyGrpc {
    // なにかを受け取るRPC(xx_pb2_proto.pyのコメントとして使用される)
    rpc GetSomething (MyReq) returns (MyResp) {}
}

message MyReq {
    int32 int_param = 1;
    string str_param = 2;
}

message MyResp {
    int32 status = 1;
    string message = 2;
}   

コマンドで生成

python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. ./my_if.proto

(いちいちコマンド打つのは面倒いのでこちらの記事を参考に設定ファイルで作成するようにしました)

my_if_pb2.pymy_if_pb2_grpc.pyが生成される。(pb22はProtocol Buffers Python APIのバージョンを示していて1と互換性がない)
内容は以下:

  • classes for the messages defined in route_guide.proto
  • classes for the service defined in route_guide.proto
    • RouteGuideStub, which can be used by clients to invoke RouteGuide RPCs
    • RouteGuideServicer, which defines the interface for implementations of the RouteGuide service
  • a function for the service defined in route_guide.proto
    • add_RouteGuideServicer_to_server, which adds a RouteGuideServicer to a grpc.Server

自分用に読み換えると以下のような雰囲気(たぶん)

  • messages用クラス
  • service用クラス
    • MyGrpcStubはMyGrpcを呼び出すのに使用するクライアント
    • MyGrpcServicerはserviceを実装する用のインターフェース
  • service用のfunc
    • add_MyGrpcServicer_to_serverはgrpcサーバにMyGrpcServicerを追加する

とりあえずインストールからスクリプト生成まで完了

サーバを作ろう

サンプルを見るのが分かりやすい。
生成されたxx_grpc.pyとかを見ながら実装します。全ソースはgithubに。

# server.py

from concurrent import futures
import time

import grpc

from proto import my_if_pb2
from proto import my_if_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


# my_if_pb2_grpcのMyGrpcServicer()を実装
class MyGrpc(my_if_pb2_grpc.MyGrpcServicer):

    def GetSomething(self, request, context):

        # いろんな処理はここで

        print("Someone requested something!")
        # データ取得は`request.xxx`でできて分かりやすい
        print(f'int_param: {request.int_param}')
        print(f'str_param: {request.str_param}')

        # 必ず設定したresponseを返却する
        response = my_if_pb2.MyResp(status=200, message="Great message.")
        return response


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    # MyGrpc()を使うよ!と登録しているかんじ
    my_if_pb2_grpc.add_MyGrpcServicer_to_server(MyGrpc(), server)
    # portの設定
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    print("start")
    serve()

サンプルとだいたい一緒になりました(シンプルなことしかしてないので当然だけど)

/code/grpc_practice
❯ python server.py
start

疎通確認できてませんがとりあえず起動はおーけー

クライアントを作ろう

import grpc

from proto import my_if_pb2
from proto import my_if_pb2_grpc


def run():
    # SSL/TLS認証を利用する場合は`secure_channel`を使用する
    # 通信先を設定する(server.pyでポートを50051に設定したのでそれを指定)
    # リトライ設定等はoptionで指定できる
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = my_if_pb2_grpc.MyGrpcStub(channel)
        # ここでデータを送信している
        resp = stub.GetSomething(my_if_pb2.MyReq(int_param=99, str_param='me'))
    # 受け取ったデータはxx.yyで受け取れる
    print(f'client received: status={resp.status}, message={resp.message}')


if __name__ == '__main__':
    print("run")
    run()

簡単です!

実際に送信してみる

1.  まずサーバを実行して待っていてもらう
2.  クライアントを実行

/code/grpc_practice
❯ python client.py
run
client received: status=200, message=Great message.
# サーバで設定していたstatusとmessageを受け取れました

3.  サーバのログを確認

/code/grpc_practice
❯ python server.py
start
Someone requested something!
int_param: 99
str_param: me
# クライアントから送信したパラメータを受け取れました

できましたー