edri.api.grpc package

Submodules

edri.api.grpc.directives module

ResponseDirective -> grpc.StatusCode mapping.

The 4th instance of the pattern REST (HTTPHandler._directive_handlers), HTML (inherits HTTPHandler's) and WS (WebsocketHandler._directive_handlers) already use: business logic raises transport-agnostic ResponseDirective instances, and each transport maps the ones it understands to its own status/metadata shape, logging and ignoring (never raising on) anything it doesn't.

Unlike the other three, gRPC has exactly one handler shape (there's no per-event handler subclass to hang a _directive_handlers class attribute and an MRO-walking directive_handlers() off — see BaseHandler for that version), so this is a flat module-level table and function instead.

edri.api.grpc.export module

Render edri.api.grpc.schema's generated FileDescriptorProto to .proto3 source text, for consumers who want a static .proto file to run through their own language's codegen instead of pointing it at the reflection service. Pure text generation over the FileDescriptorProto's own fields - no DescriptorPool/message_factory/live message classes involved (unlike edri/api/grpc/runtime.py), so this direction doesn't touch anything beyond the plain (proto2-defined) descriptor_pb2 shape schema.py already builds.

Usage: python -m edri.api.grpc.export [output_path] - writes to output_path if given, otherwise to stdout.

edri.api.grpc.export.render_proto(file_proto: google.protobuf.descriptor_pb2.FileDescriptorProto) → str[source]

Render a FileDescriptorProto (as produced by edri.api.grpc.schema.build_schema()) to .proto3 source text.

edri.api.grpc.listener module

GRPCListener: EDRI's gRPC front end.

A Process analogous to Listener (edri/api/listener.py), but running its own grpc.aio.server() rather than an ASGI app - see the design spec (docs/superpowers/specs/2026-09-12-grpc-api-design.md) for why this is a separate process rather than multiplexed into the ASGI Listener. Shares the same api_broker_queue Listener uses to register clients with Broker and exchange Events over a pipe, via the same ClientRegistry/recv_connection/ build_event helpers Listener itself now delegates to.

Import-safety note: see edri/api/grpc/__init__.py - import grpc here resolves to the third-party grpc package, not this one.

class edri.api.grpc.listener.GRPCListener(api_broker_queue: Queue[Event], host: str = 'localhost', port: int = 50051)[source]

Bases: Process

run() → None[source]

Method to be run in sub-process; can be overridden in sub-class

edri.api.grpc.runtime module

Turns a Schema (edri/api/grpc/schema.py) into live protobuf message classes, and encodes/decodes values between those messages and the dict[str, Any] shape edri.api.event_builder.build_event()/Response.as_dict() already use - so the gRPC listener never needs its own copy of "what does an Event's fields look like".

No protoc invocation, no generated _pb2.py stub files: message classes come from google.protobuf.descriptor_pool + message_factory against the FileDescriptorProto schema.py already built.

edri.api.grpc.schema module

FileDescriptorProto generator for EDRI's gRPC API.

Walks event_type_extensions (via edri.api.event_registry.ensure_populated()) and, for every Event with ApiType.GRPC not in its exclude set, builds:

  • a <EventName>Request / <EventName>Response message pair

  • a unary rpc <EventName>(<EventName>Request) returns (<EventName>Response)

  • one oneof member each in the shared ClientMessage/ServerMessage streaming envelope messages (see edri/api/grpc/listener.py for the bidi stream that serves them)

  • a Connect(stream ClientMessage) returns (stream ServerMessage) bidi rpc

on one shared EdriService. Events are processed in a name-sorted order so message/method declaration order is stable across restarts. Each event's oneof field number (the value actually written to the wire - WhichOneof() on the receiving end resolves purely from this number, never from a name) is independent of that order too: see _field_number_for()'s docstring for why positional numbering doesn't work here.

A field whose declared type type_mapping.map_field() can't represent (bare Any, a non-dataclass custom object, an Injection-validated field - Injection resolution isn't implemented yet, unions beyond X | None, ...) skips that whole event from GRPC schema generation with a logged warning, the same "best effort, don't crash startup over one bad event" posture _offers() already takes for HTML.

edri.api.grpc.type_mapping module

Python type <-> proto3 field mapping.

Pure and protobuf-runtime-free beyond the FieldDescriptorProto.Type/Label enum constants: no DescriptorPool, no message classes. schema.py uses this to decide what to put in a FileDescriptorProto; runtime.py uses the same FieldMapping to encode/decode values against the live message classes built from that descriptor. Keeping the decision in one place means the two can never disagree about how a given field is represented.

Module contents

gRPC API front end for EDRI.

Import-safety note: this package is named grpc, same as the third-party grpc PyPI package. Python 3's absolute-import default means a plain import grpc written anywhere inside edri/api/grpc/*.py resolves to the third-party package, never to this one — there is no ambiguity and no special handling needed. Left as a comment (here and in each module that does import grpc) so nobody "fixes" it into a relative import later.

Everything under this package is optional: it is only usable when the grpc extra (grpcio, grpcio-reflection, protobuf) is installed, and nothing elsewhere in edri imports this package eagerly.