Photo by Rafael Atantya on Unsplash
gRPC in Python. Part 1: Building a gRPC server
Building fast and scalable APIs using gRPC
Code
The code for this article is available at: https://github.com/theundeadmonk/python-grpc-demo/
This is part of a series of gRPC in python. We will cover the following
1) Implementing a server
2) Implementing a client
3) gRPC Streaming
4) Advanced features like interceptors, etc.
5) Serving frontends using gRPC gateway
What is gRPC
[gRpc](https://grpc.io/) is a high-performance RPC framework developed by Google. Google has thousands of microservices running in its data centers and they built gRPC as a means of communicating between all of these them.
Why gRPC
Most organizations will use REST APIs to communicate between their microservices. As organizations scale, this does have a few downsides. REST APIs do not enforce a strict schema. This means that it is hard to coordinate changes between servers and clients, and although tools such as OpenAPI and JSONSchema exist, they are an afterthought and are often cumbersome in practice. Using Protobufs, gRPC forces clients and servers to agree on the API contract, and protobufs do not allow for breaking changes.
gRPC also uses HTTP/2 as a transport mechanism. This offers several benefits over HTTP/1.1 such as multiplexing requests, etc.
Enough about the benefits of gRPC, let's see how to implement a gRPC server in Python.
Service Definition
Every gRPC service begins with a proto file. This file is where we define our gRPC service and its types. For more information on protocol buffers, checkout this tutorial here: https://developers.google.com/protocol-buffers/docs/pythontutorial
Let us define a simple gRPC service. It has a service called Greeter
. Let us call this file greet.proto
syntax = "proto3";
package tutorial;
message GreetingRequest {
string name = 1;
}
message GreetingResponse {
string greeting = 1;
}
service Greeter {
rpc Greet(GreetingRequest) returns (GreetingResponse );
}
What we have here is a simple proto file. It defines a single service called Greeter
that takes in a GreetingRequest
and returns a GreetingResponse
. Protocol buffers are strongly typed and we need to create types for all services that we define. The type GreetingReuest
contains a string called name
and the type GreetignResponse
contains a string called greeting.
Server
Now that we have our protobuf definition, let's start creating our server in Python. We'll be using poetry to manage our dependencies, but you can use any dependency management tool you like. Let's initialize our project and install the required dependencies.
poetry new python-grpc-demo
poetry add grpcio
poetry add grpcio-tools
Generating types
The good thing about using gRPC and Protobufs is that we get automatic code generation for our services. In order to do this, we use a tool called [protoc](https://grpc.io/docs/protoc-installation/) which generates the required python files.
Let's create two directories in our project protos
and grpc_types
. We should also place the greeting.proto
file in the protos
directory.
Our directory structure should now look like the following
├── poetry.lock
├── pyproject.toml
├── python_grpc_demo
│ ├── grpc_types
│ │ └── __init__.py
│ ├── __init__.py
│ └── protos
│ ├── greeting.proto
│ └── __init__.py
├── README.rst
└── tests
├── __init__.py
└── test_python_grpc_demo.py
Let us now use the protoc
command to generate out python code. protoc
comes bundled with the grpcio-tools
package that we installed earlier so let's invoke it using the following command.
$ poetry run python -m grpc_tools.protoc --proto_path=./python_grpc_demo/protos --python_out=./python_grpc_demo/grpc_types --pyi_out=./python_grpc_demo/grpc_types --grpc_python_out=./python_grpc_demo/grpc_types ./python_grpc_demo/protos/greeting.proto
This should generate the following files for us in the grpc_types
directory.
greeting_pb2_grpc.py
greeting_pb2.py
greeting_pb2.pyi
Open the greeting_pb2_grpc.py
file and edit the following line
import greeting_pb2 as greeting__pb2
to be
import python_grpc_demo.grpc_types.greeting_pb2 as greeting__pb2
Implementing the service
Let us now go ahead and implement our service. Since we already have code generated from the previous step, we can go ahead and use that to implement our gRPC service.
Let us create a new directory structure called server/servicers
and within it, a file called greeter.py
. Implement a basic grpc service that returns a greeting
import grpc
from python_grpc_demo.grpc_types.greeting_pb2_grpc import GreeterServicer
from python_grpc_demo.grpc_types.greeting_pb2 import GreetingRequest, GreetingResponse
class Greeter(GreeterServicer):
def Greet(self, request: GreetingRequest, context: grpc.ServicerContext) -> GreetingResponse:
return GreetingResponse(greeting='Hello %s', request.name)
Creating the server.
Now that we have the gRPC service implemented, let us go ahead and create a server to serve our clients. Create a file called server.py
in the server
folder and add the following code to it.
import grpc
from concurrent import futures
from python_grpc_demo.grpc_types.greeting_pb2_grpc import add_GreeterServicer_to_server
from python_grpc_demo.server.servicers.greeter import Greeter
def serve() -> None:
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
add_GreeterServicer_to_server(Greeter(), server)
port = 50051
server.add_insecure_port(f"[::]:{port}")
server.start()
print("Server started")
server.wait_for_termination()
What we've done here is simply created a gRPC server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
Added our service to it
add_GreeterServicer_to_server(Greeter(), server)
And started service on port 50051.
Modify your pyproject.toml
file and add the following lines
[tool.poetry.scripts]
run-grpc-server = "python_grpc_demo.server:serve"
We can now start our server with the following command
$ poetry run run-grpc-server
Server started
That's it! Our server is now ready to serve clients at port 50051.
Next week, we'll see how to create a client to consume our gRPC service.