Quick start¶
A client in three objects¶
import asyncio
from grpc_client_kit import ChannelPool, GrpcClient, GrpcClientConfig
async def main() -> None:
async with ChannelPool() as pool:
client = GrpcClient(
GreeterStub,
config=GrpcClientConfig(target="localhost:50051", insecure=True),
pool=pool,
)
async with client as stub:
print(await stub.Greet(GreetRequest(name="world")))
asyncio.run(main())
Three objects with three different lifetimes:
ChannelPoolowns the channels. Create one per process and share it; leaving itsasync withblock closes every channel it opened.GrpcClientConfigdescribes how a channel to this service is built — address, security, options, compression.GrpcClientowns nothing. It picks a target, asks the pool for the matching channel and constructs a stub, so creating one per request is cheap and closing one closes nothing.
async with client as stub yields the stub, not the client. Its __aexit__
deliberately leaves the channel alone: channels belong to the pool, and idle
connections are parked by gRPC core rather than closed. See
Channels & pooling for who closes what.
Letting the factory wire it¶
Assembling a pool, a balancer, a health checker and an interceptor chain by
hand gets repetitive across a dozen upstream services. GrpcClientFactory
does it from one settings object:
from grpc_client_kit import GrpcClientFactory
async with GrpcClientFactory(settings=settings) as factory:
users = factory.create_client(UserStub)
orders = factory.create_client(OrderStub, service_name="orders.v1.Orders")
async with users as stub:
await stub.GetUser(request)
Every client from one factory shares its pool, and recreating a client for
the same stub reuses the same chain and channel — a per-request DI scope does
not open a connection per request. Entering the factory's async with
starts health checking and waits for its first pass (bounded by
ready_timeout): until that pass every target reads unhealthy by design, so
returning earlier would make the first call of every freshly started pod fail
deterministically. A factory used without async with logs a warning and
keeps routing traffic to targets nobody has probed.
Leaving the block stops the checker and — only if the factory created the pool
itself — closes it, giving in-flight RPCs shutdown_grace seconds (5 by
default) to finish rather than cancelling them mid-flight: a k8s SIGTERM lands
exactly here. Hand in a pool of your own (GrpcClientFactory(pool=pool)) and
the factory borrows it without ever closing it.
The settings object is validated at construction: one missing required
field raises a TypeError naming it, instead of a bare AttributeError on
the first RPC in production.
See Configuration for the settings object the factory reads.
Handling failures¶
Failures come in two families, and the kit keeps them apart. The server said
no: a plain grpc.aio.AioRpcError carrying the server's status. The kit
said no — an open breaker, an exhausted deadline budget, a
balancer with nothing healthy left, a checker asked before it started — and
every one of those derives from GrpcClientKitError:
import grpc.aio
from grpc_client_kit import (
CircuitBreakerOpenError,
GrpcClientKitError,
NoHealthyTargetsError,
)
try:
async with client as stub:
return await stub.GetUser(request)
except NoHealthyTargetsError as exc:
... # nothing to call: exc.targets lists what was tried
except CircuitBreakerOpenError:
... # this method is tripped; fail fast, serve a fallback
except GrpcClientKitError:
... # any other local refusal — the network was never the problem
except grpc.aio.AioRpcError as exc:
if exc.code() is grpc.StatusCode.DEADLINE_EXCEEDED:
... # the whole call ran out of budget, retries included
raise
The two kit errors that stand in for an RPC outcome —
CircuitBreakerOpenError and DeadlineBudgetExhaustedError — additionally
are AioRpcErrors (carrying UNAVAILABLE and DEADLINE_EXCEEDED), so
existing handlers and every logging, metrics and tracing layer keep seeing
them as the call failures they are. Order the except clauses narrowest
first; the retry layer recognizes a tripped circuit and never retries it,
even though UNAVAILABLE is otherwise retryable.
Where to go next¶
| Page | What it covers |
|---|---|
| Configuration | GrpcClientConfig, the settings protocols, the extras |
| Channels & pooling | Channel identity, pool limits, who closes what |
| Interceptors | The chain, its order, and writing your own |
| Resilience | Timeout budgets, waiting for a connection, retry safety, the breaker |
| Deadline budgets | Propagating the caller's remaining time into every hop |
| Load balancing | Strategies and how health narrows them |
| Health checking | The probe loop, cold start, backoff |
| Observability | What the logs, metrics and spans actually contain |