Skip to content

feat(client): expose a closed property on the client facades - #1331

Closed
Aryan-Pardeshi wants to merge 424 commits into
qdrant:devfrom
Aryan-Pardeshi:feat/client-closed-property
Closed

feat(client): expose a closed property on the client facades#1331
Aryan-Pardeshi wants to merge 424 commits into
qdrant:devfrom
Aryan-Pardeshi:feat/client-closed-property

Conversation

@Aryan-Pardeshi

Copy link
Copy Markdown

Closes #1299.

QdrantRemote, AsyncQdrantRemote, QdrantLocal and AsyncQdrantLocal all expose a closed property, but the two facade classes did not. close() is public on the facade, so the only way to ask whether it had been called was client._client.closed, which reaches through a private attribute to get at a public one.

>>> client = QdrantClient(":memory:")
>>> client.closed
False
>>> client.close()
>>> client.closed
True

The property forwards straight to the inner client, so there is no second source of truth to drift.

Generated file

No generator change was needed here. closed isn't on AsyncQdrantBase, so the transformer leaves it sync in the async mirror, which is what a property has to be. I ran the generator to confirm rather than assume, and its output matches what's committed.

Tests

tests/test_closed.py covers :memory:, path= and remote, sync and async, before and after close(), plus one case asserting the facade agrees with the inner client. All six fail with AttributeError: 'QdrantClient' object has no attribute 'closed' before the change.

The remote cases pass check_compatibility=False so they don't need a server on localhost.

Note on #1300

I saw that #1300 covered this issue and was closed by its author before review. This is an independent implementation, written against the current master. Happy to close mine instead if that one is coming back.

coszio and others added 30 commits July 1, 2024 22:51
* new: add query interface tests, fix version checking

* fix: add missing file
* fix: fix grpc conversion bugs for sparse and multi vectors

* fix: fix mypy
* chore: Added init_options property

* refactor: @Property

* test: init_opts

* chore: regen async client again with Py 3.10

* chore: deepcopy kwargs
* new: set idf modifier in fastembed mixin for bm models

* fix: update poetry lock

* fix: tmp fix, restrict pyright version

* refactor: update bm model extraction, update fastembed

* fix: fix list of bm models

* fix: fix default bm models list value

* refactoring: remove redundant import
* updated test migrate

* removed second remote client

* added back multiple vector test

* added single multi vector

* modified random_multivectors to support single vector

* fix: add version check for backward compatibility tests

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* new: retry mechanism in migrate

* fixed misdeletion during merging

* fix: minor type hint update

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* fix: do not modify input structs in-place

* fix: regen async
estebany-qd and others added 23 commits August 4, 2026 21:10
* fix: update poetry lock

* fix: add type annotations, update poetry.lock

* fix: fix local persistence tests

* fix: replace del client with client.close in local mode persistence tests
…ing field (qdrant#1224)

* fix: check_match() raises TypeError when MatchText applied to non-string field

* tests: move non-string match test to test_nested_filter, cover MatchText and MatchTextAny

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* fix: spurious async client tests failures

* skip cluster-only test when server is standalone

* increase timeout for unit test performing multiple snapshot operations

* clean up stale snapshots left by previous runs

* fix: remove deleted methods, add/update cluster checks

* fix: remove unused import

* fix: remove redundant indent

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* fix: update poetry lock

* fix: add type annotations, update poetry.lock

* fix: fix local persistence tests

* fix: replace del client with client.close in local mode persistence tests

* new: update local mode values count filter behaviour
Co-authored-by: Hassan Zafar <hassanzafar619@gmail.com>
)

* Fix local mode filters cross-matching booleans and integers

Python treats bool as a subclass of int (True == 1, False == 0), but Qdrant
keeps booleans and integers as distinct payload value types. Local mode
compared them with a plain `==` / `in` / `isinstance(value, (int, float))`, so:

- MatchValue(value=1) matched a payload of True, and MatchValue(value=True)
  matched a payload of 1 (same for 0 / False)
- MatchAny / MatchExcept cross-matched the same way
- Range matched booleans as if they were 0 / 1

The server never cross-matches these (its ValueVariants keeps Integer and Bool
distinct, and booleans are not numeric for range conditions). Add a type-aware
equality helper used by the value-match conditions, and exclude booleans from
range checks. Adds an in-memory regression test.

* Cover MatchExcept in the bool/int cross-match test

MatchExcept also routes through values_match, so assert that except=[1]
keeps the True payload (bool is not the integer 1).

* Add isolated MatchAny and range asserts to the bool/int cross-match test

Lock the single-value MatchAny path and the check_range bool guard against
regressions, in addition to the existing combined-condition coverage.

