Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for First glimpse into gRPC through Python (Part 2)
Ivan Yu
Ivan Yu

Posted on • Originally published ativanyu2021.hashnode.dev

     

First glimpse into gRPC through Python (Part 2)

1. Previous Post

First glimpse into gRPC through Python (Part 1)

2. Implementation and Examples

After introduced gRPC concept and development flow in previous post, we will show how to implement gRPC in python.

Example Code link (To run the below examples, please go throughREADME.md in the link)

2.1. 4 kinds of service method

There are 4 kinds of service methods in gRPC and the difference between them is to applysteaming or not in request or response.

2.1.1. Simple RPC

This type is just like the http request and response. Client send a single request to server and server reply a single reponse.

Proto file

rpcdoSimple(Request)returns(Response){}
Enter fullscreen modeExit fullscreen mode

Server

defdoSimple(self,request,context):output=f"Hello{request.input}!"returnResponse(output=f"Hello{request.input}!")
Enter fullscreen modeExit fullscreen mode

Client

defcall_doSimple(self):withgrpc.insecure_channel('localhost:50051')aschannel:stub=SampleServiceStub(channel)request=Request(input=self.faker.name())logger.info(f"doSimple client sent:{request.input}")response=stub.doSimple(request)logger.info(f"doSimple client received:{response.output}")
Enter fullscreen modeExit fullscreen mode

Command line output

[ 2022-07-15 09:43:27,530 ][ INFO ][ call_doSimple ] doSimple client sent: Lindsay Ross[ 2022-07-15 09:43:27,531 ][ INFO ][ call_doSimple ] doSimple client received: Hello Lindsay Ross!
Enter fullscreen modeExit fullscreen mode

2.1.2. Response-streaming RPC

For this type, streaming happens in the response. Client sends a single request to server and server returns a streaming of multiple responses.

Proto file

rpcdoResponseStreaming(Request)returns(streamResponse){}
Enter fullscreen modeExit fullscreen mode

Server

defdoResponseStreaming(self,request,context):faker=Faker()name_list=[*[faker.name()foriinrange(3)],request.input]fornameinname_list:time.sleep(0.5)yieldResponse(output=name)
Enter fullscreen modeExit fullscreen mode

Client

defcall_doResponseStreaming(self):withgrpc.insecure_channel('localhost:50051')aschannel:stub=SampleServiceStub(channel)request=Request(input=self.faker.name())logger.info(f"doResponseStreaming client sent:{request.input}")response_generator=stub.doResponseStreaming(request)forresponseinresponse_generator:logger.info(f"doResponseStreaming client received:{response.output}")
Enter fullscreen modeExit fullscreen mode

Command line output

[ 2022-07-15 10:14:27,347 ][ INFO ][ call_doResponseStreaming ] doResponseStreaming client sent: Veronica Good[ 2022-07-15 10:14:27,971 ][ INFO ][ call_doResponseStreaming ] doResponseStreaming client received: Richard Torres[ 2022-07-15 10:14:28,472 ][ INFO ][ call_doResponseStreaming ] doResponseStreaming client received: Monica Russo[ 2022-07-15 10:14:28,985 ][ INFO ][ call_doResponseStreaming ] doResponseStreaming client received: Sean Lane[ 2022-07-15 10:14:29,498 ][ INFO ][ call_doResponseStreaming ] doResponseStreaming client received: Veronica Good
Enter fullscreen modeExit fullscreen mode

2.1.3. Request-streaming RPC

Similar to the above type, but the streaming happens in the request. Client sends a streaming of multiple requests to server and server returns a single response.

Proto file

rpcdoRequestStreaming(streamRequest)returns(Response){}
Enter fullscreen modeExit fullscreen mode

Server

defdoRequestStreaming(self,request_iterator,context):result_list=[]forrequestinrequest_iterator:result_list.append(request.input.upper())returnResponse(output=", ".join(result_list))
Enter fullscreen modeExit fullscreen mode

Client

