From 5e7179c705b179ea5ad80762320c352ceebf34a1 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:49:14 +0200 Subject: [PATCH 01/10] feat: add runtime property reindex API for Weaviate >= 1.39 (#2097) Adds collection.config methods for the GA runtime property reindex API: - update_property_index: declarative create-or-migrate upsert (PUT /schema/{class}/properties/{prop}/index/{indexType}) with optional wait_for_completion polling of the index status endpoint - rebuild_property_index: rebuild an existing index from scratch, with tenant selection and optional wait_for_completion - cancel_property_index_task: idempotent cancellation of a live task - get_property_indexes: parse GET /schema/{class}/indexes into new CollectionPropertyIndexes/PropertyIndexStatus read-side types All methods raise WeaviateUnsupportedFeatureError below server 1.39.0. --- weaviate/collections/classes/config.py | 77 ++++ .../collections/classes/config_methods.py | 53 +++ weaviate/collections/config/async_.pyi | 50 ++- weaviate/collections/config/executor.py | 374 ++++++++++++++++++ weaviate/collections/config/sync.pyi | 50 ++- weaviate/exceptions.py | 8 + weaviate/outputs/config.py | 12 + 7 files changed, 622 insertions(+), 2 deletions(-) diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 0f6d974c0..3fdc34d4a 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -2207,6 +2207,83 @@ class _ShardStatus: ShardStatus = _ShardStatus +class PropertyIndexTaskStatus(str, BaseEnum): + """The status of a runtime property index task submission. + + Attributes: + STARTED: A reindexing task was submitted and started. + CANCELLED: A live reindexing task was cancelled. + NO_OP: No work was necessary, e.g. the index configuration already matched the request + or there was no live task to cancel. + """ + + STARTED = "STARTED" + CANCELLED = "CANCELLED" + NO_OP = "NO_OP" + + +class PropertyIndexState(str, BaseEnum): + """The state of a property index as reported by the index status endpoint. + + Attributes: + READY: The index is ready to serve queries. + PENDING: A reindexing task for the index is queued but has not started yet. + INDEXING: A reindexing task for the index is in progress. + FAILED: The reindexing task for the index failed. + CANCELLED: The reindexing task for the index was cancelled. + """ + + READY = "ready" + PENDING = "pending" + INDEXING = "indexing" + FAILED = "failed" + CANCELLED = "cancelled" + + +@dataclass +class _PropertyIndexTask(_ConfigBase): + task_id: Optional[str] + status: PropertyIndexTaskStatus + + +PropertyIndexTask = _PropertyIndexTask + + +@dataclass +class _PropertyIndexStatus(_ConfigBase): + type: IndexName # noqa: A003 + status: PropertyIndexState + progress: Optional[float] + task_id: Optional[str] + tokenization: Optional[Tokenization] + target_tokenization: Optional[Tokenization] + algorithm: Optional[str] + target_algorithm: Optional[str] + + +PropertyIndexStatus = _PropertyIndexStatus + + +@dataclass +class _PropertyIndexes(_ConfigBase): + name: str + data_type: str + description: Optional[str] + indexes: List[PropertyIndexStatus] + + +PropertyIndexes = _PropertyIndexes + + +@dataclass +class _CollectionPropertyIndexes(_ConfigBase): + collection: str + properties: List[PropertyIndexes] + + +CollectionPropertyIndexes = _CollectionPropertyIndexes + + class _TextAnalyzerConfigCreate(_ConfigCreateModel): """Text analysis options for a property. diff --git a/weaviate/collections/classes/config_methods.py b/weaviate/collections/classes/config_methods.py index 691cf208d..01981d9af 100644 --- a/weaviate/collections/classes/config_methods.py +++ b/weaviate/collections/classes/config_methods.py @@ -4,8 +4,11 @@ from weaviate.collections.classes.config import ( DataType, GenerativeSearches, + IndexName, PQEncoderDistribution, PQEncoderType, + PropertyIndexState, + PropertyIndexTaskStatus, ReplicationDeletionStrategy, Rerankers, StopwordsPreset, @@ -19,6 +22,7 @@ _BQConfig, _CollectionConfig, _CollectionConfigSimple, + _CollectionPropertyIndexes, _GenerativeConfig, _InvertedIndexConfig, _MultiTenancyConfig, @@ -31,6 +35,9 @@ _PQConfig, _PQEncoderConfig, _Property, + _PropertyIndexes, + _PropertyIndexStatus, + _PropertyIndexTask, _PropertyVectorizerConfig, _ReferenceProperty, _ReplicationConfig, @@ -558,3 +565,49 @@ def _references_from_config(schema: Dict[str, Any]) -> List[_ReferenceProperty]: for prop in schema["properties"] if not _is_primitive(prop["dataType"]) ] + + +def _property_index_task_from_json(response: Dict[str, Any]) -> _PropertyIndexTask: + return _PropertyIndexTask( + task_id=response.get("taskId"), + status=PropertyIndexTaskStatus(response["status"]), + ) + + +def _property_index_status_from_json(index: Dict[str, Any]) -> _PropertyIndexStatus: + tokenization = index.get("tokenization") + target_tokenization = index.get("targetTokenization") + return _PropertyIndexStatus( + type=cast(IndexName, index["type"]), + status=PropertyIndexState(index["status"]), + progress=index.get("progress"), + task_id=index.get("taskId"), + tokenization=Tokenization(tokenization) if tokenization is not None else None, + target_tokenization=( + Tokenization(target_tokenization) if target_tokenization is not None else None + ), + algorithm=index.get("algorithm"), + target_algorithm=index.get("targetAlgorithm"), + ) + + +def _collection_property_indexes_from_json(response: Dict[str, Any]) -> _CollectionPropertyIndexes: + properties: List[_PropertyIndexes] = [] + for prop in response.get("properties") or []: + data_type = prop.get("dataType") + if isinstance(data_type, list): + data_type = data_type[0] if len(data_type) > 0 else "" + properties.append( + _PropertyIndexes( + name=prop["name"], + data_type=cast(str, data_type), + description=prop.get("description"), + indexes=[ + _property_index_status_from_json(index) for index in prop.get("indexes") or [] + ], + ) + ) + return _CollectionPropertyIndexes( + collection=response["collection"], + properties=properties, + ) diff --git a/weaviate/collections/config/async_.pyi b/weaviate/collections/config/async_.pyi index 015b70dab..3fe2ef26f 100644 --- a/weaviate/collections/config/async_.pyi +++ b/weaviate/collections/config/async_.pyi @@ -1,15 +1,19 @@ -from typing import Dict, List, Literal, Optional, Union, overload +from typing import Dict, List, Literal, Optional, Sequence, Union, overload from typing_extensions import deprecated from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, + CollectionPropertyIndexes, IndexName, Property, + PropertyIndexStatus, + PropertyIndexTask, ReferenceProperty, ShardStatus, ShardTypes, + Tokenization, _GenerativeProvider, _InvertedIndexConfigUpdate, _MultiTenancyConfigUpdate, @@ -90,3 +94,47 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... async def delete_property_index(self, property_name: str, index_name: IndexName) -> bool: ... + @overload + async def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> PropertyIndexStatus: ... + @overload + async def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> PropertyIndexTask: ... + @overload + async def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> PropertyIndexStatus: ... + @overload + async def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> PropertyIndexTask: ... + async def cancel_property_index_task( + self, property_name: str, index_name: IndexName + ) -> PropertyIndexTask: ... + async def get_property_indexes(self) -> CollectionPropertyIndexes: ... diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 103ab70ac..5c7d35a75 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -1,4 +1,5 @@ import asyncio +import time from typing import ( Any, Dict, @@ -20,12 +21,17 @@ from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, + CollectionPropertyIndexes, IndexName, Property, + PropertyIndexState, + PropertyIndexStatus, + PropertyIndexTask, PropertyType, ReferenceProperty, ShardStatus, ShardTypes, + Tokenization, _CollectionConfigUpdate, _GenerativeProvider, _InvertedIndexConfigUpdate, @@ -45,6 +51,8 @@ from weaviate.collections.classes.config_methods import ( _collection_config_from_json, _collection_config_simple_from_json, + _collection_property_indexes_from_json, + _property_index_task_from_json, ) from weaviate.collections.classes.config_object_ttl import _ObjectTTLConfigUpdate from weaviate.collections.classes.config_vector_index import ( @@ -53,6 +61,8 @@ from weaviate.connect import executor from weaviate.connect.v4 import ConnectionAsync, ConnectionType, _ExpectedStatusCodes from weaviate.exceptions import ( + ReindexCanceledError, + ReindexFailedError, WeaviateInvalidInputError, WeaviateUnsupportedFeatureError, ) @@ -79,6 +89,37 @@ def _property_has_text_analyzer(prop: Property) -> bool: return any(_property_has_text_analyzer(np) for np in nested_list) +def _find_property_index_status( + indexes: CollectionPropertyIndexes, property_name: str, index_name: IndexName +) -> Optional[PropertyIndexStatus]: + for prop in indexes.properties: + if prop.name != property_name: + continue + for index in prop.indexes: + if index.type == index_name: + return index + return None + + +def _terminal_property_index_status( + entry: Optional[PropertyIndexStatus], property_name: str, index_name: IndexName +) -> Optional[PropertyIndexStatus]: + """Return the entry once it is ready, raise on failure/cancellation, or return None to keep polling.""" + if entry is None: + return None + if entry.status == PropertyIndexState.READY: + return entry + if entry.status == PropertyIndexState.FAILED: + raise ReindexFailedError( + f"Reindexing the '{index_name}' index of property '{property_name}' failed." + ) + if entry.status == PropertyIndexState.CANCELLED: + raise ReindexCanceledError( + f"Reindexing the '{index_name}' index of property '{property_name}' was cancelled." + ) + return None + + class _ConfigCollectionExecutor(Generic[ConnectionType]): def __init__( self, @@ -666,3 +707,336 @@ def resp(res: Response) -> bool: error_msg="Property may not exist", status_codes=_ExpectedStatusCodes(ok_in=[200], error="property exists"), ) + + def __check_property_reindex_support(self, feature: str) -> None: + if not self._connection._weaviate_version.is_at_least(1, 39, 0): + raise WeaviateUnsupportedFeatureError( + feature, + str(self._connection._weaviate_version), + "1.39.0", + ) + + def __property_index_path(self, property_name: str, index_name: IndexName) -> str: + return ( + f"/schema/{_capitalize_first_letter(self._name)}" + + f"/properties/{property_name}" + + f"/index/{index_name}" + ) + + def __wait_for_property_index( + self, property_name: str, index_name: IndexName + ) -> executor.Result[PropertyIndexStatus]: + if isinstance(self._connection, ConnectionAsync): + + async def _execute() -> PropertyIndexStatus: + while True: + indexes = await executor.aresult(self.get_property_indexes()) + entry = _find_property_index_status(indexes, property_name, index_name) + done = _terminal_property_index_status(entry, property_name, index_name) + if done is not None: + return done + await asyncio.sleep(1) + + return _execute() + while True: + indexes = executor.result(self.get_property_indexes()) + entry = _find_property_index_status(indexes, property_name, index_name) + done = _terminal_property_index_status(entry, property_name, index_name) + if done is not None: + return done + time.sleep(1) + + @overload + def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> executor.Result[PropertyIndexStatus]: ... + + @overload + def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> executor.Result[PropertyIndexTask]: ... + + def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: bool = False, + ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: + """Create or migrate a property index in this collection. + + Note: This method is a declarative upsert operation. If the index does not exist, it is created + with the requested configuration. If it exists, it is migrated towards the requested + configuration. If the configuration already matches, no work is submitted and the returned + task reports a `NO_OP` status. The server accepts at most one configuration change per request. + + Args: + property_name: The property whose index to create or migrate. + index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + tokenization: The tokenization of the index. Required when creating a `searchable` index; + optional as a change on an existing `searchable` or `filterable` index. Not valid for + `rangeFilters`. + algorithm: The search algorithm of a `searchable` index. Only `blockmax` may be requested. + tenants: The tenants for which to create the index. Only valid when creating a + `rangeFilters` index on a multi-tenant collection. If not provided, all tenants are + affected. + wait_for_completion: Whether to wait until the index reports `ready`. By default False. + + Returns: + A `PropertyIndexTask` when `wait_for_completion=False`, or the final `PropertyIndexStatus` + of the index when `wait_for_completion=True`. + + Raises: + weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. + weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. + weaviate.exceptions.ReindexFailedError: If `wait_for_completion=True` and the reindexing task failed. + weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. + """ + self.__check_property_reindex_support("Collection config update_property_index") + _validate_input( + [_ValidateArgument(expected=[str], name="property_name", value=property_name)] + ) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + + path = self.__property_index_path(property_name, index_name) + body: Dict[str, Any] = {} + if tokenization is not None: + body["tokenization"] = ( + tokenization.value if isinstance(tokenization, Tokenization) else tokenization + ) + if algorithm is not None: + body["algorithm"] = algorithm + params: Optional[Dict[str, Any]] = ( + {"tenants": ",".join(tenants)} if tenants is not None else None + ) + + def resp(res: Response) -> PropertyIndexTask: + response = _decode_json_response_dict(res, "Update property index") + assert response is not None + return _property_index_task_from_json(response) + + if isinstance(self._connection, ConnectionAsync): + + async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: + res = await executor.aresult( + self._connection.put( + path=path, + weaviate_object=body, + params=params, + error_msg="Property index may not have been updated.", + status_codes=_ExpectedStatusCodes( + ok_in=[200, 202], error="Update property index" + ), + ) + ) + task = resp(res) + if wait_for_completion: + return await executor.aresult( + self.__wait_for_property_index(property_name, index_name) + ) + return task + + return _execute() + res = executor.result( + self._connection.put( + path=path, + weaviate_object=body, + params=params, + error_msg="Property index may not have been updated.", + status_codes=_ExpectedStatusCodes(ok_in=[200, 202], error="Update property index"), + ) + ) + task = resp(res) + if wait_for_completion: + return executor.result(self.__wait_for_property_index(property_name, index_name)) + return task + + @overload + def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> executor.Result[PropertyIndexStatus]: ... + + @overload + def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> executor.Result[PropertyIndexTask]: ... + + def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: bool = False, + ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: + """Rebuild an existing property index from scratch with its current configuration. + + Args: + property_name: The property whose index to rebuild. + index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + tenants: The tenants for which to rebuild the index on a multi-tenant collection. + If not provided, all tenants are affected. + wait_for_completion: Whether to wait until the index reports `ready`. By default False. + + Returns: + A `PropertyIndexTask` when `wait_for_completion=False`, or the final `PropertyIndexStatus` + of the index when `wait_for_completion=True`. + + Raises: + weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. + weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. + weaviate.exceptions.ReindexFailedError: If `wait_for_completion=True` and the reindexing task failed. + weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. + """ + self.__check_property_reindex_support("Collection config rebuild_property_index") + _validate_input( + [_ValidateArgument(expected=[str], name="property_name", value=property_name)] + ) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + + path = self.__property_index_path(property_name, index_name) + "/rebuild" + params: Optional[Dict[str, Any]] = ( + {"tenants": ",".join(tenants)} if tenants is not None else None + ) + + def resp(res: Response) -> PropertyIndexTask: + response = _decode_json_response_dict(res, "Rebuild property index") + assert response is not None + return _property_index_task_from_json(response) + + if isinstance(self._connection, ConnectionAsync): + + async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: + res = await executor.aresult( + self._connection.post( + path=path, + weaviate_object={}, + params=params, + error_msg="Property index may not have been rebuilt.", + status_codes=_ExpectedStatusCodes( + ok_in=[202], error="Rebuild property index" + ), + ) + ) + task = resp(res) + if wait_for_completion: + return await executor.aresult( + self.__wait_for_property_index(property_name, index_name) + ) + return task + + return _execute() + res = executor.result( + self._connection.post( + path=path, + weaviate_object={}, + params=params, + error_msg="Property index may not have been rebuilt.", + status_codes=_ExpectedStatusCodes(ok_in=[202], error="Rebuild property index"), + ) + ) + task = resp(res) + if wait_for_completion: + return executor.result(self.__wait_for_property_index(property_name, index_name)) + return task + + def cancel_property_index_task( + self, + property_name: str, + index_name: IndexName, + ) -> executor.Result[PropertyIndexTask]: + """Cancel the live reindexing task of a property index. + + This operation is idempotent: if there is no live task for the index, the returned task + reports a `NO_OP` status. Note that a coupled tokenization change (a `searchable` change on a + property that also has a `filterable` index) is a single task; cancelling it via either index + type cancels the whole task. + + Args: + property_name: The property whose reindexing task to cancel. + index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + + Returns: + A `PropertyIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. + + Raises: + weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. + weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. + """ + self.__check_property_reindex_support("Collection config cancel_property_index_task") + _validate_input( + [_ValidateArgument(expected=[str], name="property_name", value=property_name)] + ) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + + path = self.__property_index_path(property_name, index_name) + "/cancel" + + def resp(res: Response) -> PropertyIndexTask: + response = _decode_json_response_dict(res, "Cancel property index task") + assert response is not None + return _property_index_task_from_json(response) + + return executor.execute( + response_callback=resp, + method=self._connection.post, + path=path, + weaviate_object={}, + error_msg="Property index task may not have been cancelled.", + status_codes=_ExpectedStatusCodes(ok_in=[202], error="Cancel property index task"), + ) + + def get_property_indexes(self) -> executor.Result[CollectionPropertyIndexes]: + """Get the statuses of the property indexes of this collection. + + The response includes the state of any in-flight reindexing tasks, e.g. their progress and + target configuration. Poll this endpoint for a `ready` status to detect the completion of a + reindexing task. + + Returns: + A `CollectionPropertyIndexes` object containing the index statuses grouped by property. + + Raises: + weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. + weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. + """ + self.__check_property_reindex_support("Collection config get_property_indexes") + + def resp(res: Response) -> CollectionPropertyIndexes: + response = _decode_json_response_dict(res, "Get property indexes") + assert response is not None + return _collection_property_indexes_from_json(response) + + return executor.execute( + response_callback=resp, + method=self._connection.get, + path=f"/schema/{_capitalize_first_letter(self._name)}/indexes", + error_msg="Property index statuses could not be retrieved.", + status_codes=_ExpectedStatusCodes(ok_in=[200], error="Get property indexes"), + ) diff --git a/weaviate/collections/config/sync.pyi b/weaviate/collections/config/sync.pyi index e54d8c8fc..87a87f625 100644 --- a/weaviate/collections/config/sync.pyi +++ b/weaviate/collections/config/sync.pyi @@ -1,15 +1,19 @@ -from typing import Dict, List, Literal, Optional, Union, overload +from typing import Dict, List, Literal, Optional, Sequence, Union, overload from typing_extensions import deprecated from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, + CollectionPropertyIndexes, IndexName, Property, + PropertyIndexStatus, + PropertyIndexTask, ReferenceProperty, ShardStatus, ShardTypes, + Tokenization, _GenerativeProvider, _InvertedIndexConfigUpdate, _MultiTenancyConfigUpdate, @@ -88,3 +92,47 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... def delete_property_index(self, property_name: str, index_name: IndexName) -> bool: ... + @overload + def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> PropertyIndexStatus: ... + @overload + def update_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tokenization: Optional[Tokenization] = None, + algorithm: Optional[Literal["blockmax"]] = None, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> PropertyIndexTask: ... + @overload + def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[True], + ) -> PropertyIndexStatus: ... + @overload + def rebuild_property_index( + self, + property_name: str, + index_name: IndexName, + *, + tenants: Optional[Sequence[str]] = None, + wait_for_completion: Literal[False] = False, + ) -> PropertyIndexTask: ... + def cancel_property_index_task( + self, property_name: str, index_name: IndexName + ) -> PropertyIndexTask: ... + def get_property_indexes(self) -> CollectionPropertyIndexes: ... diff --git a/weaviate/exceptions.py b/weaviate/exceptions.py index ce0fe6f7e..a1bcaf6a9 100644 --- a/weaviate/exceptions.py +++ b/weaviate/exceptions.py @@ -149,6 +149,14 @@ class ExportCanceledError(WeaviateBaseError): """Export Canceled Exception.""" +class ReindexFailedError(WeaviateBaseError): + """Reindex Failed Exception.""" + + +class ReindexCanceledError(WeaviateBaseError): + """Reindex Canceled Exception.""" + + class EmptyResponseError(WeaviateBaseError): """Occurs when an HTTP request unexpectedly returns an empty response.""" diff --git a/weaviate/outputs/config.py b/weaviate/outputs/config.py index 17ebebf0e..b708d0a63 100644 --- a/weaviate/outputs/config.py +++ b/weaviate/outputs/config.py @@ -3,6 +3,7 @@ BM25Config, CollectionConfig, CollectionConfigSimple, + CollectionPropertyIndexes, GenerativeConfig, GenerativeSearches, InvertedIndexConfig, @@ -12,6 +13,11 @@ PQEncoderDistribution, PQEncoderType, PropertyConfig, + PropertyIndexes, + PropertyIndexState, + PropertyIndexStatus, + PropertyIndexTask, + PropertyIndexTaskStatus, PropertyType, ReferencePropertyConfig, ReplicationConfig, @@ -35,6 +41,7 @@ "BM25Config", "CollectionConfig", "CollectionConfigSimple", + "CollectionPropertyIndexes", "GenerativeConfig", "GenerativeSearches", "InvertedIndexConfig", @@ -45,6 +52,11 @@ "PQEncoderDistribution", "PQEncoderType", "PropertyConfig", + "PropertyIndexes", + "PropertyIndexState", + "PropertyIndexStatus", + "PropertyIndexTask", + "PropertyIndexTaskStatus", "PropertyType", "ReferencePropertyConfig", "ReplicationConfig", From 762e656d3975600974268d490413f3e5fbef5ca3 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:49:30 +0200 Subject: [PATCH 02/10] test: cover runtime property reindex endpoints (#2097) Mock tests exercise the exact REST routes against a 1.39-advertising mock server (upsert 202/NO_OP, rebuild, cancel CANCELLED/NO_OP, tenants csv encoding, status parsing incl. coupled task entries) and assert WeaviateUnsupportedFeatureError against a 1.36 mock. Integration tests cover the searchable lifecycle, rangeFilters creation, coupled tokenization changes, multi-tenant selection and the async client, skipping below server 1.39.0. --- integration/test_collection_config.py | 224 ++++++++++++++++++++ mock_tests/test_property_reindex.py | 289 ++++++++++++++++++++++++++ 2 files changed, 513 insertions(+) create mode 100644 mock_tests/test_property_reindex.py diff --git a/integration/test_collection_config.py b/integration/test_collection_config.py index 814c5d41a..fc9bcb4cf 100644 --- a/integration/test_collection_config.py +++ b/integration/test_collection_config.py @@ -7,6 +7,7 @@ import weaviate import weaviate.classes as wvc from integration.conftest import ( + AsyncCollectionFactory, CollectionFactory, OpenAICollection, _sanitize_collection_name, @@ -40,6 +41,8 @@ _NamedVectorConfigCreate, _VectorizerConfigCreate, IndexName, + PropertyIndexState, + PropertyIndexTaskStatus, ) from weaviate.collections.classes.tenants import Tenant from weaviate.exceptions import ( @@ -2678,3 +2681,224 @@ def test_text_analyzer_roundtrip_from_dict( assert config == new assert config.to_dict() == new.to_dict() client.collections.delete(name) + + +def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFactory) -> None: + """Test the full runtime lifecycle of a searchable index: create, no-op, rebuild, cancel, delete.""" + collection_dummy = collection_factory("dummy") + if collection_dummy._connection._weaviate_version.is_lower_than(1, 39, 0): + pytest.skip("Runtime property reindex requires Weaviate >= 1.39.0") + + collection = collection_factory( + properties=[ + Property( + name="name", + data_type=DataType.TEXT, + index_filterable=True, + index_searchable=False, + ) + ], + ) + collection.data.insert_many([{"name": f"object {i}"} for i in range(10)]) + + # create the searchable index declaratively and wait for it to become ready + status = collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + ) + assert status.type == "searchable" + assert status.status == PropertyIndexState.READY + assert status.tokenization == Tokenization.WORD + + # re-putting the matching configuration is a no-op + task = collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD + ) + assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.task_id is None + + # the status endpoint reports the index as ready + indexes = collection.config.get_property_indexes() + assert indexes.collection == collection.name + entry = next( + index + for prop in indexes.properties + if prop.name == "name" + for index in prop.indexes + if index.type == "searchable" + ) + assert entry.status == PropertyIndexState.READY + + # rebuild the index from scratch + status = collection.config.rebuild_property_index( + "name", "searchable", wait_for_completion=True + ) + assert status.type == "searchable" + assert status.status == PropertyIndexState.READY + + # cancelling when no task is live is an idempotent no-op + task = collection.config.cancel_property_index_task("name", "searchable") + assert task.status == PropertyIndexTaskStatus.NO_OP + + # the pre-existing delete API removes the index again + assert collection.config.delete_property_index("name", "searchable") is True + + +def test_property_reindex_range_filters(collection_factory: CollectionFactory) -> None: + """Test creating a rangeFilters index on an int property via an empty request body.""" + collection_dummy = collection_factory("dummy") + if collection_dummy._connection._weaviate_version.is_lower_than(1, 39, 0): + pytest.skip("Runtime property reindex requires Weaviate >= 1.39.0") + + collection = collection_factory( + properties=[ + Property( + name="age", + data_type=DataType.INT, + index_filterable=True, + index_range_filters=False, + ) + ], + ) + collection.data.insert_many([{"age": i} for i in range(10)]) + + status = collection.config.update_property_index( + "age", "rangeFilters", wait_for_completion=True + ) + assert status.type == "rangeFilters" + assert status.status == PropertyIndexState.READY + + entry = next( + index + for prop in collection.config.get_property_indexes().properties + if prop.name == "age" + for index in prop.indexes + if index.type == "rangeFilters" + ) + assert entry.status == PropertyIndexState.READY + + +def test_property_reindex_coupled_tokenization_change( + collection_factory: CollectionFactory, +) -> None: + """Test that a tokenization change on searchable is coupled with the filterable index as one task.""" + collection_dummy = collection_factory("dummy") + if collection_dummy._connection._weaviate_version.is_lower_than(1, 39, 0): + pytest.skip("Runtime property reindex requires Weaviate >= 1.39.0") + + collection = collection_factory( + properties=[ + Property( + name="name", + data_type=DataType.TEXT, + index_filterable=True, + index_searchable=True, + tokenization=Tokenization.WORD, + ) + ], + ) + collection.data.insert_many([{"name": f"object {i}"} for i in range(100)]) + + task = collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.FIELD + ) + assert task.status == PropertyIndexTaskStatus.STARTED + assert task.task_id is not None + + prop = next(p for p in collection.config.get_property_indexes().properties if p.name == "name") + searchable = next(i for i in prop.indexes if i.type == "searchable") + filterable = next(i for i in prop.indexes if i.type == "filterable") + if searchable.task_id is not None: + # both entries are driven by the one coupled task while it is in flight + assert searchable.task_id == task.task_id + assert filterable.task_id == task.task_id + assert searchable.target_tokenization == Tokenization.FIELD + assert filterable.target_tokenization == Tokenization.FIELD + else: + # the task already finalized before the first poll + assert searchable.tokenization == Tokenization.FIELD + + # poll the status endpoint (via the wait path of a NO_OP upsert) until the migration is done + status = collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.FIELD, wait_for_completion=True + ) + assert status.status == PropertyIndexState.READY + assert status.tokenization == Tokenization.FIELD + + prop = next(p for p in collection.config.get_property_indexes().properties if p.name == "name") + filterable = next(i for i in prop.indexes if i.type == "filterable") + assert filterable.status == PropertyIndexState.READY + assert filterable.tokenization == Tokenization.FIELD + + +def test_property_reindex_multi_tenant(collection_factory: CollectionFactory) -> None: + """Test rangeFilters creation and rebuild with a tenants selection on a multi-tenant collection.""" + collection_dummy = collection_factory("dummy") + if collection_dummy._connection._weaviate_version.is_lower_than(1, 39, 0): + pytest.skip("Runtime property reindex requires Weaviate >= 1.39.0") + + collection = collection_factory( + properties=[ + Property( + name="age", + data_type=DataType.INT, + index_filterable=True, + index_range_filters=False, + ) + ], + multi_tenancy_config=Configure.multi_tenancy(enabled=True), + ) + collection.tenants.create([Tenant(name="tenant1"), Tenant(name="tenant2")]) + collection.with_tenant("tenant1").data.insert_many([{"age": i} for i in range(5)]) + + status = collection.config.update_property_index( + "age", "rangeFilters", tenants=["tenant1", "tenant2"], wait_for_completion=True + ) + assert status.type == "rangeFilters" + assert status.status == PropertyIndexState.READY + + status = collection.config.rebuild_property_index( + "age", "rangeFilters", tenants=["tenant1"], wait_for_completion=True + ) + assert status.type == "rangeFilters" + assert status.status == PropertyIndexState.READY + + +@pytest.mark.asyncio +async def test_property_reindex_async(async_collection_factory: AsyncCollectionFactory) -> None: + """Test the runtime property reindex lifecycle through the async client.""" + collection = await async_collection_factory( + properties=[ + Property( + name="name", + data_type=DataType.TEXT, + index_filterable=True, + index_searchable=False, + ) + ], + ) + if collection._connection._weaviate_version.is_lower_than(1, 39, 0): + pytest.skip("Runtime property reindex requires Weaviate >= 1.39.0") + + await collection.data.insert_many([{"name": f"object {i}"} for i in range(10)]) + + status = await collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + ) + assert status.type == "searchable" + assert status.status == PropertyIndexState.READY + + task = await collection.config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD + ) + assert task.status == PropertyIndexTaskStatus.NO_OP + + indexes = await collection.config.get_property_indexes() + assert indexes.collection == collection.name + + status = await collection.config.rebuild_property_index( + "name", "searchable", wait_for_completion=True + ) + assert status.status == PropertyIndexState.READY + + task = await collection.config.cancel_property_index_task("name", "searchable") + assert task.status == PropertyIndexTaskStatus.NO_OP diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py new file mode 100644 index 000000000..84ddd4f3d --- /dev/null +++ b/mock_tests/test_property_reindex.py @@ -0,0 +1,289 @@ +import json +from typing import Generator + +import grpc +import pytest +from pytest_httpserver import HTTPServer +from werkzeug.wrappers import Response + +import weaviate +from mock_tests.conftest import MOCK_IP, MOCK_PORT, MOCK_PORT_GRPC +from weaviate.collections.classes.config import ( + PropertyIndexState, + PropertyIndexTaskStatus, + Tokenization, +) +from weaviate.exceptions import WeaviateUnsupportedFeatureError + +COLLECTION = "TestCollection" +SCHEMA_PATH = f"/v1/schema/{COLLECTION}" +TASK_ID = "00000000-0000-0000-0000-000000000001" + + +@pytest.fixture(scope="function") +def weaviate_139_mock(ready_mock: HTTPServer) -> Generator[HTTPServer, None, None]: + """A mock server advertising Weaviate 1.39.0, which supports runtime property reindexing.""" + ready_mock.expect_request("/v1/meta").respond_with_json({"version": "1.39.0"}) + ready_mock.expect_request("/v1/nodes").respond_with_json( + {"nodes": [{"gitHash": "ABC", "status": "HEALTHY"}]} + ) + ready_mock.expect_request("/v1/.well-known/openid-configuration").respond_with_response( + Response(json.dumps({}), status=404) + ) + yield ready_mock + + +@pytest.fixture(scope="function") +def client_139( + weaviate_139_mock: HTTPServer, start_grpc_server: grpc.Server +) -> Generator[weaviate.WeaviateClient, None, None]: + client = weaviate.connect_to_local(port=MOCK_PORT, host=MOCK_IP, grpc_port=MOCK_PORT_GRPC) + yield client + client.close() + + +def test_update_property_index_started( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable", + method="PUT", + json={"tokenization": "word", "algorithm": "blockmax"}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.update_property_index( + "name", + "searchable", + tokenization=Tokenization.WORD, + algorithm="blockmax", + ) + assert task.task_id == TASK_ID + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +def test_update_property_index_no_op( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable", + method="PUT", + json={"tokenization": "word"}, + ).respond_with_json({"status": "NO_OP"}, status=200) + + task = client_139.collections.use(COLLECTION).config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD + ) + assert task.task_id is None + assert task.status == PropertyIndexTaskStatus.NO_OP + weaviate_139_mock.check_assertions() + + +def test_update_property_index_range_filters_with_tenants( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """A rangeFilters creation sends an empty body and encodes tenants as a csv query param.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/age/index/rangeFilters", + method="PUT", + query_string={"tenants": "tenant1,tenant2"}, + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.update_property_index( + "age", "rangeFilters", tenants=["tenant1", "tenant2"] + ) + assert task.task_id == TASK_ID + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +def test_update_property_index_wait_for_completion( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable", + method="PUT", + json={"tokenization": "word"}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + weaviate_139_mock.expect_request(f"{SCHEMA_PATH}/indexes", method="GET").respond_with_json( + { + "collection": COLLECTION, + "properties": [ + { + "name": "name", + "dataType": "text", + "indexes": [{"type": "searchable", "status": "ready", "tokenization": "word"}], + } + ], + } + ) + + status = client_139.collections.use(COLLECTION).config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + ) + assert status.type == "searchable" + assert status.status == PropertyIndexState.READY + assert status.tokenization == Tokenization.WORD + weaviate_139_mock.check_assertions() + + +def test_rebuild_property_index( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable/rebuild", + method="POST", + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.rebuild_property_index( + "name", "searchable" + ) + assert task.task_id == TASK_ID + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +def test_rebuild_property_index_with_tenants( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/age/index/rangeFilters/rebuild", + method="POST", + query_string={"tenants": "tenant1,tenant2"}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.rebuild_property_index( + "age", "rangeFilters", tenants=["tenant1", "tenant2"] + ) + assert task.task_id == TASK_ID + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +def test_cancel_property_index_task_cancelled( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable/cancel", + method="POST", + ).respond_with_json({"taskId": TASK_ID, "status": "CANCELLED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( + "name", "searchable" + ) + assert task.task_id == TASK_ID + assert task.status == PropertyIndexTaskStatus.CANCELLED + weaviate_139_mock.check_assertions() + + +def test_cancel_property_index_task_no_op( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable/cancel", + method="POST", + ).respond_with_json({"status": "NO_OP"}, status=202) + + task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( + "name", "searchable" + ) + assert task.task_id is None + assert task.status == PropertyIndexTaskStatus.NO_OP + weaviate_139_mock.check_assertions() + + +def test_get_property_indexes( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """A coupled tokenization change renders both entries with a shared taskId and progress.""" + weaviate_139_mock.expect_request(f"{SCHEMA_PATH}/indexes", method="GET").respond_with_json( + { + "collection": COLLECTION, + "properties": [ + { + "name": "name", + "dataType": "text", + "description": "a text property", + "indexes": [ + { + "type": "searchable", + "status": "indexing", + "progress": 0.5, + "taskId": TASK_ID, + "tokenization": "word", + "targetTokenization": "field", + "algorithm": "wand", + "targetAlgorithm": "blockmax", + }, + { + "type": "filterable", + "status": "indexing", + "progress": 0.5, + "taskId": TASK_ID, + "tokenization": "word", + "targetTokenization": "field", + }, + ], + }, + { + "name": "age", + "dataType": "int", + "indexes": [{"type": "rangeFilters", "status": "ready"}], + }, + ], + } + ) + + indexes = client_139.collections.use(COLLECTION).config.get_property_indexes() + assert indexes.collection == COLLECTION + assert len(indexes.properties) == 2 + + name = indexes.properties[0] + assert name.name == "name" + assert name.data_type == "text" + assert name.description == "a text property" + assert len(name.indexes) == 2 + searchable, filterable = name.indexes + assert searchable.type == "searchable" + assert searchable.status == PropertyIndexState.INDEXING + assert searchable.progress == 0.5 + assert searchable.task_id == TASK_ID + assert searchable.tokenization == Tokenization.WORD + assert searchable.target_tokenization == Tokenization.FIELD + assert searchable.algorithm == "wand" + assert searchable.target_algorithm == "blockmax" + assert filterable.type == "filterable" + assert filterable.task_id == TASK_ID # coupled change: one task drives both entries + assert filterable.target_tokenization == Tokenization.FIELD + assert filterable.algorithm is None + assert filterable.target_algorithm is None + + age = indexes.properties[1] + assert age.name == "age" + assert age.data_type == "int" + assert age.description is None + assert len(age.indexes) == 1 + assert age.indexes[0].type == "rangeFilters" + assert age.indexes[0].status == PropertyIndexState.READY + assert age.indexes[0].progress is None + assert age.indexes[0].task_id is None + assert age.indexes[0].tokenization is None + + weaviate_139_mock.check_assertions() + + +def test_property_reindex_unsupported_version( + weaviate_client: weaviate.WeaviateClient, +) -> None: + """Every new method raises against a server older than 1.39.0 (the mock advertises 1.36).""" + config = weaviate_client.collections.use(COLLECTION).config + + with pytest.raises(WeaviateUnsupportedFeatureError): + config.update_property_index("name", "searchable", tokenization=Tokenization.WORD) + with pytest.raises(WeaviateUnsupportedFeatureError): + config.rebuild_property_index("name", "searchable") + with pytest.raises(WeaviateUnsupportedFeatureError): + config.cancel_property_index_task("name", "searchable") + with pytest.raises(WeaviateUnsupportedFeatureError): + config.get_property_indexes() From 6bcb4c5ebbc04668bb6f3b310538e9be349a59b8 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:19:14 +0200 Subject: [PATCH 03/10] fix: address review findings for property reindex API (#2097) - accept a bare string for the tenants argument of update_property_index and rebuild_property_index, normalizing it to a single-element list so it cannot be exploded into a per-character csv (export API precedent) - override to_dict on _PropertyIndexes/_CollectionPropertyIndexes so the nested dataclass lists serialize to JSON-compatible dicts (_CollectionConfig precedent) - drop the dead list branch for dataType in the index status parser - cover the ReindexFailedError/ReindexCanceledError wait paths of both update and rebuild, pin the empty request body of the rebuild/cancel mocks, and prove json.dumps(...to_dict()) round-trips --- mock_tests/test_property_reindex.py | 122 +++++++++++++++++- weaviate/collections/classes/config.py | 10 ++ .../collections/classes/config_methods.py | 17 +-- weaviate/collections/config/async_.pyi | 10 +- weaviate/collections/config/executor.py | 26 ++-- weaviate/collections/config/sync.pyi | 10 +- 6 files changed, 162 insertions(+), 33 deletions(-) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 84ddd4f3d..225f72263 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -13,7 +13,11 @@ PropertyIndexTaskStatus, Tokenization, ) -from weaviate.exceptions import WeaviateUnsupportedFeatureError +from weaviate.exceptions import ( + ReindexCanceledError, + ReindexFailedError, + WeaviateUnsupportedFeatureError, +) COLLECTION = "TestCollection" SCHEMA_PATH = f"/v1/schema/{COLLECTION}" @@ -128,12 +132,117 @@ def test_update_property_index_wait_for_completion( weaviate_139_mock.check_assertions() +def test_update_property_index_bare_str_tenant( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """A bare string tenant is normalized to a single csv value, not exploded into characters.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/age/index/rangeFilters", + method="PUT", + query_string={"tenants": "tenant1"}, + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.update_property_index( + "age", "rangeFilters", tenants="tenant1" + ) + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +@pytest.mark.parametrize( + "index_status,exception", + [("failed", ReindexFailedError), ("cancelled", ReindexCanceledError)], +) +def test_update_property_index_wait_raises( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_status: str, + exception: type, +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable", + method="PUT", + json={"tokenization": "word"}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + weaviate_139_mock.expect_request(f"{SCHEMA_PATH}/indexes", method="GET").respond_with_json( + { + "collection": COLLECTION, + "properties": [ + { + "name": "name", + "dataType": "text", + "indexes": [ + { + "type": "searchable", + "status": index_status, + "progress": 0.42, + "taskId": TASK_ID, + "tokenization": "word", + } + ], + } + ], + } + ) + + with pytest.raises(exception): + client_139.collections.use(COLLECTION).config.update_property_index( + "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + ) + weaviate_139_mock.check_assertions() + + +@pytest.mark.parametrize( + "index_status,exception", + [("failed", ReindexFailedError), ("cancelled", ReindexCanceledError)], +) +def test_rebuild_property_index_wait_raises( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_status: str, + exception: type, +) -> None: + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable/rebuild", + method="POST", + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + weaviate_139_mock.expect_request(f"{SCHEMA_PATH}/indexes", method="GET").respond_with_json( + { + "collection": COLLECTION, + "properties": [ + { + "name": "name", + "dataType": "text", + "indexes": [ + { + "type": "searchable", + "status": index_status, + "progress": 0.42, + "taskId": TASK_ID, + "tokenization": "word", + } + ], + } + ], + } + ) + + with pytest.raises(exception): + client_139.collections.use(COLLECTION).config.rebuild_property_index( + "name", "searchable", wait_for_completion=True + ) + weaviate_139_mock.check_assertions() + + def test_rebuild_property_index( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient ) -> None: weaviate_139_mock.expect_request( f"{SCHEMA_PATH}/properties/name/index/searchable/rebuild", method="POST", + json={}, ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) task = client_139.collections.use(COLLECTION).config.rebuild_property_index( @@ -151,6 +260,7 @@ def test_rebuild_property_index_with_tenants( f"{SCHEMA_PATH}/properties/age/index/rangeFilters/rebuild", method="POST", query_string={"tenants": "tenant1,tenant2"}, + json={}, ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) task = client_139.collections.use(COLLECTION).config.rebuild_property_index( @@ -167,6 +277,7 @@ def test_cancel_property_index_task_cancelled( weaviate_139_mock.expect_request( f"{SCHEMA_PATH}/properties/name/index/searchable/cancel", method="POST", + json={}, ).respond_with_json({"taskId": TASK_ID, "status": "CANCELLED"}, status=202) task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( @@ -183,6 +294,7 @@ def test_cancel_property_index_task_no_op( weaviate_139_mock.expect_request( f"{SCHEMA_PATH}/properties/name/index/searchable/cancel", method="POST", + json={}, ).respond_with_json({"status": "NO_OP"}, status=202) task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( @@ -270,6 +382,14 @@ def test_get_property_indexes( assert age.indexes[0].task_id is None assert age.indexes[0].tokenization is None + # the nested dataclasses serialize all the way down to a JSON-compatible dict + out = json.loads(json.dumps(indexes.to_dict())) + assert out["collection"] == COLLECTION + assert out["properties"][0]["indexes"][0]["taskId"] == TASK_ID + assert out["properties"][0]["indexes"][0]["targetTokenization"] == "field" + assert out["properties"][1]["dataType"] == "int" + assert out["properties"][1]["indexes"][0]["status"] == "ready" + weaviate_139_mock.check_assertions() diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 3fdc34d4a..4e462962e 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -2271,6 +2271,11 @@ class _PropertyIndexes(_ConfigBase): description: Optional[str] indexes: List[PropertyIndexStatus] + def to_dict(self) -> Dict[str, Any]: + out = super().to_dict() + out["indexes"] = [index.to_dict() for index in self.indexes] + return out + PropertyIndexes = _PropertyIndexes @@ -2280,6 +2285,11 @@ class _CollectionPropertyIndexes(_ConfigBase): collection: str properties: List[PropertyIndexes] + def to_dict(self) -> Dict[str, Any]: + out = super().to_dict() + out["properties"] = [prop.to_dict() for prop in self.properties] + return out + CollectionPropertyIndexes = _CollectionPropertyIndexes diff --git a/weaviate/collections/classes/config_methods.py b/weaviate/collections/classes/config_methods.py index 01981d9af..4ad29beb4 100644 --- a/weaviate/collections/classes/config_methods.py +++ b/weaviate/collections/classes/config_methods.py @@ -592,22 +592,17 @@ def _property_index_status_from_json(index: Dict[str, Any]) -> _PropertyIndexSta def _collection_property_indexes_from_json(response: Dict[str, Any]) -> _CollectionPropertyIndexes: - properties: List[_PropertyIndexes] = [] - for prop in response.get("properties") or []: - data_type = prop.get("dataType") - if isinstance(data_type, list): - data_type = data_type[0] if len(data_type) > 0 else "" - properties.append( + return _CollectionPropertyIndexes( + collection=response["collection"], + properties=[ _PropertyIndexes( name=prop["name"], - data_type=cast(str, data_type), + data_type=prop["dataType"], description=prop.get("description"), indexes=[ _property_index_status_from_json(index) for index in prop.get("indexes") or [] ], ) - ) - return _CollectionPropertyIndexes( - collection=response["collection"], - properties=properties, + for prop in response.get("properties") or [] + ], ) diff --git a/weaviate/collections/config/async_.pyi b/weaviate/collections/config/async_.pyi index 3fe2ef26f..eb2d40dfe 100644 --- a/weaviate/collections/config/async_.pyi +++ b/weaviate/collections/config/async_.pyi @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Optional, Sequence, Union, overload +from typing import Dict, List, Literal, Optional, Union, overload from typing_extensions import deprecated @@ -102,7 +102,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> PropertyIndexStatus: ... @overload @@ -113,7 +113,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... @overload @@ -122,7 +122,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> PropertyIndexStatus: ... @overload @@ -131,7 +131,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... async def cancel_property_index_task( diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 5c7d35a75..2179e7b49 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -754,7 +754,7 @@ def update_property_index( *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> executor.Result[PropertyIndexStatus]: ... @@ -766,7 +766,7 @@ def update_property_index( *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> executor.Result[PropertyIndexTask]: ... @@ -777,7 +777,7 @@ def update_property_index( *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: """Create or migrate a property index in this collection. @@ -794,9 +794,9 @@ def update_property_index( optional as a change on an existing `searchable` or `filterable` index. Not valid for `rangeFilters`. algorithm: The search algorithm of a `searchable` index. Only `blockmax` may be requested. - tenants: The tenants for which to create the index. Only valid when creating a - `rangeFilters` index on a multi-tenant collection. If not provided, all tenants are - affected. + tenants: The tenant/list of tenants for which to create the index. Only valid when + creating a `rangeFilters` index on a multi-tenant collection. If not provided, all + tenants are affected. wait_for_completion: Whether to wait until the index reports `ready`. By default False. Returns: @@ -823,6 +823,8 @@ def update_property_index( ) if algorithm is not None: body["algorithm"] = algorithm + if isinstance(tenants, str): + tenants = [tenants] params: Optional[Dict[str, Any]] = ( {"tenants": ",".join(tenants)} if tenants is not None else None ) @@ -874,7 +876,7 @@ def rebuild_property_index( property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> executor.Result[PropertyIndexStatus]: ... @@ -884,7 +886,7 @@ def rebuild_property_index( property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> executor.Result[PropertyIndexTask]: ... @@ -893,7 +895,7 @@ def rebuild_property_index( property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: """Rebuild an existing property index from scratch with its current configuration. @@ -901,8 +903,8 @@ def rebuild_property_index( Args: property_name: The property whose index to rebuild. index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. - tenants: The tenants for which to rebuild the index on a multi-tenant collection. - If not provided, all tenants are affected. + tenants: The tenant/list of tenants for which to rebuild the index on a multi-tenant + collection. If not provided, all tenants are affected. wait_for_completion: Whether to wait until the index reports `ready`. By default False. Returns: @@ -922,6 +924,8 @@ def rebuild_property_index( _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) path = self.__property_index_path(property_name, index_name) + "/rebuild" + if isinstance(tenants, str): + tenants = [tenants] params: Optional[Dict[str, Any]] = ( {"tenants": ",".join(tenants)} if tenants is not None else None ) diff --git a/weaviate/collections/config/sync.pyi b/weaviate/collections/config/sync.pyi index 87a87f625..6a25d9001 100644 --- a/weaviate/collections/config/sync.pyi +++ b/weaviate/collections/config/sync.pyi @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Optional, Sequence, Union, overload +from typing import Dict, List, Literal, Optional, Union, overload from typing_extensions import deprecated @@ -100,7 +100,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> PropertyIndexStatus: ... @overload @@ -111,7 +111,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... @overload @@ -120,7 +120,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], ) -> PropertyIndexStatus: ... @overload @@ -129,7 +129,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): property_name: str, index_name: IndexName, *, - tenants: Optional[Sequence[str]] = None, + tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... def cancel_property_index_task( From ce26136e71070f22e213aa988c50b92a071e8ee6 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:39:29 +0200 Subject: [PATCH 04/10] fix: validate reindex arguments before use (#2098 review) Validate the tenants argument of update_property_index and rebuild_property_index as str | List[str] | None before the csv join so invalid input raises WeaviateInvalidInputError instead of a raw TypeError, matching the library's _validate_input idiom already applied to property_name/index_name. Document the WeaviateInvalidInputError in the affected docstrings and cover the validation with mock tests. --- mock_tests/test_property_reindex.py | 15 +++++++++++++++ weaviate/collections/config/executor.py | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 225f72263..05d6f5e6f 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -393,6 +393,21 @@ def test_get_property_indexes( weaviate_139_mock.check_assertions() +def test_property_reindex_invalid_input( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """Invalid argument types raise WeaviateInvalidInputError before any request is sent.""" + config = client_139.collections.use(COLLECTION).config + + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + config.update_property_index("age", "rangeFilters", tenants=123) # type: ignore + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + config.rebuild_property_index("age", "rangeFilters", tenants=[1, 2]) # type: ignore + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + config.cancel_property_index_task(123, "searchable") # type: ignore + weaviate_139_mock.check_assertions() + + def test_property_reindex_unsupported_version( weaviate_client: weaviate.WeaviateClient, ) -> None: diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 2179e7b49..f56057e41 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -804,6 +804,7 @@ def update_property_index( of the index when `wait_for_completion=True`. Raises: + weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid. weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. weaviate.exceptions.ReindexFailedError: If `wait_for_completion=True` and the reindexing task failed. @@ -814,6 +815,9 @@ def update_property_index( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + _validate_input( + [_ValidateArgument(expected=[str, List[str], None], name="tenants", value=tenants)] + ) path = self.__property_index_path(property_name, index_name) body: Dict[str, Any] = {} @@ -912,6 +916,7 @@ def rebuild_property_index( of the index when `wait_for_completion=True`. Raises: + weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid. weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. weaviate.exceptions.ReindexFailedError: If `wait_for_completion=True` and the reindexing task failed. @@ -922,6 +927,9 @@ def rebuild_property_index( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + _validate_input( + [_ValidateArgument(expected=[str, List[str], None], name="tenants", value=tenants)] + ) path = self.__property_index_path(property_name, index_name) + "/rebuild" if isinstance(tenants, str): @@ -991,6 +999,7 @@ def cancel_property_index_task( A `PropertyIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. Raises: + weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid. weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. """ From b3bafa2e29bc842bd1f232ae042b7b8651b3fe0d Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:31:19 +0200 Subject: [PATCH 05/10] fix: neutral error message for property index deletion (#2098) The static delete_property_index error prefix asserted a single cause ("Property may not exist") but the DELETE 422 also covers an invalid index type and the in-flight-reindex mutation guard, whose server message the prefix contradicted. Name the failed operation instead and let the appended response body carry the cause, matching the file's phrasing style. Pin the behavior with a mock test surfacing a server-style 422 in-flight-reindex message. --- mock_tests/test_property_reindex.py | 23 +++++++++++++++++++++++ weaviate/collections/config/executor.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 05d6f5e6f..de38a6cbd 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -393,6 +393,29 @@ def test_get_property_indexes( weaviate_139_mock.check_assertions() +def test_delete_property_index_surfaces_server_message( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """A DELETE rejection surfaces the server's cause behind a neutral prefix. + + The 422 mutation guard (in-flight reindex task) carries an actionable server message; + the client prefix must only name the failed operation, not assert a cause. + """ + server_message = "cannot delete index: a reindex task is in progress for property 'name'" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/searchable", + method="DELETE", + ).respond_with_json({"error": [{"message": server_message}]}, status=422) + + with pytest.raises(weaviate.exceptions.UnexpectedStatusCodeError) as e: + client_139.collections.use(COLLECTION).config.delete_property_index("name", "searchable") + assert e.value.status_code == 422 + assert "Property index may not have been deleted." in e.value.message + assert server_message in e.value.message + assert "may not exist" not in e.value.message + weaviate_139_mock.check_assertions() + + def test_property_reindex_invalid_input( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient ) -> None: diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index f56057e41..a14b23688 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -704,8 +704,8 @@ def resp(res: Response) -> bool: response_callback=resp, method=self._connection.delete, path=path, - error_msg="Property may not exist", - status_codes=_ExpectedStatusCodes(ok_in=[200], error="property exists"), + error_msg="Property index may not have been deleted.", + status_codes=_ExpectedStatusCodes(ok_in=[200], error="Delete property index"), ) def __check_property_reindex_support(self, feature: str) -> None: From a64ec1507802a5d73cd880054f3591bb095d6032 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:42:25 +0200 Subject: [PATCH 06/10] feat: accept PropertyIndexType enum for index_name arguments (#2098) Adds a PropertyIndexType str-enum (SEARCHABLE, FILTERABLE, RANGE_FILTERS) accepted alongside the IndexName literals on update_property_index, rebuild_property_index, cancel_property_index_task and delete_property_index (backward- compatible widening of the v1.36 signature). The value is normalized to its wire form at the top of each method so paths, params and error messages never render the enum repr. Route-equality mock tests pin that the enum and literal forms hit identical routes, incl. RANGE_FILTERS -> rangeFilters. Read-side status types keep plain literals/str for forward compatibility. --- mock_tests/test_property_reindex.py | 64 ++++++++++++++++++++++++- weaviate/classes/config.py | 2 + weaviate/collections/classes/config.py | 14 ++++++ weaviate/collections/config/async_.pyi | 15 +++--- weaviate/collections/config/executor.py | 34 ++++++++----- weaviate/collections/config/sync.pyi | 15 +++--- 6 files changed, 120 insertions(+), 24 deletions(-) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index de38a6cbd..1dab01ff3 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -1,5 +1,5 @@ import json -from typing import Generator +from typing import Generator, Union import grpc import pytest @@ -11,6 +11,7 @@ from weaviate.collections.classes.config import ( PropertyIndexState, PropertyIndexTaskStatus, + PropertyIndexType, Tokenization, ) from weaviate.exceptions import ( @@ -393,6 +394,67 @@ def test_get_property_indexes( weaviate_139_mock.check_assertions() +@pytest.mark.parametrize( + "index_name,wire", + [ + (PropertyIndexType.SEARCHABLE, "searchable"), + ("searchable", "searchable"), + (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), + ("rangeFilters", "rangeFilters"), + ], +) +def test_update_property_index_enum_and_literal_hit_same_route( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_name: Union[PropertyIndexType, str], + wire: str, +) -> None: + """The enum and literal forms of index_name hit the exact same wire route.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/{wire}", + method="PUT", + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.update_property_index( + "name", + index_name, # type: ignore + ) + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +@pytest.mark.parametrize( + "index_name,wire", + [ + (PropertyIndexType.SEARCHABLE, "searchable"), + ("searchable", "searchable"), + (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), + ("rangeFilters", "rangeFilters"), + ], +) +def test_delete_property_index_enum_and_literal_hit_same_route( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_name: Union[PropertyIndexType, str], + wire: str, +) -> None: + """The enum and literal forms of index_name hit the exact same wire route.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/name/index/{wire}", + method="DELETE", + ).respond_with_json({}, status=200) + + assert ( + client_139.collections.use(COLLECTION).config.delete_property_index( + "name", + index_name, # type: ignore + ) + is True + ) + weaviate_139_mock.check_assertions() + + def test_delete_property_index_surfaces_server_message( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient ) -> None: diff --git a/weaviate/classes/config.py b/weaviate/classes/config.py index c154062d3..7fdd8c117 100644 --- a/weaviate/classes/config.py +++ b/weaviate/classes/config.py @@ -7,6 +7,7 @@ PQEncoderDistribution, PQEncoderType, Property, + PropertyIndexType, Reconfigure, ReferenceProperty, ReplicationDeletionStrategy, @@ -37,6 +38,7 @@ "MultiVectorAggregation", "ReplicationDeletionStrategy", "Property", + "PropertyIndexType", "PQEncoderDistribution", "PQEncoderType", "ReferenceProperty", diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 4e462962e..7aafa7bc4 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -117,6 +117,20 @@ ] +class PropertyIndexType(str, BaseEnum): + """The available property index types in Weaviate. + + Attributes: + SEARCHABLE: The searchable index, used for keyword (BM25) searches over text properties. + FILTERABLE: The filterable index, used for exact-match filtering. + RANGE_FILTERS: The rangeable index, used for range filtering. + """ + + SEARCHABLE = "searchable" + FILTERABLE = "filterable" + RANGE_FILTERS = "rangeFilters" + + class ConsistencyLevel(str, BaseEnum): """The consistency levels when writing to Weaviate with replication enabled. diff --git a/weaviate/collections/config/async_.pyi b/weaviate/collections/config/async_.pyi index eb2d40dfe..baee508e3 100644 --- a/weaviate/collections/config/async_.pyi +++ b/weaviate/collections/config/async_.pyi @@ -10,6 +10,7 @@ from weaviate.collections.classes.config import ( Property, PropertyIndexStatus, PropertyIndexTask, + PropertyIndexType, ReferenceProperty, ShardStatus, ShardTypes, @@ -93,12 +94,14 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def add_vector( self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... - async def delete_property_index(self, property_name: str, index_name: IndexName) -> bool: ... + async def delete_property_index( + self, property_name: str, index_name: Union[PropertyIndexType, IndexName] + ) -> bool: ... @overload async def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -109,7 +112,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -120,7 +123,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -129,12 +132,12 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... async def cancel_property_index_task( - self, property_name: str, index_name: IndexName + self, property_name: str, index_name: Union[PropertyIndexType, IndexName] ) -> PropertyIndexTask: ... async def get_property_indexes(self) -> CollectionPropertyIndexes: ... diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index a14b23688..910882def 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -27,6 +27,7 @@ PropertyIndexState, PropertyIndexStatus, PropertyIndexTask, + PropertyIndexType, PropertyType, ReferenceProperty, ShardStatus, @@ -670,7 +671,7 @@ async def _execute() -> None: def delete_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], ) -> executor.Result[bool]: """Delete a property index from the collection in Weaviate. @@ -686,6 +687,8 @@ def delete_property_index( weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. weaviate.exceptions.WeaviateInvalidInputError: If the property or index does not exist. """ + if isinstance(index_name, PropertyIndexType): + index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) @@ -750,7 +753,7 @@ async def _execute() -> PropertyIndexStatus: def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -762,7 +765,7 @@ def update_property_index( def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -773,7 +776,7 @@ def update_property_index( def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -789,7 +792,8 @@ def update_property_index( Args: property_name: The property whose index to create or migrate. - index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, a `PropertyIndexType` value or one of the literals + `searchable`, `filterable` or `rangeFilters`. tokenization: The tokenization of the index. Required when creating a `searchable` index; optional as a change on an existing `searchable` or `filterable` index. Not valid for `rangeFilters`. @@ -811,6 +815,8 @@ def update_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config update_property_index") + if isinstance(index_name, PropertyIndexType): + index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) @@ -878,7 +884,7 @@ async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -888,7 +894,7 @@ def rebuild_property_index( def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, @@ -897,7 +903,7 @@ def rebuild_property_index( def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, @@ -906,7 +912,8 @@ def rebuild_property_index( Args: property_name: The property whose index to rebuild. - index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, a `PropertyIndexType` value or one of the literals + `searchable`, `filterable` or `rangeFilters`. tenants: The tenant/list of tenants for which to rebuild the index on a multi-tenant collection. If not provided, all tenants are affected. wait_for_completion: Whether to wait until the index reports `ready`. By default False. @@ -923,6 +930,8 @@ def rebuild_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config rebuild_property_index") + if isinstance(index_name, PropertyIndexType): + index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) @@ -982,7 +991,7 @@ async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: def cancel_property_index_task( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], ) -> executor.Result[PropertyIndexTask]: """Cancel the live reindexing task of a property index. @@ -993,7 +1002,8 @@ def cancel_property_index_task( Args: property_name: The property whose reindexing task to cancel. - index_name: The type of the index, one of `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, a `PropertyIndexType` value or one of the literals + `searchable`, `filterable` or `rangeFilters`. Returns: A `PropertyIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. @@ -1004,6 +1014,8 @@ def cancel_property_index_task( weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. """ self.__check_property_reindex_support("Collection config cancel_property_index_task") + if isinstance(index_name, PropertyIndexType): + index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) diff --git a/weaviate/collections/config/sync.pyi b/weaviate/collections/config/sync.pyi index 6a25d9001..3b95aad2a 100644 --- a/weaviate/collections/config/sync.pyi +++ b/weaviate/collections/config/sync.pyi @@ -10,6 +10,7 @@ from weaviate.collections.classes.config import ( Property, PropertyIndexStatus, PropertyIndexTask, + PropertyIndexType, ReferenceProperty, ShardStatus, ShardTypes, @@ -91,12 +92,14 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def add_vector( self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... - def delete_property_index(self, property_name: str, index_name: IndexName) -> bool: ... + def delete_property_index( + self, property_name: str, index_name: Union[PropertyIndexType, IndexName] + ) -> bool: ... @overload def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -107,7 +110,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def update_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -118,7 +121,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -127,12 +130,12 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def rebuild_property_index( self, property_name: str, - index_name: IndexName, + index_name: Union[PropertyIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> PropertyIndexTask: ... def cancel_property_index_task( - self, property_name: str, index_name: IndexName + self, property_name: str, index_name: Union[PropertyIndexType, IndexName] ) -> PropertyIndexTask: ... def get_property_indexes(self) -> CollectionPropertyIndexes: ... From 0120c6546d197f40bd79fbb282e0f78640054ada Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:43:25 +0200 Subject: [PATCH 07/10] docs: state the coupled retokenization contract explicitly (#2098) Documents on update_property_index that a tokenization change via the searchable index also retokenizes an existing filterable index as one coupled task (shared taskId) and thereby changes filter matching, with filterable as the target for bucket-only changes and cancellation applying to the whole task. Explains why PropertyIndexes.data_type is a plain str (primitives match the DataType enum, references carry the target collection name) and pins reference-property parsing with a mock test. --- mock_tests/test_property_reindex.py | 38 +++++++++++++++++++++++++ weaviate/collections/classes/config.py | 3 ++ weaviate/collections/config/executor.py | 6 ++++ 3 files changed, 47 insertions(+) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 1dab01ff3..a5e9f4ece 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -9,6 +9,7 @@ import weaviate from mock_tests.conftest import MOCK_IP, MOCK_PORT, MOCK_PORT_GRPC from weaviate.collections.classes.config import ( + DataType, PropertyIndexState, PropertyIndexTaskStatus, PropertyIndexType, @@ -455,6 +456,43 @@ def test_delete_property_index_enum_and_literal_hit_same_route( weaviate_139_mock.check_assertions() +def test_get_property_indexes_reference_property( + weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient +) -> None: + """Reference properties carry the target collection name as dataType and still parse.""" + weaviate_139_mock.expect_request(f"{SCHEMA_PATH}/indexes", method="GET").respond_with_json( + { + "collection": COLLECTION, + "properties": [ + { + "name": "title", + "dataType": "text", + "indexes": [{"type": "searchable", "status": "ready", "tokenization": "word"}], + }, + { + "name": "ofArticle", + "dataType": "Article", + "indexes": [{"type": "filterable", "status": "ready"}], + }, + ], + } + ) + + indexes = client_139.collections.use(COLLECTION).config.get_property_indexes() + title, ref = indexes.properties + # primitive values match the DataType str-enum + assert title.data_type == DataType.TEXT + # reference properties carry the qualified target collection name instead + assert ref.name == "ofArticle" + assert ref.data_type == "Article" + assert ref.indexes[0].type == "filterable" + + out = json.loads(json.dumps(indexes.to_dict())) + assert out["properties"][1]["dataType"] == "Article" + + weaviate_139_mock.check_assertions() + + def test_delete_property_index_surfaces_server_message( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient ) -> None: diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 7aafa7bc4..6c9c8f0f6 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -2281,6 +2281,9 @@ class _PropertyIndexStatus(_ConfigBase): @dataclass class _PropertyIndexes(_ConfigBase): name: str + # For primitive properties the value matches the `DataType` str-enum (comparisons like + # `data_type == DataType.TEXT` work); reference properties instead carry the qualified + # target collection name (e.g. "Article"), which is why this is a plain str, not `DataType`. data_type: str description: Optional[str] indexes: List[PropertyIndexStatus] diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 910882def..13338d3fd 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -790,6 +790,12 @@ def update_property_index( configuration. If the configuration already matches, no work is submitted and the returned task reports a `NO_OP` status. The server accepts at most one configuration change per request. + Caution: changing `tokenization` via the `searchable` index ALSO retokenizes the property's + `filterable` index when one exists. Both indexes are migrated by a single coupled task (their + status entries share one `taskId`), and the retokenization changes how filters match on that + property. To retokenize only the filterable bucket, target the `filterable` index instead. + Cancelling via either index type cancels the whole coupled task. + Args: property_name: The property whose index to create or migrate. index_name: The type of the index, a `PropertyIndexType` value or one of the literals From e65fa0b26e0970dff0eea52c98774c6334def89a Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:52:56 +0200 Subject: [PATCH 08/10] fix: drop internal alias wording, extend route-equality coverage (#2098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RANGE_FILTERS docstring said "rangeable", the internal write-path alias the RFC deliberately keeps unsurfaced — say rangeFilters instead. Extends the route-equality parametrization with FILTERABLE and adds enum-vs-literal route cases for rebuild_property_index and cancel_property_index_task. --- mock_tests/test_property_reindex.py | 52 ++++++++++++++++++++++++++ weaviate/collections/classes/config.py | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index a5e9f4ece..8ebb373b6 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -400,6 +400,8 @@ def test_get_property_indexes( [ (PropertyIndexType.SEARCHABLE, "searchable"), ("searchable", "searchable"), + (PropertyIndexType.FILTERABLE, "filterable"), + ("filterable", "filterable"), (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), ("rangeFilters", "rangeFilters"), ], @@ -430,6 +432,8 @@ def test_update_property_index_enum_and_literal_hit_same_route( [ (PropertyIndexType.SEARCHABLE, "searchable"), ("searchable", "searchable"), + (PropertyIndexType.FILTERABLE, "filterable"), + ("filterable", "filterable"), (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), ("rangeFilters", "rangeFilters"), ], @@ -456,6 +460,54 @@ def test_delete_property_index_enum_and_literal_hit_same_route( weaviate_139_mock.check_assertions() +@pytest.mark.parametrize( + "index_name", + [PropertyIndexType.RANGE_FILTERS, "rangeFilters"], +) +def test_rebuild_property_index_enum_and_literal_hit_same_route( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_name: Union[PropertyIndexType, str], +) -> None: + """The enum and literal forms of index_name hit the exact same rebuild route.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/age/index/rangeFilters/rebuild", + method="POST", + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "STARTED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.rebuild_property_index( + "age", + index_name, # type: ignore + ) + assert task.status == PropertyIndexTaskStatus.STARTED + weaviate_139_mock.check_assertions() + + +@pytest.mark.parametrize( + "index_name", + [PropertyIndexType.RANGE_FILTERS, "rangeFilters"], +) +def test_cancel_property_index_task_enum_and_literal_hit_same_route( + weaviate_139_mock: HTTPServer, + client_139: weaviate.WeaviateClient, + index_name: Union[PropertyIndexType, str], +) -> None: + """The enum and literal forms of index_name hit the exact same cancel route.""" + weaviate_139_mock.expect_request( + f"{SCHEMA_PATH}/properties/age/index/rangeFilters/cancel", + method="POST", + json={}, + ).respond_with_json({"taskId": TASK_ID, "status": "CANCELLED"}, status=202) + + task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( + "age", + index_name, # type: ignore + ) + assert task.status == PropertyIndexTaskStatus.CANCELLED + weaviate_139_mock.check_assertions() + + def test_get_property_indexes_reference_property( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient ) -> None: diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 6c9c8f0f6..8ae165140 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -123,7 +123,7 @@ class PropertyIndexType(str, BaseEnum): Attributes: SEARCHABLE: The searchable index, used for keyword (BM25) searches over text properties. FILTERABLE: The filterable index, used for exact-match filtering. - RANGE_FILTERS: The rangeable index, used for range filtering. + RANGE_FILTERS: The rangeFilters index, used for range filtering. """ SEARCHABLE = "searchable" From d4a4ef48eb3c2cc5d0de401714ef86d7a3bf76f5 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:28:05 +0200 Subject: [PATCH 09/10] refactor: scope reindex type names to inverted indexes (#2098) Renames the unreleased public types PropertyIndexType/State/Status/ Task/TaskStatus to InvertedIndexType/State/Status/Task/TaskStatus and PropertyIndexes/CollectionPropertyIndexes to PropertyInvertedIndexes/ CollectionInvertedIndexes, incl. private counterparts and parser names. The runtime reindex API only ever touches inverted indexes; a future vector reindex must not collide with these names. Method names, the released IndexName alias and the deliberately generic Reindex*Error exceptions are unchanged. --- integration/test_collection_config.py | 36 +++--- mock_tests/test_property_reindex.py | 58 +++++----- weaviate/classes/config.py | 4 +- weaviate/collections/classes/config.py | 30 ++--- .../collections/classes/config_methods.py | 32 +++--- weaviate/collections/config/async_.pyi | 32 +++--- weaviate/collections/config/executor.py | 106 +++++++++--------- weaviate/collections/config/sync.pyi | 32 +++--- weaviate/outputs/config.py | 24 ++-- 9 files changed, 177 insertions(+), 177 deletions(-) diff --git a/integration/test_collection_config.py b/integration/test_collection_config.py index fc9bcb4cf..7764b7c38 100644 --- a/integration/test_collection_config.py +++ b/integration/test_collection_config.py @@ -41,8 +41,8 @@ _NamedVectorConfigCreate, _VectorizerConfigCreate, IndexName, - PropertyIndexState, - PropertyIndexTaskStatus, + InvertedIndexState, + InvertedIndexTaskStatus, ) from weaviate.collections.classes.tenants import Tenant from weaviate.exceptions import ( @@ -2706,14 +2706,14 @@ def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFac "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True ) assert status.type == "searchable" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY assert status.tokenization == Tokenization.WORD # re-putting the matching configuration is a no-op task = collection.config.update_property_index( "name", "searchable", tokenization=Tokenization.WORD ) - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP assert task.task_id is None # the status endpoint reports the index as ready @@ -2726,18 +2726,18 @@ def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFac for index in prop.indexes if index.type == "searchable" ) - assert entry.status == PropertyIndexState.READY + assert entry.status == InvertedIndexState.READY # rebuild the index from scratch status = collection.config.rebuild_property_index( "name", "searchable", wait_for_completion=True ) assert status.type == "searchable" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY # cancelling when no task is live is an idempotent no-op task = collection.config.cancel_property_index_task("name", "searchable") - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP # the pre-existing delete API removes the index again assert collection.config.delete_property_index("name", "searchable") is True @@ -2765,7 +2765,7 @@ def test_property_reindex_range_filters(collection_factory: CollectionFactory) - "age", "rangeFilters", wait_for_completion=True ) assert status.type == "rangeFilters" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY entry = next( index @@ -2774,7 +2774,7 @@ def test_property_reindex_range_filters(collection_factory: CollectionFactory) - for index in prop.indexes if index.type == "rangeFilters" ) - assert entry.status == PropertyIndexState.READY + assert entry.status == InvertedIndexState.READY def test_property_reindex_coupled_tokenization_change( @@ -2801,7 +2801,7 @@ def test_property_reindex_coupled_tokenization_change( task = collection.config.update_property_index( "name", "searchable", tokenization=Tokenization.FIELD ) - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED assert task.task_id is not None prop = next(p for p in collection.config.get_property_indexes().properties if p.name == "name") @@ -2821,12 +2821,12 @@ def test_property_reindex_coupled_tokenization_change( status = collection.config.update_property_index( "name", "searchable", tokenization=Tokenization.FIELD, wait_for_completion=True ) - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY assert status.tokenization == Tokenization.FIELD prop = next(p for p in collection.config.get_property_indexes().properties if p.name == "name") filterable = next(i for i in prop.indexes if i.type == "filterable") - assert filterable.status == PropertyIndexState.READY + assert filterable.status == InvertedIndexState.READY assert filterable.tokenization == Tokenization.FIELD @@ -2854,13 +2854,13 @@ def test_property_reindex_multi_tenant(collection_factory: CollectionFactory) -> "age", "rangeFilters", tenants=["tenant1", "tenant2"], wait_for_completion=True ) assert status.type == "rangeFilters" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY status = collection.config.rebuild_property_index( "age", "rangeFilters", tenants=["tenant1"], wait_for_completion=True ) assert status.type == "rangeFilters" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY @pytest.mark.asyncio @@ -2885,12 +2885,12 @@ async def test_property_reindex_async(async_collection_factory: AsyncCollectionF "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True ) assert status.type == "searchable" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY task = await collection.config.update_property_index( "name", "searchable", tokenization=Tokenization.WORD ) - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP indexes = await collection.config.get_property_indexes() assert indexes.collection == collection.name @@ -2898,7 +2898,7 @@ async def test_property_reindex_async(async_collection_factory: AsyncCollectionF status = await collection.config.rebuild_property_index( "name", "searchable", wait_for_completion=True ) - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY task = await collection.config.cancel_property_index_task("name", "searchable") - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 8ebb373b6..2a21c8128 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -10,9 +10,9 @@ from mock_tests.conftest import MOCK_IP, MOCK_PORT, MOCK_PORT_GRPC from weaviate.collections.classes.config import ( DataType, - PropertyIndexState, - PropertyIndexTaskStatus, - PropertyIndexType, + InvertedIndexState, + InvertedIndexTaskStatus, + InvertedIndexType, Tokenization, ) from weaviate.exceptions import ( @@ -64,7 +64,7 @@ def test_update_property_index_started( algorithm="blockmax", ) assert task.task_id == TASK_ID - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @@ -81,7 +81,7 @@ def test_update_property_index_no_op( "name", "searchable", tokenization=Tokenization.WORD ) assert task.task_id is None - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP weaviate_139_mock.check_assertions() @@ -100,7 +100,7 @@ def test_update_property_index_range_filters_with_tenants( "age", "rangeFilters", tenants=["tenant1", "tenant2"] ) assert task.task_id == TASK_ID - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @@ -129,7 +129,7 @@ def test_update_property_index_wait_for_completion( "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True ) assert status.type == "searchable" - assert status.status == PropertyIndexState.READY + assert status.status == InvertedIndexState.READY assert status.tokenization == Tokenization.WORD weaviate_139_mock.check_assertions() @@ -148,7 +148,7 @@ def test_update_property_index_bare_str_tenant( task = client_139.collections.use(COLLECTION).config.update_property_index( "age", "rangeFilters", tenants="tenant1" ) - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @@ -251,7 +251,7 @@ def test_rebuild_property_index( "name", "searchable" ) assert task.task_id == TASK_ID - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @@ -269,7 +269,7 @@ def test_rebuild_property_index_with_tenants( "age", "rangeFilters", tenants=["tenant1", "tenant2"] ) assert task.task_id == TASK_ID - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @@ -286,7 +286,7 @@ def test_cancel_property_index_task_cancelled( "name", "searchable" ) assert task.task_id == TASK_ID - assert task.status == PropertyIndexTaskStatus.CANCELLED + assert task.status == InvertedIndexTaskStatus.CANCELLED weaviate_139_mock.check_assertions() @@ -303,7 +303,7 @@ def test_cancel_property_index_task_no_op( "name", "searchable" ) assert task.task_id is None - assert task.status == PropertyIndexTaskStatus.NO_OP + assert task.status == InvertedIndexTaskStatus.NO_OP weaviate_139_mock.check_assertions() @@ -360,7 +360,7 @@ def test_get_property_indexes( assert len(name.indexes) == 2 searchable, filterable = name.indexes assert searchable.type == "searchable" - assert searchable.status == PropertyIndexState.INDEXING + assert searchable.status == InvertedIndexState.INDEXING assert searchable.progress == 0.5 assert searchable.task_id == TASK_ID assert searchable.tokenization == Tokenization.WORD @@ -379,7 +379,7 @@ def test_get_property_indexes( assert age.description is None assert len(age.indexes) == 1 assert age.indexes[0].type == "rangeFilters" - assert age.indexes[0].status == PropertyIndexState.READY + assert age.indexes[0].status == InvertedIndexState.READY assert age.indexes[0].progress is None assert age.indexes[0].task_id is None assert age.indexes[0].tokenization is None @@ -398,18 +398,18 @@ def test_get_property_indexes( @pytest.mark.parametrize( "index_name,wire", [ - (PropertyIndexType.SEARCHABLE, "searchable"), + (InvertedIndexType.SEARCHABLE, "searchable"), ("searchable", "searchable"), - (PropertyIndexType.FILTERABLE, "filterable"), + (InvertedIndexType.FILTERABLE, "filterable"), ("filterable", "filterable"), - (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), + (InvertedIndexType.RANGE_FILTERS, "rangeFilters"), ("rangeFilters", "rangeFilters"), ], ) def test_update_property_index_enum_and_literal_hit_same_route( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient, - index_name: Union[PropertyIndexType, str], + index_name: Union[InvertedIndexType, str], wire: str, ) -> None: """The enum and literal forms of index_name hit the exact same wire route.""" @@ -423,25 +423,25 @@ def test_update_property_index_enum_and_literal_hit_same_route( "name", index_name, # type: ignore ) - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @pytest.mark.parametrize( "index_name,wire", [ - (PropertyIndexType.SEARCHABLE, "searchable"), + (InvertedIndexType.SEARCHABLE, "searchable"), ("searchable", "searchable"), - (PropertyIndexType.FILTERABLE, "filterable"), + (InvertedIndexType.FILTERABLE, "filterable"), ("filterable", "filterable"), - (PropertyIndexType.RANGE_FILTERS, "rangeFilters"), + (InvertedIndexType.RANGE_FILTERS, "rangeFilters"), ("rangeFilters", "rangeFilters"), ], ) def test_delete_property_index_enum_and_literal_hit_same_route( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient, - index_name: Union[PropertyIndexType, str], + index_name: Union[InvertedIndexType, str], wire: str, ) -> None: """The enum and literal forms of index_name hit the exact same wire route.""" @@ -462,12 +462,12 @@ def test_delete_property_index_enum_and_literal_hit_same_route( @pytest.mark.parametrize( "index_name", - [PropertyIndexType.RANGE_FILTERS, "rangeFilters"], + [InvertedIndexType.RANGE_FILTERS, "rangeFilters"], ) def test_rebuild_property_index_enum_and_literal_hit_same_route( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient, - index_name: Union[PropertyIndexType, str], + index_name: Union[InvertedIndexType, str], ) -> None: """The enum and literal forms of index_name hit the exact same rebuild route.""" weaviate_139_mock.expect_request( @@ -480,18 +480,18 @@ def test_rebuild_property_index_enum_and_literal_hit_same_route( "age", index_name, # type: ignore ) - assert task.status == PropertyIndexTaskStatus.STARTED + assert task.status == InvertedIndexTaskStatus.STARTED weaviate_139_mock.check_assertions() @pytest.mark.parametrize( "index_name", - [PropertyIndexType.RANGE_FILTERS, "rangeFilters"], + [InvertedIndexType.RANGE_FILTERS, "rangeFilters"], ) def test_cancel_property_index_task_enum_and_literal_hit_same_route( weaviate_139_mock: HTTPServer, client_139: weaviate.WeaviateClient, - index_name: Union[PropertyIndexType, str], + index_name: Union[InvertedIndexType, str], ) -> None: """The enum and literal forms of index_name hit the exact same cancel route.""" weaviate_139_mock.expect_request( @@ -504,7 +504,7 @@ def test_cancel_property_index_task_enum_and_literal_hit_same_route( "age", index_name, # type: ignore ) - assert task.status == PropertyIndexTaskStatus.CANCELLED + assert task.status == InvertedIndexTaskStatus.CANCELLED weaviate_139_mock.check_assertions() diff --git a/weaviate/classes/config.py b/weaviate/classes/config.py index 7fdd8c117..a806aa04a 100644 --- a/weaviate/classes/config.py +++ b/weaviate/classes/config.py @@ -4,10 +4,10 @@ DataType, GenerativeSearches, IndexName, + InvertedIndexType, PQEncoderDistribution, PQEncoderType, Property, - PropertyIndexType, Reconfigure, ReferenceProperty, ReplicationDeletionStrategy, @@ -38,7 +38,7 @@ "MultiVectorAggregation", "ReplicationDeletionStrategy", "Property", - "PropertyIndexType", + "InvertedIndexType", "PQEncoderDistribution", "PQEncoderType", "ReferenceProperty", diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 8ae165140..9026dd693 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -117,7 +117,7 @@ ] -class PropertyIndexType(str, BaseEnum): +class InvertedIndexType(str, BaseEnum): """The available property index types in Weaviate. Attributes: @@ -2221,7 +2221,7 @@ class _ShardStatus: ShardStatus = _ShardStatus -class PropertyIndexTaskStatus(str, BaseEnum): +class InvertedIndexTaskStatus(str, BaseEnum): """The status of a runtime property index task submission. Attributes: @@ -2236,7 +2236,7 @@ class PropertyIndexTaskStatus(str, BaseEnum): NO_OP = "NO_OP" -class PropertyIndexState(str, BaseEnum): +class InvertedIndexState(str, BaseEnum): """The state of a property index as reported by the index status endpoint. Attributes: @@ -2255,18 +2255,18 @@ class PropertyIndexState(str, BaseEnum): @dataclass -class _PropertyIndexTask(_ConfigBase): +class _InvertedIndexTask(_ConfigBase): task_id: Optional[str] - status: PropertyIndexTaskStatus + status: InvertedIndexTaskStatus -PropertyIndexTask = _PropertyIndexTask +InvertedIndexTask = _InvertedIndexTask @dataclass -class _PropertyIndexStatus(_ConfigBase): +class _InvertedIndexStatus(_ConfigBase): type: IndexName # noqa: A003 - status: PropertyIndexState + status: InvertedIndexState progress: Optional[float] task_id: Optional[str] tokenization: Optional[Tokenization] @@ -2275,18 +2275,18 @@ class _PropertyIndexStatus(_ConfigBase): target_algorithm: Optional[str] -PropertyIndexStatus = _PropertyIndexStatus +InvertedIndexStatus = _InvertedIndexStatus @dataclass -class _PropertyIndexes(_ConfigBase): +class _PropertyInvertedIndexes(_ConfigBase): name: str # For primitive properties the value matches the `DataType` str-enum (comparisons like # `data_type == DataType.TEXT` work); reference properties instead carry the qualified # target collection name (e.g. "Article"), which is why this is a plain str, not `DataType`. data_type: str description: Optional[str] - indexes: List[PropertyIndexStatus] + indexes: List[InvertedIndexStatus] def to_dict(self) -> Dict[str, Any]: out = super().to_dict() @@ -2294,13 +2294,13 @@ def to_dict(self) -> Dict[str, Any]: return out -PropertyIndexes = _PropertyIndexes +PropertyInvertedIndexes = _PropertyInvertedIndexes @dataclass -class _CollectionPropertyIndexes(_ConfigBase): +class _CollectionInvertedIndexes(_ConfigBase): collection: str - properties: List[PropertyIndexes] + properties: List[PropertyInvertedIndexes] def to_dict(self) -> Dict[str, Any]: out = super().to_dict() @@ -2308,7 +2308,7 @@ def to_dict(self) -> Dict[str, Any]: return out -CollectionPropertyIndexes = _CollectionPropertyIndexes +CollectionInvertedIndexes = _CollectionInvertedIndexes class _TextAnalyzerConfigCreate(_ConfigCreateModel): diff --git a/weaviate/collections/classes/config_methods.py b/weaviate/collections/classes/config_methods.py index 4ad29beb4..dbb7762d2 100644 --- a/weaviate/collections/classes/config_methods.py +++ b/weaviate/collections/classes/config_methods.py @@ -5,10 +5,10 @@ DataType, GenerativeSearches, IndexName, + InvertedIndexState, + InvertedIndexTaskStatus, PQEncoderDistribution, PQEncoderType, - PropertyIndexState, - PropertyIndexTaskStatus, ReplicationDeletionStrategy, Rerankers, StopwordsPreset, @@ -22,9 +22,11 @@ _BQConfig, _CollectionConfig, _CollectionConfigSimple, - _CollectionPropertyIndexes, + _CollectionInvertedIndexes, _GenerativeConfig, _InvertedIndexConfig, + _InvertedIndexStatus, + _InvertedIndexTask, _MultiTenancyConfig, _MultiVectorConfig, _MuveraConfig, @@ -35,9 +37,7 @@ _PQConfig, _PQEncoderConfig, _Property, - _PropertyIndexes, - _PropertyIndexStatus, - _PropertyIndexTask, + _PropertyInvertedIndexes, _PropertyVectorizerConfig, _ReferenceProperty, _ReplicationConfig, @@ -567,19 +567,19 @@ def _references_from_config(schema: Dict[str, Any]) -> List[_ReferenceProperty]: ] -def _property_index_task_from_json(response: Dict[str, Any]) -> _PropertyIndexTask: - return _PropertyIndexTask( +def _inverted_index_task_from_json(response: Dict[str, Any]) -> _InvertedIndexTask: + return _InvertedIndexTask( task_id=response.get("taskId"), - status=PropertyIndexTaskStatus(response["status"]), + status=InvertedIndexTaskStatus(response["status"]), ) -def _property_index_status_from_json(index: Dict[str, Any]) -> _PropertyIndexStatus: +def _inverted_index_status_from_json(index: Dict[str, Any]) -> _InvertedIndexStatus: tokenization = index.get("tokenization") target_tokenization = index.get("targetTokenization") - return _PropertyIndexStatus( + return _InvertedIndexStatus( type=cast(IndexName, index["type"]), - status=PropertyIndexState(index["status"]), + status=InvertedIndexState(index["status"]), progress=index.get("progress"), task_id=index.get("taskId"), tokenization=Tokenization(tokenization) if tokenization is not None else None, @@ -591,16 +591,16 @@ def _property_index_status_from_json(index: Dict[str, Any]) -> _PropertyIndexSta ) -def _collection_property_indexes_from_json(response: Dict[str, Any]) -> _CollectionPropertyIndexes: - return _CollectionPropertyIndexes( +def _collection_inverted_indexes_from_json(response: Dict[str, Any]) -> _CollectionInvertedIndexes: + return _CollectionInvertedIndexes( collection=response["collection"], properties=[ - _PropertyIndexes( + _PropertyInvertedIndexes( name=prop["name"], data_type=prop["dataType"], description=prop.get("description"), indexes=[ - _property_index_status_from_json(index) for index in prop.get("indexes") or [] + _inverted_index_status_from_json(index) for index in prop.get("indexes") or [] ], ) for prop in response.get("properties") or [] diff --git a/weaviate/collections/config/async_.pyi b/weaviate/collections/config/async_.pyi index baee508e3..7041b4e24 100644 --- a/weaviate/collections/config/async_.pyi +++ b/weaviate/collections/config/async_.pyi @@ -5,12 +5,12 @@ from typing_extensions import deprecated from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, - CollectionPropertyIndexes, + CollectionInvertedIndexes, IndexName, + InvertedIndexStatus, + InvertedIndexTask, + InvertedIndexType, Property, - PropertyIndexStatus, - PropertyIndexTask, - PropertyIndexType, ReferenceProperty, ShardStatus, ShardTypes, @@ -95,49 +95,49 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... async def delete_property_index( - self, property_name: str, index_name: Union[PropertyIndexType, IndexName] + self, property_name: str, index_name: Union[InvertedIndexType, IndexName] ) -> bool: ... @overload async def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> PropertyIndexStatus: ... + ) -> InvertedIndexStatus: ... @overload async def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> PropertyIndexTask: ... + ) -> InvertedIndexTask: ... @overload async def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> PropertyIndexStatus: ... + ) -> InvertedIndexStatus: ... @overload async def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> PropertyIndexTask: ... + ) -> InvertedIndexTask: ... async def cancel_property_index_task( - self, property_name: str, index_name: Union[PropertyIndexType, IndexName] - ) -> PropertyIndexTask: ... - async def get_property_indexes(self) -> CollectionPropertyIndexes: ... + self, property_name: str, index_name: Union[InvertedIndexType, IndexName] + ) -> InvertedIndexTask: ... + async def get_property_indexes(self) -> CollectionInvertedIndexes: ... diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 13338d3fd..0b855a22e 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -21,13 +21,13 @@ from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, - CollectionPropertyIndexes, + CollectionInvertedIndexes, IndexName, + InvertedIndexState, + InvertedIndexStatus, + InvertedIndexTask, + InvertedIndexType, Property, - PropertyIndexState, - PropertyIndexStatus, - PropertyIndexTask, - PropertyIndexType, PropertyType, ReferenceProperty, ShardStatus, @@ -52,8 +52,8 @@ from weaviate.collections.classes.config_methods import ( _collection_config_from_json, _collection_config_simple_from_json, - _collection_property_indexes_from_json, - _property_index_task_from_json, + _collection_inverted_indexes_from_json, + _inverted_index_task_from_json, ) from weaviate.collections.classes.config_object_ttl import _ObjectTTLConfigUpdate from weaviate.collections.classes.config_vector_index import ( @@ -91,8 +91,8 @@ def _property_has_text_analyzer(prop: Property) -> bool: def _find_property_index_status( - indexes: CollectionPropertyIndexes, property_name: str, index_name: IndexName -) -> Optional[PropertyIndexStatus]: + indexes: CollectionInvertedIndexes, property_name: str, index_name: IndexName +) -> Optional[InvertedIndexStatus]: for prop in indexes.properties: if prop.name != property_name: continue @@ -103,18 +103,18 @@ def _find_property_index_status( def _terminal_property_index_status( - entry: Optional[PropertyIndexStatus], property_name: str, index_name: IndexName -) -> Optional[PropertyIndexStatus]: + entry: Optional[InvertedIndexStatus], property_name: str, index_name: IndexName +) -> Optional[InvertedIndexStatus]: """Return the entry once it is ready, raise on failure/cancellation, or return None to keep polling.""" if entry is None: return None - if entry.status == PropertyIndexState.READY: + if entry.status == InvertedIndexState.READY: return entry - if entry.status == PropertyIndexState.FAILED: + if entry.status == InvertedIndexState.FAILED: raise ReindexFailedError( f"Reindexing the '{index_name}' index of property '{property_name}' failed." ) - if entry.status == PropertyIndexState.CANCELLED: + if entry.status == InvertedIndexState.CANCELLED: raise ReindexCanceledError( f"Reindexing the '{index_name}' index of property '{property_name}' was cancelled." ) @@ -671,7 +671,7 @@ async def _execute() -> None: def delete_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], ) -> executor.Result[bool]: """Delete a property index from the collection in Weaviate. @@ -687,7 +687,7 @@ def delete_property_index( weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. weaviate.exceptions.WeaviateInvalidInputError: If the property or index does not exist. """ - if isinstance(index_name, PropertyIndexType): + if isinstance(index_name, InvertedIndexType): index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] @@ -728,10 +728,10 @@ def __property_index_path(self, property_name: str, index_name: IndexName) -> st def __wait_for_property_index( self, property_name: str, index_name: IndexName - ) -> executor.Result[PropertyIndexStatus]: + ) -> executor.Result[InvertedIndexStatus]: if isinstance(self._connection, ConnectionAsync): - async def _execute() -> PropertyIndexStatus: + async def _execute() -> InvertedIndexStatus: while True: indexes = await executor.aresult(self.get_property_indexes()) entry = _find_property_index_status(indexes, property_name, index_name) @@ -753,36 +753,36 @@ async def _execute() -> PropertyIndexStatus: def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> executor.Result[PropertyIndexStatus]: ... + ) -> executor.Result[InvertedIndexStatus]: ... @overload def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> executor.Result[PropertyIndexTask]: ... + ) -> executor.Result[InvertedIndexTask]: ... def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, - ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: + ) -> executor.Result[Union[InvertedIndexTask, InvertedIndexStatus]]: """Create or migrate a property index in this collection. Note: This method is a declarative upsert operation. If the index does not exist, it is created @@ -798,7 +798,7 @@ def update_property_index( Args: property_name: The property whose index to create or migrate. - index_name: The type of the index, a `PropertyIndexType` value or one of the literals + index_name: The type of the index, a `InvertedIndexType` value or one of the literals `searchable`, `filterable` or `rangeFilters`. tokenization: The tokenization of the index. Required when creating a `searchable` index; optional as a change on an existing `searchable` or `filterable` index. Not valid for @@ -810,7 +810,7 @@ def update_property_index( wait_for_completion: Whether to wait until the index reports `ready`. By default False. Returns: - A `PropertyIndexTask` when `wait_for_completion=False`, or the final `PropertyIndexStatus` + A `InvertedIndexTask` when `wait_for_completion=False`, or the final `InvertedIndexStatus` of the index when `wait_for_completion=True`. Raises: @@ -821,7 +821,7 @@ def update_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config update_property_index") - if isinstance(index_name, PropertyIndexType): + if isinstance(index_name, InvertedIndexType): index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] @@ -845,14 +845,14 @@ def update_property_index( {"tenants": ",".join(tenants)} if tenants is not None else None ) - def resp(res: Response) -> PropertyIndexTask: + def resp(res: Response) -> InvertedIndexTask: response = _decode_json_response_dict(res, "Update property index") assert response is not None - return _property_index_task_from_json(response) + return _inverted_index_task_from_json(response) if isinstance(self._connection, ConnectionAsync): - async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: + async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: res = await executor.aresult( self._connection.put( path=path, @@ -890,42 +890,42 @@ async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> executor.Result[PropertyIndexStatus]: ... + ) -> executor.Result[InvertedIndexStatus]: ... @overload def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> executor.Result[PropertyIndexTask]: ... + ) -> executor.Result[InvertedIndexTask]: ... def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, - ) -> executor.Result[Union[PropertyIndexTask, PropertyIndexStatus]]: + ) -> executor.Result[Union[InvertedIndexTask, InvertedIndexStatus]]: """Rebuild an existing property index from scratch with its current configuration. Args: property_name: The property whose index to rebuild. - index_name: The type of the index, a `PropertyIndexType` value or one of the literals + index_name: The type of the index, a `InvertedIndexType` value or one of the literals `searchable`, `filterable` or `rangeFilters`. tenants: The tenant/list of tenants for which to rebuild the index on a multi-tenant collection. If not provided, all tenants are affected. wait_for_completion: Whether to wait until the index reports `ready`. By default False. Returns: - A `PropertyIndexTask` when `wait_for_completion=False`, or the final `PropertyIndexStatus` + A `InvertedIndexTask` when `wait_for_completion=False`, or the final `InvertedIndexStatus` of the index when `wait_for_completion=True`. Raises: @@ -936,7 +936,7 @@ def rebuild_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config rebuild_property_index") - if isinstance(index_name, PropertyIndexType): + if isinstance(index_name, InvertedIndexType): index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] @@ -953,14 +953,14 @@ def rebuild_property_index( {"tenants": ",".join(tenants)} if tenants is not None else None ) - def resp(res: Response) -> PropertyIndexTask: + def resp(res: Response) -> InvertedIndexTask: response = _decode_json_response_dict(res, "Rebuild property index") assert response is not None - return _property_index_task_from_json(response) + return _inverted_index_task_from_json(response) if isinstance(self._connection, ConnectionAsync): - async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: + async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: res = await executor.aresult( self._connection.post( path=path, @@ -997,8 +997,8 @@ async def _execute() -> Union[PropertyIndexTask, PropertyIndexStatus]: def cancel_property_index_task( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], - ) -> executor.Result[PropertyIndexTask]: + index_name: Union[InvertedIndexType, IndexName], + ) -> executor.Result[InvertedIndexTask]: """Cancel the live reindexing task of a property index. This operation is idempotent: if there is no live task for the index, the returned task @@ -1008,11 +1008,11 @@ def cancel_property_index_task( Args: property_name: The property whose reindexing task to cancel. - index_name: The type of the index, a `PropertyIndexType` value or one of the literals + index_name: The type of the index, a `InvertedIndexType` value or one of the literals `searchable`, `filterable` or `rangeFilters`. Returns: - A `PropertyIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. + A `InvertedIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. Raises: weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid. @@ -1020,7 +1020,7 @@ def cancel_property_index_task( weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. """ self.__check_property_reindex_support("Collection config cancel_property_index_task") - if isinstance(index_name, PropertyIndexType): + if isinstance(index_name, InvertedIndexType): index_name = cast(IndexName, index_name.value) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] @@ -1029,10 +1029,10 @@ def cancel_property_index_task( path = self.__property_index_path(property_name, index_name) + "/cancel" - def resp(res: Response) -> PropertyIndexTask: + def resp(res: Response) -> InvertedIndexTask: response = _decode_json_response_dict(res, "Cancel property index task") assert response is not None - return _property_index_task_from_json(response) + return _inverted_index_task_from_json(response) return executor.execute( response_callback=resp, @@ -1043,7 +1043,7 @@ def resp(res: Response) -> PropertyIndexTask: status_codes=_ExpectedStatusCodes(ok_in=[202], error="Cancel property index task"), ) - def get_property_indexes(self) -> executor.Result[CollectionPropertyIndexes]: + def get_property_indexes(self) -> executor.Result[CollectionInvertedIndexes]: """Get the statuses of the property indexes of this collection. The response includes the state of any in-flight reindexing tasks, e.g. their progress and @@ -1051,7 +1051,7 @@ def get_property_indexes(self) -> executor.Result[CollectionPropertyIndexes]: reindexing task. Returns: - A `CollectionPropertyIndexes` object containing the index statuses grouped by property. + A `CollectionInvertedIndexes` object containing the index statuses grouped by property. Raises: weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. @@ -1059,10 +1059,10 @@ def get_property_indexes(self) -> executor.Result[CollectionPropertyIndexes]: """ self.__check_property_reindex_support("Collection config get_property_indexes") - def resp(res: Response) -> CollectionPropertyIndexes: + def resp(res: Response) -> CollectionInvertedIndexes: response = _decode_json_response_dict(res, "Get property indexes") assert response is not None - return _collection_property_indexes_from_json(response) + return _collection_inverted_indexes_from_json(response) return executor.execute( response_callback=resp, diff --git a/weaviate/collections/config/sync.pyi b/weaviate/collections/config/sync.pyi index 3b95aad2a..869d51f78 100644 --- a/weaviate/collections/config/sync.pyi +++ b/weaviate/collections/config/sync.pyi @@ -5,12 +5,12 @@ from typing_extensions import deprecated from weaviate.collections.classes.config import ( CollectionConfig, CollectionConfigSimple, - CollectionPropertyIndexes, + CollectionInvertedIndexes, IndexName, + InvertedIndexStatus, + InvertedIndexTask, + InvertedIndexType, Property, - PropertyIndexStatus, - PropertyIndexTask, - PropertyIndexType, ReferenceProperty, ShardStatus, ShardTypes, @@ -93,49 +93,49 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]] ) -> None: ... def delete_property_index( - self, property_name: str, index_name: Union[PropertyIndexType, IndexName] + self, property_name: str, index_name: Union[InvertedIndexType, IndexName] ) -> bool: ... @overload def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> PropertyIndexStatus: ... + ) -> InvertedIndexStatus: ... @overload def update_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> PropertyIndexTask: ... + ) -> InvertedIndexTask: ... @overload def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], - ) -> PropertyIndexStatus: ... + ) -> InvertedIndexStatus: ... @overload def rebuild_property_index( self, property_name: str, - index_name: Union[PropertyIndexType, IndexName], + index_name: Union[InvertedIndexType, IndexName], *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, - ) -> PropertyIndexTask: ... + ) -> InvertedIndexTask: ... def cancel_property_index_task( - self, property_name: str, index_name: Union[PropertyIndexType, IndexName] - ) -> PropertyIndexTask: ... - def get_property_indexes(self) -> CollectionPropertyIndexes: ... + self, property_name: str, index_name: Union[InvertedIndexType, IndexName] + ) -> InvertedIndexTask: ... + def get_property_indexes(self) -> CollectionInvertedIndexes: ... diff --git a/weaviate/outputs/config.py b/weaviate/outputs/config.py index b708d0a63..7f156ce50 100644 --- a/weaviate/outputs/config.py +++ b/weaviate/outputs/config.py @@ -3,21 +3,21 @@ BM25Config, CollectionConfig, CollectionConfigSimple, - CollectionPropertyIndexes, + CollectionInvertedIndexes, GenerativeConfig, GenerativeSearches, InvertedIndexConfig, + InvertedIndexState, + InvertedIndexStatus, + InvertedIndexTask, + InvertedIndexTaskStatus, MultiTenancyConfig, PQConfig, PQEncoderConfig, PQEncoderDistribution, PQEncoderType, PropertyConfig, - PropertyIndexes, - PropertyIndexState, - PropertyIndexStatus, - PropertyIndexTask, - PropertyIndexTaskStatus, + PropertyInvertedIndexes, PropertyType, ReferencePropertyConfig, ReplicationConfig, @@ -41,7 +41,7 @@ "BM25Config", "CollectionConfig", "CollectionConfigSimple", - "CollectionPropertyIndexes", + "CollectionInvertedIndexes", "GenerativeConfig", "GenerativeSearches", "InvertedIndexConfig", @@ -52,11 +52,11 @@ "PQEncoderDistribution", "PQEncoderType", "PropertyConfig", - "PropertyIndexes", - "PropertyIndexState", - "PropertyIndexStatus", - "PropertyIndexTask", - "PropertyIndexTaskStatus", + "PropertyInvertedIndexes", + "InvertedIndexState", + "InvertedIndexStatus", + "InvertedIndexTask", + "InvertedIndexTaskStatus", "PropertyType", "ReferencePropertyConfig", "ReplicationConfig", From fbe4a6320790eef8e028dd11e602bfd6a392cac6 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:42:01 +0200 Subject: [PATCH 10/10] refactor: require InvertedIndexType on the new reindex methods (#2098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tightens index_name to InvertedIndexType only on update_property_index, rebuild_property_index and cancel_property_index_task (all overloads and impls); delete_property_index keeps accepting the IndexName literals as released v1.36 API. Runtime leniency is preserved — the value is still normalized and validated as a str, so raw strings keep working and keep hitting the same routes (pinned by the literal legs of the route-equality mock tests, which sit outside the pyright scope). --- integration/test_collection_config.py | 39 ++++++++++----- mock_tests/test_property_reindex.py | 4 ++ weaviate/collections/config/async_.pyi | 10 ++-- weaviate/collections/config/executor.py | 64 +++++++++++++------------ weaviate/collections/config/sync.pyi | 10 ++-- 5 files changed, 74 insertions(+), 53 deletions(-) diff --git a/integration/test_collection_config.py b/integration/test_collection_config.py index 7764b7c38..542eeaa1d 100644 --- a/integration/test_collection_config.py +++ b/integration/test_collection_config.py @@ -43,6 +43,7 @@ IndexName, InvertedIndexState, InvertedIndexTaskStatus, + InvertedIndexType, ) from weaviate.collections.classes.tenants import Tenant from weaviate.exceptions import ( @@ -2703,7 +2704,10 @@ def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFac # create the searchable index declaratively and wait for it to become ready status = collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + "name", + InvertedIndexType.SEARCHABLE, + tokenization=Tokenization.WORD, + wait_for_completion=True, ) assert status.type == "searchable" assert status.status == InvertedIndexState.READY @@ -2711,7 +2715,7 @@ def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFac # re-putting the matching configuration is a no-op task = collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.WORD + "name", InvertedIndexType.SEARCHABLE, tokenization=Tokenization.WORD ) assert task.status == InvertedIndexTaskStatus.NO_OP assert task.task_id is None @@ -2730,13 +2734,13 @@ def test_property_reindex_searchable_lifecycle(collection_factory: CollectionFac # rebuild the index from scratch status = collection.config.rebuild_property_index( - "name", "searchable", wait_for_completion=True + "name", InvertedIndexType.SEARCHABLE, wait_for_completion=True ) assert status.type == "searchable" assert status.status == InvertedIndexState.READY # cancelling when no task is live is an idempotent no-op - task = collection.config.cancel_property_index_task("name", "searchable") + task = collection.config.cancel_property_index_task("name", InvertedIndexType.SEARCHABLE) assert task.status == InvertedIndexTaskStatus.NO_OP # the pre-existing delete API removes the index again @@ -2762,7 +2766,7 @@ def test_property_reindex_range_filters(collection_factory: CollectionFactory) - collection.data.insert_many([{"age": i} for i in range(10)]) status = collection.config.update_property_index( - "age", "rangeFilters", wait_for_completion=True + "age", InvertedIndexType.RANGE_FILTERS, wait_for_completion=True ) assert status.type == "rangeFilters" assert status.status == InvertedIndexState.READY @@ -2799,7 +2803,7 @@ def test_property_reindex_coupled_tokenization_change( collection.data.insert_many([{"name": f"object {i}"} for i in range(100)]) task = collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.FIELD + "name", InvertedIndexType.SEARCHABLE, tokenization=Tokenization.FIELD ) assert task.status == InvertedIndexTaskStatus.STARTED assert task.task_id is not None @@ -2819,7 +2823,10 @@ def test_property_reindex_coupled_tokenization_change( # poll the status endpoint (via the wait path of a NO_OP upsert) until the migration is done status = collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.FIELD, wait_for_completion=True + "name", + InvertedIndexType.SEARCHABLE, + tokenization=Tokenization.FIELD, + wait_for_completion=True, ) assert status.status == InvertedIndexState.READY assert status.tokenization == Tokenization.FIELD @@ -2851,13 +2858,16 @@ def test_property_reindex_multi_tenant(collection_factory: CollectionFactory) -> collection.with_tenant("tenant1").data.insert_many([{"age": i} for i in range(5)]) status = collection.config.update_property_index( - "age", "rangeFilters", tenants=["tenant1", "tenant2"], wait_for_completion=True + "age", + InvertedIndexType.RANGE_FILTERS, + tenants=["tenant1", "tenant2"], + wait_for_completion=True, ) assert status.type == "rangeFilters" assert status.status == InvertedIndexState.READY status = collection.config.rebuild_property_index( - "age", "rangeFilters", tenants=["tenant1"], wait_for_completion=True + "age", InvertedIndexType.RANGE_FILTERS, tenants=["tenant1"], wait_for_completion=True ) assert status.type == "rangeFilters" assert status.status == InvertedIndexState.READY @@ -2882,13 +2892,16 @@ async def test_property_reindex_async(async_collection_factory: AsyncCollectionF await collection.data.insert_many([{"name": f"object {i}"} for i in range(10)]) status = await collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.WORD, wait_for_completion=True + "name", + InvertedIndexType.SEARCHABLE, + tokenization=Tokenization.WORD, + wait_for_completion=True, ) assert status.type == "searchable" assert status.status == InvertedIndexState.READY task = await collection.config.update_property_index( - "name", "searchable", tokenization=Tokenization.WORD + "name", InvertedIndexType.SEARCHABLE, tokenization=Tokenization.WORD ) assert task.status == InvertedIndexTaskStatus.NO_OP @@ -2896,9 +2909,9 @@ async def test_property_reindex_async(async_collection_factory: AsyncCollectionF assert indexes.collection == collection.name status = await collection.config.rebuild_property_index( - "name", "searchable", wait_for_completion=True + "name", InvertedIndexType.SEARCHABLE, wait_for_completion=True ) assert status.status == InvertedIndexState.READY - task = await collection.config.cancel_property_index_task("name", "searchable") + task = await collection.config.cancel_property_index_task("name", InvertedIndexType.SEARCHABLE) assert task.status == InvertedIndexTaskStatus.NO_OP diff --git a/mock_tests/test_property_reindex.py b/mock_tests/test_property_reindex.py index 2a21c8128..c6813ea13 100644 --- a/mock_tests/test_property_reindex.py +++ b/mock_tests/test_property_reindex.py @@ -421,6 +421,7 @@ def test_update_property_index_enum_and_literal_hit_same_route( task = client_139.collections.use(COLLECTION).config.update_property_index( "name", + # runtime leniency pin: raw strings must keep hitting the same route index_name, # type: ignore ) assert task.status == InvertedIndexTaskStatus.STARTED @@ -453,6 +454,7 @@ def test_delete_property_index_enum_and_literal_hit_same_route( assert ( client_139.collections.use(COLLECTION).config.delete_property_index( "name", + # runtime leniency pin: raw strings must keep hitting the same route index_name, # type: ignore ) is True @@ -478,6 +480,7 @@ def test_rebuild_property_index_enum_and_literal_hit_same_route( task = client_139.collections.use(COLLECTION).config.rebuild_property_index( "age", + # runtime leniency pin: raw strings must keep hitting the same route index_name, # type: ignore ) assert task.status == InvertedIndexTaskStatus.STARTED @@ -502,6 +505,7 @@ def test_cancel_property_index_task_enum_and_literal_hit_same_route( task = client_139.collections.use(COLLECTION).config.cancel_property_index_task( "age", + # runtime leniency pin: raw strings must keep hitting the same route index_name, # type: ignore ) assert task.status == InvertedIndexTaskStatus.CANCELLED diff --git a/weaviate/collections/config/async_.pyi b/weaviate/collections/config/async_.pyi index 7041b4e24..035ae95e7 100644 --- a/weaviate/collections/config/async_.pyi +++ b/weaviate/collections/config/async_.pyi @@ -101,7 +101,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -112,7 +112,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -123,7 +123,7 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -132,12 +132,12 @@ class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]): async def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> InvertedIndexTask: ... async def cancel_property_index_task( - self, property_name: str, index_name: Union[InvertedIndexType, IndexName] + self, property_name: str, index_name: InvertedIndexType ) -> InvertedIndexTask: ... async def get_property_indexes(self) -> CollectionInvertedIndexes: ... diff --git a/weaviate/collections/config/executor.py b/weaviate/collections/config/executor.py index 0b855a22e..7fb371bce 100644 --- a/weaviate/collections/config/executor.py +++ b/weaviate/collections/config/executor.py @@ -680,7 +680,8 @@ def delete_property_index( Args: property_name: The property name from which to delete the index. - index_name: The type of the index to delete. + index_name: The type of the index to delete, an `InvertedIndexType` value or one of + the literals `searchable`, `filterable` or `rangeFilters`. Raises: weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails. @@ -753,7 +754,7 @@ async def _execute() -> InvertedIndexStatus: def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -765,7 +766,7 @@ def update_property_index( def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -776,7 +777,7 @@ def update_property_index( def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -798,8 +799,7 @@ def update_property_index( Args: property_name: The property whose index to create or migrate. - index_name: The type of the index, a `InvertedIndexType` value or one of the literals - `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, an `InvertedIndexType` value. tokenization: The tokenization of the index. Required when creating a `searchable` index; optional as a change on an existing `searchable` or `filterable` index. Not valid for `rangeFilters`. @@ -821,17 +821,19 @@ def update_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config update_property_index") - if isinstance(index_name, InvertedIndexType): - index_name = cast(IndexName, index_name.value) + index = cast( + IndexName, + index_name.value if isinstance(index_name, InvertedIndexType) else index_name, + ) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) - _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index)]) _validate_input( [_ValidateArgument(expected=[str, List[str], None], name="tenants", value=tenants)] ) - path = self.__property_index_path(property_name, index_name) + path = self.__property_index_path(property_name, index) body: Dict[str, Any] = {} if tokenization is not None: body["tokenization"] = ( @@ -867,7 +869,7 @@ async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: task = resp(res) if wait_for_completion: return await executor.aresult( - self.__wait_for_property_index(property_name, index_name) + self.__wait_for_property_index(property_name, index) ) return task @@ -883,14 +885,14 @@ async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: ) task = resp(res) if wait_for_completion: - return executor.result(self.__wait_for_property_index(property_name, index_name)) + return executor.result(self.__wait_for_property_index(property_name, index)) return task @overload def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -900,7 +902,7 @@ def rebuild_property_index( def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, @@ -909,7 +911,7 @@ def rebuild_property_index( def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: bool = False, @@ -918,8 +920,7 @@ def rebuild_property_index( Args: property_name: The property whose index to rebuild. - index_name: The type of the index, a `InvertedIndexType` value or one of the literals - `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, an `InvertedIndexType` value. tenants: The tenant/list of tenants for which to rebuild the index on a multi-tenant collection. If not provided, all tenants are affected. wait_for_completion: Whether to wait until the index reports `ready`. By default False. @@ -936,17 +937,19 @@ def rebuild_property_index( weaviate.exceptions.ReindexCanceledError: If `wait_for_completion=True` and the reindexing task was cancelled. """ self.__check_property_reindex_support("Collection config rebuild_property_index") - if isinstance(index_name, InvertedIndexType): - index_name = cast(IndexName, index_name.value) + index = cast( + IndexName, + index_name.value if isinstance(index_name, InvertedIndexType) else index_name, + ) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) - _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index)]) _validate_input( [_ValidateArgument(expected=[str, List[str], None], name="tenants", value=tenants)] ) - path = self.__property_index_path(property_name, index_name) + "/rebuild" + path = self.__property_index_path(property_name, index) + "/rebuild" if isinstance(tenants, str): tenants = [tenants] params: Optional[Dict[str, Any]] = ( @@ -975,7 +978,7 @@ async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: task = resp(res) if wait_for_completion: return await executor.aresult( - self.__wait_for_property_index(property_name, index_name) + self.__wait_for_property_index(property_name, index) ) return task @@ -991,13 +994,13 @@ async def _execute() -> Union[InvertedIndexTask, InvertedIndexStatus]: ) task = resp(res) if wait_for_completion: - return executor.result(self.__wait_for_property_index(property_name, index_name)) + return executor.result(self.__wait_for_property_index(property_name, index)) return task def cancel_property_index_task( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, ) -> executor.Result[InvertedIndexTask]: """Cancel the live reindexing task of a property index. @@ -1008,8 +1011,7 @@ def cancel_property_index_task( Args: property_name: The property whose reindexing task to cancel. - index_name: The type of the index, a `InvertedIndexType` value or one of the literals - `searchable`, `filterable` or `rangeFilters`. + index_name: The type of the index, an `InvertedIndexType` value. Returns: A `InvertedIndexTask` with status `CANCELLED` if a live task was cancelled or `NO_OP` otherwise. @@ -1020,14 +1022,16 @@ def cancel_property_index_task( weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status. """ self.__check_property_reindex_support("Collection config cancel_property_index_task") - if isinstance(index_name, InvertedIndexType): - index_name = cast(IndexName, index_name.value) + index = cast( + IndexName, + index_name.value if isinstance(index_name, InvertedIndexType) else index_name, + ) _validate_input( [_ValidateArgument(expected=[str], name="property_name", value=property_name)] ) - _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)]) + _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index)]) - path = self.__property_index_path(property_name, index_name) + "/cancel" + path = self.__property_index_path(property_name, index) + "/cancel" def resp(res: Response) -> InvertedIndexTask: response = _decode_json_response_dict(res, "Cancel property index task") diff --git a/weaviate/collections/config/sync.pyi b/weaviate/collections/config/sync.pyi index 869d51f78..eb2112c9d 100644 --- a/weaviate/collections/config/sync.pyi +++ b/weaviate/collections/config/sync.pyi @@ -99,7 +99,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -110,7 +110,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def update_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tokenization: Optional[Tokenization] = None, algorithm: Optional[Literal["blockmax"]] = None, @@ -121,7 +121,7 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[True], @@ -130,12 +130,12 @@ class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]): def rebuild_property_index( self, property_name: str, - index_name: Union[InvertedIndexType, IndexName], + index_name: InvertedIndexType, *, tenants: Union[List[str], str, None] = None, wait_for_completion: Literal[False] = False, ) -> InvertedIndexTask: ... def cancel_property_index_task( - self, property_name: str, index_name: Union[InvertedIndexType, IndexName] + self, property_name: str, index_name: InvertedIndexType ) -> InvertedIndexTask: ... def get_property_indexes(self) -> CollectionInvertedIndexes: ...