|
1 | 1 | # Getting Started with gRPC-Python
|
2 | 2 |
|
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! |
4 | 4 |
|
| 5 | +Perfect for Python developers new to gRPC, those seeking a refresher, or anyone building distributed |
| 6 | +systems. No prior gRPC experience needed! |
5 | 7 |
|
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:** |
7 | 9 |
|
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. |
11 | 12 | - Client/Server Communication: Implement seamless interactions.
|
12 | 13 |
|
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. |
17 | 19 | - A strong foundation in gRPC for real-world projects.
|
18 | 20 |
|
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 |
20 | 341 |
|
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/). |
0 commit comments