API Reference¶
Auto-generated from source using mkdocstrings.
grpc_client_kit¶
Batteries-optional async gRPC client toolkit.
Channel pooling with health monitoring, client-side load balancing (round-robin, random, weighted), resilience (retries, timeouts, circuit breakers) and observability (logging, OpenTelemetry tracing, metrics) — with every integration behind an extra.
AsyncAroundClientInterceptor
¶
Bases: AsyncClientInterceptor, ABC
A logical interceptor that wraps calls instead of re-issuing them.
Subclasses implement around_call — an async generator that yields exactly once:
- code before
yieldruns before the RPC is created, which is where the call details may still be rewritten; - the RPC happens at the
yield, including full consumption of a streaming response; - code after
yield, or inexcept/finally, runs once the call has finished.
A failure reaches the yield as grpc.aio.AioRpcError, mid-stream failures included, so an
ordinary try/except around it covers the whole call. Refusing a call is a matter of raising
before the yield: the RPC is then never created. Swallowing an exception is not supported —
the call has already failed by then, and there is no response to return in its place.
Source code in grpc_client_kit/interceptors/base.py
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
around_call(call)
abstractmethod
¶
intercept(call)
async
¶
Issue the call inside around_call (base plumbing; subclasses override the generator).
Source code in grpc_client_kit/interceptors/base.py
AsyncCircuitBreakerInterceptor
¶
Bases: AsyncAroundClientInterceptor
Async circuit breaker interceptor for gRPC clients.
This interceptor implements the Circuit Breaker pattern on a per-method basis. It prevents cascading failures by "opening" the circuit for methods that fail repeatedly, immediately rejecting subsequent calls until a recovery period has passed.
States: - CLOSED: Normal operation. Successes and failures are tracked. - OPEN: Service is failing. Calls are immediately rejected with CircuitBreakerOpenError. - HALF-OPEN: Limited trial calls are allowed to see if the service has recovered.
Scope
An interceptor is bound to one channel, hence to one target. Give each target its own
instance (GrpcClientFactory does) and state is effectively per (target, method), so one
failing backend cannot trip the breaker for its healthy peers.
What counts as a failure
Only the statuses in _should_record_failure — the ones that describe the server rather
than the request. A malformed request or a rejected permission is the caller's problem and
must not be able to trip a circuit. A cancellation is nobody's failure: asyncio.CancelledError
derives from BaseException, so it passes this interceptor's handlers untouched and only
the trial slot it was holding is given back.
Features: - Thread-safe and async-safe implementation. - Per-method state isolation. - LRU cache for method states to prevent memory leaks in long-running processes. - Observability via optional metrics registry.
Source code in grpc_client_kit/interceptors/circuit_breaker.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(fail_threshold=5, recovery_timeout=60.0, half_open_max_calls=1, max_methods=1000, metrics=None)
¶
Initialize the circuit breaker interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fail_threshold
|
int
|
Number of failures before transitioning from CLOSED to OPEN. |
5
|
recovery_timeout
|
float
|
Seconds to wait in OPEN state before transitioning to HALF-OPEN. |
60.0
|
half_open_max_calls
|
int
|
Maximum number of concurrent trial calls allowed in HALF-OPEN. |
1
|
max_methods
|
int
|
Maximum number of distinct methods to track in LRU cache. |
1000
|
metrics
|
GrpcClientMetricsProtocol | None
|
Optional metrics registry for recording state transitions. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If any threshold or timeout is out of range. |
Source code in grpc_client_kit/interceptors/circuit_breaker.py
around_call(call)
async
¶
Refuse the call while the circuit is open, otherwise run it and record how it ended.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ClientCall
|
The call being guarded. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[None]
|
Once, with the RPC in flight. For a streaming response the yield spans the whole stream, |
AsyncIterator[None]
|
so a server that fails halfway through it still counts as a failure. |
Raises:
| Type | Description |
|---|---|
CircuitBreakerOpenError
|
If the circuit for this method is open, or every half-open
trial slot is taken. Raised before the |
Source code in grpc_client_kit/interceptors/circuit_breaker.py
get_states()
async
¶
Get the current state of all monitored methods.
Returns:
| Type | Description |
|---|---|
dict[str, CircuitBreakerStatus]
|
A dictionary mapping method names to their status (state, failure_count, etc.). |
Source code in grpc_client_kit/interceptors/circuit_breaker.py
intercept(call)
async
¶
Issue the call inside around_call (base plumbing; subclasses override the generator).
Source code in grpc_client_kit/interceptors/base.py
AsyncClientContextInterceptor
¶
Bases: AsyncClientInterceptor
Async client interceptor that injects metadata from a provider.
This interceptor allows for dynamic metadata injection (e.g., authorization tokens, request IDs, tenant IDs) into every outgoing gRPC call.
Features:
- Support for both synchronous and asynchronous metadata providers.
- Automatic validation and normalization of gRPC metadata keys.
- Automatic encoding of binary metadata values.
- The same injection on all four RPC kinds, streams included. Injection used to reach
unary-unary calls only: a channel files an interceptor by class, and an interceptor deriving
from all four gRPC base classes lands in the first list alone (see base).
Note
The metadata is written onto the call details before the RPC exists, so this layer
implements base.AsyncClientInterceptor.intercept and hands the call straight on instead of
using the around_call seam. Wrapping a response stream to observe an outcome it has no
use for would cost a generator hop per item, and that wrapper is not free in another way
either: when a caller abandons a stream, closing it drives the GeneratorExit back into
the wrapper, which the event loop reports as an error if it has closed the generator first
(measured at loop shutdown as RuntimeError: generator didn't stop after athrow()).
Source code in grpc_client_kit/interceptors/context.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(metadata_provider)
¶
Initialize the context interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata_provider
|
Callable[[], dict[str, str | bytes] | Awaitable[dict[str, str | bytes]]]
|
A callable that returns a dictionary of metadata. Can be a regular function or an async function. |
required |
Source code in grpc_client_kit/interceptors/context.py
intercept(call)
async
¶
Attach the provider's metadata to the call and issue it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ClientCall
|
The call to issue; its details are rewritten before the RPC exists. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The response of a unary call, or the iterator of a streaming one, untouched. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the provider returned a key or a value gRPC cannot carry. Raising before anything is issued means no call ever leaves with metadata that would be rejected further down. |
Source code in grpc_client_kit/interceptors/context.py
AsyncClientInterceptor
¶
Bases: ABC
A logical client interceptor: one implementation covering all four RPC kinds.
Subclasses implement intercept, which is handed a ClientCall and owns the call from there:
it decides whether to issue it at all, how many times, and with which details. That is the hook
for layers that re-issue calls — retries — or refuse them outright. Everything that only needs
to wrap a call should subclass AsyncAroundClientInterceptor instead and write one generator.
Deliberately not a grpc.aio.ClientInterceptor: passing one straight to a channel raises
ValueError from grpc instead of silently registering it for unary-unary calls only. Chains
reach a channel through flatten_interceptors, which turns each logical interceptor into the
four adapters a channel can file correctly.
Source code in grpc_client_kit/interceptors/base.py
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
intercept(call)
abstractmethod
async
¶
Run one RPC and return its outcome.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ClientCall
|
The call to run, and the handle used to issue it. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
For a unary response, the response message ( |
Any
|
streaming response, the response iterator — which must be built over an already issued |
Any
|
call, i.e. over the result of |
Raises:
| Type | Description |
|---|---|
AioRpcError
|
To fail the call, whether the error came off the wire or was raised by this interceptor without issuing anything. |
Source code in grpc_client_kit/interceptors/base.py
AsyncLoggingInterceptor
¶
Bases: AsyncAroundClientInterceptor
Async logging interceptor for gRPC clients with sensitive data filtering.
Provides comprehensive structured logging for every gRPC call, including start time, duration, status, and metadata.
Features:
- Real Outcomes: The terminal record carries the status the caller observed, whether it
arrived at the end of a unary call or in the middle of a response stream.
- Sensitive Data Redaction: Automatically masks common authentication headers.
- Method-Level Sensitivity: Entire methods or regex patterns can be marked
as sensitive to prevent payload logging.
- Request ID Correlation: Automatically extracts and includes request-id
from metadata in all log entries.
- Payload Logging: Optional logging of request and response bodies (truncated to 1KB).
- LRU Cache: Efficiently caches sensitivity check results for method names.
- Structured Logging: Uses the standard Python logging module with extra dict.
Note
Every log record gets a freshly built extra mapping derived from the call-wide fields
(service, method, request id, sensitivity). Records therefore never inherit fields from an
earlier stage of the same call, and handlers can retain the mapping they were given.
Source code in grpc_client_kit/interceptors/client_logging.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(service_name, sensitive_methods=None, sensitive_patterns=None, sensitive_headers=None, log_request_payload=False, log_response_payload=False, log_metadata=True, max_cache_size=1000, success_log_level=logging.INFO)
¶
Initialize the logging interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service_name
|
str
|
Name of the service for log categorization. |
required |
sensitive_methods
|
set[str] | None
|
Set of full method names to treat as sensitive. |
None
|
sensitive_patterns
|
list[str] | None
|
List of regex patterns for sensitive method names. |
None
|
sensitive_headers
|
set[str] | None
|
Set of header names (case-insensitive) to redact. |
None
|
log_request_payload
|
bool
|
Whether to log the request object. |
False
|
log_response_payload
|
bool
|
Whether to log the response object. |
False
|
log_metadata
|
bool
|
Whether to log gRPC metadata (redacted). |
True
|
max_cache_size
|
int
|
Size of the sensitivity check LRU cache. |
1000
|
success_log_level
|
int
|
Level of the record a successful call emits. One INFO line per
successful RPC is a flood at high QPS — drop this to |
INFO
|
Source code in grpc_client_kit/interceptors/client_logging.py
around_call(call)
async
¶
Log one RPC: a record when it starts, and exactly one saying how it ended.
Source code in grpc_client_kit/interceptors/client_logging.py
intercept(call)
async
¶
Issue the call inside around_call (base plumbing; subclasses override the generator).
Source code in grpc_client_kit/interceptors/base.py
AsyncRetryInterceptor
¶
Bases: AsyncClientInterceptor
Async retry interceptor for gRPC clients.
Retries failed calls with exponential backoff and jitter, without ever extending the deadline of the call it is retrying.
Reading the outcome
In grpc.aio a continuation resolves to a Call the moment the RPC is created and never
raises, so a retry layer that treats that object as the response sees every call succeed.
This one goes through ClientCall.invoke_unary, which awaits the Call and therefore fails
with the real status — that is the only reason a retry ever fires on a live connection.
Call budget
The relative timeout carried by the call details is the budget for the whole call, so it is converted into a deadline once, on entry. Every subsequent attempt is issued with the budget that is left (gRPC only understands relative timeouts, so the remainder is recomputed right before each attempt), and a retry is abandoned when the backoff alone would outlive the budget. Without this, N attempts of T seconds would silently stretch a T-second call to N * T.
Safety
Retrying an RPC that the server already executed duplicates its side effects, so what gets retried is deliberately narrow — but the default is a compromise, not a guarantee:
DEFAULT_RETRYABLE_CODEScovers the statuses that usually mean the request never reached the handler. Usually is not always: a connection that dies mid-handler surfaces asUNAVAILABLE, and an application may abort withRESOURCE_EXHAUSTEDafter a write — in both cases a retry duplicates the request. Where a duplicate write is unaffordable, setidempotent_methods; with the whitelist in place, nothing outside it is retried.- Codes like
INTERNAL,UNKNOWN,ABORTEDorDEADLINE_EXCEEDEDare worse still — the write has very likely been applied — and are never retried unless a caller opts in throughretryable_codes. idempotent_methods, when given, is a whitelist that applies to every call type: a unary-unary method outside of it is not retried even on a retryable code.- Streaming responses need that whitelist. Restarting a stream replays items the consumer
has already seen, so
retry_streamingalone is not enough to enable it. - Calls with a streaming request are never retried: the request iterator is consumed by the first attempt and cannot be replayed without buffering it whole.
- Native gRPC retries (
retryPolicyin a service config) run below this layer and multiply with it: kit attempts times native attempts reach the server. Configure one source of retries, not both — the client warns when it sees both.
Source code in grpc_client_kit/interceptors/retry.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(max_attempts=3, initial_backoff=0.1, max_backoff=10.0, backoff_multiplier=2.0, jitter=0.1, retryable_codes=None, retry_streaming=False, idempotent_methods=None, on_retry=None, metrics=None)
¶
Initialize the retry interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_attempts
|
int
|
Total number of attempts (including the first one). |
3
|
initial_backoff
|
float
|
Initial seconds to wait before first retry. |
0.1
|
max_backoff
|
float
|
Maximum seconds to wait between retries. |
10.0
|
backoff_multiplier
|
float
|
Factor by which backoff increases each attempt. |
2.0
|
jitter
|
float
|
Random variation factor (0.0 to 1.0) applied to backoff. |
0.1
|
retryable_codes
|
set[StatusCode] | None
|
gRPC status codes that trigger a retry. Defaults to
|
None
|
retry_streaming
|
bool
|
Whether to enable retries for Unary-Stream calls. |
False
|
idempotent_methods
|
set[str] | None
|
Full method names that may be retried. Required for streaming retries, and a whitelist for unary calls when given. |
None
|
on_retry
|
Callable[[str, int, StatusCode | None, float], Awaitable[None]] | None
|
Optional async callback called on every retry attempt. |
None
|
metrics
|
RetryMetricsProtocol | None
|
Optional registry told about every scheduled retry. The request metrics sit above this layer and see one entry per logical call, so without this a retry storm — N wire attempts collapsing into one success — is invisible on a dashboard. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If any of the backoff parameters is out of range. |
Source code in grpc_client_kit/interceptors/retry.py
intercept(call)
async
¶
Issue the call, retrying it as far as the configuration and the budget allow.
Source code in grpc_client_kit/interceptors/retry.py
AsyncTimeoutInterceptor
¶
Bases: AsyncClientInterceptor
Async timeout interceptor that sets the budget for a whole gRPC call.
The timeout set here is the budget for the entire logical call, not for a single network
attempt. As the outermost resilience interceptor it runs once per call, so everything nested
below it — retries in particular — has to fit into the budget it installs. AsyncRetryInterceptor
honours this by converting the timeout into a deadline and shrinking it before every attempt.
Behavior:
- If a call already carries a timeout, the smaller of the two wins: an explicit per-call
deadline set by the caller can only tighten the configured one, never loosen it.
- If no timeout is present, the configured one is applied.
- A timeout of None or 0 means "no deadline"; calls of that method are left untouched.
- The budget applies to all four RPC kinds. It used to reach unary-unary calls only: a channel
files an interceptor by class, and an interceptor deriving from all four gRPC base classes
lands in the first list alone (see base), which left every stream deadline-free.
Note
The budget is written onto the call details before the RPC exists, so this layer implements
base.AsyncClientInterceptor.intercept and hands the call straight on instead of using the
around_call seam. Wrapping a response stream to observe an outcome it has no use for
would cost a generator hop per item, and that wrapper is not free in another way either:
when a caller abandons a stream, closing it drives the GeneratorExit back into the
wrapper, which the event loop reports as an error if it has closed the generator first
(measured at loop shutdown as RuntimeError: generator didn't stop after athrow()).
Source code in grpc_client_kit/interceptors/timeout.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(default_timeout=10.0, per_method_timeouts=None)
¶
Initialize the timeout interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default_timeout
|
float | None
|
Total call budget in seconds for methods without a specific value.
|
10.0
|
per_method_timeouts
|
dict[str, float | None] | None
|
Mapping of full method names to specific budgets. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If any configured timeout is negative. |
Source code in grpc_client_kit/interceptors/timeout.py
intercept(call)
async
¶
Give the call its budget and issue it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ClientCall
|
The call to issue; its details are rewritten before the RPC exists. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The response of a unary call, or the iterator of a streaming one, untouched. |
Source code in grpc_client_kit/interceptors/timeout.py
AsyncWaitForReadyInterceptor
¶
Bases: AsyncClientInterceptor
Marks outgoing calls as willing to wait for the channel to be ready.
Behavior:
- A call whose method resolves to True is issued with wait_for_ready=True: instead of
failing while the channel is still connecting, it waits and runs as soon as it is up.
- A call whose method resolves to False is issued fail-fast, explicitly.
- None — globally or for one method — leaves the call exactly as it arrived.
- A call whose caller already set wait_for_ready keeps that value. An explicit decision at
the call site is more specific than a configured default, and it is the only way to opt one
call out of a policy set for the whole client.
Default
True, and the pairing with a deadline is what makes that defensible. On its own the flag
trades one failure mode for another: the call no longer fails fast, it waits — and a wait
for a backend that never comes back is a hang, which is worse than the error it replaced.
Bounded by a deadline the trade is one-sided: the call either connects and runs, or ends in
DEADLINE_EXCEEDED after exactly the time it was allowed, which is what the caller asked
for either way. So waiting is enabled only for calls that carry a deadline
(see require_deadline), and a chain built by this kit carries one by default.
Note
The flag is written onto the call details before the RPC exists, so this layer implements
base.AsyncClientInterceptor.intercept and hands the call straight on instead of using the
around_call seam — for the reasons timeout.AsyncTimeoutInterceptor documents: a
wrapper around a response stream would cost a generator hop per item and turns a caller
abandoning that stream into noise at loop shutdown.
Source code in grpc_client_kit/interceptors/wait_for_ready.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
adapters
cached
property
¶
The four single-ABC objects through which a channel registers this interceptor.
Built once per instance and kept: a chain's identity — and with it the channel pool's idea of which channels are interchangeable — is the identity of the objects in it.
__init__(default=True, per_method=None, require_deadline=True)
¶
Initialize the wait-for-ready interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default
|
bool | None
|
Value for methods without an entry in |
True
|
per_method
|
dict[str, bool | None] | None
|
Values for individual methods, keyed by full method name
( |
None
|
require_deadline
|
bool
|
Whether waiting is limited to calls that carry a deadline. Leave it on unless something else bounds the call, because an unbounded wait never ends by itself: the call sits there for as long as the backend stays unreachable. |
True
|
Source code in grpc_client_kit/interceptors/wait_for_ready.py
intercept(call)
async
¶
Apply this method's wait-for-ready setting and issue the call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ClientCall
|
The call to issue; its details are rewritten before the RPC exists. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The response of a unary call, or the iterator of a streaming one, untouched. |
Source code in grpc_client_kit/interceptors/wait_for_ready.py
ChannelKey
dataclass
¶
Full identity of a pooled channel.
Two callers may share a channel only when every field matches: gRPC binds credentials, options, compression and interceptors to a channel at creation time and none of them can be changed or added afterwards, so a channel built for one combination cannot serve another.
Attributes:
| Name | Type | Description |
|---|---|---|
target |
str
|
The target address (host:port). |
insecure |
bool
|
Whether the channel is insecure. |
credentials |
ChannelCredentials | None
|
TLS credentials, compared by identity because gRPC credentials define no equality — reuse one credentials object instead of rebuilding it per call. |
options |
tuple[tuple[str, Any], ...] | None
|
gRPC channel options, normalized to a tuple and compared by value. |
compression |
Compression | None
|
The channel compression setting. |
interceptors_token |
str | None
|
Stable identity of the interceptor chain, or None when there is none. |
Source code in grpc_client_kit/channel.py
build(target, insecure=False, credentials=None, options=None, compression=None, interceptors=None)
classmethod
¶
Build a key from the arguments of a channel request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
insecure
|
bool
|
Whether to use an insecure channel. |
False
|
credentials
|
ChannelCredentials | None
|
Optional TLS credentials for a secure channel. |
None
|
options
|
list[tuple[str, Any]] | None
|
Optional gRPC channel options. |
None
|
compression
|
Compression | None
|
Optional gRPC compression setting. |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional interceptor chain to bind to the channel. |
None
|
Returns:
| Type | Description |
|---|---|
ChannelKey
|
The hashable identity of the requested channel. |
Source code in grpc_client_kit/channel.py
ChannelPool
¶
Bases: ChannelProviderProtocol
Advanced channel pool for gRPC channels.
Channels are pooled by their full identity (see :class:ChannelKey), not by target alone:
asking for host:1 over TLS never returns the insecure channel someone else opened for the
same address, and a client never inherits another client's interceptor chain.
Async-safe through per-identity locking to minimize contention and prevent deadlocks.
Source code in grpc_client_kit/channel.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | |
__init__(max_channels_per_target=DEFAULT_MAX_CHANNELS_PER_TARGET, idle_timeout=DEFAULT_IDLE_TIMEOUT, health_checker=None, metrics=None)
¶
Initialize the channel pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_channels_per_target
|
int
|
Maximum number of concurrent channels to keep per identity. |
DEFAULT_MAX_CHANNELS_PER_TARGET
|
idle_timeout
|
float
|
Seconds without active RPCs after which a channel parks its connection.
Enforced by gRPC core ( |
DEFAULT_IDLE_TIMEOUT
|
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker for monitoring target health. |
None
|
metrics
|
GrpcClientMetricsProtocol | None
|
Optional metrics registry for pool statistics. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If max_channels_per_target is not positive. |
Source code in grpc_client_kit/channel.py
close_all(grace=None)
async
¶
Close all pooled channels.
The pool stays usable afterwards: this drains it rather than retiring it, so a shared pool
can outlive one shutdown and an async with block can be entered again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grace
|
float | None
|
Optional time to wait for active RPCs to finish. |
None
|
Source code in grpc_client_kit/channel.py
get_channel(target, insecure=False, credentials=None, options=None, compression=None, interceptors=None, key=None)
async
¶
Get or create a gRPC channel for the requested channel identity.
Implements round-robin selection among healthy channels of that identity. If all channels are unhealthy or the limit is reached, it will either create a new channel or return an existing one as a fallback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
insecure
|
bool
|
Whether to use an insecure channel. |
False
|
credentials
|
ChannelCredentials | None
|
Optional TLS credentials for a secure channel. |
None
|
options
|
list[tuple[str, Any]] | None
|
Optional gRPC channel options. |
None
|
compression
|
Compression | None
|
Optional gRPC compression setting. |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional list of interceptors to bind to the channel. |
None
|
key
|
ChannelKey | None
|
Precomputed identity from :meth: |
None
|
Returns:
| Type | Description |
|---|---|
Channel
|
An async gRPC channel. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the pool is closing. |
Source code in grpc_client_kit/channel.py
health_check(target=None)
async
¶
Check if pool is healthy (ready to accept requests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str | None
|
Optional target to check for reachability instead of the pool itself. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the pool (or the given target) can serve requests. |
Source code in grpc_client_kit/channel.py
make_key(target, insecure=False, credentials=None, options=None, compression=None, interceptors=None)
¶
Validate a channel request and build its pool identity.
The result is safe to cache and hand back through get_channel(key=...): a client whose
target, configuration and chain never change should pay for validation and identity
hashing once, not on every call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
insecure
|
bool
|
Whether to use an insecure channel. |
False
|
credentials
|
ChannelCredentials | None
|
Optional TLS credentials for a secure channel. |
None
|
options
|
list[tuple[str, Any]] | None
|
Optional gRPC channel options. |
None
|
compression
|
Compression | None
|
Optional gRPC compression setting. |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional interceptor chain to bind to the channel. |
None
|
Returns:
| Type | Description |
|---|---|
ChannelKey
|
The hashable identity the pool files this request under. |
Source code in grpc_client_kit/channel.py
update_channel_health(target, is_healthy)
async
¶
Update health status for every pooled channel of a target.
All channel identities sharing the address are updated, since health is a property of the server rather than of the channel configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
is_healthy
|
bool
|
Whether the target is healthy. |
required |
Source code in grpc_client_kit/channel.py
ChannelPoolSettingsProtocol
¶
Bases: Protocol
Protocol for gRPC channel pool settings.
Source code in grpc_client_kit/protocols.py
ChannelProviderProtocol
¶
Bases: Protocol
Protocol for gRPC channel management.
Source code in grpc_client_kit/protocols.py
close_all(grace=None)
async
¶
get_channel(target, insecure=False, credentials=None, options=None, compression=None, interceptors=None)
async
¶
Get or create a gRPC channel for the target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port) |
required |
insecure
|
bool
|
Whether to use insecure channel |
False
|
credentials
|
ChannelCredentials | None
|
Optional channel credentials for secure channel |
None
|
options
|
list[tuple[str, Any]] | None
|
Optional gRPC channel options |
None
|
compression
|
Compression | None
|
Optional gRPC compression |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional list of interceptors |
None
|
Returns:
| Type | Description |
|---|---|
Channel
|
An async gRPC channel |
Source code in grpc_client_kit/protocols.py
CircuitBreakerConfig
dataclass
¶
Configuration for circuit breaker.
Source code in grpc_client_kit/interceptors/__init__.py
CircuitBreakerMetricsProtocol
¶
Bases: Protocol
Optional extension: circuit breaker state transitions and rejections.
A rejection by an open breaker never touches the network, yet through record_request alone
it is indistinguishable from a backend that really failed. A registry that also implements
this protocol can chart the breaker itself: its state per method, and how many calls it
refused locally.
Source code in grpc_client_kit/protocols.py
record_circuit_rejection(method)
¶
Record one call refused locally by an open circuit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
Full gRPC method path. |
required |
CircuitBreakerOpenError
¶
Bases: AioRpcError, GrpcClientKitError
Raised when the circuit is open.
Source code in grpc_client_kit/interceptors/circuit_breaker.py
CircuitBreakerSettingsProtocol
¶
Bases: Protocol
Protocol for gRPC circuit breaker settings.
Source code in grpc_client_kit/protocols.py
CircuitBreakerStatus
¶
Bases: TypedDict
Read-only snapshot of one method's circuit, as returned by get_states().
Source code in grpc_client_kit/interceptors/circuit_breaker.py
ClientCall
dataclass
¶
One outgoing RPC as an interceptor sees it, whichever of the four kinds it is.
Attributes:
| Name | Type | Description |
|---|---|---|
method |
str
|
Full method name ( |
rpc_type |
RpcType
|
Which of the four RPC kinds this call is. |
details |
Any
|
The |
request |
Any
|
The request message, or the request iterator for a streaming-request call. |
Source code in grpc_client_kit/interceptors/base.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
request_streaming
property
¶
Whether the caller streams the request (stream-unary and stream-stream).
response
property
¶
The unary response once it has arrived; None for streams and for calls that failed.
response_streaming
property
¶
Whether the server streams the response (unary-stream and stream-stream).
underlying_call
property
¶
The grpc.aio.Call of the attempt last issued, or None while none has been.
invoke_stream()
async
¶
Issue the call now and return the iterator over its responses.
The RPC is created before this returns, deliberately. grpc.aio binds the Call an
interceptor created to whatever iterator that interceptor hands back, and an iterator that
only creates its call on the first __anext__ leaves that binding empty — code(),
cancel() and the call's own finalizer then fail on None.
Returns:
| Type | Description |
|---|---|
AsyncIterator[Any]
|
The response iterator. A failed RPC raises |
AsyncIterator[Any]
|
which for a mid-stream failure happens after some items have already been delivered. |
Source code in grpc_client_kit/interceptors/base.py
invoke_unary()
async
¶
Issue the call and, for a unary request, wait for its single response.
Returns:
| Type | Description |
|---|---|
Any
|
The response message — or, for a streaming request, the |
Raises:
| Type | Description |
|---|---|
AioRpcError
|
If the RPC failed. The continuation never raises — it resolves to
a |
Source code in grpc_client_kit/interceptors/base.py
ConnectivityConfig
dataclass
¶
How a channel keeps its connection alive, and how fast it comes back after losing it.
These are gRPC channel arguments, which means two things. They are set once, when the channel is
created, and cannot be changed afterwards; and they are spelled as an untyped list of
("grpc.some_thing_ms", 30000) pairs that every caller currently has to remember, in the
right unit, by heart. This is that list, named, in seconds, and validated.
Keepalive pings detect a connection that has silently gone away — a dropped NAT mapping, a load balancer that closed one side, a peer that vanished — which TCP alone can leave undetected until a call has already hung on it. The reconnect backoff decides how long a channel that has lost its connection waits before dialing again, and gRPC's own upper bound for that (two minutes) is a long time for a backend that restarts in seconds.
Opt-in, deliberately. Pinging is a conversation the server has to agree to: it enforces its own
minimum interval and answers a client that pings too often with GOAWAY and
ENHANCE_YOUR_CALM, so a client library that switched this on for everybody would be a way to
get connections dropped by servers that were never configured for it. The defaults here are what
a caller gets once they have decided to tune the channel at all: conservative enough for a
server left at its own defaults, which permits pings on a connection with calls in flight.
Attributes:
| Name | Type | Description |
|---|---|---|
keepalive_time |
float | None
|
Seconds of inactivity before a keepalive ping is sent. |
keepalive_timeout |
float | None
|
Seconds to wait for the ping to be answered before the connection is considered dead. |
permit_without_calls |
bool
|
Whether to keep pinging while no call is in flight. Off by default: this is the case servers police most tightly, and an idle connection that dies is usually cheaper to re-establish than to keep watching. |
max_pings_without_data |
int | None
|
How many pings may be sent on a connection carrying no data before the client stops; 0 means no limit. |
initial_reconnect_backoff |
float | None
|
Seconds to wait before the first reconnect attempt. |
min_reconnect_backoff |
float | None
|
Lower bound for the wait between reconnect attempts. |
max_reconnect_backoff |
float | None
|
Upper bound for the wait between reconnect attempts, which is what decides how long a client keeps failing after a backend is already back. |
Source code in grpc_client_kit/config.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
__post_init__()
¶
Validate the configuration.
Raises:
| Type | Description |
|---|---|
ValueError
|
If a duration is not positive, if the ping allowance is negative, or if the reconnect bounds contradict each other. |
Source code in grpc_client_kit/config.py
to_options()
¶
Return these settings as gRPC channel arguments.
The order is the declaration order of the fields and never varies, which is what keeps a
channel's identity stable: options are part of the channel pool's key (see
channel.ChannelKey), so a list assembled differently from one call to the next would open
a new channel each time.
Returns:
| Type | Description |
|---|---|
list[tuple[str, Any]]
|
The channel arguments, in seconds converted to the milliseconds gRPC expects. Fields |
list[tuple[str, Any]]
|
left at |
Source code in grpc_client_kit/config.py
DeadlineBudgetConfig
dataclass
¶
Configuration for propagating the caller's deadline budget into outgoing calls.
Adding this to a chain installs deadline.AsyncDeadlineBudgetInterceptor, which trims each
call's deadline to what the request budget in the current context still allows. It needs the
deadline extra (grpc-client-kit[deadline]); without it the layer is skipped and the
chain behaves as if it had never been configured.
The budget itself is installed by the caller with grpc_client_kit.use_budget. A chain
configured here but never given a budget changes nothing about any call.
Attributes:
| Name | Type | Description |
|---|---|---|
reserve_for_next |
float
|
Seconds every call keeps back for the work that follows it. |
Source code in grpc_client_kit/interceptors/__init__.py
DeadlineBudgetExhaustedError
¶
Bases: AioRpcError, GrpcClientKitError
Raised instead of issuing a call the request has no time left for.
A grpc.aio.AioRpcError rather than the budget library's own exception, and carrying
DEADLINE_EXCEEDED, because that is what the caller of an RPC is written to handle: the
layers above (logging, tracing, metrics) record it like any other failed call. The original
deadline_budget.DeadlineExceededError is kept as the __cause__ for anyone who maps it.
Source code in grpc_client_kit/interceptors/deadline.py
__init__(method, reason)
¶
Build the error naming the call that was refused and why.
Source code in grpc_client_kit/interceptors/deadline.py
DeadlineBudgetProtocol
¶
Bases: Protocol
What the kit needs of a request budget: the shape of deadline_budget.BudgetContext.
Typing against the shape rather than against the class is what keeps the library optional — the
kit never imports deadline-budget to describe a budget — and it leaves the door open for a
caller who tracks deadlines their own way.
Source code in grpc_client_kit/deadline.py
expired()
¶
remaining()
¶
timeout_for_call(call_name, reserve_for_next=0.0)
¶
Return the timeout the named call may use, bounded by what is left of the budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call_name
|
str
|
Name the per-call caps are keyed by. |
required |
reserve_for_next
|
float
|
Seconds to keep back for the steps that follow this call. |
0.0
|
Returns:
| Type | Description |
|---|---|
float
|
The timeout in seconds. |
Raises:
| Type | Description |
|---|---|
DeadlineExceededError
|
If the budget is already exhausted. |
Source code in grpc_client_kit/deadline.py
GrpcClient
¶
Factory for gRPC stubs. Manages target selection and channel acquisition from pool.
Interceptor Chains
A chain is built per target and cached, because gRPC binds interceptors to a channel when
the channel is created. Stateful interceptors — the circuit breaker above all — therefore
track one backend each: a single failing member of a load-balanced set can no longer trip
the breaker for its healthy peers. Pass interceptor_factory to get that isolation;
passing a ready interceptors list instead shares one chain (and one breaker) across
every target of this client.
Ownership Semantics
This client DOES NOT own the ChannelPool it uses. It is a lightweight wrapper that
acquires channels from a shared pool. Closing the client (via aexit) does NOT
close the underlying channels or the pool.
Source code in grpc_client_kit/client.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
__aenter__()
async
¶
__aexit__(exc_type, exc_val, exc_tb)
async
¶
Context manager exit.
NOTE: This does NOT close the underlying channel, as channels are managed by the ChannelPool. The pool owns channel lifetimes; idle connections are parked by gRPC core, not closed.
Source code in grpc_client_kit/client.py
__init__(stub_class, config, pool, balancer=None, interceptors=None, interceptor_factory=None)
¶
Initialize the gRPC client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stub_class
|
type[T]
|
The gRPC stub class to instantiate (e.g., MyServiceStub). |
required |
config
|
GrpcClientConfig
|
Configuration for the client (target, security, etc.). |
required |
pool
|
ChannelProviderProtocol
|
The channel pool to acquire channels from. |
required |
balancer
|
LoadBalancer | None
|
Optional load balancer for multiple targets. |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional ready chain, shared by every target of this client. |
None
|
interceptor_factory
|
Callable[[str], list[ClientInterceptor]] | None
|
Optional builder called once per target to create a dedicated chain for it. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If target configuration is ambiguous or missing, or if both an interceptor chain and an interceptor factory are given. |
Source code in grpc_client_kit/client.py
circuit_breaker_states()
async
¶
Snapshot the circuit breakers of every chain this client has built.
During an incident this is the question an operator asks first — "is the breaker open, or is the backend down?" — and it must be answerable without spelunking through the chain.
Returns:
| Type | Description |
|---|---|
dict[str, dict[str, Any]]
|
Mapping of target to that target's breaker snapshot (method to status). Targets whose |
dict[str, dict[str, Any]]
|
chain has no breaker, or no chain built yet, are absent. |
Source code in grpc_client_kit/client.py
connect()
async
¶
Acquires a channel from the pool and returns a new stub instance.
Returns:
| Type | Description |
|---|---|
T
|
An instance of the stub_class configured with a pooled channel. |
Raises:
| Type | Description |
|---|---|
NoHealthyTargetsError
|
If balancer is used and no healthy targets are available. |
ValueError
|
If target is not specified. |
Source code in grpc_client_kit/client.py
interceptors_for(target)
¶
Return this client's interceptor chain for one target.
The chain is built at most once per target and then reused. That caching is what keeps the pool key stable: rebuilding the chain on every call would mint a new channel identity each time and the pool would open a channel per RPC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port) the chain will be bound to. |
required |
Returns:
| Type | Description |
|---|---|
list[ClientInterceptor]
|
The interceptor chain for the target, empty when the client has no interceptors. |
Source code in grpc_client_kit/client.py
GrpcClientConfig
dataclass
¶
Core configuration for establishing a gRPC channel connection.
Attributes:
| Name | Type | Description |
|---|---|---|
target |
str | None
|
The target address (host:port) or a gRPC-compatible resolver URI. |
insecure |
bool
|
Whether to use an insecure channel (default: False). |
credentials |
ChannelCredentials | None
|
SSL/TLS credentials for secure channels. |
options |
list[tuple[str, Any]] | None
|
A list of key-value pairs to configure the gRPC channel. Anything set here wins
over |
compression |
Compression | None
|
The compression algorithm to use for the channel. |
connectivity |
ConnectivityConfig | None
|
Keepalive and reconnect tuning, spelled in seconds instead of as raw channel
arguments. |
Source code in grpc_client_kit/config.py
__post_init__()
¶
channel_options()
¶
Return the full option list a channel is opened with.
Explicit options come first and untouched, followed by the arguments connectivity stands
for — minus any whose key the caller already used. Filtering those out is what makes "an
explicit option wins" true in the only sense gRPC can honour it: the argument is passed once,
with the caller's value, so there is no duplicate for gRPC to resolve one way or the other.
The result is a pure function of the configuration, in a fixed order, which matters because
the pool keys channels by their options among other things (see channel.ChannelKey): two
clients configured alike share a channel, and one client asking twice does not open two.
Without connectivity the caller's own list is handed back as it is — None included — so
a client that does not tune anything keeps exactly the channel identity it always had.
Returns:
| Type | Description |
|---|---|
list[tuple[str, Any]] | None
|
The options to open the channel with, or None when there are none at all. |
Source code in grpc_client_kit/config.py
GrpcClientFactory
¶
High-level factory for creating configured GrpcClient instances.
Encapsulates mapping from settings objects to kit-specific configuration objects.
Source code in grpc_client_kit/factory.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 | |
health_checker
property
¶
The checker this factory built from settings, or None when none is configured.
__aenter__()
async
¶
Start the health checker and wait for its first verdicts.
Waiting is the point: until the first pass every target reads unhealthy by design, so a
factory that returned before it would hand out clients whose very first call fails with
NoHealthyTargetsError on every fresh start — a deterministic error spike per deploy.
Source code in grpc_client_kit/factory.py
__aexit__(exc_type, exc_val, exc_tb)
async
¶
Async context manager exit. Calls close() with the configured shutdown grace.
__init__(settings=None, pool=None, shutdown_grace=5.0, ready_timeout=10.0)
¶
Initialize the factory.
Ownership Semantics
If pool is provided, this factory uses it but does NOT own it. Closing the factory
will not close the provided pool.
If pool is NOT provided, the factory creates and OWNS its own ChannelPool.
Closing the factory will close this internal pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
settings
|
GrpcClientSettingsProtocol | None
|
Base settings object (optional) |
None
|
pool
|
ChannelProviderProtocol | None
|
Shared channel provider (if not provided, creates a ChannelPool based on settings) |
None
|
shutdown_grace
|
float | None
|
Seconds in-flight RPCs get to finish when the |
5.0
|
ready_timeout
|
float | None
|
Seconds |
10.0
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ImportError
|
If health checking is configured but the [health] extra is missing. |
Source code in grpc_client_kit/factory.py
circuit_breaker_states()
async
¶
Snapshot the circuit breakers of every chain this factory has built.
Returns:
| Type | Description |
|---|---|
dict[str, dict[str, Any]]
|
Mapping of |
dict[str, dict[str, Any]]
|
status). Chains without a breaker are absent. |
Source code in grpc_client_kit/factory.py
close(grace=None)
async
¶
Close the underlying pool and stop the health checker.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grace
|
float | None
|
Optional time in seconds to allow active RPCs to complete. |
None
|
Source code in grpc_client_kit/factory.py
create_client(stub_class, target=None, service_name=None, metrics=None, interceptors=None)
¶
Create a fully configured gRPC client instance.
This method applies resilience (retries, timeouts, circuit breakers) and observability (metrics, logging, tracing) based on factory settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stub_class
|
type[T]
|
The gRPC stub class to instantiate. |
required |
target
|
str | None
|
Optional target override. If not provided, uses target from settings. |
None
|
service_name
|
str | None
|
Name of the service for observability. Defaults to stub class name. |
None
|
metrics
|
GrpcClientMetricsProtocol | None
|
Optional metrics registry, overriding |
None
|
interceptors
|
list[ClientInterceptor] | None
|
Optional list of additional custom interceptors. These instances are shared by every target, unlike the chain the factory builds around them. |
None
|
Returns:
| Type | Description |
|---|---|
GrpcClient[T]
|
A configured GrpcClient instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither a target nor a list of targets is configured. |
Source code in grpc_client_kit/factory.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | |
wait_until_ready(timeout=None)
async
¶
Wait until the first health check pass has classified every target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float | None
|
Maximum seconds to wait. None waits indefinitely. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True once the first pass completed — and trivially when no checker is configured, |
bool
|
since there is then nothing to wait for. False if the wait timed out or the checker |
bool
|
is not running. |
Source code in grpc_client_kit/factory.py
GrpcClientKitError
¶
GrpcClientMetricsProtocol
¶
Bases: Protocol
Protocol for gRPC client metrics collection.
Source code in grpc_client_kit/protocols.py
record_inflight_delta(service, method, rpc_type, delta)
¶
Record a change in in-flight requests.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
str
|
Name of the service. |
required |
method
|
str
|
Name of the method. |
required |
rpc_type
|
str
|
Type of RPC. |
required |
delta
|
int
|
Change in in-flight requests (e.g., +1 or -1). |
required |
Source code in grpc_client_kit/protocols.py
record_pool_stats(active_channels, idle_targets)
¶
record_request(service, method, rpc_type, status, grpc_code, duration)
¶
Record a completed gRPC request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
str
|
Name of the service. |
required |
method
|
str
|
Name of the method. |
required |
rpc_type
|
str
|
Type of RPC (unary_unary, unary_stream, etc.). |
required |
status
|
str
|
Status of the request (success, error, cancelled). |
required |
grpc_code
|
str
|
gRPC status code name. |
required |
duration
|
float
|
Request duration in seconds. |
required |
Source code in grpc_client_kit/protocols.py
GrpcClientSettingsProtocol
¶
Bases: Protocol
Protocol for base gRPC client settings.
Every field here is one a settings object must carry. Anything a client may or may not configure (credentials, channel options, redaction, metrics registry) belongs to the extras protocols below, so that a plain settings model satisfies this one.
Source code in grpc_client_kit/protocols.py
balancer
property
¶
Load balancer settings.
circuit_breaker
property
¶
Circuit breaker settings.
health_checker
property
¶
Health checker settings.
insecure
property
¶
Whether to use insecure connection.
logging_enabled
property
¶
Whether logging is enabled.
metrics_enabled
property
¶
Whether metrics are enabled.
pool
property
¶
Channel pool settings.
retry
property
¶
Retry settings.
target
property
¶
Single target address (host:port).
targets
property
¶
List of target addresses for load balancing.
timeout
property
¶
Timeout settings.
tracing_enabled
property
¶
Whether tracing is enabled.
HealthChecker
¶
Bases: HealthCheckerProtocol
Monitors target health via the gRPC health protocol.
Features: - Concurrent health checks for multiple targets. - Persistent gRPC channels for efficiency. - Exponential backoff for failed targets. - Status change callbacks.
Cold start
A target is healthy only once a check has said so. Targets that have never been
checked are reported as unhealthy, so a load balancer cannot fan traffic out to
addresses nobody has probed yet, and asking a checker that was never started
raises :class:HealthCheckerNotRunningError instead of silently claiming health.
Call :meth:wait_until_ready after :meth:start to await the first pass.
Source code in grpc_client_kit/health.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 | |
is_running
property
¶
Check if health checker is currently running.
__init__(check_interval=DEFAULT_CHECK_INTERVAL, timeout=DEFAULT_TIMEOUT, max_backoff=MAX_BACKOFF, on_status_change=None, insecure=False, credentials=None, pool=None, fail_fast_callback=False, options=None, compression=None, service='')
¶
Initialize the health checker.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
check_interval
|
float
|
Seconds between checks for healthy targets. |
DEFAULT_CHECK_INTERVAL
|
timeout
|
float
|
Timeout in seconds for each health check RPC. |
DEFAULT_TIMEOUT
|
max_backoff
|
float
|
Maximum seconds to wait between checks for a failed target. |
MAX_BACKOFF
|
on_status_change
|
HealthStatusCallbackProtocol | None
|
Optional callback for status changes. |
None
|
insecure
|
bool
|
Whether to use insecure channels for health checks. |
False
|
credentials
|
ChannelCredentials | None
|
Optional TLS credentials for secure health checks. |
None
|
pool
|
ChannelPool | None
|
Optional ChannelPool to automatically update channel health. |
None
|
fail_fast_callback
|
bool
|
Whether to raise if the callback fails. |
False
|
options
|
list[tuple[str, Any]] | None
|
gRPC channel options for health check channels. Pass the same options the application channels use, so both negotiate HTTP/2 identically. |
None
|
compression
|
Compression | None
|
Compression for health check channels. |
None
|
service
|
str
|
Service name probed via the health protocol. The default |
''
|
Source code in grpc_client_kit/health.py
check_health(target)
async
¶
Perform an active health check RPC for the target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the target is SERVING, False otherwise. |
Raises:
| Type | Description |
|---|---|
Exception
|
Whatever the status change callback raised, if the checker was
built with |
Source code in grpc_client_kit/health.py
is_healthy(target)
async
¶
Check if the target is healthy from cache.
Only evidence counts: a target is healthy after a check saw it SERVING, and unhealthy until then, so an unchecked target never receives traffic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the last check reported SERVING, False otherwise. |
Raises:
| Type | Description |
|---|---|
HealthCheckerNotRunningError
|
If the target has no recorded status and the check loop is not running, so no status will ever be recorded. |
Source code in grpc_client_kit/health.py
start(targets)
async
¶
Start background health checking for the given targets.
Source code in grpc_client_kit/health.py
stop(timeout=5.0)
async
¶
Stop background health checking and cleanup resources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
Maximum time to wait for graceful shutdown (default 5s) |
5.0
|
Source code in grpc_client_kit/health.py
wait_until_ready(timeout=None)
async
¶
Wait until the first check pass has classified every monitored target.
Until that pass completes, every target is reported unhealthy, so callers that
route traffic immediately after :meth:start should await this first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float | None
|
Maximum seconds to wait. None waits indefinitely. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the first pass completed, False if it timed out or the loop is not running. |
Source code in grpc_client_kit/health.py
HealthCheckerNotRunningError
¶
Bases: GrpcClientKitError, RuntimeError
Raised when health state is requested but no check loop can ever produce it.
Lives here rather than in health so that catching it does not require the [health]
extra: the caller of a balancer meets this error, and a balancer works on a bare install.
Also a RuntimeError — being asked before start() is a lifecycle mistake in the calling
code, and existing except RuntimeError handlers keep working.
Attributes:
| Name | Type | Description |
|---|---|---|
target |
The target whose health was requested. |
Source code in grpc_client_kit/errors.py
__init__(target)
¶
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target whose health was requested. |
required |
Source code in grpc_client_kit/errors.py
HealthCheckerProtocol
¶
Bases: Protocol
Protocol for target health monitoring.
Source code in grpc_client_kit/protocols.py
check_health(target)
async
¶
is_healthy(target)
async
¶
Check if target is healthy (from cache).
Implementations report health from evidence only: a target that has never been checked is not healthy. An implementation that cannot produce evidence at all (its check loop is not running) may raise instead of answering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address (host:port) |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the last check reported healthy, False otherwise |
Source code in grpc_client_kit/protocols.py
HealthCheckerSettingsProtocol
¶
Bases: Protocol
Protocol for gRPC health checker settings.
Source code in grpc_client_kit/protocols.py
HealthStatusCallbackProtocol
¶
Bases: Protocol
Protocol for health status change callbacks.
Source code in grpc_client_kit/protocols.py
InterceptorChainBuilder
¶
Builder for a gRPC client interceptor chain with strict ordering.
The order is a property of the builder, not of the call sequence: build always emits
extra outer -> observability -> resilience -> extra inner regardless of which with_*
method was called first. See the module docstring for why each layer sits where it does.
Source code in grpc_client_kit/interceptors/__init__.py
build()
¶
Build the final chain, outermost first, expanded into what a channel can register.
Source code in grpc_client_kit/interceptors/__init__.py
with_custom(interceptors)
¶
Add custom interceptors to the outer slot; an alias of with_extra_outer.
with_extra_inner(interceptors)
¶
Add custom interceptors below the resilience layers, run once per attempt.
Source code in grpc_client_kit/interceptors/__init__.py
with_extra_outer(interceptors)
¶
Add custom interceptors that wrap the whole chain.
Metadata injection belongs here: the logging interceptor correlates on request-id taken
from the call metadata, so anything injected further in never reaches the logs or the spans.
Source code in grpc_client_kit/interceptors/__init__.py
with_observability(config)
¶
Add observability interceptors (Logging, Tracing, Metrics).
Source code in grpc_client_kit/interceptors/__init__.py
with_resilience(timeout=None, retry=None, circuit_breaker=None, deadline_budget=None, wait_for_ready=None)
¶
Add resilience interceptors (Timeout, Deadline budget, Wait-for-ready, Retry, Breaker).
Source code in grpc_client_kit/interceptors/__init__.py
LoadBalancer
¶
Bases: ABC
Base class for gRPC client load balancers.
Provides a standard interface for selecting a target from a list of addresses, with support for health filtering.
Source code in grpc_client_kit/balancers.py
__init__(targets, health_checker=None)
¶
Initialize the load balancer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
list[str]
|
List of target addresses (host:port). |
required |
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker for filtering unhealthy targets. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If targets list is empty or any target is invalid. |
Source code in grpc_client_kit/balancers.py
report_failure(target, quarantine=5.0)
¶
Quarantine a target that a real call just found unreachable.
Active probing has an inherent window: with the default check interval a dead backend
keeps receiving its share of traffic for up to that interval, every call burning a full
client deadline. A call that got UNAVAILABLE is fresher evidence than any probe, so
it takes the target out of the rotation immediately, for quarantine seconds — long
enough for the next probe (or a recovered backend) to have the casting vote.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address the failed call was routed to. |
required |
quarantine
|
float
|
Seconds to keep the target out of the rotation. |
5.0
|
Source code in grpc_client_kit/balancers.py
select_target()
abstractmethod
async
¶
Select a target from the available targets.
Returns:
| Type | Description |
|---|---|
str
|
The selected target address (host:port). |
Raises:
| Type | Description |
|---|---|
NoHealthyTargetsError
|
If all targets are unhealthy. |
Source code in grpc_client_kit/balancers.py
LoadBalancerConfig
dataclass
¶
LoadBalancerSettingsProtocol
¶
Bases: Protocol
Protocol for gRPC load balancer settings.
Source code in grpc_client_kit/protocols.py
LoadBalancingStrategy
¶
NoHealthyTargetsError
¶
Bases: GrpcClientKitError
Raised when no healthy targets are available.
Source code in grpc_client_kit/balancers.py
ObservabilityConfig
dataclass
¶
Configuration for observability.
Attributes:
| Name | Type | Description |
|---|---|---|
tracing |
bool
|
Whether to add the OpenTelemetry tracing layer (needs the |
metrics |
bool
|
Whether to add the metrics layer. |
logging |
bool
|
Whether to add the structured logging layer. |
service_name |
str
|
Client service name used in log categorization and span attributes. |
metrics_registry |
GrpcClientMetricsProtocol | None
|
The collector RPC metrics are recorded into. |
sensitive_methods |
set[str] | None
|
Full method names whose payloads and errors are suppressed in logs. |
sensitive_patterns |
list[str] | None
|
Regex patterns marking methods as sensitive. |
sensitive_headers |
set[str] | None
|
Header names (case-insensitive) redacted in logged metadata. |
log_request_payload |
bool
|
Whether to log request payloads (truncated). |
log_response_payload |
bool
|
Whether to log response payloads (truncated). |
enable_method_label |
bool
|
Whether metrics carry the method name as a label. Turn off for clients of services with thousands of methods, where per-method labels blow up the cardinality of every counter and histogram. |
success_log_level |
int
|
Level of the record a successful call emits; one INFO line per RPC is
a flood at high QPS, so high-volume clients drop this to |
Source code in grpc_client_kit/interceptors/__init__.py
RandomLoadBalancer
¶
Bases: LoadBalancer
Random load balancer with health check support.
Selects a random target from the list of healthy targets. Caches healthy targets for 1 second to improve performance.
Source code in grpc_client_kit/balancers.py
__init__(targets, health_checker=None)
¶
Initialize the Random balancer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
list[str]
|
List of target addresses. |
required |
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker. |
None
|
Source code in grpc_client_kit/balancers.py
report_failure(target, quarantine=5.0)
¶
Quarantine a target that a real call just found unreachable.
Active probing has an inherent window: with the default check interval a dead backend
keeps receiving its share of traffic for up to that interval, every call burning a full
client deadline. A call that got UNAVAILABLE is fresher evidence than any probe, so
it takes the target out of the rotation immediately, for quarantine seconds — long
enough for the next probe (or a recovered backend) to have the casting vote.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address the failed call was routed to. |
required |
quarantine
|
float
|
Seconds to keep the target out of the rotation. |
5.0
|
Source code in grpc_client_kit/balancers.py
select_target()
async
¶
Select a random healthy target.
Source code in grpc_client_kit/balancers.py
RetryConfig
dataclass
¶
Configuration for retries.
Attributes:
| Name | Type | Description |
|---|---|---|
max_attempts |
int
|
Total number of attempts, the first one included. |
initial_backoff |
float
|
Seconds to wait before the first retry. |
max_backoff |
float
|
Upper bound for the wait between retries. |
backoff_multiplier |
float
|
Factor by which the backoff grows each attempt. |
jitter |
float
|
Random variation factor (0.0 to 1.0) applied to the backoff. |
retryable_codes |
set[StatusCode] | None
|
Status codes that trigger a retry. Defaults to
|
retry_streaming |
bool
|
Whether Unary-Stream calls may be retried at all. |
idempotent_methods |
set[str] | None
|
Full method names that may be retried. Required for streaming retries, and a whitelist for unary retries when given. |
on_retry |
Callable[[str, int, StatusCode | None, float], Awaitable[None]] | None
|
Optional async callback |
metrics |
RetryMetricsProtocol | None
|
Optional registry told about every scheduled retry ( |
Source code in grpc_client_kit/interceptors/__init__.py
RetryMetricsProtocol
¶
Bases: Protocol
Optional extension: retry attempts, invisible to record_request by design.
The metrics layer sits above the retry layer and records one entry per logical call, so a
retry storm — N wire attempts collapsing into one success — cannot be seen through
GrpcClientMetricsProtocol alone. A registry that also implements this protocol gets told
about every retry the moment it is scheduled.
Source code in grpc_client_kit/protocols.py
record_retry(service, method, attempt, grpc_code)
¶
Record one scheduled retry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
str
|
Name of the service. |
required |
method
|
str
|
Name of the method. |
required |
attempt
|
int
|
Number of the upcoming attempt (1 is the first retry). |
required |
grpc_code
|
str
|
Status code name of the failure that caused the retry. |
required |
Source code in grpc_client_kit/protocols.py
RetrySettingsProtocol
¶
Bases: Protocol
Protocol for gRPC retry settings.
Source code in grpc_client_kit/protocols.py
RoundRobinLoadBalancer
¶
Bases: LoadBalancer
Round-robin load balancer with health check support.
Selects targets in a fixed cyclic order, skipping unhealthy ones.
Source code in grpc_client_kit/balancers.py
__init__(targets, health_checker=None)
¶
Initialize the Round-Robin balancer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
list[str]
|
List of target addresses. |
required |
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker. |
None
|
Source code in grpc_client_kit/balancers.py
report_failure(target, quarantine=5.0)
¶
Quarantine a target that a real call just found unreachable.
Active probing has an inherent window: with the default check interval a dead backend
keeps receiving its share of traffic for up to that interval, every call burning a full
client deadline. A call that got UNAVAILABLE is fresher evidence than any probe, so
it takes the target out of the rotation immediately, for quarantine seconds — long
enough for the next probe (or a recovered backend) to have the casting vote.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address the failed call was routed to. |
required |
quarantine
|
float
|
Seconds to keep the target out of the rotation. |
5.0
|
Source code in grpc_client_kit/balancers.py
select_target()
async
¶
Select the next target in the round-robin sequence.
Source code in grpc_client_kit/balancers.py
TimeoutConfig
dataclass
¶
Configuration for timeouts.
A timeout is the budget of a whole call, retries included, not of a single attempt.
Attributes:
| Name | Type | Description |
|---|---|---|
default |
float | None
|
Budget in seconds for methods without an entry in |
per_method |
dict[str, float | None]
|
Per-method budgets, keyed by full method name ( |
Source code in grpc_client_kit/interceptors/__init__.py
TimeoutSettingsProtocol
¶
WaitForReadyConfig
dataclass
¶
Configuration for waiting on a connection instead of failing fast on a cold channel.
A grpc.aio channel connects lazily, so calls made in the first moments of a client's life —
or right after a backend restart — fail with UNAVAILABLE before anything is even attempted.
Setting this makes them wait for the connection instead, bounded by the call's deadline.
Attributes:
| Name | Type | Description |
|---|---|---|
default |
bool | None
|
Value for methods without an entry in |
per_method |
dict[str, bool | None]
|
Values for individual methods, keyed by full method name
( |
require_deadline |
bool
|
Whether waiting applies only to calls that carry a deadline. On by default: an unbounded wait replaces a fast failure with a hang. |
Source code in grpc_client_kit/interceptors/__init__.py
WeightedLoadBalancer
¶
Bases: LoadBalancer
Weighted random load balancer with health check support.
Selects targets based on provided weights, giving higher preference to targets with larger weights. Skips unhealthy targets.
Source code in grpc_client_kit/balancers.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | |
__init__(targets, weights, health_checker=None)
¶
Initialize the Weighted balancer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
list[str]
|
List of target addresses. |
required |
weights
|
dict[str, float]
|
Mapping of target to its weight (default 1.0). |
required |
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker. |
None
|
Source code in grpc_client_kit/balancers.py
report_failure(target, quarantine=5.0)
¶
Quarantine a target that a real call just found unreachable.
Active probing has an inherent window: with the default check interval a dead backend
keeps receiving its share of traffic for up to that interval, every call burning a full
client deadline. A call that got UNAVAILABLE is fresher evidence than any probe, so
it takes the target out of the rotation immediately, for quarantine seconds — long
enough for the next probe (or a recovered backend) to have the casting vote.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str
|
The target address the failed call was routed to. |
required |
quarantine
|
float
|
Seconds to keep the target out of the rotation. |
5.0
|
Source code in grpc_client_kit/balancers.py
select_target()
async
¶
Select a target using weighted random selection among healthy ones.
Source code in grpc_client_kit/balancers.py
__getattr__(name)
¶
Resolve extras-gated exports on first access.
HealthChecker needs the [health] extra, so importing this package must not import it: a
module-level import would make import grpc_client_kit fail on a bare install.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The attribute being looked up. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The resolved attribute. |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If the package has no such attribute. |
ImportError
|
If the attribute needs an extra that is not installed. |
Source code in grpc_client_kit/__init__.py
build_interceptors(timeout=None, retry=None, circuit_breaker=None, observability=None, extra_interceptors=None, extra_inner_interceptors=None, deadline_budget=None, wait_for_ready=None)
¶
Build a standard interceptor chain in the order documented for this module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
TimeoutConfig | None
|
Timeout configuration. |
None
|
retry
|
RetryConfig | None
|
Retry configuration. |
None
|
circuit_breaker
|
CircuitBreakerConfig | None
|
Circuit breaker configuration. |
None
|
observability
|
ObservabilityConfig | None
|
Observability configuration. |
None
|
extra_interceptors
|
Sequence[ClientInterceptorLike] | None
|
Custom interceptors for the outer slot, ahead of logging, tracing and metrics. This is where metadata injection belongs so that logs and spans can correlate on it. |
None
|
extra_inner_interceptors
|
Sequence[ClientInterceptorLike] | None
|
Custom interceptors for the inner slot, below the resilience layers. These run once per attempt, closest to the wire. |
None
|
deadline_budget
|
DeadlineBudgetConfig | None
|
Deadline budget propagation configuration. Needs the |
None
|
wait_for_ready
|
WaitForReadyConfig | None
|
Wait-for-ready configuration, for calls made on a channel that may still be connecting. |
None
|
Returns:
| Type | Description |
|---|---|
list[ClientInterceptor]
|
A flat chain, outermost first, ready to be bound to a channel — by GrpcClient or by |
list[ClientInterceptor]
|
|
list[ClientInterceptor]
|
docstring. |
Source code in grpc_client_kit/interceptors/__init__.py
create_balancer(targets, config=None, health_checker=None)
¶
Factory function to create a load balancer from targets and config.
By default, creates a Round-Robin balancer if no config is provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
list[str]
|
List of target addresses (host:port) |
required |
config
|
LoadBalancerConfig | None
|
Balancer configuration (strategy and weights) |
None
|
health_checker
|
HealthCheckerProtocol | None
|
Optional health checker for filtering unhealthy targets |
None
|
Returns:
| Type | Description |
|---|---|
LoadBalancer
|
A concrete LoadBalancer instance |
Raises:
| Type | Description |
|---|---|
ValueError
|
If targets list is empty or config is invalid. |
Source code in grpc_client_kit/balancers.py
current_budget()
¶
Return the budget installed for the current task, or None if there is none.
Returns:
| Type | Description |
|---|---|
DeadlineBudgetProtocol | None
|
The budget |
Source code in grpc_client_kit/deadline.py
flatten_interceptors(interceptors)
¶
Expand every logical interceptor into the four adapters a channel can file correctly.
Order is preserved, and that is what makes the expansion safe: a channel appends each entry to the list of its own kind in the order it reads them, so four adapters of A ahead of four adapters of B put A ahead of B in all four lists at once.
Interceptors written against grpc's intercept_* methods directly are passed through
untouched, so a chain may mix both kinds while the kit is being migrated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interceptors
|
Iterable[ClientInterceptorLike]
|
The chain, outermost first. |
required |
Returns:
| Type | Description |
|---|---|
list[ClientInterceptor]
|
A flat list a channel accepts, outermost first. |
Source code in grpc_client_kit/interceptors/base.py
logical_interceptor(interceptor)
¶
Return the logical interceptor behind a channel adapter, or the argument unchanged.
Lets callers question a flattened chain (isinstance checks in tests, a handle onto the
circuit breaker) without knowing whether an entry is an adapter or an old-style interceptor.
Source code in grpc_client_kit/interceptors/base.py
metadata_to_dict(metadata)
¶
Convert gRPC metadata to a flat dictionary.
Handles various metadata formats (list of tuples, grpc.aio.Metadata object). Performs the following transformations: - Decodes bytes keys and values to UTF-8 strings. - If a value is binary and not valid UTF-8, it is base64 encoded with a 'base64:' prefix. - Joins multiple values for the same key with commas (RFC 2616 style).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata
|
MetadataType | None
|
The gRPC metadata to convert. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
A dictionary mapping metadata keys to their string values. |
Source code in grpc_client_kit/utils.py
use_budget(budget)
¶
Install budget as the current one for the duration of the block.
The previous value is restored on the way out, whether the block ends normally or by exception, so nesting works and an inner budget cannot outlive its block. Passing None is the way to detach an inherited budget — for background work that must not die with the request that spawned it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
budget
|
DeadlineBudgetProtocol | None
|
The budget to install, or None to run this block without one. |
required |
Yields:
| Type | Description |
|---|---|
DeadlineBudgetProtocol | None
|
The budget that was installed, for convenience at the call site. |