defcall_doRequestStreaming(self):defget_fake_name_generator():faker=Faker()for_inrange(10):time.sleep(0.5)name=faker.name()logger.info(f"doRequestStreaming client sent:{name}.")yieldRequest(input=name)withgrpc.insecure_channel('localhost:50051')aschannel:stub=SampleServiceStub(channel)request=get_fake_name_generator()response=stub.doRequestStreaming(request)logger.info(f"doRequestStreaming client received:{response.output}")
Enter fullscreen modeExit fullscreen mode

Command line output

[ 2022-07-15 10:21:08,058 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: Courtney Hammond.[ 2022-07-15 10:21:08,562 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: James Petersen.[ 2022-07-15 10:21:09,070 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: Tom Anderson.[ 2022-07-15 10:21:09,073 ][ INFO ][ call_doRequestStreaming ] doRequestStreaming client received: COURTNEY HAMMOND, JAMES PETERSEN, TOM ANDERSON
Enter fullscreen modeExit fullscreen mode

2.1.4. Bidirectionally-streaming RPC

As you may already guess the meaning from the name, the streaming happens in both request and response.

Proto file

rpcdoBidirectional(streamRequest)returns(streamResponse){}
Enter fullscreen modeExit fullscreen mode

Server

defdoBidirectional(self,request_iterator,context):forrequestinrequest_iterator:yieldResponse(output=request.input+" is excellent!")
Enter fullscreen modeExit fullscreen mode

Client

defcall_doBidirectional(self):defget_fake_name_generator():faker=Faker()for_inrange(3):time.sleep(0.5)name=faker.name()logger.info(f"doRequestStreaming client sent:{name}.")yieldRequest(input=name)withgrpc.insecure_channel('localhost:50051')aschannel:stub=SampleServiceStub(channel)request=get_fake_name_generator()response_generator=stub.doBidirectional(request)forresponseinresponse_generator:logger.info(f"doBidirectional client received:{response.output}")
Enter fullscreen modeExit fullscreen mode

Command line output

[ 2022-07-15 10:41:11,994 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: Sherry Hanson.[ 2022-07-15 10:41:11,996 ][ INFO ][ call_doBidirectional ] doBidirectional client received: Sherry Hanson is excellent![ 2022-07-15 10:41:12,497 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: Danielle Jones.[ 2022-07-15 10:41:12,499 ][ INFO ][ call_doBidirectional ] doBidirectional client received: Danielle Jones is excellent![ 2022-07-15 10:41:12,999 ][ INFO ][ get_fake_name_generator ] doRequestStreaming client sent: Alexis Goodwin.[ 2022-07-15 10:41:13,001 ][ INFO ][ call_doBidirectional ] doBidirectional client received: Alexis Goodwin is excellent!
Enter fullscreen modeExit fullscreen mode

2.2. Special Data Types and default value

gRPC can handles some speical data types, like date time, list or map, but need to pay some attention to avoid bug.

2.2.1. Date Time

gRPC use timestamp to handle date time and it uses timezone.

In proto file

google.protobuf.Timestampdate=1;
Enter fullscreen modeExit fullscreen mode

You need to include timezone in the input date time in order to transfer the data correctly to the server side.

Below is the output send the dateWITHOUT timezone from client to server side:

[ 2022-07-15 11:04:52,633 ][ INFO ][ call_doSpecialDataType ] doSpecialDataType client sent: request.date=2022-07-15 11:04:52[ 2022-07-15 11:04:52,654 ][ INFO ][ doSpecialDataType ] doSpecialDataType Server received: request.date=2022-07-15 20:04:52
Enter fullscreen modeExit fullscreen mode

We can see there are 9 hours difference between client and server since gRPC regards the date time without timezone as UTC time. When the date input received by the server, it automatically add 9 hours as we are in UTC+09:00 region.

2.2.2. List

gRPC can handle list datatype by simply addingrepeated in front of the field in proto file.

repeatedstringnames=1;
Enter fullscreen modeExit fullscreen mode

2.2.3. Map

gRPC can handle map datatype by defining map as the type and specify key and value type in proto file.

map<string,string>name2phoneNumMap=3;
Enter fullscreen modeExit fullscreen mode

2.2.4. Default value

According to thislink, it mentioned that

if a scalar message field is set to its default, the value will not be serialized on the wire.

We will explain the meaning in the following example:

proto file

repeatedCardInfocardInfos=4;messageCardInfo{stringname=1;int32numberOfCreditCard=2;}
Enter fullscreen modeExit fullscreen mode

We have a list of cardInfo and each card info contains an integer field callednumberOfCreditCard. In the response, we setnumberOfCreditCard of last CardInfo of the cardInfo list to be 0.

Server

cardInfos=request.cardInfoscardInfos.append(CardInfo(name="Boris Lee",numberOfCreditCard=0))
Enter fullscreen modeExit fullscreen mode

Command line output

[ 2022-07-15 11:21:39,200 ][ INFO ][ call_doSpecialDataType ] doSpecialDataType client received:  response.cardInfos= [name: "Katherine Soto"numberOfCreditCard: 1, name: "Kerry Powell"numberOfCreditCard: 1, name: "Mrs. Christina Hicks DDS"numberOfCreditCard: 1, name: "Boris Lee" <- No "numberOfCreditCard"]
Enter fullscreen modeExit fullscreen mode

We can see that Boris Lee doesNOT have numberOfCreditCard. Since 0 is regarded as default value, it will not be serialized on the wire and transfer back to client.

To solve this problem, we need to addoptional in front of the field in proto file.

optionalint32numberOfCreditCard=2;
Enter fullscreen modeExit fullscreen mode

Generate the code and run the program again, you can see the zero appeared.

, name: "Boris Lee"numberOfCreditCard: 0]
Enter fullscreen modeExit fullscreen mode

3. Unit test

We can use python nativeunit test framework to write unit test for gRPC. This is crucial as you do not need to switch on/off the gRPC server again and again whenever there is change in your code in order to test your code manually.

3.1. Create test server

First, you need to create a test server for receiving gRPC call in thesetUp method so that whenever the test method is called, it will first set up the test server.

classSampleServiceTest(unittest.TestCase):defsetUp(self):logger.info(f"=== Method:{self._testMethodName} =======")servicers={sample_service_pb2.DESCRIPTOR.services_by_name['SampleService']:SampleService()}self.test_server=grpc_testing.server_from_dictionary(servicers,grpc_testing.strict_real_time())
Enter fullscreen modeExit fullscreen mode

3.2. Create test method

Next, you need to create at least one test method for each gRPC method you want to test.

deftest_doSimple(self):faker=Faker()request=sample_service_pb2.Request(input=faker.name())doSimple_method=self.test_server.invoke_unary_unary(method_descriptor=(sample_service_pb2.DESCRIPTOR.services_by_name['SampleService'].methods_by_name['doSimple']),invocation_metadata={},request=request,timeout=1)response,_,code,_=doSimple_method.termination()self.assertEqual(code,grpc.StatusCode.OK)self.assertEqual(response.output,f"Hello{request.input}!")
Enter fullscreen modeExit fullscreen mode

3.3. Run test

Finally, run your test in the main thread

if__name__=='__main__':unittest.main()
Enter fullscreen modeExit fullscreen mode

For detail, take a look insample_service_test.py

4. Reason I wrote this series of blogs

I wrote this blog as I found that when I first learnt about gRPC, it is a little bit difficult to grasp the development method in a fast manner. There are a lot of built-in examples in the gRPC site, but it is a little bit too complicate for a beginner.

At first, what I wanted to know the most are:

1. How can I make a new method?2. How can I call the method in a microservice through gRPC?3. How can I handle the input and output?4. How can I do the unit test?
Enter fullscreen modeExit fullscreen mode

There are resources in Internet, but they are either scattered around or they used a lot of words to explain without a diagram. As a result, I decided to write this blog to present gRPC (using Python) in a simple and practical way so newcomer can pick up the concept easily and then start development immediately.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Joined

More fromIvan Yu

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp