Package: grpc-interceptor
SummarySimplified Python gRPC interceptors.The Pythongrpcpackage provides service interceptors, but they're a bit hard to
use because of their flexibility. Thegrpcinterceptors don't have direct access
to the request and response objects, or the service context. Access to these are often
desired, to be able to log data in the request or response, or set status codes on the
context.InstallationTo just get the interceptors (and probably not write your own):$pipinstallgrpc-interceptorTo also get the testing framework, which is good if you're writing your own interceptors:$pipinstallgrpc-interceptor[testing]UsageServer InterceptorTo define your own interceptor (we can useExceptionToStatusInterceptoras an example):fromgrpc_interceptorimportServerInterceptorfromgrpc_interceptor.exceptionsimportGrpcExceptionclassExceptionToStatusInterceptor(ServerInterceptor):defintercept(self,method:Callable,request_or_iterator:Any,context:grpc.ServicerContext,method_name:str,)->Any:"""Override this method to implement a custom interceptor.You should call method(request_or_iterator, context) to invoke thenext handler (either the RPC method implementation, or thenext interceptor in the list).Args:method: The next interceptor, or method implementation.request_or_iterator: The RPC request, as a protobuf message.context: The ServicerContext pass by gRPC to the service.method_name: A string of the form"/protobuf.package.Service/Method"Returns:This should generally return the result ofmethod(request_or_iterator, context), which is typically the RPCmethod response, as a protobuf message. The interceptoris free to modify this in some way, however."""try:returnmethod(request_or_iterator,context)exceptGrpcExceptionase:context.set_code(e.status_code)context.set_details(e.details)raiseThen inject your interceptor when you create thegrpcserver:interceptors=[ExceptionToStatusInterceptor()]server=grpc.server(futures.ThreadPoolExecutor(max_workers=10),interceptors=interceptors)To useExceptionToStatusInterceptor:fromgrpc_interceptor.exceptionsimportNotFoundclassMyService(my_pb2_grpc.MyServiceServicer):defMyRpcMethod(self,request:MyRequest,context:grpc.ServicerContext)->MyResponse:thing=lookup_thing()ifnotthing:raiseNotFound("Sorry, your thing is missing")...This results in the gRPC status status code being set toNOT_FOUND,
and the details"Sorry, your thing is missing". This saves you the hassle of
catching exceptions in your service handler, or passing the context down into
helper functions so they can callcontext.abortorcontext.set_code. It allows
the more Pythonic approach of just raising an exception from anywhere in the code,
and having it be handled automatically.Client InterceptorWe will use an invocation metadata injecting interceptor as an example of defining
a client interceptor:fromgrpc_interceptorimportClientCallDetails,ClientInterceptorclassMetadataClientInterceptor(ClientInterceptor):defintercept(self,method:Callable,request_or_iterator:Any,call_details:grpc.ClientCallDetails,):"""Override this method to implement a custom interceptor.This method is called for all unary and streaming RPCs. The interceptorimplementation should call `method` using a `grpc.ClientCallDetails` and the`request_or_iterator` object as parameters. The `request_or_iterator`parameter may be type checked to determine if this is a singluar requestfor unary RPCs or an iterator for client-streaming or client-server streamingRPCs.Args:method: A function that proceeds with the invocation by executing the nextinterceptor in the chain or invoking the actual RPC on the underlyingchannel.request_or_iterator: RPC request message or iterator of request messagesfor streaming requests.call_details: Describes an RPC to be invoked.Returns:The type of the return should match the type of the return value receivedby calling `method`. This is an object that is both a`Call <https://grpc.github.io/grpc/python/grpc.html#grpc.Call>`_ for theRPC and a `Future <https://grpc.github.io/grpc/python/grpc.html#grpc.Future>`_.The actual result from the RPC can be got by calling `.result()` on thevalue returned from `method`."""new_details=ClientCallDetails(call_details.method,call_details.timeout,[("authorization","Bearer mysecrettoken")],call_details.credentials,call_details.wait_for_ready,call_details.compression,)returnmethod(request_or_iterator,new_details)Now inject your interceptor when you create thegrpcchannel:interceptors=[MetadataClientInterceptor()]withgrpc.insecure_channel("grpc-server:50051")aschannel:channel=grpc.intercept_channel(channel,*interceptors)...Client interceptors can also be used toretry RPCsthat fail due to specific errors, or a host of other use cases. There are some basic
approaches inthe teststo get you started.Note: Themethodin a client interceptor is acontinuationas described in theclient interceptor section of the gRPC docs.
When you invoke the continuation, you get a future back, which resolves to either the
result, or exception. This is different than invoking a client stub, which returns the
result directly. If the interceptor needs the value returned by the call, or to catch
exceptions, then you'll need to dofuture = method(request_or_iterator, call_details),
followed byfuture.result(). Check out the tests forexamples.DocumentationThe examples above showed usage for simple unary-unary RPC calls. For examples of
streaming and asyncio RPCs, read thecomplete documentation here.Note that there is no asyncio client interceptors at the moment, though contributions
are welcome.