|
1 | 1 | import datetime |
2 | | -from typing import Generator, List, Optional, Union |
| 2 | +import time |
| 3 | +from typing import Any, Dict, Generator, List, Optional, Union |
3 | 4 |
|
4 | 5 | import pytest as pytest |
5 | 6 | from _pytest.fixtures import SubRequest |
|
11 | 12 | OpenAICollection, |
12 | 13 | _sanitize_collection_name, |
13 | 14 | ) |
| 15 | +from weaviate.collections import Collection |
14 | 16 | from weaviate.collections.classes.config import ( |
15 | 17 | _BQConfig, |
16 | 18 | _CollectionConfig, |
|
37 | 39 | Rerankers, |
38 | 40 | _RerankerProvider, |
39 | 41 | Tokenization, |
| 42 | + _NamedVectorConfig, |
40 | 43 | _NamedVectorConfigCreate, |
41 | 44 | _VectorizerConfigCreate, |
42 | 45 | IndexName, |
@@ -2038,3 +2041,77 @@ def test_delete_property_index( |
2038 | 2041 | assert config.properties[0].index_range_filters is False |
2039 | 2042 | assert config.properties[0].index_searchable is _index_searchable |
2040 | 2043 | assert config.properties[0].index_filterable is _index_filterable |
| 2044 | + |
| 2045 | + |
| 2046 | +def _vector_config_without_index( |
| 2047 | + collection: Collection[Any, Any], vector_name: str, timeout: float = 30 |
| 2048 | +) -> Dict[str, _NamedVectorConfig]: |
| 2049 | + """Poll the collection config until `vector_name` no longer has an index. |
| 2050 | +
|
| 2051 | + The drop is applied asynchronously, a 200 from the endpoint only means that Weaviate accepted |
| 2052 | + the request. It then becomes visible in two steps: the vector first stays in the schema with a |
| 2053 | + `vector_index_config` of `None`, and once the index is gone from disk the entry is removed from |
| 2054 | + the schema altogether. Both shapes must parse, so accept either. |
| 2055 | + """ |
| 2056 | + start = time.time() |
| 2057 | + while True: |
| 2058 | + vector_config = collection.config.get().vector_config |
| 2059 | + assert vector_config is not None |
| 2060 | + if ( |
| 2061 | + vector_name not in vector_config |
| 2062 | + or vector_config[vector_name].vector_index_config is None |
| 2063 | + ): |
| 2064 | + return vector_config |
| 2065 | + if time.time() - start > timeout: |
| 2066 | + pytest.fail(f"vector index of {vector_name} was not dropped within {timeout}s") |
| 2067 | + time.sleep(0.2) |
| 2068 | + |
| 2069 | + |
| 2070 | +def test_delete_vector_index(collection_factory: CollectionFactory) -> None: |
| 2071 | + """Test that dropping the index of a named vector leaves the rest of the collection usable.""" |
| 2072 | + collection_dummy = collection_factory("dummy") |
| 2073 | + if collection_dummy._connection._weaviate_version.is_lower_than(1, 39, 0): |
| 2074 | + pytest.skip("delete vector index not supported before 1.39.0") |
| 2075 | + |
| 2076 | + collection = collection_factory( |
| 2077 | + properties=[Property(name="name", data_type=DataType.TEXT)], |
| 2078 | + vector_config=[ |
| 2079 | + Configure.Vectors.self_provided(name="dropped"), |
| 2080 | + Configure.Vectors.self_provided(name="kept"), |
| 2081 | + ], |
| 2082 | + ) |
| 2083 | + collection.data.insert( |
| 2084 | + properties={"name": "banana"}, |
| 2085 | + vector={"dropped": [1, 2], "kept": [3, 4]}, |
| 2086 | + ) |
| 2087 | + |
| 2088 | + config = collection.config.get() |
| 2089 | + assert config.vector_config is not None |
| 2090 | + assert config.vector_config["dropped"].vector_index_config is not None |
| 2091 | + |
| 2092 | + with pytest.raises(weaviate.exceptions.UnexpectedStatusCodeError): |
| 2093 | + collection.config.delete_vector_index("does_not_exist") |
| 2094 | + |
| 2095 | + assert collection.config.delete_vector_index("dropped") is True |
| 2096 | + |
| 2097 | + vector_config = _vector_config_without_index(collection, "dropped") |
| 2098 | + # vectors that were not dropped keep their index |
| 2099 | + assert vector_config["kept"].vector_index_config is not None |
| 2100 | + |
| 2101 | + # `list_all` parses the schema through the simple config parser, it must cope with the drop too |
| 2102 | + with weaviate.connect_to_local() as client: |
| 2103 | + simple = client.collections.list_all()[collection.name] |
| 2104 | + assert simple.vector_config is not None |
| 2105 | + assert simple.vector_config["kept"].vector_index_config is not None |
| 2106 | + |
| 2107 | + # searching the vector that still has an index keeps working |
| 2108 | + assert len(collection.query.near_vector([3, 4], target_vector="kept").objects) == 1 |
| 2109 | + |
| 2110 | + |
| 2111 | +def test_delete_vector_index_invalid_input(collection_factory: CollectionFactory) -> None: |
| 2112 | + """Test that a non-string vector name is rejected before hitting the network.""" |
| 2113 | + collection = collection_factory( |
| 2114 | + vector_config=[Configure.Vectors.self_provided(name="vec")], |
| 2115 | + ) |
| 2116 | + with pytest.raises(WeaviateInvalidInputError): |
| 2117 | + collection.config.delete_vector_index(42) # type: ignore[arg-type] |
0 commit comments