PythonでgRPCを使う場合には、grpcio-toolsパッケージを使いますが、この使い方で目的のパッケージの中にあるコードとして生成しようとした時に、ハマったので解説します。
実現したいこと
用意した定義ファイルはこちら
proto/gisapp.proto
syntax = "proto3";
package proto;
service GISCalc {
rpc RouteLength(RouteLengthRequest) returns (RouteLengthResponse) {}
}
message Point {
double latitude = 1;
double lognitude = 2;
}
message RouteLengthRequest { repeated Point route = 1; }
message RouteLengthResponse { double length = 1; }
ここから、以下のように使えるように proto
パッケージの中に生成したコードを収めたいとします。
test.py
import grpc
from proto import gisapp_pb2_grpc, gisapp_pb2
channel = grpc.insecure_channel('localhost:50051')
client = gisapp_pb2_grpc.GISCalcStub(channel)
req = gisapp_pb2.RouteLengthRequest()
res = client.RouteLength()
つまり、以下のようなディレクトリ構成にします。
`- proto
|- gisapp_pb2.py
`- gisapp_pb2_grpc.py
どうすればよいか
パッケージにしたいディレクトリの中に、定義ファイルを置きます。
`- proto
`- gisapp.proto
ルートディレクトリにて、以下のように実行します。
python -m grpc_tools.protoc \
-I=. \
--python_out=. \
--grpc_python_out=. \
proto/gisapp.proto
すると、以下のような、proto
パッケージ下にあるプログラムとして生成されます。
proto/gisapp_pb_grpc.py
import grpc
from proto import gisapp_pb2 as proto_dot_gisapp__pb2
...
解説
パッケージ構成は、-I
で指定したフォルダから、定義ファイル gisapp.proto
までのディレクトリ構成になります。以下のような関係になります。
-
-I./proto proto/gisapp.proto
→import gisapp_pb2
-
-I. proto/gisapp.proto
→from proto import gisapp_pb2
次に、出力先に引数である --python_out
、--grpc_python_out
は、指定したパスをルートとするパッケージとして出力されます。
-
-I./proto proto/gisapp.proto
かつ-
--python_out=./proto
→proto/proto/gisapp_pb2.py
にimport gisapp_pb2
が作られる -
--python_out=.
→proto/gisapp_pb2.py
にimport gisapp_pb2
-
-
-I. proto/gisapp.proto
かつ-
--python_out=./proto
→proto/proto/gisapp_pb2.py
にfrom proto import gisapp_pb2
が作られる -
--python_out=.
→proto/gisapp_pb2.py
にfrom proto import gisapp_pb2
が作られる
-
よって、前述のコマンドのとおりになります。
ところで
betterproto というツールで生成したところ、どこのパッケージ下にもおける1ファイルが生成されるようになったので、上記のようなパッケージの問題は消失しました。
pip install betterproto[compiler]==2.0.0b3
python -m grpc_tools.protoc \
-I=. \
--python_betterproto_out=. \
proto/gisapp.proto
proto/__init__.py
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: proto/gisapp.proto
# plugin: python-betterproto
from dataclasses import dataclass
from typing import Dict, List, Optional
import betterproto
from betterproto.grpc.grpclib_server import ServiceBase
import grpclib
@dataclass(eq=False, repr=False)
class Point(betterproto.Message):
latitude: float = betterproto.double_field(1)
longitude: float = betterproto.double_field(2)
@dataclass(eq=False, repr=False)
class RouteLengthRequest(betterproto.Message):
route: List["Point"] = betterproto.message_field(1)
@dataclass(eq=False, repr=False)
class RouteLengthResponse(betterproto.Message):
length: float = betterproto.double_field(1)
class GisCalcStub(betterproto.ServiceStub):
async def route_length(
self, *, route: Optional[List["Point"]] = None
) -> "RouteLengthResponse":
...
class GisCalcBase(ServiceBase):
async def route_length(
self, route: Optional[List["Point"]]
) -> "RouteLengthResponse":
...