AsyncClient + AsyncCollection — Phase 1 (universal wrapper)#19
Merged
Conversation
Phase 1 of #18 — the fourth deferred item from #11. Adds async/await support across every vd backend through a universal wrapper, with opt-in native implementations to follow in per-backend PRs. Architecture (same pattern as PR #16's SupportsHybrid + RRF fallback): the universal AsyncClientWrapper/AsyncCollectionWrapper adapts every sync vd Collection/Client by dispatching every method to asyncio.to_thread. Adapters with native async SDKs override the wrapper in Phase 2. vd/base.py - AsyncClient / AsyncCollection runtime-checkable Protocols mirror the Client/Collection contracts. Stdlib has no AsyncMutableMapping; the AsyncCollection exposes explicit get/set/delete/keys/count (Motor / aiopg convention) instead of dunders. - SupportsNativeAsync marker — present on both the wrapper (with native_async=False) and native adapters (with native_async=True), so consumers can prefer truly non-blocking adapters in high-concurrency HTTP servers while still treating the wrapper as a valid AsyncClient. vd/asynchronous.py (new) - AsyncClientWrapper / AsyncCollectionWrapper — thin wrappers over any sync Client/Collection; every method dispatches through asyncio.to_thread. AsyncCollection.keys() / search() materialize once in a worker thread then stream from memory (most backends' sync iter is already O(N)). Both wrappers expose `.sync` as a documented escape hatch. - connect_async(backend, **kwargs) — async sibling of vd.connect(), returns an awaitable. Phase 2 will plug per-backend native adapters in here; for Phase 1 every backend uses the universal wrapper. - hybrid_search_async(collection, query, ...) — async sibling of vd.hybrid_search; dispatches the whole fused call to a worker thread. - Module name is `asynchronous` to avoid the `async` keyword. Exports - vd.AsyncClient, vd.AsyncCollection, vd.SupportsNativeAsync. - vd.AsyncClientWrapper, vd.AsyncCollectionWrapper. - vd.connect_async, vd.hybrid_search_async. Tests - tests/test_async.py — 12 contract tests on the memory backend (no infra needed). Cover the entry point + protocol satisfaction, async context manager, create/get/delete/list_collections, CRUD on AsyncCollection, search with filter + egress, upsert/add_documents, escape hatches, hybrid_search_async happy + error paths. - pyproject.toml — adds pytest-asyncio>=0.23 to dev deps; configures `asyncio_mode = "auto"` so `async def test_*` runs without per-test decorators. Verification - 12 async tests pass. - Full suite: 262 passed, 189 skipped (server backends require docker). No regressions vs the prior 250 sync tests — 12 net new. Phase 2 (one follow-up issue per backend, to be opened): native async adapters for chroma, qdrant, weaviate, elasticsearch, redis, mongodb, lancedb, milvus, pinecone, turbopuffer (the 10 backends with native async SDKs). Each will set `native_async = True` and bypass to_thread. Refs #11, closes #18
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 + AsyncCollection — Phase 1 (universal wrapper)
Closes #18. Refs #11 (fourth deferred item).
What ships
vd.connect_async(backend, ...)— async sibling ofvd.connect. Returns anAsyncClientfor every backend.AsyncClient/AsyncCollectionruntime-checkable protocols (vd.base).AsyncClientWrapper/AsyncCollectionWrapper(vd.asynchronous) that adapt any sync vd Client/Collection by dispatching every method throughasyncio.to_thread.vd.hybrid_search_async(collection, ...)— async sibling ofvd.hybrid_search.SupportsNativeAsyncmarker protocol with anative_async: boolflag — present on both the wrapper (False) and Phase 2 native adapters (True), so consumers in event-loop apps can prefer real non-blocking I/O without losing universal coverage.Architecture
Mirrors the wrapper-default + native opt-in pattern from PR #16's
SupportsHybrid+ RRF fallback: one universal wrapper means every backend supports async day one; native adapters override the wrapper in follow-up PRs (Phase 2).The
AsyncCollectionsurface uses explicitget/set/delete/keys/countmethods rather than dunders. There's no stdlibAsyncMutableMapping; this matches the Motor / aiopg convention.Verification
AsyncCollection, search with filter + egress, upsert /add_documents, escape hatches (.sync,.native), andhybrid_search_asynchappy + error paths.Phase 2 (follow-up issues — to be opened)
Per-backend native async implementations for the 10 backends shipping async SDKs:
chroma (
AsyncClientAPI), qdrant (AsyncQdrantClient), weaviate (WeaviateAsyncClient), elasticsearch (AsyncElasticsearch), redis (redis.asyncio), mongodb (AsyncMongoClient), lancedb (AsyncConnection), milvus (AsyncMilvusClient, 2.5+), pinecone (PineconeAsyncio), turbopuffer. Each is its own clean piece, same shape as the SupportsHybrid follow-up (#17).