Skip to content

blackbull.grpc

blackbull.grpc

Pure-Python gRPC server support for BlackBull.

gRPC runs on HTTP/2; BlackBull already ships a complete HTTP/2 implementation, so this package adds only the gRPC-specific pieces:

  • :mod:~blackbull.grpc.codec — Length-Prefixed-Message framing.
  • :mod:~blackbull.grpc.compression — gzip message compression (grpc-encoding).
  • :class:~blackbull.grpc.registry.GrpcServiceRegistry/Service/Method → handler.
  • :class:~blackbull.grpc.status.GrpcStatus / :class:GrpcError — canonical codes.
  • :func:~blackbull.grpc.asgi.serve_grpc + :class:GrpcContext — the ASGI bridge.

Wire it into an app with app.enable_grpc(registry); gRPC requests (content-type: application/grpc) are then multiplexed onto the same HTTP/2 port as the app's REST and WebSocket traffic. All four RPC kinds are served — unary, server-streaming, client-streaming, and bidirectional — where a response-streaming handler is an async generator and a request-streaming handler takes an async iterator of request messages (request_iter).

Protobuf is not a dependency: handlers receive and return raw message bytes, so the application chooses its own serialisation (grpc_tools.protoc output, protobuf, or hand-rolled).

GrpcContext

Per-call context handed to a gRPC handler.

Exposes request metadata (the HTTP/2 headers), the call deadline and peer, and lets the handler set the outgoing status, a human-readable message, leading/trailing metadata, or abort the call outright — the subset of grpcio's ServicerContext that a raw-bytes transport can honour.

The response-start machinery (_send_started) is bound by :func:serve_grpc just before the handler runs; handlers touch it only via :meth:send_initial_metadata.

abort(status, details='')

Raise :class:GrpcError to end the call with a non-OK status.

invocation_metadata()

Return all request metadata (HTTP/2 headers) as (name, value) pairs — grpcio's ServicerContext.invocation_metadata. Pseudo- headers (:method, :path, …) are excluded; they are call routing, not application metadata.

metadata(name, default=b'')

Return a request header (call metadata) value, or default.

peer()

Return the client address as grpcio formats it (ipv4:host:port / ipv6:[host]:port), or '' when the transport did not supply one.

send_initial_metadata(metadata) async

Send response leading metadata now (the initial HTTP/2 HEADERS), before the first response message — grpcio's ServicerContext.send_initial_metadata.

Optional: if never called, the response HEADERS are still emitted lazily (just before the first message, or with the trailers for an empty response). Calling it flushes them early with metadata attached — used to hand the client leading metadata (auth challenges, stream ids) up front. Raises :class:ValueError once the HEADERS have already gone out (grpcio's "initial metadata no longer allowed").

time_remaining()

Seconds left until the call deadline (never negative), or None when the client set no grpc-timeout — grpcio's ServicerContext.time_remaining. Lets a handler shed work it cannot finish in time.

trailing_metadata()

The trailing metadata set so far (a copy) — lets helpers compose with, rather than clobber, what the handler already set (e.g. blackbull-protobuf's abort_with_details appending grpc-status-details-bin).

GrpcDecodeError

Bases: ValueError

Raised when a DATA buffer is not a valid sequence of Length-Prefixed-Messages (truncated prefix or short body).

GrpcError

Bases: Exception

Raised by a service handler (or context.abort) to end the RPC with a non-OK status. serve_grpc translates it into grpc-status / grpc-message trailers.

GrpcServiceRegistry

Holds the path -> GrpcMethod table for gRPC methods.

add_method(path, handler, *, streaming=None, client_streaming=None)

Register handler for the fully-qualified method path (/package.Service/Method or package.Service/Method).

streaming is the response axis — server-streaming (the handler is an async generator that yields response messages). When None (the default) it is auto-detected with :func:inspect.isasyncgenfunction; pass True to force it for a handler whose async-generator nature is hidden behind a wrapper, or False to force a single response. Forcing False on an async-generator function is a contradiction and raises ValueError.

client_streaming is the request axis — the handler takes an async iterator of request messages (first parameter request_iter / requests / request_iterator / request_stream) instead of a single request: bytes. When None it is auto-detected from that first parameter name; pass an explicit bool to override.

add_service(service, methods)

Register every method_name -> handler in methods under the fully-qualified service name (e.g. "helloworld.Greeter").

Each handler's streaming-ness is auto-detected individually.

lookup(path)

Return the handler for path, or None if unregistered.

Backwards-compatible accessor (returns just the callable); use :meth:lookup_method when the streaming flag is needed.

lookup_method(path)

Return the :class:GrpcMethod for path, or None if unregistered.

method(path, *, streaming=None, client_streaming=None)

Decorator form of :meth:add_method.

methods()

Return all registered method paths, in registration order.

decode_messages(data)

Parse data into a list of (compressed, payload) messages.

A single DATA buffer may contain zero, one, or many framed messages (gRPC permits multiple messages per stream and does not align them to DATA-frame boundaries). Raises :class:GrpcDecodeError on a truncated prefix or a message body shorter than its declared length.

encode_message(payload, *, compressed=False)

Frame payload as a single gRPC Length-Prefixed-Message.

serve_grpc(registry, conn, receive, send) async

Serve a single gRPC call (unary or server-streaming) through the ASGI (scope, receive, send) bridge. Never raises for handler/protocol errors: every failure path is reported as a gRPC status.