Releases: modern-python/modern-di-grpc
Releases · modern-python/modern-di-grpc
Release list
2.0.0
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) andDIAioInterceptor(async). Construct with
the root container and pass togrpc.server(..., interceptors=[...])/
grpc.aio.server(interceptors=[...]).intercept_servicere-wraps each
RPC's handler — unwrappedNone(unknown method) passes through so gRPC
answersUNIMPLEMENTEDon its own — via the matching
grpc.*_rpc_method_handlerfactory for all four RPC types (unary-unary,
unary-stream, stream-unary, stream-stream).- Per-RPC child containers. Every RPC opens one
Scope.REQUESTchild,
stashed in aContextVarfor@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/**kwargsguard are
needed.ServicerContextprovider, auto-registered.grpc_context_provider
(ContextProvider(ServicerContext, scope=Scope.REQUEST)) is registered on
the container idempotently by the interceptor's__init__, so
ServicerContextinjects out of the box. The protobuf requestMessageis
deliberately not exposed as a provider — it would add aprotobufruntime
dependency.fetch_di_container(). Returns the current RPC's child container;
raisesLookupErroroutside 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 afterserver.stop(...)— matchingmodern-di-flask.
Packaging
- Requires
grpcio>=1.48,<2andmodern-di>=2.25,<3; Python 3.10–3.14. The
1.48floor is verified: the full test suite (24 tests) passes on Python
3.10.20 withgrpcio==1.48.2— the sync and asyncServerInterceptor
classes, all four*_rpc_method_handlerfactories, andgrpc.server/
grpc.aio.serverall behave as this integration expects at that version.
The committed test proto stubs are generated by a newer protobuf toolchain
and don't import under1.48's matchingprotobuf 3.20.3runtime; that's
a test-fixture concern only — the runtime package depends ongrpcioand
modern-dialone, noprotobuf— 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 beyondgrpcioand modern-di.
protobufandgrpcio-toolsare dev-only (generating the test proto
stubs).
Downstream
No action needed — this is a new package.
Internals
- 100% line coverage;
ruff,ty, andeof-fixerclean 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
committedGreeterproto covering all four RPC types; no mocks. - Uses the portable planning convention (
planning/) with anarchitecture/
truth home; releases are tag-driven.