{"id":22018498,"url":"https://github.com/flavienbwk/grpc-python-docker-example","last_synced_at":"2025-05-07T03:26:28.306Z","repository":{"id":40957993,"uuid":"276884595","full_name":"flavienbwk/gRPC-Python-Docker-Example","owner":"flavienbwk","description":"A simple guide to use gRPC with python and docker","archived":false,"fork":false,"pushed_at":"2023-07-05T21:13:08.000Z","size":122,"stargazers_count":26,"open_issues_count":5,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-31T05:51:08.471Z","etag":null,"topics":["docker","docker-compose","grpc","grpc-python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flavienbwk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-03T11:33:16.000Z","updated_at":"2024-11-27T17:17:13.000Z","dependencies_parsed_at":"2023-01-23T01:31:22.484Z","dependency_job_id":null,"html_url":"https://github.com/flavienbwk/gRPC-Python-Docker-Example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flavienbwk%2FgRPC-Python-Docker-Example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flavienbwk%2FgRPC-Python-Docker-Example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flavienbwk%2FgRPC-Python-Docker-Example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flavienbwk%2FgRPC-Python-Docker-Example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flavienbwk","download_url":"https://codeload.github.com/flavienbwk/gRPC-Python-Docker-Example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252805696,"owners_count":21807057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","grpc","grpc-python","python3"],"created_at":"2024-11-30T05:12:21.149Z","updated_at":"2025-05-07T03:26:28.282Z","avatar_url":"https://github.com/flavienbwk.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gRPC Python Docker example\n\nIn this guide, we are going to build a simple gRPC client and server that take an image as input and return a negative and resized version of the image.\n\n## 1. Dockerfile and dependencies\n\nNothing specific, just pip install the `grpcio` module for gRPC communication and the `numpy` + `Pillow` libraries to manipulate our image.\n\nWe are also going to use the `pickle` library (included in Python) to transform our numpy image, read by Pillow, into bytes.\n\n```Dockerfile\nFROM ubuntu:bionic\n\nRUN apt-get update\nRUN apt-get install python3 python3-pip -y\n\nCOPY ./requirements.txt /requirements.txt\nRUN pip3 install -r /requirements.txt\n```\n\nOur `requirements.txt` file :\n\n```python-requirements\nnumpy==1.19.0\nPillow==8.1.1\ngrpcio==1.38.0\nprotobuf==3.17.1\n```\n\n## 2. Docker-compose file\n\nTo bind our local files to our container files and execute the program easily, let's create a [`docker-compose.yml`](./docker-compose.yml) file :\n\n```yml\nversion: '3.3'\n\nservices:\n\n    client:\n        build: .\n        command: python3 /usr/app/client.py\n        volumes:\n            - ./input:/usr/app/input    # Our input image directory\n            - ./output:/usr/app/output  # Our output image directory\n            - ./client.py:/usr/app/client.py:ro\n            - ./grpc_compiled:/usr/app/grpc_compiled\n        depends_on: \n            - server\n\n    server:\n        build: .\n        command: python3 /usr/app/server.py\n        volumes:\n            - ./server.py:/usr/app/server.py:ro\n            - ./grpc_compiled:/usr/app/grpc_compiled\n```\n\n## 3. Defining your proto file\n\ngRPC works with `.proto` files to know which data to handle. Let's create a [`image_transform.proto`](./image_transform.proto) file :\n\n```proto\nsyntax = \"proto3\";\n\npackage flavienbwk;\n\nservice EncodeService {\n    rpc GetEncode(sourceImage) returns (transformedImage) {}\n}\n\n// input\nmessage sourceImage {\n    bytes image = 1; // Our numpy image in bytes (serialized by pickle)\n    int32 width = 2; // Width to which we want to resize our image\n    int32 height = 3; // Height to which we want to resize our image\n}\n\n// output\nmessage transformedImage {\n    bytes image = 1; // Our negative resized image in bytes (serialized by pickle)\n}\n```\n\n## 4. Compile your .proto file\n\n`.proto` files must be compiled with the `grpcio-tools` library to generate two classes that will be used to perform the communication between our client and server\n\nFirst, install the `grpcio-tools` library :\n\n```console\npip3 install grpcio-tools\n```\n\nAnd compile our [`image_transform.proto`](./image_transform.proto) file with :\n\n```console\npython3 -m grpc_tools.protoc -I. --python_out=./grpc_compiled --grpc_python_out=./grpc_compiled image_transform.proto\n```\n\nFiles `image_transform_pb2.py` and `image_transform_pb2_grpc.py` files will appear in `grpc_compiled/`\n\n## 5. Client\n\nOur [client.py](./client.py) file reads the image which becomes a numpy array and sends the query to the server along with the resize information. Then we save the image returned by the server in `./output/eiffel-tower-transformed.jpg`.\n\n```python\nfrom PIL import Image\nimport numpy as np\nimport pickle\nimport grpc\nimport sys\n\nsys.path.append(\"/usr/app/grpc_compiled\")\nimport image_transform_pb2\nimport image_transform_pb2_grpc\n\ndef run():\n    channel = grpc.insecure_channel('server:13000')\n    stub = image_transform_pb2_grpc.EncodeServiceStub(channel)\n    image_np = np.array(Image.open('/usr/app/input/eiffel-tower.jpg'))\n    image = Image.fromarray(image_np.astype('uint8')) # Transforming np array image into Pillow's Image class\n    query = image_transform_pb2.sourceImage(\n        image=pickle.dumps(image),\n        width=320,\n        height=180\n    )\n    response = stub.GetEncode(query)\n    image_transformed = pickle.loads(response.image)\n    image_transformed.save('/usr/app/output/eiffel-tower-transformed.jpg')\n\nif __name__ == \"__main__\":\n    run()\n```\n\n## 6. Server\n\nOur [server.py](./server.py) file receives the image, resize width and height. Then it returns a resized, negative-transformed image to the client.\n\n```python\nfrom concurrent import futures\nimport numpy as np\nimport pickle\nimport grpc\nimport time\nimport sys\n\nsys.path.append(\"/usr/app/grpc_compiled\")\nimport image_transform_pb2\nimport image_transform_pb2_grpc\n\ndef image_to_negative(image: np.ndarray) -\u003e np.ndarray:\n    \"\"\"Transforms a classic image into its negative\"\"\"\n    negative = image.copy()\n    for i in range(0, image.size[0]-1):\n        for j in range(0, image.size[1]-1):\n            pixelColorVals = image.getpixel((i,j))\n            redPixel    = 255 - pixelColorVals[0] # Negate red pixel\n            greenPixel  = 255 - pixelColorVals[1] # Negate green pixel\n            bluePixel   = 255 - pixelColorVals[2] # Negate blue pixel\n            negative.putpixel((i,j),(redPixel, greenPixel, bluePixel))\n    return negative\n\nclass EService(image_transform_pb2_grpc.EncodeServiceServicer):\n\n    def GetEncode(self, request, context):\n        print(\"Received job !\")\n        image = pickle.loads(request.image)\n        image = image.resize((request.width, request.height))\n        image_transformed = image_to_negative(image)\n        return image_transform_pb2.transformedImage(image=pickle.dumps(image_transformed))\n\ndef serve():\n    server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))\n    image_transform_pb2_grpc.add_EncodeServiceServicer_to_server(EService(),server)\n    server.add_insecure_port('[::]:13000')\n    server.start()\n    print(\"Server started. Awaiting jobs...\")\n    try:\n        while True: # since server.start() will not block, a sleep-loop is added to keep alive\n            time.sleep(60*60*24)\n    except KeyboardInterrupt:\n        server.stop(0)\n\nif __name__ == '__main__':\n    serve()\n```\n\n## 7. Run !\n\nSo let's run `docker-compose up` !\n\nYou will see our original `eiffel-tower.jpg` image will transform into its negative and resized version `eiffel-tower-transformed.jpg`\n\n| [eiffel-tower.jpg](./input/eiffel-tower.jpg) (640px / 360px) | [eiffel-tower-transformed.jpg](./output/eiffel-tower-transformed.jpg) (320px / 180px) |\n| ------------------------------------------------------ | ------------------------------------------------------------------------------ |\n| ![Original image](./input/eiffel-tower.jpg)                  | ![Transformed image](./output/eiffel-tower-transformed.jpg)                           |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflavienbwk%2Fgrpc-python-docker-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflavienbwk%2Fgrpc-python-docker-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflavienbwk%2Fgrpc-python-docker-example/lists"}