Featured

Building a gRPC Service with Nested Messages, Repeated Fields, and Oneof in Python

Introduction: 

gRPC is a high-performance, open-source framework developed by Google for building efficient and scalable distributed systems. It provides a language-agnostic way to define and implement services using Protocol Buffers (protobuf) as the interface definition language.

In this tutorial, we'll explore how to build a gRPC service in Python that incorporates advanced features such as nested messages, repeated fields, and oneof. These features allow us to create complex data structures and handle scenarios where multiple values or mutually exclusive fields are involved.

 


Prerequisites:

  • Basic understanding of gRPC and Protocol Buffers.
  • Python development environment set up.

 

Step 1: Define the Protocol Buffers (protobuf) File 

Start by defining the service and message definitions in a proto file. Here's an example of a proto file named example.proto that defines a gRPC service with nested messages, repeated fields, and oneof:

syntax = "proto3";

package example;

// run python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. example.proto

service MyService {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
  rpc GetUser (UserRequest) returns (UserResponse) {}
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

message UserRequest {
  int32 id = 1;
}

message UserResponse {
  string name = 1;
  repeated Email email = 2;
 
  message Email {
    string address = 1;
    bool is_primary = 2;
  }
 
  message Contact {
    oneof contact_type {
      string phone_number = 1;
      string social_media = 2;
    }
  }
 
  Contact contact = 3;
}

Step 2: Generate Python Code from the Proto File 

To generate the Python code from the proto file, you can use the protoc command-line tool. Execute the following command in your terminal:

 
python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. example.proto

This will generate two Python files: example_pb2.py and example_pb2_grpc.py, which contain the necessary code to work with the defined gRPC service and messages.

 

Step 3: Implement the Server 

Next, let's implement the server-side logic. Here's an example of how the server code can be structured in server.py file:

import grpc
import example_pb2
import example_pb2_grpc
from concurrent import futures
import time

class MyServiceServicer(example_pb2_grpc.MyServiceServicer):
    def SayHello(self, request, context):
        message = f"Hello, {request.name}!"
        return example_pb2.HelloResponse(message=message)
    def GetUser(self, request, context):
        # Assuming we have a function to retrieve user information based on the ID
        user = get_user_by_id(request.id)
        return user

def get_user_by_id(user_id):
    # Assuming implementation to retrieve user information
    # based on the provided ID
   
    # Dummy user data for demonstration purposes
    dummy_users = {
        1: {
            "name": "John Doe",
            "email": [
                {"address": "john.doe@example.com", "is_primary": True},
                {"address": "johndoe@gmail.com", "is_primary": False}
            ],
            "contact": {
                "type": "phone_number",
                "value": "1234567890"
            }
        },
        2: {
            "name": "Jane Smith",
            "email": [
                {"address": "jane.smith@example.com", "is_primary": True},
                {"address": "janesmith@gmail.com", "is_primary": False}
            ],
            "contact": {
                "type": "social_media",
                "value": "@janesmith"
            }
        }
    }

    # Retrieve the user data based on the provided ID
    if user_id in dummy_users:
        user_data = dummy_users[user_id]
        user = example_pb2.UserResponse()
        user.name = user_data["name"]
        for email_data in user_data["email"]:
            email = user.email.add()
            email.address = email_data["address"]
            email.is_primary = email_data["is_primary"]
        if "contact" in user_data:
            contact_data = user_data["contact"]
            contact = user.contact
            if "type" in contact_data:
                if contact_data["type"] == "phone_number":
                    contact.phone_number = contact_data["value"]
                elif contact_data["type"] == "social_media":
                    contact.social_media = contact_data["value"]
        return user
    else:
        raise Exception("User not found")



def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    example_pb2_grpc.add_MyServiceServicer_to_server(MyServiceServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("Server started. Listening on port 50051.")
    try:
        while True:
            time.sleep(86400)
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()


 

Step 4: Implement the Client 

Now, let's implement the client-side logic to interact with the gRPC server. Here's an example client code in client.py file:

import grpc
import example_pb2
import example_pb2_grpc

def run():
    channel = grpc.insecure_channel('localhost:50051')
    stub = example_pb2_grpc.MyServiceStub(channel)
    response = stub.SayHello(example_pb2.HelloRequest(name='Royyan'))
    print(response.message)
   
    # Call the getUser method
    user_request = example_pb2.UserRequest(id=1)
    user_response = stub.GetUser(user_request)
    print("User Name:", user_response.name)
    for email in user_response.email:
        print("Email Address:", email.address)
        print("Is Primary:", email.is_primary)
    if user_response.HasField("contact"):
        if user_response.contact.HasField("phone_number"):
            print("Phone Number:", user_response.contact.phone_number)
        elif user_response.contact.HasField("social_media"):
            print("Social Media:", user_response.contact.social_media)

if __name__ == '__main__':
    run()


 

Step 5: Run the Server and Client 

To test the gRPC service, start the server by executing python server.py in one terminal window. Then, in another terminal window, run the client script using python client.py. You should see the server's response and user information printed by the client.

 

Conclusion: 

In this tutorial, we explored how to build a gRPC service in Python with advanced features such as nested messages, repeated fields, and oneof. We defined the service and message definitions in a proto file, generated the Python code, implemented the server-side logic, and created a client to interact with the server. This demonstrates the power and flexibility of gRPC and Protocol Buffers for building efficient and scalable distributed systems.

You can find the complete code and resources for this tutorial on Github

Cheers!

 

Comments

Popular Posts