Getting Started with gRPC-Python

1. Introduction

In this codelab, you'll use gRPC-Python to create a client and server that form the foundation of a route-mapping application written in Python.

By the end of the tutorial, you will have a client that connects to a remote server using gRPC to get the name or postal address of what's located at specific coordinates on a map. A fully fledged application might use this client-server design to enumerate or summarize points of interest along a route.

The service is defined in a Protocol Buffers file, which will be used to generate boilerplate code for the client and server so that they can communicate with each other, saving you time and effort in implementing that functionality.

This generated code takes care of not only the complexities of the communication between the server and client, but also data serialization and deserialization.

What you'll learn

  • How to use Protocol Buffers to define a service API.
  • How to build a gRPC-based client and server from a Protocol Buffers definition using automated code generation.
  • An understanding of client-server communication with gRPC.

This codelab is aimed at Python developers new to gRPC or seeking a refresher of gRPC, or anyone else interested in building distributed systems. No prior gRPC experience is required.

2. Before you begin

What you'll need

  • Python 3.9 or higher. We recommend Python 3.13. For platform-specific installation instructions, see Python Setup and Usage. Alternatively, install a non-system Python using tools like uv or pyenv.
  • pip to install Python packages.
  • venv to create Python virtual environments.

The ensurepip and venv packages are part of the Python Standard Library and are typically available by default.

However, some Debian-based distributions (including Ubuntu) choose to exclude them when redistributing python. To install the packages, run:

sudo apt install python3-pip python3-venv

Get the code

To streamline your learning, this codelab offers a pre-built source code scaffold to help you get started. The following steps will guide you through completing the application, including gRPC code generation using the grpc_tools.protoc Protocol Buffer compiler plugin.

grpc-codelabs

The scaffold source code for this codelab is available in the codelabs/grpc-python-getting-started/start_here directory. If you prefer not to implement the code yourself, the completed source code is available in the completed directory.

First, create the codelab working directory and cd into it:

mkdir grpc-python-getting-started && cd grpc-python-getting-started

Download and extract the codelab:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-python-getting-started/start_here

Alternatively, you can download the .zip file containing only the codelab directory and manually unzip it.

3. Define the service

Your first step is to define the application's gRPC service, its RPC method, and its request and response message types using Protocol Buffers interface definition language. Your service will provide:

  • An RPC method called GetFeature that the server implements and the client calls.
  • The message types Point and Feature are data structures exchanged between the client and server when using the GetFeature method. The client provides map coordinates as a Point in its GetFeature request to the server, and the server replies with a corresponding Feature that describes whatever is located at those coordinates.

This RPC method and its message types will all be defined in the protos/route_guide.proto file of the provided source code.

Protocol Buffers are commonly known as protobuf. For more information on gRPC terminology, see gRPC's Core concepts, architecture, and lifecycle.

Message types

In the protos/route_guide.proto file of the source code, first define the Point message type. A Point represents a latitude-longitude coordinate pair on a map. For this codelab, use integers for the coordinates:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

The numbers 1 and 2 are unique ID numbers for each of the fields in the message structure.

Next, define the Feature message type. A Feature uses a string field for the name or postal address of something at a location specified by a Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

Service method

The route_guide.proto file has a service structure named RouteGuide that defines one or more methods provided by the application's service.

Add the rpc method GetFeature inside the RouteGuide definition. As explained earlier, this method will look up the name or address of a location from a given set of coordinates, so have GetFeature return a Feature for a given Point:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

This is a unary RPC method: a simple RPC where the client sends a request to the server and waits for a response to come back, just like a local function call.

4. Generate the client and server code

Next, generate the boilerplate gRPC code for both the client and server from the .proto file using the protocol buffer compiler.

For gRPC Python code generation, we created grpcio-tools. It includes:

  1. The regular protoc compiler that generates Python code from message definitions.
  2. gRPC protobuf plugin that generates Python code (client and server stubs) from the service definitions.

We'll install the grpcio-tools Python package using pip. Let's create a new python virtual environment (venv) to isolate your project's dependencies from the system packages:

python3 -m venv --upgrade-deps .venv

To activate the virtual environment in bash/zsh shell:

source .venv/bin/activate

For Windows and non-standard shells, see the table at https://docs.python.org/3/library/venv.html#how-venvs-work.

Next, install the grpcio-tools (this also installs the grpcio package):

