You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(client): add __repr__ to QdrantClient, AsyncQdrantClient, QdrantRemote, AsyncQdrantRemote, QdrantLocal, AsyncQdrantLocal
Problem
None of the six client classes implements __repr__. The default Python repr() falls back to <qdrant_client.qdrant_client.QdrantClient object at 0x10c4a8eb0>, which is useless for debugging, logging, and Jupyter inspection.
httpx.Client, redis.Redis, boto3.client, and motor.AsyncIOMotorClient all ship with a meaningful __repr__. Verified: grep -n "__repr__\|__str__" qdrant_client/{qdrant_client,async_qdrant_client,qdrant_remote,async_qdrant_remote}.py qdrant_client/local/{qdrant_local,async_qdrant_local}.py returns nothing. No open or closed issue mentions the gap; checked via gh search issues "client repr".
Motivation
Three concrete user-facing problems this addresses:
Debugging. When a long-running process logs an exception, the offending QdrantClient instance is shown as <qdrant_client.qdrant_client.QdrantClient object at 0x...>. The user has to know to call repr(client._client) or check init_options to see the host/path/mode.
Jupyter.client in a notebook cell prints the memory address. Users coming from httpx.Client or boto3.client expect to see connection info at a glance.
Log aggregation. Structured logging libraries (e.g. structlog, loguru) use repr() for objects by default. A meaningful repr makes log search and incident triage possible.
Background
The facade classes (QdrantClient, AsyncQdrantClient) hold an inner _client that is either a QdrantRemote/AsyncQdrantRemote or a QdrantLocal/AsyncQdrantLocal. The mode and the relevant connection info (host, port, gRPC vs REST, path) are already on these inner objects — __repr__ just needs to format and expose them.
The async facade and async remote are generated by the AST transformer pipeline at tools/async_client_generator/. The transformer converts def → async def for methods in async_methods (built from iscoroutinefunction(AsyncQdrantBase)). __repr__ is not in AsyncQdrantBase, so it stays sync in the async mirror — which is correct, because repr() is called synchronously by Python's built-in formatter.
Each __repr__ is a single method (3-6 lines). No new abstractions, no new state, no public API surface change beyond the special-method names.
For the generated async files (async_qdrant_client.py, async_qdrant_remote.py, local/async_qdrant_local.py), the AST transformer leaves __repr__ sync in the async mirror (since it's not in AsyncQdrantBase and not in async_methods). That is the correct behavior — repr() is always called synchronously. No exclude_methods or regen-script sed step needed.
Alternatives considered
Use dataclasses or __slots__ to auto-generate __repr__: rejected. The QdrantRemote repr needs derived fields (scheme, host:port formatting) that don't map cleanly to dataclass fields.
Only add __repr__ to the facade classes (QdrantClient/AsyncQdrantClient): rejected. Users who construct a QdrantRemote directly (rare but supported) and users in tests who want to inspect the inner client get nothing useful.
Use pprint for the repr: rejected. The default __repr__ returns a string, not a stream. pprint.pformat(...) adds visual noise for one-liners; not idiomatic for a short class summary.
Include api_key in the repr with masking: rejected. Secrets in reprs are a footgun (logs, exception tracebacks, debuggers all call repr). Better to omit entirely.
Scope
In scope:
6 small __repr__ methods, one per class.
1 test file with ~12 tests (sync + async × 3 classes × multiple configs).
Knowledge graph + repo_map updates.
Out of scope:
__str__ (Python convention is to make __str__ and __repr__ the same unless they serve different audiences; here they don't).
A client_options property returning connection state (orthogonal; users can already inspect init_options for that).
Acceptance criteria
repr(QdrantClient("localhost:6333", prefer_grpc=True)) returns a string that includes the host and the gRPC preference.
repr(QdrantClient(":memory:")) returns a string that includes :memory:.
repr(QdrantClient(path="./storage")) returns a string that includes the path.
The repr for the async client matches the sync client semantically (same fields, just different class name).
The repr never includes api_key or any other secret.
12+ tests in tests/test_repr.py, all pass alongside the existing suite.
mypy clean on all 6 modified files.
ruff check + format clean on the new test file.
Running bash tools/generate_async_client.sh regenerates the async files with the __repr__ methods intact (no regen script changes needed).
Backward compatibility
Fully additive. The new __repr__ is a special-method name that doesn't conflict with anything. The default object.__repr__ is replaced only by the new method, which produces a more useful string.
Risks
Minimal. The repr is purely a debug/UX concern; it doesn't affect behavior, public API, or async semantics.
Low risk of name collision: __repr__ is a reserved special-method name. User code cannot override it on the client instances.
Notes for the implementer
The base branch for this work is upstream/dev (not master). Maintainer joein closed docs(client): correct gRPC default timeout in QdrantClient docstring #1269 on 2026-07-21 with "All the PRs should point dev branch, not master", and the PR template at .github/PULL_REQUEST_TEMPLATE.md:4 says the same.
The test file follows the pattern in tests/test_context_manager.py (pytest, no live server, mock the inner client for remote tests).
The repr for QdrantLocal should distinguish :memory: from a path. The persistent flag is already on the object, so a single conditional is enough.
feat(client): add
__repr__to QdrantClient, AsyncQdrantClient, QdrantRemote, AsyncQdrantRemote, QdrantLocal, AsyncQdrantLocalProblem
None of the six client classes implements
__repr__. The default Pythonrepr()falls back to<qdrant_client.qdrant_client.QdrantClient object at 0x10c4a8eb0>, which is useless for debugging, logging, and Jupyter inspection.httpx.Client,redis.Redis,boto3.client, andmotor.AsyncIOMotorClientall ship with a meaningful__repr__. Verified:grep -n "__repr__\|__str__" qdrant_client/{qdrant_client,async_qdrant_client,qdrant_remote,async_qdrant_remote}.py qdrant_client/local/{qdrant_local,async_qdrant_local}.pyreturns nothing. No open or closed issue mentions the gap; checked viagh search issues "client repr".Motivation
Three concrete user-facing problems this addresses:
QdrantClientinstance is shown as<qdrant_client.qdrant_client.QdrantClient object at 0x...>. The user has to know to callrepr(client._client)or checkinit_optionsto see the host/path/mode.clientin a notebook cell prints the memory address. Users coming fromhttpx.Clientorboto3.clientexpect to see connection info at a glance.structlog,loguru) userepr()for objects by default. A meaningful repr makes log search and incident triage possible.Background
The facade classes (
QdrantClient,AsyncQdrantClient) hold an inner_clientthat is either aQdrantRemote/AsyncQdrantRemoteor aQdrantLocal/AsyncQdrantLocal. The mode and the relevant connection info (host, port, gRPC vs REST, path) are already on these inner objects —__repr__just needs to format and expose them.QdrantRemote.__init__stores_host,_grpc_port,_prefer_grpc,_scheme,_https.QdrantLocal.__init__storeslocationandpersistent.The async facade and async remote are generated by the AST transformer pipeline at
tools/async_client_generator/. The transformer convertsdef→async deffor methods inasync_methods(built fromiscoroutinefunction(AsyncQdrantBase)).__repr__is not inAsyncQdrantBase, so it stays sync in the async mirror — which is correct, becauserepr()is called synchronously by Python's built-in formatter.Current behavior
Expected behavior
Async versions are identical (no
Asyncprefix on the repr — the class name itself changes, andrepr()is sync anyway).The
api_keyis never included in the repr (security — secrets don't belong in reprs).Proposed solution
Add a
__repr__method to each of the six classes:QdrantClient— formats the inner_clientrepr with amode=prefixAsyncQdrantClient— same, but on the async classQdrantRemote— formats host/port/scheme/prefer_grpcAsyncQdrantRemote— same, but on the async classQdrantLocal— formats locationAsyncQdrantLocal— same, but on the async classEach
__repr__is a single method (3-6 lines). No new abstractions, no new state, no public API surface change beyond the special-method names.For the generated async files (
async_qdrant_client.py,async_qdrant_remote.py,local/async_qdrant_local.py), the AST transformer leaves__repr__sync in the async mirror (since it's not inAsyncQdrantBaseand not inasync_methods). That is the correct behavior —repr()is always called synchronously. Noexclude_methodsor regen-script sed step needed.Alternatives considered
dataclassesor__slots__to auto-generate__repr__: rejected. TheQdrantRemoterepr needs derived fields (scheme, host:port formatting) that don't map cleanly to dataclass fields.__repr__to the facade classes (QdrantClient/AsyncQdrantClient): rejected. Users who construct aQdrantRemotedirectly (rare but supported) and users in tests who want to inspect the inner client get nothing useful.pprintfor the repr: rejected. The default__repr__returns a string, not a stream.pprint.pformat(...)adds visual noise for one-liners; not idiomatic for a short class summary.api_keyin the repr with masking: rejected. Secrets in reprs are a footgun (logs, exception tracebacks, debuggers all call repr). Better to omit entirely.Scope
In scope:
__repr__methods, one per class.Out of scope:
__str__(Python convention is to make__str__and__repr__the same unless they serve different audiences; here they don't).closedproperty on the facade classes (orthogonal; thewithblock from feat(client): supportwithblock on QdrantClient andasync withon AsyncQdrantClient #1285 already covers the common case).client_optionsproperty returning connection state (orthogonal; users can already inspectinit_optionsfor that).Acceptance criteria
repr(QdrantClient("localhost:6333", prefer_grpc=True))returns a string that includes the host and the gRPC preference.repr(QdrantClient(":memory:"))returns a string that includes:memory:.repr(QdrantClient(path="./storage"))returns a string that includes the path.api_keyor any other secret.tests/test_repr.py, all pass alongside the existing suite.bash tools/generate_async_client.shregenerates the async files with the__repr__methods intact (no regen script changes needed).Backward compatibility
Fully additive. The new
__repr__is a special-method name that doesn't conflict with anything. The defaultobject.__repr__is replaced only by the new method, which produces a more useful string.Risks
__repr__is a reserved special-method name. User code cannot override it on the client instances.Notes for the implementer
upstream/dev(notmaster). Maintainer joein closed docs(client): correct gRPC default timeout in QdrantClient docstring #1269 on 2026-07-21 with "All the PRs should pointdevbranch, not master", and the PR template at.github/PULL_REQUEST_TEMPLATE.md:4says the same.tests/test_context_manager.py(pytest, no live server, mock the inner client for remote tests).QdrantLocalshould distinguish:memory:from a path. Thepersistentflag is already on the object, so a single conditional is enough.