* fix: handle floats in cross-match local mode filters, add congruence tests

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
…dropping it (qdrant#1083) (qdrant#1247)

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* fix: fix embed paths

* tests: add local inference test for complex prefetch
…on (qdrant#1260)

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* new: 1.19.0 updates

* fix: fix search params as a dict in local mode

* fix: update qdrant backward compatibility version

* fix: add version check to the test

* fix: add version check to the test
* fix: fix nested payload local mode

* test: update test data in complex filter
QdrantRemote, AsyncQdrantRemote, QdrantLocal and AsyncQdrantLocal all expose
closed, but the facades did not, so checking whether a client had been closed
meant reaching through the private _client attribute.

close() is already public on the facade; closed now is too, forwarding to the
inner client.

__repr__ aside, the async mirror needs no generator change: closed is not on
AsyncQdrantBase, so the transformer leaves the property sync, which is what a
property has to be.

Closes qdrant#1299
@netlify

netlify Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploy Preview for poetic-froyo-8baba7 ready!

Name Link
🔨 Latest commit 160b140
🔍 Latest deploy log https://app.netlify.com/projects/poetic-froyo-8baba7/deploys/6a7a329291ab7200075ba05f
😎 Deploy Preview https://deploy-preview-1331--poetic-froyo-8baba7.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added public closed properties to QdrantClient and AsyncQdrantClient. Each property delegates to the underlying client. Added synchronous and asynchronous tests for local, persistent local, and remote configurations before and after closing.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: joein

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation adds synchronous closed properties to both facades and tests the required states and supported client modes [#1299].
Out of Scope Changes check ✅ Passed The changes are limited to the two facade properties and focused tests required by the linked issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes exposing the closed property on both client facades.
Description check ✅ Passed The description directly explains the closed property, implementation, compatibility, tests, and supported client modes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_closed.py`:
- Around line 39-53: Add a test alongside
test_async_local_client_reports_closed_state that constructs AsyncQdrantClient
with a persistent local path, asserts closed is False before awaiting close(),
then asserts closed is True afterward.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 74d2da97-798b-4c13-a976-8496641e4a33

📥 Commits

Reviewing files that changed from the base of the PR and between 550484d and 160b140.

📒 Files selected for processing (3)
  • qdrant_client/async_qdrant_client.py
  • qdrant_client/qdrant_client.py
  • tests/test_closed.py

Comment thread tests/test_closed.py
Comment on lines +39 to +53
async def test_async_local_client_reports_closed_state():
client = AsyncQdrantClient(":memory:")
assert client.closed is False

await client.close()
assert client.closed is True


@pytest.mark.asyncio
async def test_async_remote_client_reports_closed_state():
client = AsyncQdrantClient("localhost", port=6333, check_compatibility=False)
assert client.closed is False

await client.close()
assert client.closed is True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add asynchronous persistent-local coverage.

The tests cover AsyncQdrantClient in memory and remote modes, but not with path=.... Add the before-and-after await client.close() assertions for an asynchronous persistent-local client.

Proposed test
 `@pytest.mark.asyncio`
 async def test_async_local_client_reports_closed_state():
     client = AsyncQdrantClient(":memory:")
     assert client.closed is False
 
     await client.close()
     assert client.closed is True
 
+@pytest.mark.asyncio
+async def test_async_persistent_local_client_reports_closed_state(tmp_path):
+    client = AsyncQdrantClient(path=str(tmp_path / "storage"))
+    assert client.closed is False
+
+    await client.close()
+    assert client.closed is True
+
 `@pytest.mark.asyncio`
 async def test_async_remote_client_reports_closed_state():
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def test_async_local_client_reports_closed_state():
client = AsyncQdrantClient(":memory:")
assert client.closed is False
await client.close()
assert client.closed is True
@pytest.mark.asyncio
async def test_async_remote_client_reports_closed_state():
client = AsyncQdrantClient("localhost", port=6333, check_compatibility=False)
assert client.closed is False
await client.close()
assert client.closed is True
async def test_async_local_client_reports_closed_state():
client = AsyncQdrantClient(":memory:")
assert client.closed is False
await client.close()
assert client.closed is True
`@pytest.mark.asyncio`
async def test_async_persistent_local_client_reports_closed_state(tmp_path):
client = AsyncQdrantClient(path=str(tmp_path / "storage"))
assert client.closed is False
await client.close()
assert client.closed is True
`@pytest.mark.asyncio`
async def test_async_remote_client_reports_closed_state():
client = AsyncQdrantClient("localhost", port=6333, check_compatibility=False)
assert client.closed is False
await client.close()
assert client.closed is True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_closed.py` around lines 39 - 53, Add a test alongside
test_async_local_client_reports_closed_state that constructs AsyncQdrantClient
with a persistent local path, asserts closed is False before awaiting close(),
then asserts closed is True afterward.

@Aryan-Pardeshi

Copy link
Copy Markdown
Author

Closing in favour of #1348 — same change, branched cleanly off dev. This one came off master, which this repo doesn't take PRs against, and showed as conflicting once retargeted.

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.

feat(client): expose 'closed' property on QdrantClient and AsyncQdrantClient facades