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
andFeature
are data structures exchanged between the client and server when using theGetFeature
method. The client provides map coordinates as aPoint
in itsGetFeature
request to the server, and the server replies with a correspondingFeature
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:
- The regular protoc compiler that generates Python code from
message
definitions. - 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
:
route_guide_pb2.py
contains the code that dynamically creates classes generated from themessage
definitions.route_guide_pb2.pyi
is a "stub file" or "type hint file" generated from themessage
definitions. It only contains the signatures with no implementation. Stub files can be used by IDEs to provide better autocompletion and error detection.route_guide_pb2_grpc.py
is generated from theservice
definitions and contains gRPC-specific classes and functions.
gRPC-specific code contains:
RouteGuideStub
, which can be used by a gRPC client to invoke RouteGuide RPCs.RouteGuideServicer
, which defines the interface for implementations of theRouteGuide
service.add_RouteGuideServicer_to_server
function which is used to register aRouteGuideServicer
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
- Learn how gRPC works in Introduction to gRPC and Core concepts
- Work through the Basics tutorial
- Explore the Python API reference