grpcio-status
grpcio-status provides the Python binding for the google.rpc.Status protobuf, enabling rich (structured) error details to be packed into gRPC trailing metadata and unpacked on the client side. It sits on top of grpcio and protobuf, exposing two primary helpers — rpc_status.to_status() and rpc_status.from_call() — both marked EXPERIMENTAL in the source. The package is versioned in strict lockstep with grpcio (current: 1.78.0) and is released on the same cadence, typically every 4–6 weeks.
Warnings
- breaking grpcio-status>=1.51 requires protobuf>=4.21.6 and is incompatible with any dependency that pins protobuf<4.0.0. Mixing versions causes an unresolvable dependency conflict at install time.
- breaking grpcio and grpcio-status must share the exact same version. Installing mismatched versions (e.g. grpcio==1.77.0 with grpcio-status==1.78.0) causes runtime AttributeError or silent misbehaviour.
- gotcha rpc_status.from_call() returns None — not an empty Status — when the server used set_code()/set_details() instead of abort_with_status(rpc_status.to_status(...)). A failed RPC does not automatically generate a rich status proto.
- gotcha from_call() raises ValueError if the gRPC call's status code or message text is inconsistent with the values inside the embedded google.rpc.Status proto. This happens when server code sets status code/message separately after packing the proto.
- gotcha Both rpc_status.from_call() and rpc_status.to_status() are explicitly marked EXPERIMENTAL in the gRPC source and documentation; their signatures could change without a major version bump.
- gotcha The importable package name is grpc_status (underscore) but the PyPI slug is grpcio-status (hyphen). Confusing them produces a ModuleNotFoundError even when the package is correctly installed.
- deprecated grpcio 1.78.0 introduces a new dependency on typing-extensions~=4.13. If your environment pins typing-extensions to an older version the install will fail.
Install
-
pip install grpcio-status -
pip install "grpcio-status==1.78.0" "grpcio==1.78.0" "googleapis-common-protos>=1.5.5"
Imports
- rpc_status
from grpc_status import rpc_status
- rpc_status.from_call
from grpc_status import rpc_status; status = rpc_status.from_call(rpc_error)
- rpc_status.to_status
from grpc_status import rpc_status; context.abort_with_status(rpc_status.to_status(rich_status))
- status_pb2.Status
from google.rpc import status_pb2
- error_details_pb2
from google.rpc import error_details_pb2
Quickstart
# pip install grpcio grpcio-status googleapis-common-protos
import grpc
from grpc_status import rpc_status
from google.rpc import status_pb2, code_pb2, error_details_pb2
from google.protobuf import any_pb2
# --- SERVER SIDE (inside a servicer method) ---
def MyRpc(request, context):
# Build a structured error detail
detail = any_pb2.Any()
detail.Pack(
error_details_pb2.BadRequest(
field_violations=[
error_details_pb2.BadRequest.FieldViolation(
field="name",
description="must not be empty",
)
]
)
)
rich_status = status_pb2.Status(
code=code_pb2.INVALID_ARGUMENT,
message="Invalid request",
details=[detail],
)
# abort_with_status atomically sets code + message + trailing metadata
context.abort_with_status(rpc_status.to_status(rich_status))
# --- CLIENT SIDE ---
def call_rpc(stub):
try:
stub.MyRpc(request=object()) # placeholder
except grpc.RpcError as rpc_error:
# from_call returns None when no rich status was packed
status = rpc_status.from_call(rpc_error)
if status is None:
print("No rich status; plain code:", rpc_error.code())
return
print("gRPC status code:", rpc_error.code())
for detail in status.details:
if detail.Is(error_details_pb2.BadRequest.DESCRIPTOR):
info = error_details_pb2.BadRequest()
detail.Unpack(info)
for v in info.field_violations:
print(f"Field '{v.field}': {v.description}")