Skip to content

feat(python): add non-blocking AsyncClient.connect for event-loop-safe construction#879

Merged
userFRM merged 2 commits into
mainfrom
fix/round8-python
Jun 17, 2026
Merged

feat(python): add non-blocking AsyncClient.connect for event-loop-safe construction#879
userFRM merged 2 commits into
mainfrom
fix/round8-python

Conversation

@userFRM

@userFRM userFRM commented Jun 17, 2026

Copy link
Copy Markdown
Owner

AsyncClient only had the synchronous AsyncClient(creds, config) constructor and AsyncClient.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 inside async def main() under asyncio.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:

client = await AsyncClient.connect(creds, config)
client = await AsyncClient.connect_from_file("creds.txt")

They resolve the handshake off the event loop through the same awaitable bridge the existing *_async historical methods use, so other coroutines keep running while the connection comes up. The synchronous AsyncClient(creds, config) and AsyncClient.from_file(...) constructors stay exactly as they were for construction outside a running loop, and the class example now points async users at await AsyncClient.connect(...).

What's in here:

  • connect and connect_from_file staticmethods on AsyncClient, 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.
  • .pyi stub gains both as staticmethods returning Awaitable[AsyncClient] with docstrings. stubtest passes against the compiled extension.
  • CHANGELOG and docs-site changelog note the new non-blocking constructors under 13.0.0-rc.1.

Checks:

  • Cross-binding parity gate clean (the AsyncClient connect row already reflected the real shape, TS Client.connect returns a Promise; nothing weakened).
  • cargo build / cargo clippy -- -D warnings / cargo fmt --check clean on the Python crate.
  • Rust-side unit tests pass (54 passed), including the awaitable-bridge GIL and concurrency tests the new constructors rely on.
  • maturin develop builds the wheel; the two new methods are exported and connect(...) returns an awaitable without blocking at call time.

🤖 Generated with Claude Code

claude added 2 commits June 17, 2026 22:01
…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>
@userFRM userFRM merged commit 76e2a7d into main Jun 17, 2026
@userFRM userFRM deleted the fix/round8-python branch June 17, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants