Skip to content

2.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 11 Jul 12:41
906e8d9

modern-di-grpc 2.0.0 — first release

First release of the gRPC integration for modern-di. It wires a
modern-di container into a
grpcio server and resolves dependencies into servicer
methods, scoped per RPC call. The version starts at 2.0.0 to track the
modern-di 2.x ecosystem alongside the other official integrations.

gRPC's idiomatic extension point is a ServerInterceptor, so this integration
follows modern-di's interceptor + @inject decorator path (like
modern-di-starlette/-faststream), with both a sync and an async server
supported.

Feature

  • DIInterceptor (sync) and DIAioInterceptor (async). Construct with
    the root container and pass to grpc.server(..., interceptors=[...]) /
    grpc.aio.server(interceptors=[...]). intercept_service re-wraps each
    RPC's handler — unwrapped None (unknown method) passes through so gRPC
    answers UNIMPLEMENTED on its own — via the matching
    grpc.*_rpc_method_handler factory for all four RPC types (unary-unary,
    unary-stream, stream-unary, stream-stream).
  • Per-RPC child containers. Every RPC opens one Scope.REQUEST child,
    stashed in a ContextVar for @inject/fetch_di_container() to read, and
    closed (close_sync/close_async) after the call — including on the error
    path, and with the ContextVar reset even if the close itself raises.
    Response-streaming wrappers are (async) generators, so the child stays open
    for the whole stream instead of closing before the first item is produced.
  • FromDI + @inject. Mark a servicer-method parameter with
    Annotated[T, FromDI(provider_or_type)] and decorate the method with
    @inject; the decorator produces a sync, async, or async-generator wrapper
    matching the method and resolves each marked parameter from the current
    RPC's child. gRPC's fixed (request, context) positional calling
    convention means no signature rewriting and no *args/**kwargs guard are
    needed.
  • ServicerContext provider, auto-registered. grpc_context_provider
    (ContextProvider(ServicerContext, scope=Scope.REQUEST)) is registered on
    the container idempotently by the interceptor's __init__, so
    ServicerContext injects out of the box. The protobuf request Message is
    deliberately not exposed as a provider — it would add a protobuf runtime
    dependency.
  • fetch_di_container(). Returns the current RPC's child container;
    raises LookupError outside an intercepted RPC.
  • User-owned root lifecycle. gRPC has no server start/stop hook to attach
    to, so the caller opens the container, hands it to the interceptor, and
    closes it after server.stop(...) — matching modern-di-flask.

Packaging

  • Requires grpcio>=1.48,<2 and modern-di>=2.25,<3; Python 3.10–3.14. The
    1.48 floor is verified: the full test suite (24 tests) passes on Python
    3.10.20 with grpcio==1.48.2 — the sync and async ServerInterceptor
    classes, all four *_rpc_method_handler factories, and grpc.server/
    grpc.aio.server all behave as this integration expects at that version.
    The committed test proto stubs are generated by a newer protobuf toolchain
    and don't import under 1.48's matching protobuf 3.20.3 runtime; that's
    a test-fixture concern only — the runtime package depends on grpcio and
    modern-di alone, no protobuf — so verification regenerated the stubs
    in an ephemeral environment (grpcio-tools==1.48.2) rather than changing
    what's committed.
  • Ships py.typed; zero runtime dependencies beyond grpcio and modern-di.
    protobuf and grpcio-tools are dev-only (generating the test proto
    stubs).

Downstream

No action needed — this is a new package.

Internals

  • 100% line coverage; ruff, ty, and eof-fixer clean across Python
    3.10–3.14. Tests run a real server (127.0.0.1:0) with a real client
    channel — both the sync thread-pool server and the aio server — against a
    committed Greeter proto covering all four RPC types; no mocks.
  • Uses the portable planning convention (planning/) with an architecture/
    truth home; releases are tag-driven.