feat(python): add non-blocking AsyncClient.connect for event-loop-safe construction#879
Merged
Conversation
…e construction AsyncClient previously only offered the synchronous AsyncClient(creds, config) constructor and AsyncClient.from_file(path). Both run the authentication round-trip and gRPC channel setup to completion before returning, so calling them from inside a coroutine stalls the running event loop for the whole handshake. That is the blocking-call-on-the-loop anti-pattern an async client must avoid. This adds two awaitable constructors that resolve the handshake off the event loop and yield a connected AsyncClient: await AsyncClient.connect(creds, config) and await AsyncClient.connect_from_file(path, config=None). They are wired through the same future_into_py awaitable bridge the existing *_async historical methods use, so other coroutines keep running while the connection is established. The synchronous constructors stay available for construction outside a running loop, and the class example now steers async callers to await AsyncClient.connect(...). The .pyi stub declares both as staticmethods returning Awaitable[AsyncClient] with docstrings; stubtest passes against the runtime. The cross-binding parity gate is clean: the existing AsyncClient connect row holds and the new methods enroll without weakening any check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AsyncClient only had the synchronous
AsyncClient(creds, config)constructor andAsyncClient.from_file(path). Both run the auth round-trip and gRPC channel setup to completion before returning. The stub's own example builds the client insideasync def main()underasyncio.run, so that handshake runs on the event-loop thread and stalls the loop for its whole duration. For an async client that's the exact blocking-call-on-the-loop pattern we don't want to ship.This adds two awaitable constructors so async callers can connect from inside a coroutine without freezing the loop:
They resolve the handshake off the event loop through the same awaitable bridge the existing
*_asynchistorical methods use, so other coroutines keep running while the connection comes up. The synchronousAsyncClient(creds, config)andAsyncClient.from_file(...)constructors stay exactly as they were for construction outside a running loop, and the class example now points async users atawait AsyncClient.connect(...).What's in here:
connectandconnect_from_filestaticmethods onAsyncClient, built on the existing future-into-awaitable helper. The blocking handshake never holds the interpreter lock: it runs off the loop and the connected-client object is materialized on the conversion side exactly like every other async return..pyistub gains both as staticmethods returningAwaitable[AsyncClient]with docstrings. stubtest passes against the compiled extension.Checks:
Client.connectreturns a Promise; nothing weakened).cargo build/cargo clippy -- -D warnings/cargo fmt --checkclean on the Python crate.maturin developbuilds the wheel; the two new methods are exported andconnect(...)returns an awaitable without blocking at call time.🤖 Generated with Claude Code