Kalebu Gwalugano" />

Using gRPC with Python

Microservice is now the architecture of choice for many developers when crafting cloud-native applications. A microservices application is a collection of loosely coupled services that communicate with each other, enhancing collaboration, maintainability, scalability, and deployment. There are several options for enabling this communication between microservices. REST is the most popular among developers, sometimes used synonymously with APIs. However, gRPC can be a better alternative to REST. In this tutorial, you’re going to learn the basics of gRPC and how to get started with it using Python.

What Is gRPC?

gRPC is a high-performance remote procedure call framework built on top of HTTP/2. A client application can directly call a method on a server application on a different machine as if it were a local object, ensuring a smooth client-server integration. gRPC abstracts the process of directly calling a certain endpoint on the server, as you would typically do with REST.

Landing-2.svg

It uses protocol buffers as its interface definition language (IDL) and for data serialization as it handles client-server communications. Proto buffers will be discussed in more detail below, but you can also check the documentation for more information.

Why Use gRPC?

It’s true that REST can seem like the best option in many cases. REST offers a mature ecosystem with a lot of support and tools for multiple languages; however, it has some trade-offs, including low consistency when integrating an API across different languages and more difficult, higher-latency streaming. This is where gRPC comes into play.

Here are some of the benefits that gRPC offers:

Language Agnostic

The framework is designed to work with multiple languages. The server will always get the same kind of request, no matter what language the client uses. This also applies to the client, thus enforcing consistency in API communication.

Better Streaming

gRPC has strong support for streaming client streaming, server streaming, and bi-directional streaming, and it’s easier to implement compared to REST.

Better Speed

gRPC offers better speeds than REST, GraphQL, and SOAP because it uses protocol buffers for data serialization instead of JSON or XML. This makes the payload smaller and faster.

Code Generation

Using the protoc or protocol buffers compiler, you can easily compile the .proto files into language-specific code. This simplifies the process of building your API service and client libraries.

Getting Started with gRPC in Python

Now that you know more about gRPC, you’re going to see it in action. This tutorial will show you how to create a simple API service and client that fetches real-time prices for cryptocurrencies.

Prerequisites

To follow this tutorial, you’ll need the following dependencies:

Installing Dependencies

Use the following commands to install the dependencies on your machine:

# Create a directory for the project
mkdir crypto-service
cd crypto-service

# Create a virtual environment

virtualenv crypto

# For those in Windows

crypto/Scripts/activate
pip install grpcio grpcio-tools

# For those in Linux and MacOS

source crypto/bin/activate 
pip3 install grpcio grpcio-tools

Creating the Workflow

When you’re creating API services and clients with gRPC, you will usually follow this workflow:

  • Define services and messages in .proto file(s).
  • Compile the .proto file(s) to code artifacts.
  • Use generated code artifacts to implement the server code and client code.

Protocol Buffer Basics

In gRPC, the structure of the incoming request, outgoing response, and handling method must be predefined in the .proto file(s). The incoming request and outgoing response are usually defined as messages. The handling method is defined as an RPC method with input and output messages, and these RPC methods are organized into groups known as a service. One .proto file can consist of multiple services, and each service can have multiple methods.

For instance, say you want to create a simple API service that will receive a request consisting of only the name of the cryptocurrency, then return the minimum, maximum, and average price of that cryptocurrency in a day and return none when the cryptocurrency is not found on the record.

Such a service in gRPC should look something like this:

messages (requests and responses) methods service
cryptocurrency ( name) get_price GExchange
market_price ( min, max, avg)

To create this in gRPC, create a new .proto file as shown below:

touch crypto_service.proto

Here is how a protocol buffer definition will look. Copy and paste the below code to the crypto_service.proto file you created:

syntax = "proto3";

// Incoming request from client
message cryptocurrency{
   optional string name = 1;
}

// Response to be returned by API service
message market_price{
   optional float max_price = 1;
   optional float min_price = 2;
   optional float avg_price = 3;
}

// Service definition for GExchange
service GExchange {

   // get_price method definition
   rpc get_price (cryptocurrency) returns (market_price) {};

}

Compiling with Protoc

Now you have a proto buffer definition for the crypto service. The next step is using the protoc compiler to compile the crypto_service.proto file to code artifacts. You can use a single command to do this through your terminal (or command prompt for Windows users), as shown below:

python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. crypto_service.proto

When you run the above command, it will generate two Python files from crypto_service.proto named:

  • crypto_service_pb2.py
  • crypto_service_pb2_grpc.py

