🐍

Pythonでいい感じの「Hello World !」 ~grpc編~

に公開

0. 対象読者

1年前の自分に向けて書くつもりで書きます!

1. はじめに

はじめまして!なおずみと申します!
Zenn初投稿なので、まずはここからってことで、「Hello World」から始めます〜
最近grpc初めて触ったので使ってみる!次はfastAPIでやろうかな〜

2. 今回やること

今回のスコープは以下の通り

  • grpc
  • poetry
  • Docker
  • pytest

3. 前提

  • 開発環境はMac
  • Docker入ってること
  • poetry入ってること

4. poetryで新しくプロジェクトの作成

以下コマンドで、プロジェクトを作る

poetry new grpc

5. 必要なものを追加していく

poetry add grpcio
poetry add grpcio-tools
poetry add pytest

6. protoファイルから、grpcコードの作成

protoファイルは、基本的に公式のやつを丸ぱくりして、protoファイルのversionだけ追加する。

hello_world.proto
syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

次に、以下のコマンドをターミナルで実行して、grpcコードの作成。

poetry run python -m grpc_tools.protoc -I ./proto \
--python_out=./src/pb \
--pyi_out=./src/pb \
--grpc_python_out=./src/pb \
./proto/hello_world.proto

作成完了すると、ディレクトリはこんな感じ

.
├── README.md
└── grpc
    ├── README.md
    ├── poetry.lock
    ├── proto
    │   └── hello_world.proto
    ├── pyproject.toml
    ├── src
    │   ├── grpc
    │   │   └── __init__.py
    │   └── pb
    │       ├── hello_world_pb2.py
    │       ├── hello_world_pb2.pyi
    │       └── hello_world_pb2_grpc.py
    └── tests
        └── __init__.py

pbフォルダに以下の__init__.pyを追加してpathを通す

__init__.py
import sys
from pathlib import Path

sys.path.append(str(Path(__file__).parent))

7. サーバの立ち上げ

grpcサーバを立ち上げるファイルを作成して、

server.py
from concurrent import futures
import grpc
from pb import hello_world_pb2_grpc
from pb import hello_world_pb2


class Greeter(hello_world_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return hello_world_pb2.HelloReply(message=f"Hello, {request.name}!")

    def SayHelloAgain(self, request, context):
        return hello_world_pb2.HelloReply(message=f"Hello again, {request.name}!")


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_world_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port("[::]:50051")
    server.start()
    print("Server started on port 50051")
    server.wait_for_termination()


if __name__ == "__main__":
    serve()

以下のコマンドでサーバーを立ち上げる

poetry run python ./src/grpc/server.py

結果確認→良さそ~

Server started on port 50051

8. クライアント側の立ち上げ

クライアント側のファイルを作成して、

client.py
import grpc
from pb import hello_world_pb2
from pb import hello_world_pb2_grpc


def run():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = hello_world_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(hello_world_pb2.HelloRequest(name="naozumi"))
        print("Greeter client received: " + response.message)
        response = stub.SayHelloAgain(hello_world_pb2.HelloRequest(name="naozumi"))
        print("Greeter client received: " + response.message)


if __name__ == "__main__":
    run()

以下のコマンドでサーバー叩いてみる

poetry run python ./src/grpc/client.py

結果確認→良さそ〜

Greeter client received: Hello, naozumi!
Greeter client received: Hello again, naozumi!

9. Dockerにまとめる

Dockerで動かせるように、Dockerfileを作成する

Dockerfile
FROM public.ecr.aws/docker/library/python:3.13.0-slim-bookworm

WORKDIR /app

COPY pyproject.toml poetry.lock ./

ENV POETRY_REQUESTS_TIMEOUT=10800

RUN python -m pip install --upgrade pip && \
    pip install poetry --no-cache-dir && \
    poetry config virtualenvs.create false && \
    poetry install --no-interaction --no-ansi --no-root && \
    poetry cache clear --all pypi
    
COPY src /app/src

CMD ["poetry", "run", "python", "src/grpc/server.py"]

EXPOSE 50051

そのままだと、ModuleImportでこけて動かないので、server.pyでpathを通す

server.py
from concurrent import futures
import grpc

# 以下が追加したとこ
import sys
sys.path.append("./src")

from pb import hello_world_pb2_grpc
from pb import hello_world_pb2

以下のコマンドで動作確認

docker build -t grpc .
docker run -d -p 50051:50051 grpc

一応リクエスト投げてみる

poetry run python ./src/grpc/client.py

結果確認→良さそ〜

Greeter client received: Hello, naozumi!
Greeter client received: Hello again, naozumi!

基本的なのは、ここまで!
こっからは、テストとか、CICDとかやっていく!

10. testコードの作成

pytest-grpcを追加する。本番環境では使わないので、--devつけて。

poetry add --dev pytest-grpc

testコードを以下のように記述する
想定通りのレスポンスが返ってくるかどうかのユニットテストだけかく〜

test_server.py
import pytest
from src.pb import hello_world_pb2
from src.pb import hello_world_pb2_grpc
from src.grpc.server import Greeter


@pytest.fixture(scope="module")
def grpc_add_to_server():
    return hello_world_pb2_grpc.add_GreeterServicer_to_server


@pytest.fixture(scope="module")
def grpc_servicer():
    return Greeter()


@pytest.fixture(scope="module")
def grpc_stub(grpc_channel):
    return hello_world_pb2_grpc.GreeterStub(grpc_channel)


def test_say_hello(grpc_stub):
    response = grpc_stub.SayHello(hello_world_pb2.HelloRequest(name="test user"))
    assert response.message == "Hello, test user!"


def test_say_hello_again(grpc_stub):
    response = grpc_stub.SayHelloAgain(hello_world_pb2.HelloRequest(name="test user"))
    assert response.message == "Hello again, test user!"

以下のコマンドでテストを走らせる

poetry run pytest

結果確認→良さそ〜

================================================================== test session starts ==================================================================
platform darwin -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0
rootdir: ~/grpc
configfile: pyproject.toml
plugins: grpc-0.8.0
collected 2 items                                                                                                                                       

tests/test_server.py ..                                                                                                                           [100%]

=================================================================== 2 passed in 0.04s ===================================================================

まとめ

いかがだったでしょうか??
ソースコードも載せるので良かったら覗いてください!
今回はgrpcでのhello worldでしたが、fastapiとかも記事にしようかと思います〜
誰かの何かの参考になれば幸いです〜

https://github.com/zumibanbanG/python_hello_world

GitHubで編集を提案

Discussion