From 53839251b676ab7b8c20c24762fe86f728a63365 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sat, 7 Feb 2026 17:14:11 -0800 Subject: [PATCH 1/4] Add if_not_exists parameter to create_collection - Add if_not_exists boolean parameter to create_collection() method in both QdrantClient and AsyncQdrantClient - When if_not_exists=True, only create collection if it doesn't already exist - Prevents need for manual collection_exists() check before creation - Add comprehensive tests for sync and async clients - Maintain backward compatibility with default if_not_exists=False Fixes #1022 --- qdrant_client/async_qdrant_client.py | 8 ++ qdrant_client/qdrant_client.py | 7 ++ tests/test_create_collection_if_not_exists.py | 91 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/test_create_collection_if_not_exists.py diff --git a/qdrant_client/async_qdrant_client.py b/qdrant_client/async_qdrant_client.py index 6258d6f6..32188586 100644 --- a/qdrant_client/async_qdrant_client.py +++ b/qdrant_client/async_qdrant_client.py @@ -1574,6 +1574,7 @@ async def create_collection( timeout: int | None = None, strict_mode_config: types.StrictModeConfig | None = None, metadata: types.Payload | None = None, + if_not_exists: bool = False, **kwargs: Any, ) -> bool: """Create empty collection with given parameters @@ -1620,11 +1621,18 @@ async def create_collection( If timeout is reached - request will return with service error. strict_mode_config: Configure limitations for the collection, such as max size, rate limits, etc. metadata: Arbitrary JSON-like metadata for the collection + if_not_exists: + If True, collection will only be created if it doesn't already exist. + If False (default), will attempt to create regardless of existence. Returns: Operation result """ assert len(kwargs) == 0, f"Unknown arguments: {list(kwargs.keys())}" + + if if_not_exists and await self.collection_exists(collection_name): + return True + return await self._client.create_collection( collection_name=collection_name, vectors_config=vectors_config, diff --git a/qdrant_client/qdrant_client.py b/qdrant_client/qdrant_client.py index 3dff8cf6..77dc064a 100644 --- a/qdrant_client/qdrant_client.py +++ b/qdrant_client/qdrant_client.py @@ -1639,6 +1639,7 @@ def create_collection( timeout: int | None = None, strict_mode_config: types.StrictModeConfig | None = None, metadata: types.Payload | None = None, + if_not_exists: bool = False, **kwargs: Any, ) -> bool: """Create empty collection with given parameters @@ -1685,12 +1686,18 @@ def create_collection( If timeout is reached - request will return with service error. strict_mode_config: Configure limitations for the collection, such as max size, rate limits, etc. metadata: Arbitrary JSON-like metadata for the collection + if_not_exists: + If True, collection will only be created if it doesn't already exist. + If False (default), will attempt to create regardless of existence. Returns: Operation result """ assert len(kwargs) == 0, f"Unknown arguments: {list(kwargs.keys())}" + if if_not_exists and self.collection_exists(collection_name): + return True + return self._client.create_collection( collection_name=collection_name, vectors_config=vectors_config, diff --git a/tests/test_create_collection_if_not_exists.py b/tests/test_create_collection_if_not_exists.py new file mode 100644 index 00000000..434c94e2 --- /dev/null +++ b/tests/test_create_collection_if_not_exists.py @@ -0,0 +1,91 @@ +import pytest +from qdrant_client import QdrantClient, models +from qdrant_client.async_qdrant_client import AsyncQdrantClient + + +def test_create_collection_if_not_exists_sync(): + """Test that create_collection with if_not_exists parameter works correctly for sync client""" + client = QdrantClient(":memory:") + collection_name = "test_collection_if_not_exists" + + # Create collection with if_not_exists=True (collection doesn't exist yet) + result = client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=True, + ) + assert result is True + assert client.collection_exists(collection_name) + + # Try to create again with if_not_exists=True (should not fail, return True) + result = client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=True, + ) + assert result is True + + # Clean up + client.delete_collection(collection_name) + + +def test_create_collection_if_not_exists_false_sync(): + """Test that create_collection with if_not_exists=False still creates collection""" + client = QdrantClient(":memory:") + collection_name = "test_collection_if_not_exists_false" + + # Create collection without if_not_exists (default behavior) + result = client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + assert result is True + assert client.collection_exists(collection_name) + + # Clean up + client.delete_collection(collection_name) + + +@pytest.mark.asyncio +async def test_create_collection_if_not_exists_async(): + """Test that create_collection with if_not_exists parameter works correctly for async client""" + client = AsyncQdrantClient(":memory:") + collection_name = "test_collection_if_not_exists_async" + + # Create collection with if_not_exists=True (collection doesn't exist yet) + result = await client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=True, + ) + assert result is True + assert await client.collection_exists(collection_name) + + # Try to create again with if_not_exists=True (should not fail, return True) + result = await client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=True, + ) + assert result is True + + # Clean up + await client.delete_collection(collection_name) + + +@pytest.mark.asyncio +async def test_create_collection_if_not_exists_false_async(): + """Test that create_collection with if_not_exists=False still creates collection""" + client = AsyncQdrantClient(":memory:") + collection_name = "test_collection_if_not_exists_false_async" + + # Create collection without if_not_exists (default behavior) + result = await client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + assert result is True + assert await client.collection_exists(collection_name) + + # Clean up + await client.delete_collection(collection_name) From 437d667ee41d1c8ee1d30c2a3f4b15350d3bef80 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Thu, 19 Feb 2026 22:33:51 -0800 Subject: [PATCH 2/4] Add negative test cases for create_collection duplicate behavior Verify that creating an existing collection without if_not_exists=True raises a ValueError, confirming the default behavior is preserved. Covers both sync and async clients. Co-Authored-By: Claude Opus 4.6 --- tests/test_create_collection_if_not_exists.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_create_collection_if_not_exists.py b/tests/test_create_collection_if_not_exists.py index 434c94e2..3168f31f 100644 --- a/tests/test_create_collection_if_not_exists.py +++ b/tests/test_create_collection_if_not_exists.py @@ -89,3 +89,42 @@ async def test_create_collection_if_not_exists_false_async(): # Clean up await client.delete_collection(collection_name) + + +def test_create_collection_without_if_not_exists_raises_on_duplicate_sync(): + """Test that creating an existing collection without if_not_exists=True raises an error.""" + client = QdrantClient(":memory:") + collection_name = "test_duplicate_error" + + client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + + with pytest.raises(ValueError, match="already exists"): + client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + + client.delete_collection(collection_name) + + +@pytest.mark.asyncio +async def test_create_collection_without_if_not_exists_raises_on_duplicate_async(): + """Test that creating an existing collection without if_not_exists=True raises an error (async).""" + client = AsyncQdrantClient(":memory:") + collection_name = "test_duplicate_error_async" + + await client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + + with pytest.raises(ValueError, match="already exists"): + await client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + ) + + await client.delete_collection(collection_name) From 5f2a2eb0470c79768426610c6c8e58f237287d61 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Wed, 11 Mar 2026 18:27:52 -0700 Subject: [PATCH 3/4] Refactor tests to use pytest fixtures for reliable cleanup - Use pytest fixtures with yield for sync and async clients - Ensure AsyncQdrantClient.close() is always called - Remove manual delete_collection calls that could be skipped on assertion failure --- tests/test_create_collection_if_not_exists.py | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/tests/test_create_collection_if_not_exists.py b/tests/test_create_collection_if_not_exists.py index 3168f31f..c2e1b12b 100644 --- a/tests/test_create_collection_if_not_exists.py +++ b/tests/test_create_collection_if_not_exists.py @@ -3,128 +3,120 @@ from qdrant_client.async_qdrant_client import AsyncQdrantClient -def test_create_collection_if_not_exists_sync(): - """Test that create_collection with if_not_exists parameter works correctly for sync client""" +@pytest.fixture +def sync_client(): client = QdrantClient(":memory:") + yield client + client.close() + + +@pytest.fixture +async def async_client(): + client = AsyncQdrantClient(":memory:") + yield client + await client.close() + + +def test_create_collection_if_not_exists_sync(sync_client): + """Test that create_collection with if_not_exists parameter works correctly for sync client""" collection_name = "test_collection_if_not_exists" # Create collection with if_not_exists=True (collection doesn't exist yet) - result = client.create_collection( + result = sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), if_not_exists=True, ) assert result is True - assert client.collection_exists(collection_name) + assert sync_client.collection_exists(collection_name) # Try to create again with if_not_exists=True (should not fail, return True) - result = client.create_collection( + result = sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), if_not_exists=True, ) assert result is True - # Clean up - client.delete_collection(collection_name) - -def test_create_collection_if_not_exists_false_sync(): +def test_create_collection_if_not_exists_false_sync(sync_client): """Test that create_collection with if_not_exists=False still creates collection""" - client = QdrantClient(":memory:") collection_name = "test_collection_if_not_exists_false" # Create collection without if_not_exists (default behavior) - result = client.create_collection( + result = sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) assert result is True - assert client.collection_exists(collection_name) - - # Clean up - client.delete_collection(collection_name) + assert sync_client.collection_exists(collection_name) @pytest.mark.asyncio -async def test_create_collection_if_not_exists_async(): +async def test_create_collection_if_not_exists_async(async_client): """Test that create_collection with if_not_exists parameter works correctly for async client""" - client = AsyncQdrantClient(":memory:") collection_name = "test_collection_if_not_exists_async" # Create collection with if_not_exists=True (collection doesn't exist yet) - result = await client.create_collection( + result = await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), if_not_exists=True, ) assert result is True - assert await client.collection_exists(collection_name) + assert await async_client.collection_exists(collection_name) # Try to create again with if_not_exists=True (should not fail, return True) - result = await client.create_collection( + result = await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), if_not_exists=True, ) assert result is True - # Clean up - await client.delete_collection(collection_name) - @pytest.mark.asyncio -async def test_create_collection_if_not_exists_false_async(): +async def test_create_collection_if_not_exists_false_async(async_client): """Test that create_collection with if_not_exists=False still creates collection""" - client = AsyncQdrantClient(":memory:") collection_name = "test_collection_if_not_exists_false_async" # Create collection without if_not_exists (default behavior) - result = await client.create_collection( + result = await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) assert result is True - assert await client.collection_exists(collection_name) - - # Clean up - await client.delete_collection(collection_name) + assert await async_client.collection_exists(collection_name) -def test_create_collection_without_if_not_exists_raises_on_duplicate_sync(): +def test_create_collection_without_if_not_exists_raises_on_duplicate_sync(sync_client): """Test that creating an existing collection without if_not_exists=True raises an error.""" - client = QdrantClient(":memory:") collection_name = "test_duplicate_error" - client.create_collection( + sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) with pytest.raises(ValueError, match="already exists"): - client.create_collection( + sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) - client.delete_collection(collection_name) - @pytest.mark.asyncio -async def test_create_collection_without_if_not_exists_raises_on_duplicate_async(): +async def test_create_collection_without_if_not_exists_raises_on_duplicate_async(async_client): """Test that creating an existing collection without if_not_exists=True raises an error (async).""" - client = AsyncQdrantClient(":memory:") collection_name = "test_duplicate_error_async" - await client.create_collection( + await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) with pytest.raises(ValueError, match="already exists"): - await client.create_collection( + await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), ) - - await client.delete_collection(collection_name) From 967c346b5aedb2871bfb33c17e647db140c2e83c Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Wed, 11 Mar 2026 21:31:56 -0700 Subject: [PATCH 4/4] explicitly pass if_not_exists=False in test cases --- tests/test_create_collection_if_not_exists.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_create_collection_if_not_exists.py b/tests/test_create_collection_if_not_exists.py index c2e1b12b..e8f6bc26 100644 --- a/tests/test_create_collection_if_not_exists.py +++ b/tests/test_create_collection_if_not_exists.py @@ -43,10 +43,11 @@ def test_create_collection_if_not_exists_false_sync(sync_client): """Test that create_collection with if_not_exists=False still creates collection""" collection_name = "test_collection_if_not_exists_false" - # Create collection without if_not_exists (default behavior) + # Create collection with explicit if_not_exists=False result = sync_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=False, ) assert result is True assert sync_client.collection_exists(collection_name) @@ -80,10 +81,11 @@ async def test_create_collection_if_not_exists_false_async(async_client): """Test that create_collection with if_not_exists=False still creates collection""" collection_name = "test_collection_if_not_exists_false_async" - # Create collection without if_not_exists (default behavior) + # Create collection with explicit if_not_exists=False result = await async_client.create_collection( collection_name=collection_name, vectors_config=models.VectorParams(size=10, distance=models.Distance.COSINE), + if_not_exists=False, ) assert result is True assert await async_client.collection_exists(collection_name)