You can look at the contents of the files, but don’t worry about them because you’re not going to edit them. If you check crypto_service_pb2_grpc.py, you will notice there are three classes generated, two of which are GExchangeServicer and GExchangeStub. You’re going to use them to implement server-side code and client code, respectively.

Implementing the gRPC Server

Next, you’re going to implement the gRPC server from generated artifacts and create a new Python file within the same directory titled crypto_server.py.

touch crypto_server.py

You’ll need to first import all the necessary requirements, then create a class that will inherit GExchangeServicer. Its methods will have the same name as defined in the crypto_service.proto file. For this tutorial, the name is get_price as shown below:

import grpc
from concurrent import futures
import crypto_service_pb2 as pb2
import crypto_service_pb2_grpc as pb2_grpc

# A class for handling GExchange service
class GExchange(pb2_grpc.GExchangeServicer):
   def get_price(self, request, context):
       return pb2.market_price(**data.get(request.name, {}))

The only thing your gRPC server needs to be complete is the instruction for your service and the initial dummy data to query from. You’re going to use Python’s built-in feature futures, gRPC library with some components from generated artifacts to instruct your Python code on how to run the GExchange service. Once you add them, your code should look like this:

import grpc
from concurrent import futures
import crypto_service_pb2 as pb2
import crypto_service_pb2_grpc as pb2_grpc

class GExchange(pb2_grpc.GExchangeServicer):
   def get_price(self, request, context):
       return pb2.market_price(**data.get(request.name, {}))

def serve():
   server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
   pb2_grpc.add_GExchangeServicer_to_server(GExchange(), server)
   server.add_insecure_port("[::]:50051")
   server.start()
   server.wait_for_termination()

data = {
   "Ethereum": {"max_price": 4000.0, "min_price": 3590.0, "avg_price": 3800.0},
   "Bitcoin": {"max_price": 50000.0, "min_price": 48539.0, "avg_price": 49072.0},
   "Cardano": {"max_price": 3.3, "min_price": 2.9, "avg_price": 3.12},
}

if __name__ == "__main__":
   print("running the gRPC server")
   serve()

You can now run the gRPC service as you would normally run the Python script, as shown below:

python3 crypto_server.py
.....
running the gRPC server

Implementing the gRPC Client

In this section, you will learn how to implement a gRPC client in Python. First, create a new Python file, fetch_prices.py.

# creates a new file
touch fetch_prices.py

Import all the necessary libraries and then create a class for your client. In the class constructor, create a stub or client that will connect to a gRPC. Here’s how to do that:

import grpc
import crypto_service_pb2 as pb2
import crypto_service_pb2_grpc as pb2_grpc

class fetchPrices(object):
   def __init__(self):
       # creates a gRPC channel to connect to a server
       self.channel = grpc.insecure_channel("localhost:50051")
       # creates a gRPC stub(client) to communicate to server
       self.stub = pb2_grpc.GExchangeStub(self.channel)

Finally, create a method that will receive a cryptocurrency name as string, transform it into a message object, use the stub to send a request to the gRPC server, then return the response. Your code should look like this:

import grpc
import crypto_service_pb2 as pb2
import crypto_service_pb2_grpc as pb2_grpc

class fetchPrices(object):
   def __init__(self):
       self.channel = grpc.insecure_channel("localhost:50051")
       self.stub = pb2_grpc.GExchangeStub(self.channel)

   def get_price(self, name):
       request = pb2.cryptocurrency(name=name)
       response = self.stub.get_price(request)
       return response

if __name__ == "__main__":
   client = fetchPrices()
   print(client.get_price("Bitcoin"))
   print(client.get_price("Ethereum"))
   print(client.get_price("Cardano"))

Your gRPC server should already be running. If not, run it first; otherwise, your client scripts will raise an exception. Once you do that, you’re ready to go.

Now run your fetch_prices.py script:

python3 fetch_prices.py
........
max_price: 50000.0
min_price: 48539.0
avg_price: 49072.0

max_price: 4000.0
min_price: 3590.0
avg_price: 3800.0

max_price: 3.299999952316284
min_price: 2.9000000953674316
avg_price: 3.119999885559082

After running, your script should be able to effectively communicate with the gRPC server, and the response should look like what’s shown above since you used the same example for the data.

Conclusion

Now you should have a better understanding of gRPC and how to use it as a Python developer. This tutorial demonstrated unary RPCs, in which a client sends a single request to a server and gets a single response back. There are multiple types of gRPC implementation, and today’s exercise is just the beginning of what you can learn.

Using gRPC can offer better performance and more flexibility for communications between your microservices. For more examples of using gRPC in Python, check its GitHub site.

About The Author