Skip to content

Commit d7bb143

Browse files
author
flavienbwk
committed
Added first steps
1 parent b1659d6 commit d7bb143

11 files changed

+403
-2
lines changed

Dockerfile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM ubuntu:bionic
2+
3+
RUN apt-get update
4+
RUN apt-get install python3 python3-pip -y
5+
6+
COPY ./requirements.txt /requirements.txt
7+
RUN pip3 install -r /requirements.txt

README.md

+102-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,102 @@
1-
# Docker-gRPC-Python-Example
2-
An example of a simple gRPC communication in Python built by Docker.
1+
# gRPC Python Docker example
2+
3+
A simple gRPC communication example in Python built by Docker
4+
5+
In this example, 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.
6+
7+
## 1. Dockerfile and dependencies
8+
9+
Nothing specific, just pip install the `grpcio` module for gRPC communication and the `numpy` + `Pillow` libraries to manipulate our image.
10+
11+
We are also going to use the `pickle` library (included in Python) to transform our numpy image, read by Pillow, into bytes.
12+
13+
```Dockerfile
14+
FROM ubuntu:bionic
15+
16+
RUN apt-get update
17+
RUN apt-get install python3 python3-pip -y
18+
19+
COPY ./requirements.txt /requirements.txt
20+
RUN pip3 install -r /requirements.txt
21+
```
22+
23+
Our `requirements.txt` file :
24+
25+
```python-requirements
26+
numpy==1.19.0
27+
Pillow==7.2.0
28+
grpcio==1.30.0
29+
```
30+
31+
## 2. Defining your proto file
32+
33+
gRPC works with `.proto` files to know which data to handle. Let's create a [`image_transform.proto`](./image_transform.proto) file :
34+
35+
```proto
36+
syntax = "proto3";
37+
38+
package flavienbwk;
39+
40+
service EncodeService {
41+
rpc GetEncode(sourceImage) returns (transformedImage) {}
42+
}
43+
44+
// input
45+
message sourceImage {
46+
bytes image = 1; // Our numpy image in bytes (by pickle)
47+
int32 width = 2; // Width to which we want to resize our image
48+
int32 height = 3; // Height to which we want to resize our image
49+
}
50+
51+
// output
52+
message transformedImage {
53+
bytes image = 1; // Our negative resized image in bytes (by pickle)
54+
}
55+
```
56+
57+
## 3. Compile your .proto file
58+
59+
`.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
60+
61+
First, install the `grpcio-tools` library :
62+
63+
```console
64+
pip3 install grpcio-tools
65+
```
66+
67+
And compile our [`image_transform.proto`](./image_transform.proto) file with :
68+
69+
```console
70+
python3 -m grpc_tools.protoc -I. --python_out=./grpc_compiled --grpc_python_out=./grpc_compiled image_transform.proto
71+
```
72+
73+
Files `image_transform_pb2.py` and `image_transform_pb2_grpc.py` files will appear in `grpc_compiled/`
74+
75+
## 4. Client
76+
77+
Our [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 under `eiffel-tower-transformed.jpg`.
78+
79+
```client.py
80+
from PIL import Image
81+
import numpy as np
82+
import pickle
83+
import grpc
84+
85+
import grpc_compiled.image_transform_pb2
86+
import grpc_compiled.image_transform_pb2_grpc
87+
88+
def run():
89+
channel = grpc.insecure_channel('server:13000')
90+
stub = image_transform_pb2_grpc.EncodeServiceStub(channel)
91+
image = np.array(Image.open('eiffel-tower.jpg'))
92+
query = image_transform_pb2.sourceImage(
93+
image=pickle.dumps(image),
94+
width=320,
95+
height=180
96+
)
97+
response = stub.GetEncode(query)
98+
response.image
99+
100+
if __name__ == "__main__":
101+
run()
102+
```

client.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from PIL import Image
2+
import numpy as np
3+
import pickle
4+
import grpc
5+
6+
import grpc_compiled.image_transform_pb2
7+
import grpc_compiled.image_transform_pb2_grpc
8+
9+
def run():
10+
channel = grpc.insecure_channel('server:13000')
11+
stub = image_transform_pb2_grpc.EncodeServiceStub(channel)
12+
image = np.array(Image.open('eiffel-tower.jpg'))
13+
query = image_transform_pb2.sourceImage(
14+
image=pickle.dumps(image),
15+
width=320,
16+
height=180
17+
)
18+
response = stub.GetEncode(query)
19+
response.image
20+
21+
if __name__ == "__main__":
22+
run()

docker-compose.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: '3'
2+
3+
services:
4+
5+
client:
6+
build: .
7+
command: python3 /usr/app/client.py
8+
volumes:
9+
- ${PWD}/eiffel-tower.jpg:/usr/app/eiffel-tower.jpg # Our input image
10+
- ${PWD}/client.py:/usr/app/client.py
11+
- ./grpc_compiled:/usr/app/grpc_compiled
12+
13+
server:
14+
build: .
15+
command: python3 /usr/app/server.py
16+
volumes:
17+
- ${PWD}/eiffel-tower.jpg:/usr/app/eiffel-tower-transformed.jpg # Our output image
18+
- ${PWD}/client.py:/usr/app/server.py
19+
- ./grpc_compiled:/usr/app/grpc_compiled

eiffel-tower-transformed.jpg

69.5 KB
Loading

eiffel-tower.jpg

69.5 KB
Loading

grpc_compiled/image_transform_pb2.py

+150
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
5+
import image_transform_pb2 as image__transform__pb2
6+
7+
8+
class EncodeServiceStub(object):
9+
"""Missing associated documentation comment in .proto file."""
10+
11+
def __init__(self, channel):
12+
"""Constructor.
13+
14+
Args:
15+
channel: A grpc.Channel.
16+
"""
17+
self.GetEncode = channel.unary_unary(
18+
'/flavienbwk.EncodeService/GetEncode',
19+
request_serializer=image__transform__pb2.sourceImage.SerializeToString,
20+
response_deserializer=image__transform__pb2.transformedImage.FromString,
21+
)
22+
23+
24+
class EncodeServiceServicer(object):
25+
"""Missing associated documentation comment in .proto file."""
26+
27+
def GetEncode(self, request, context):
28+
"""Missing associated documentation comment in .proto file."""
29+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
30+
context.set_details('Method not implemented!')
31+
raise NotImplementedError('Method not implemented!')
32+
33+
34+
def add_EncodeServiceServicer_to_server(servicer, server):
35+
rpc_method_handlers = {
36+
'GetEncode': grpc.unary_unary_rpc_method_handler(
37+
servicer.GetEncode,
38+
request_deserializer=image__transform__pb2.sourceImage.FromString,
39+
response_serializer=image__transform__pb2.transformedImage.SerializeToString,
40+
),
41+
}
42+
generic_handler = grpc.method_handlers_generic_handler(
43+
'flavienbwk.EncodeService', rpc_method_handlers)
44+
server.add_generic_rpc_handlers((generic_handler,))
45+
46+
47+
# This class is part of an EXPERIMENTAL API.
48+
class EncodeService(object):
49+
"""Missing associated documentation comment in .proto file."""
50+
51+
@staticmethod
52+
def GetEncode(request,
53+
target,
54+
options=(),
55+
channel_credentials=None,
56+
call_credentials=None,
57+
compression=None,
58+
wait_for_ready=None,
59+
timeout=None,
60+
metadata=None):
61+
return grpc.experimental.unary_unary(request, target, '/flavienbwk.EncodeService/GetEncode',
62+
image__transform__pb2.sourceImage.SerializeToString,
63+
image__transform__pb2.transformedImage.FromString,
64+
options, channel_credentials,
65+
call_credentials, compression, wait_for_ready, timeout, metadata)

image_transform.proto

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
syntax = "proto3";
2+
3+
package flavienbwk;
4+
5+
service EncodeService {
6+
rpc GetEncode(sourceImage) returns (transformedImage) {}
7+
}
8+
9+
// input
10+
message sourceImage {
11+
bytes image = 1; // Our numpy image (read by Pillow) under a bytes representation
12+
int32 width = 2; // Width to which we want to resize our image
13+
int32 height = 3; // Height to which we want to resize our image
14+
}
15+
16+
// output
17+
message transformedImage {
18+
bytes image = 1; // Our negative resized image
19+
}

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
numpy==1.19.0
2+
Pillow==7.2.0
3+
grpcio==1.30.0

0 commit comments

Comments
 (0)