Skip to content

Commit e1e54df

Browse files
authored
Python Getting Started: Codelab in README.md (#21)
- Format files, add missing print statements, address dry-run feedback - Python Getting Started: Codelab in README.md
1 parent 8e42fbe commit e1e54df

File tree

9 files changed

+391
-49
lines changed

9 files changed

+391
-49
lines changed
Lines changed: 334 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,344 @@
11
# Getting Started with gRPC-Python
22

3-
Get hands-on with gRPC for Python in this interactive codelab! <!-- TODO(arvindbr8): Insert link once codelab is published. -->
3+
Get hands-on with gRPC for Python in this interactive codelab!
44

5+
Perfect for Python developers new to gRPC, those seeking a refresher, or anyone building distributed
6+
systems. No prior gRPC experience needed!
57

6-
Perfect for Python developers new to gRPC, those seeking a refresher, or anyone building distributed systems. No prior gRPC experience needed!
8+
**Build a complete gRPC service from scratch, learning:**
79

8-
#### Build a complete gRPC service from scratch, learning:
9-
- Protocol Buffers (protobuf): Define service contracts & data.
10-
- gRPC Code Generation: Auto-generate Python code.
10+
- Protocol Buffers (protobuf): Define service contracts & data.
11+
- gRPC Code Generation: Auto-generate Python code.
1112
- Client/Server Communication: Implement seamless interactions.
1213

13-
#### You'll gain:
14-
- A working gRPC service in Python.
15-
- Hands-on experience with Protocol Buffers and code generation.
16-
- Skills to design, build, & test gRPC clients and servers.
14+
**You'll gain:**
15+
16+
- A working gRPC service in Python.
17+
- Hands-on experience with Protocol Buffers and code generation.
18+
- Skills to design, build, & test gRPC clients and servers.
1719
- A strong foundation in gRPC for real-world projects.
1820

19-
## How to use this directory
21+
### How to use this directory
22+
23+
- [start_here](./start_here/) directory serves as a starting point for the codelab.
24+
- [completed](./completed/) directory showcases the finished code, giving you a peak of how the
25+
final implementation should look like.
26+
27+
## Prerequisites
28+
29+
### This codelab
30+
31+
```sh
32+
cd ~/your-dev-dir
33+
git clone https://github.com/grpc-ecosystem/grpc-codelabs.git
34+
cd grpc-codelabs/
35+
```
36+
37+
### Python3
38+
39+
For this codelab, we require python 3.9 or higher, but recommend python 3.11. System-specific
40+
instructions can be found in Python documentation: [Python Setup and Usage]
41+
(https://docs.python.org/3/using/index.html).
42+
43+
### Pip3
44+
45+
We recommend using the latest pip, see [Installation - pip]
46+
(https://pip.pypa.io/en/stable/installation/). In some OS distributions, `ensurepip` is not
47+
available out-of-box. On Debian/Ubuntu, you may need to run.
48+
49+
```sh
50+
sudo apt-get install python3-pip
51+
```
52+
53+
If necessary, upgrade your version of pip:
54+
55+
```sh
56+
python3 -m ensurepip --upgrade
57+
```
58+
59+
If your python installation is owned by the system, pip will be installed in the user directory. If
60+
you may see a warning like this, ensure the pip directory is in PATH:
61+
62+
```
63+
WARNING: The scripts pip3 and pip3.9 are installed in '/Users/sergiitk/Library/Python/3.9/bin' which is not on PATH.
64+
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
65+
```
66+
67+
### Venv
68+
69+
[venv](https://docs.python.org/3/library/venv.html) is a built-in tool to create python virtual
70+
environments. However, some OS distributions choose to exclude it. You can check if it's available
71+
on your system with
72+
73+
```sh
74+
python3 -m venv --help
75+
```
76+
77+
In debian/ubuntu, this also will advise you on what package to install. You may need to run
78+
something like this:
79+
80+
```sh
81+
sudo apt-get install python3-venv
82+
```
83+
84+
Once `venv` is installed, create a virtual environment:
85+
86+
```sh
87+
cd codelabs/grpc-python-getting-started
88+
python3 -m venv .venv
89+
```
90+
91+
#### Activate virtual environment
92+
93+
```sh
94+
cd "$(git rev-parse --show-toplevel || echo .)" && cd codelabs/grpc-python-getting-started
95+
source ./.venv/bin/activate
96+
```
97+
98+
## Define proto
99+
100+
Your working directory will be `codelabs/grpc-python-getting-started/start_here`. Assuming you
101+
followed `venv` activation section, you can cd into the start folder with:
102+
103+
```sh
104+
cd start_here/
105+
```
106+
107+
Our first step is to define the gRPC *service* and the method *request* and *response* types using
108+
[protocol buffers](https://protobuf.dev/overview).
109+
110+
Let’s start by defining the messages and service in `route_guide.proto`.
111+
112+
### Define proto messages
113+
114+
Our `.proto` file contains protocol buffer message type definitions for all the request and response
115+
types used in our service methods.
116+
117+
Let’s define the `Point` message type:
118+
119+
```proto
120+
message Point {
121+
int32 latitude = 1;
122+
int32 longitude = 2;
123+
}
124+
```
125+
126+
Let’s also define the `Feature` message type:
127+
128+
```proto
129+
message Feature {
130+
// The name of the feature.
131+
string name = 1;
132+
133+
// The point where the feature is detected.
134+
Point location = 2;
135+
}
136+
```
137+
138+
### Define RouteGuide service
139+
140+
To define a service, you specify a named `service` in your `.proto` file:
141+
142+
```proto
143+
service RouteGuide {
144+
// Definition of the service goes here
145+
}
146+
```
147+
148+
### Define RPC Method
149+
150+
Then you define `rpc` methods inside your service definition, specifying their request and response
151+
types. In this section of the codelab, let’s define a Unary RPC method.
152+
153+
> Unary RPC method - A *simple RPC* where the client sends a request to the server using the stub
154+
and waits for a response to come back, just like a normal function call.
155+
156+
```proto
157+
// Obtains the feature at a given position.
158+
rpc GetFeature(Point) returns (Feature) {}
159+
```
160+
161+
> [!TIP]
162+
> For the complete .proto file, see
163+
> [completed/protos/route_guide.proto](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/completed/protos/route_guide.proto)
164+
165+
## Generating client and server code
166+
167+
Next you need to generate the gRPC client and server interfaces from your .proto service definition.
168+
169+
First, install the grpcio-tools package:
170+
171+
```sh
172+
pip install --require-virtualenv grpcio-tools
173+
```
174+
175+
If you see `ERROR: Could not find an activated virtualenv (required)`, please follow the section
176+
[Activate virtual environment](#activate-virtual-environment), then cd into `start_here`.
177+
178+
Use the following command to generate the Python code:
179+
180+
```sh
181+
python -m grpc_tools.protoc --proto_path=./protos \
182+
--python_out=. --pyi_out=. --grpc_python_out=. \
183+
./protos/route_guide.proto
184+
```
185+
186+
Note that as we’ve already provided a version of the generated code in the `completed` directory,
187+
running this command regenerates the appropriate file rather than creating a new one. The generated
188+
code files are called `route_guide_pb2.py` and `route_guide_pb2_grpc.py` and contain:
189+
190+
* classes for the messages defined in `route_guide.proto`
191+
* classes for the service defined in `route_guide.proto`
192+
* `RouteGuideStub`, which can be used by clients to invoke RouteGuide RPCs
193+
* `RouteGuideServicer`, which defines the interface for implementations of the RouteGuide service
194+
* a function for the service defined in `route_guide.proto`
195+
* `add_RouteGuideServicer_to_server`, which adds a RouteGuideServicer to a `grpc.Server`.
196+
197+
> [!Note]
198+
> The `2` in pb2 indicates that the generated code is following Protocol Buffers Python API version
199+
> 2. Version 1 is obsolete. It has no relation to the Protocol Buffers Language version, which is
200+
> the one indicated by `syntax = "proto3"` or `syntax = "proto2"` in a `.proto` file.
201+
202+
203+
## Creating the server
204+
205+
First let’s look at how you create a `RouteGuide` server. Creating and running a `RouteGuide` server
206+
breaks down into two work items:
207+
208+
* Implementing the servicer interface generated from our service definition with functions that
209+
perform the actual “work” of the service.
210+
* Running a gRPC server to listen for requests from clients and transmit responses.
211+
212+
You can find the initial `RouteGuide` server in [`start_here/route_guide_server.py`](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/start_here/route_guide_server.py).
213+
214+
### Implementing RouteGuide
215+
216+
`route_guide_server.py` has a `RouteGuideServicer` class that subclasses the generated class
217+
`route_guide_pb2_grpc.RouteGuideServicer`:
218+
219+
```py
220+
# RouteGuideServicer provides an implementation
221+
# of the methods of the RouteGuide service.
222+
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):
223+
```
224+
225+
`RouteGuideServicer` implements all the `RouteGuide` service methods.
226+
227+
Let us look into a simple RPC implementation in detail. Method `GetFeature` gets a `Point` from the
228+
client and returns the corresponding feature information from its database in `Feature`.
229+
230+
```py
231+
def GetFeature(self, request, context):
232+
feature = get_feature(self.db, request)
233+
if feature is None:
234+
return route_guide_pb2.Feature(name="", location=request)
235+
else:
236+
return feature
237+
```
238+
239+
The method is passed a `route_guide_pb2.Point` request for the RPC, and a `grpc.ServicerContext`
240+
object that provides RPC-specific information such as timeout limits. It returns a
241+
`route_guide_pb2.Feature` response.
242+
243+
> [!TIP]
244+
> For the completed route guide server, see
245+
> [`completed/route_guide_server.py`](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/completed/route_guide_server.py).
246+
247+
## Starting the server
248+
249+
Once you have implemented all the `RouteGuide` methods, the next step is to start up a gRPC server
250+
so that clients can actually use your service:
251+
252+
```py
253+
def serve():
254+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
255+
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(RouteGuideServicer(), server)
256+
listen_addr = "[::]:50051"
257+
server.add_insecure_port(listen_addr)
258+
print(f"Starting server on {listen_addr}")
259+
server.start()
260+
server.wait_for_termination()
261+
```
262+
263+
The server `start()` method is non-blocking. A new thread will be instantiated to handle requests.
264+
The thread calling `server.start()` will often not have any other work to do in the meantime. In
265+
this case, you can call `server.wait_for_termination()` to cleanly block the calling thread until
266+
the server terminates.
267+
268+
> [!TIP]
269+
> For the completed route guide server, see
270+
> [`completed/route_guide_server.py`](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/completed/route_guide_server.py).
271+
272+
## Creating the client
273+
274+
In this section, we’ll look at creating a client for our `RouteGuide` service. You can see the
275+
initial client code in [`start_here/route_guide_client.py`](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/start_here/route_guide_client.py).
276+
277+
### Creating a stub
278+
279+
To call service methods, we first need to create a *stub*.
280+
281+
We instantiate the `RouteGuideStub` class of the `route_guide_pb2_grpc` module, generated from our
282+
`.proto` inside of the `route_guide_client.py` file.
283+
284+
```py
285+
channel = grpc.insecure_channel('localhost:50051')
286+
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
287+
```
288+
289+
### Calling service methods
290+
291+
For RPC methods that return a single response (“response-unary” methods), gRPC Python supports both
292+
synchronous (blocking) and asynchronous (non-blocking) control flow semantics.
293+
294+
### Simple RPC
295+
296+
First, let's define a `Point` to call the service with. This should be as simple as instantiating an
297+
object from the `route_guide_pb2` package with some properties:
298+
299+
```py
300+
point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)
301+
```
302+
303+
A synchronous call to the simple RPC `GetFeature` is nearly as straightforward as calling a local
304+
method. The RPC call waits for the server to respond, and will either return a response or raise an
305+
exception. We can call the method and see the response like this:
306+
307+
```py
308+
feature = stub.GetFeature(point)
309+
print(feature)
310+
```
311+
312+
You can inspect the fields of the Feature object and output the result of the request:
313+
314+
```py
315+
if feature.name:
316+
print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
317+
else:
318+
print(f"Found no feature at at {format_point(feature.location)}")
319+
```
320+
321+
> [!TIP]
322+
> For the completed route guide client, see
323+
> [`completed/route_guide_client.py`](https://github.com/grpc-ecosystem/grpc-codelabs/blob/main/codelabs/grpc-python-getting-started/completed/route_guide_client.py).
324+
325+
## Try it out!
326+
327+
Run the server:
328+
329+
```sh
330+
python route_guide_server.py
331+
```
332+
333+
From a different terminal, [Activate virtual environment](#activate-virtual-environment), then run
334+
the client:
335+
336+
```sh
337+
python route_guide_client.py
338+
```
339+
340+
## What’s next
20341

21-
- [start_here](start_here/) directory serves as a starting point for the
22-
codelab.
23-
- [completed](completed/) directory showcases the finished code, giving you a
24-
peak of how the final implementation should look like.
342+
* Learn how gRPC works in [Introduction to gRPC](https://grpc.io/docs/what-is-grpc/introduction/)
343+
and [Core concepts](https://grpc.io/docs/what-is-grpc/core-concepts/).
344+
* Explore the [Python API reference](https://grpc.github.io/grpc/python/).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2024 gRPC authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.

codelabs/grpc-python-getting-started/completed/protos/route_guide.proto

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,11 @@ package routeguide;
2323

2424
// Interface exported by the server.
2525
service RouteGuide {
26-
// A simple RPC.
27-
//
2826
// Obtains the feature at a given position.
29-
//
30-
// A feature with an empty name is returned if there's no feature at the given
31-
// position.
3227
rpc GetFeature(Point) returns (Feature) {}
3328
}
3429

35-
// Points are represented as latitude-longitude pairs in the E7 representation
36-
// (degrees multiplied by 10**7 and rounded to the nearest integer).
37-
// Latitudes should be in the range +/- 90 degrees and longitude should be in
38-
// the range +/- 180 degrees (inclusive).
30+
// Points are represented as latitude-longitude pairs.
3931
message Point {
4032
int32 latitude = 1;
4133
int32 longitude = 2;

0 commit comments

Comments
 (0)