From 7f625f73f03c5c22fe4f4aec23572875e012030f Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 3 Oct 2026 12:45:41 +0300 Subject: [PATCH 1/2] docs: fix README validate order and plain-language pass - Call container.validate() after DIInterceptor(container), which registers grpc_context_provider - Drop version-history wording from the async/lifecycle paragraph - Remove dashes; fix shared footer wording --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 19cd87d..83e3b29 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ uv add modern-di-grpc # or: pip install modern-di-grpc ## Usage -gRPC has no dependency-injection system of its own, so `modern-di-grpc` pairs an `@inject` decorator with inert `FromDI` markers. A `DIInterceptor` (sync) or `DIAioInterceptor` (async) opens one `Scope.REQUEST` child container per RPC and resolves the `FromDI`-marked parameters of `@inject`-decorated servicer methods. Constructing the interceptor registers the `ServicerContext` provider on the container automatically — there is no separate setup call. +gRPC has no dependency-injection system of its own, so `modern-di-grpc` pairs an `@inject` decorator with inert `FromDI` markers. A `DIInterceptor` (sync) or `DIAioInterceptor` (async) opens one `Scope.REQUEST` child container per RPC and resolves the `FromDI`-marked parameters of `@inject`-decorated servicer methods. Constructing the interceptor registers the `ServicerContext` provider on the container automatically; there is no separate setup call. ```python import typing @@ -74,11 +74,11 @@ class GreeterService(greeter_pb2_grpc.GreeterServicer): container = Container(groups=[AppGroup]) -container.validate() # optional fail-fast; modern-di 3.1 validates nowhere implicitly server = grpc.server( futures.ThreadPoolExecutor(max_workers=10), interceptors=[DIInterceptor(container)], ) +container.validate() # optional fail-fast; must come after DIInterceptor registers its providers greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterService(), server) server.add_insecure_port("[::]:50051") server.start() @@ -86,7 +86,7 @@ server.wait_for_termination() container.close_sync() ``` -For an async server, pass `DIAioInterceptor(container)` to `grpc.aio.server(...)` and write `async def` servicer methods; `@inject` adapts to sync, async, and async-generator (server-streaming) methods across all four RPC types. gRPC has no server startup/shutdown hook, so the root container's shutdown is yours to own. As of modern-di 3.1 a container is **open from construction**, so no `.open()` call is required before constructing the interceptor (`DIInterceptor`/`DIAioInterceptor` never open the root themselves, and no longer need to). Validation is explicit in 3.1: call `.validate()` if you want a broken graph to fail at start-up rather than at the first RPC that touches it. Call `close_sync()` (or `await close_async()` on `grpc.aio`) after the server stops — that half is still yours, since gRPC gives the adapter no shutdown hook. +For an async server, pass `DIAioInterceptor(container)` to `grpc.aio.server(...)` and write `async def` servicer methods; `@inject` adapts to sync, async, and async-generator (server-streaming) methods across all four RPC types. A container is open from construction, so there is no `.open()` call before constructing the interceptor, and neither interceptor opens the root itself. Validation is explicit: call `.validate()` after constructing the interceptor if you want a broken graph to fail at start-up rather than at the first RPC that touches it. gRPC has no server startup or shutdown hook, so closing the root container is up to you: call `close_sync()` (or `await close_async()` on `grpc.aio`) after the server stops. ## API @@ -105,7 +105,7 @@ For an async server, pass `DIAioInterceptor(container)` to `grpc.aio.server(...) ## Part of `modern-python` -Built on [`modern-di`](https://github.com/modern-python/modern-di), a dependency-injection framework with IoC container and scopes. +Built on [`modern-di`](https://github.com/modern-python/modern-di), a dependency-injection framework with an IoC container and scopes. Browse the full list of templates and libraries in -[`modern-python`](https://github.com/modern-python) — see the org profile for the categorized index. +[`modern-python`](https://github.com/modern-python); the org profile has the categorized index. From 1e5a297fc388e07b5f039c6015debdbc9510bc14 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 3 Oct 2026 12:46:42 +0300 Subject: [PATCH 2/2] docs: validate the example container after constructing the interceptor DIInterceptor registers grpc_context_provider, so validating first would fail for any provider that depends on grpc.ServicerContext. --- examples/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/app.py b/examples/app.py index cff6aa2..c06c308 100644 --- a/examples/app.py +++ b/examples/app.py @@ -42,9 +42,9 @@ def SayHello( # noqa: N802 def build_server(port: str = "127.0.0.1:0") -> tuple[grpc.Server, int, Container]: """Wire the container, register the interceptor, and start listening; return the bound port.""" container = Container(groups=[AppGroup]) - container.validate() # optional fail-fast; gRPC gives the adapter no root-lifecycle hook, - # so the close at the end of this function is the caller-owned half - server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[DIInterceptor(container)]) + interceptor = DIInterceptor(container) + container.validate() # optional fail-fast; must come after DIInterceptor registers its providers + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[interceptor]) greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterService(), server) bound_port = server.add_insecure_port(port) server.start()