gRPC Interceptor
grpc-interceptor is a Python library that simplifies the implementation of gRPC interceptors. It provides base classes and utility interceptors that offer direct access to request, response, and service context objects, which are typically harder to access with standard `grpc` library interceptors. The library emphasizes a small, readable codebase and minimal dependencies, primarily `grpcio`. It is actively maintained with regular minor releases, currently at version 0.15.4.
Warnings
- breaking Version 0.15.0 and later dropped support for Python 3.6.0.
- breaking Asynchronous server interceptors (AsyncServerInterceptor and AsyncExceptionToStatusInterceptor) introduced in v0.15.0 require grpcio >= 1.32.0. Using older grpcio versions will prevent async features from working.
- gotcha The internal type of `MethodName` changed from a `NamedTuple` in version 0.14.1. Code relying on `MethodName` being iterable or having `NamedTuple`-specific behavior might break.
- gotcha Do not confuse `grpc_interceptor.ServerInterceptor` or `grpc_interceptor.ClientInterceptor` with the native `grpc.ServerInterceptor` or `grpc.ClientInterceptor` classes. They have different APIs and intended usage.
- gotcha For async server streaming RPCs, an alternate API was introduced where the RPC method might return `None` instead of an `async_generator`. If your interceptor logic expects an `async_generator` in all streaming cases, it may need adjustment.
- gotcha Prior to v0.15.3, calling `context.abort` from an interceptor might have resulted in the wrong gRPC status code being set for the RPC. This was fixed in v0.15.3.
- gotcha As of v0.15.4, interceptors will be skipped for RPC methods that are not registered in the gRPC server. Previous versions might have invoked interceptors even for unregistered methods, potentially leading to unexpected behavior.
Install
-
pip install grpc-interceptor -
pip install grpc-interceptor[testing]
Imports
- ServerInterceptor
from grpc_interceptor import ServerInterceptor
- AsyncServerInterceptor
from grpc_interceptor import AsyncServerInterceptor
- ClientInterceptor
from grpc_interceptor import ClientInterceptor
- ExceptionToStatusInterceptor
from grpc_interceptor import ExceptionToStatusInterceptor
- AsyncExceptionToStatusInterceptor
from grpc_interceptor import AsyncExceptionToStatusInterceptor
- GrpcException
from grpc_interceptor.exceptions import GrpcException
- NotFound
from grpc_interceptor.exceptions import NotFound
Quickstart
import grpc
from concurrent import futures
from grpc_interceptor import ServerInterceptor
from grpc_interceptor.exceptions import GrpcException, NotFound
# Assuming a generated protobuf service like my_pb2_grpc and my_pb2
# For demonstration, we'll mock these:
class MockRequest:
def __init__(self, name):
self.name = name
class MockResponse:
def __init__(self, message):
self.message = message
class MockServicerContext:
def __init__(self):
self.code = grpc.StatusCode.OK
self.details = ''
def set_code(self, code):
self.code = code
def set_details(self, details):
self.details = details
def abort(self, code, details):
self.set_code(code)
self.set_details(details)
raise GrpcException(code, details)
class CustomExceptionInterceptor(ServerInterceptor):
def intercept(self, method, request, context, method_name):
try:
return method(request, context)
except GrpcException as e:
context.set_code(e.status_code)
context.set_details(e.details)
raise # Re-raise to let gRPC handle it after context is set
except Exception as e:
# Catch other unexpected exceptions
context.set_code(grpc.StatusCode.INTERNAL)
context.set_details(f"An unexpected error occurred: {e}")
raise
class MyServiceServicer:
def SayHello(self, request, context):
if request.name == "Error":
raise NotFound("Name not found!")
return MockResponse(message=f"Hello, {request.name}!")
def serve():
# In a real application, you'd use generated stubs and an actual gRPC server
interceptors = [CustomExceptionInterceptor()]
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=interceptors)
# In a real app, you'd add your service here:
# my_pb2_grpc.add_MyServiceServicer_to_server(MyServiceServicer(), server)
# For this example, we'll simulate the service call through the interceptor
service_instance = MyServiceServicer()
print("Simulating RPC calls through the interceptor:")
# Successful call
req_success = MockRequest("World")
ctx_success = MockServicerContext()
try:
res_success = interceptors[0].intercept(service_instance.SayHello, req_success, ctx_success, "/MyService/SayHello")
print(f"Success: {res_success.message} (Status: {ctx_success.code.name})")
except Exception as e:
print(f"Unexpected error in successful call: {e}")
# Error call
req_error = MockRequest("Error")
ctx_error = MockServicerContext()
try:
interceptors[0].intercept(service_instance.SayHello, req_error, ctx_error, "/MyService/SayHello")
except GrpcException as e:
print(f"Caught expected error: {ctx_error.details} (Status: {ctx_error.code.name})")
except Exception as e:
print(f"Caught unexpected error type: {e}")
if __name__ == '__main__':
serve()