pip install grpcio-tools

Use the following command to generate the Python boilerplate code:

python -m grpc_tools.protoc --proto_path=./protos  \
 --python_out=. --pyi_out=. --grpc_python_out=. \
 ./protos/route_guide.proto

This will generate the following files for the interfaces we defined in route_guide.proto:

  1. route_guide_pb2.py contains the code that dynamically creates classes generated from the message definitions.
  2. route_guide_pb2.pyi is a "stub file" or "type hint file" generated from the message definitions. It only contains the signatures with no implementation. Stub files can be used by IDEs to provide better autocompletion and error detection.
  3. route_guide_pb2_grpc.py is generated from the service definitions and contains gRPC-specific classes and functions.

gRPC-specific code contains:

  1. RouteGuideStub, which can be used by a gRPC client to invoke RouteGuide RPCs.
  2. RouteGuideServicer, which defines the interface for implementations of the RouteGuide service.
  3. add_RouteGuideServicer_to_server function which is used to register a RouteGuideServicer to a gRPC server.

5. Create the service

First let's look at how you create a RouteGuide server. Creating and running a RouteGuide server breaks down into two work items:

  • Implementing the servicer interface generated from our service definition with functions that perform the actual "work" of the service.
  • Running a gRPC server at a specific port to listen for requests from clients and transmit responses.

You can find the initial RouteGuide server in start_here/route_guide_server.py.

Implement RouteGuide

route_guide_server.py has a RouteGuideServicer class that subclasses the generated class route_guide_pb2_grpc.RouteGuideServicer:

# RouteGuideServicer provides an implementation
# of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):

RouteGuideServicer implements all the RouteGuide service methods.

Let's look into a simple RPC implementation in detail. Method GetFeature gets a Point from the client and returns the corresponding feature information from its database in Feature.

def GetFeature(self, request, context):
    feature = get_feature(self.db, request)
    if feature is None:
        return route_guide_pb2.Feature(name="", location=request)
    else:
        return feature

The method is passed a route_guide_pb2.Point request for the RPC, and a grpc.ServicerContext object that provides RPC-specific information such as timeout limits. It returns a route_guide_pb2.Feature response.

Starting the server

Once you have implemented all the RouteGuide methods, the next step is to start up a gRPC server so that clients can actually use your service:

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
        RouteGuideServicer(),
        server,
    )
    listen_addr = "localhost:50051"
    server.add_insecure_port(listen_addr)
    print(f"Starting server on {listen_addr}")
    server.start()
    server.wait_for_termination()

The server start() method is non-blocking. A new thread will be instantiated to handle requests. The thread calling server.start() will often not have any other work to do in the meantime. In this case, you can call server.wait_for_termination() to cleanly block the calling thread until the server terminates.

6. Create the client

In this section, we'll look at creating a client for our RouteGuide service. You can see the initial client code in start_here/route_guide_client.py.

Create a stub

To call service methods, we first need to create a stub.

We instantiate the RouteGuideStub class of the route_guide_pb2_grpc module, generated from our .proto inside of the route_guide_client.py file.

channel = grpc.insecure_channel("localhost:50051")
stub = route_guide_pb2_grpc.RouteGuideStub(channel)

Calling service methods

For RPC methods that return a single response–known as response-unary methods– gRPC Python supports both synchronous (blocking) and asynchronous (non-blocking) control flow semantics.

Simple RPC

First, let's define a Point to call the service with. This should be as simple as instantiating an object from the route_guide_pb2 package with some properties:

point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)

A synchronous call to the simple RPC GetFeature is nearly as straightforward as calling a local method. The RPC call waits for the server to respond, and will either return a response or raise an exception. We can call the method and see the response like this:

feature = stub.GetFeature(point)
print(feature)

You can inspect the fields of the Feature object and output the result of the request:

if feature.name:
    print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
else:
    print(f"Found no feature at at {format_point(feature.location)}")

7. Try it out

Run the server:

python route_guide_server.py

From a different terminal, activate the virtual environment again, then run the client:

python route_guide_client.py

You'll see output like this, with timestamps omitted for clarity:

name: "16 Old Brook Lane, Warwick, NY 10990, USA"
location {
  latitude: 412346009
  longitude: -744026814
}

Feature called '16 Old Brook Lane, Warwick, NY 10990, USA' at latitude: 412346009, longitude: -744026814

8. What's next