diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f66edf27a..ea7a6abfb 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -28,7 +28,7 @@ env: WEAVIATE_134: 1.34.19 WEAVIATE_135: 1.35.18 WEAVIATE_136: 1.36.12 - WEAVIATE_137: 1.37.1-4e61e26.amd64 + WEAVIATE_137: 1.37.5-e0fe0d5.amd64 jobs: lint-and-format: diff --git a/integration/test_collection_config.py b/integration/test_collection_config.py index 3f33a30b7..814c5d41a 100644 --- a/integration/test_collection_config.py +++ b/integration/test_collection_config.py @@ -1025,9 +1025,7 @@ def test_config_export_and_recreate_from_dict(collection_factory: CollectionFact Property(name="booleans", data_type=DataType.BOOL_ARRAY), Property(name="geo", data_type=DataType.GEO_COORDINATES), Property(name="phone", data_type=DataType.PHONE_NUMBER), - Property( - name="field_index_searchable", data_type=DataType.TEXT, index_searchable=False - ), + Property(name="field_searchable_off", data_type=DataType.TEXT, index_searchable=False), Property( name="field_index_range_filters_false", data_type=DataType.INT, @@ -1054,7 +1052,9 @@ def test_config_export_and_recreate_from_dict(collection_factory: CollectionFact tokenization=Tokenization.FIELD, ), Property( - name="nested_searchable", data_type=DataType.TEXT, index_searchable=False + name="nested_searchable_off", + data_type=DataType.TEXT, + index_searchable=False, ), Property( name="nested_filterable", data_type=DataType.TEXT, index_filterable=False @@ -1598,7 +1598,7 @@ def test_replication_config_with_async_config(collection_factory: CollectionFact factor=1, async_enabled=True, async_config=Configure.Replication.async_config( - max_workers=8, + propagation_concurrency=4, hashtree_height=20, ), ), @@ -1608,8 +1608,12 @@ def test_replication_config_with_async_config(collection_factory: CollectionFact assert config.replication_config.async_enabled is True assert config.replication_config.async_config is not None ac = config.replication_config.async_config - assert ac.max_workers == 8 + assert ac.propagation_concurrency == 4 assert ac.hashtree_height == 20 + if collection._connection._weaviate_version.is_at_least(1, 37, 3): + # Server removed max_workers / alive_nodes_checking_frequency from the schema in 1.37.3 + assert ac.max_workers is None + assert ac.alive_nodes_checking_frequency is None def test_replication_config_remove_async_config_by_disabling_async_replication( @@ -1624,14 +1628,14 @@ def test_replication_config_remove_async_config_by_disabling_async_replication( factor=1, async_enabled=True, async_config=Configure.Replication.async_config( - max_workers=8, + propagation_concurrency=4, hashtree_height=20, ), ), ) config = collection.config.get() assert config.replication_config.async_config is not None - assert config.replication_config.async_config.max_workers == 8 + assert config.replication_config.async_config.propagation_concurrency == 4 collection.config.update( replication_config=Reconfigure.replication( @@ -1653,14 +1657,14 @@ def test_replication_config_remove_async_config(collection_factory: CollectionFa factor=1, async_enabled=True, async_config=Configure.Replication.async_config( - max_workers=8, + propagation_concurrency=4, hashtree_height=20, ), ), ) config = collection.config.get() assert config.replication_config.async_config is not None - assert config.replication_config.async_config.max_workers == 8 + assert config.replication_config.async_config.propagation_concurrency == 4 collection.config.update( replication_config=Reconfigure.replication( @@ -1685,7 +1689,7 @@ def test_replication_config_unset_single_async_field( factor=1, async_enabled=True, async_config=Configure.Replication.async_config( - max_workers=8, + propagation_concurrency=4, hashtree_height=20, ), ), @@ -1693,21 +1697,21 @@ def test_replication_config_unset_single_async_field( config = collection.config.get() ac = config.replication_config.async_config assert ac is not None - assert ac.max_workers == 8 + assert ac.propagation_concurrency == 4 assert ac.hashtree_height == 20 - # Update with only max_workers — hashtree_height reverts to server default + # Update with only propagation_concurrency — hashtree_height reverts to server default collection.config.update( replication_config=Reconfigure.replication( async_config=Reconfigure.Replication.async_config( - max_workers=8, + propagation_concurrency=4, ), ), ) config = collection.config.get() ac = config.replication_config.async_config assert ac is not None - assert ac.max_workers == 8 + assert ac.propagation_concurrency == 4 assert ac.hashtree_height != 20 @@ -1734,16 +1738,16 @@ def test_replication_config_add_async_config_to_existing_collection( collection.config.update( replication_config=Reconfigure.replication( async_config=Reconfigure.Replication.async_config( - max_workers=8, propagation_concurrency=4, + hashtree_height=20, ), ), ) config = collection.config.get() assert config.replication_config.async_config is not None ac = config.replication_config.async_config - assert ac.max_workers == 8 assert ac.propagation_concurrency == 4 + assert ac.hashtree_height == 20 def test_update_property_descriptions(collection_factory: CollectionFactory) -> None: diff --git a/weaviate/collections/classes/config.py b/weaviate/collections/classes/config.py index 43d86375d..0f6d974c0 100644 --- a/weaviate/collections/classes/config.py +++ b/weaviate/collections/classes/config.py @@ -2473,7 +2473,7 @@ def __add_to_module_config( else: return_dict["moduleConfig"][addition_key] = addition_val - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self, *, emit_default_vector_index_type: bool = True) -> Dict[str, Any]: ret_dict: Dict[str, Any] = {} for cls_field in type(self).model_fields: @@ -2494,14 +2494,23 @@ def _to_dict(self) -> Dict[str, Any]: ret_dict["vectorIndexType"] = val.vector_index_type().value ret_dict[cls_field] = val._to_dict() elif isinstance(val, _VectorConfigCreate): - ret_dict["vectorConfig"] = {val.name or "default": val._to_dict()} + ret_dict["vectorConfig"] = { + val.name or "default": val._to_dict( + emit_default_vector_index_type=emit_default_vector_index_type + ) + } elif ( isinstance(val, list) and len(val) > 0 and all(isinstance(item, _NamedVectorConfigCreate) for item in val) ): val = cast(List[_NamedVectorConfigCreate], val) - ret_dict["vectorConfig"] = {item.name: item._to_dict() for item in val} + ret_dict["vectorConfig"] = { + item.name: item._to_dict( + emit_default_vector_index_type=emit_default_vector_index_type + ) + for item in val + } elif ( isinstance(val, list) and len(val) > 0 @@ -2514,11 +2523,17 @@ def _to_dict(self) -> Dict[str, Any]: raise WeaviateInvalidInputError( "Vector config name must be set when specifying multiple vectors" ) - ret_dict["vectorConfig"][item.name] = item._to_dict() + ret_dict["vectorConfig"][item.name] = item._to_dict( + emit_default_vector_index_type=emit_default_vector_index_type + ) else: assert isinstance(val, _ConfigCreateModel) ret_dict[cls_field] = val._to_dict() - if self.vectorIndexConfig is None and "vectorConfig" not in ret_dict: + if ( + self.vectorIndexConfig is None + and "vectorConfig" not in ret_dict + and emit_default_vector_index_type + ): ret_dict["vectorIndexType"] = VectorIndexType.HNSW ret_dict["class"] = self.name @@ -2583,7 +2598,17 @@ def async_config( """Create a configuration object create for async replication settings when creating a collection. This is only available with WeaviateDB `>=v1.36.0`. + + Note: + `max_workers` and `alive_nodes_checking_frequency` were removed from the + Weaviate server schema in v1.37.3. Passing them has no effect against any + server `>=v1.37.3` (the server silently drops them) and emits a + ``DeprecationWarning``. Both arguments will be removed in a future release. """ + if max_workers is not None: + _Warnings.async_replication_field_removed_server_side("max_workers") + if alive_nodes_checking_frequency is not None: + _Warnings.async_replication_field_removed_server_side("alive_nodes_checking_frequency") return _AsyncReplicationConfigCreate( maxWorkers=max_workers, hashtreeHeight=hashtree_height, @@ -2624,7 +2649,17 @@ def async_config( """Create a configuration object for async replication settings when updating a collection. This is only available with WeaviateDB `>=v1.36.0`. + + Note: + `max_workers` and `alive_nodes_checking_frequency` were removed from the + Weaviate server schema in v1.37.3. Passing them has no effect against any + server `>=v1.37.3` (the server silently drops them) and emits a + ``DeprecationWarning``. Both arguments will be removed in a future release. """ + if max_workers is not None: + _Warnings.async_replication_field_removed_server_side("max_workers") + if alive_nodes_checking_frequency is not None: + _Warnings.async_replication_field_removed_server_side("alive_nodes_checking_frequency") return _AsyncReplicationConfigUpdate( maxWorkers=max_workers, hashtreeHeight=hashtree_height, diff --git a/weaviate/collections/classes/config_named_vectors.py b/weaviate/collections/classes/config_named_vectors.py index 295410b38..93d011b8b 100644 --- a/weaviate/collections/classes/config_named_vectors.py +++ b/weaviate/collections/classes/config_named_vectors.py @@ -77,12 +77,12 @@ class _NamedVectorConfigCreate(_ConfigCreateModel): default=None, alias="vector_index_config" ) - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self, *, emit_default_vector_index_type: bool = True) -> Dict[str, Any]: ret_dict: Dict[str, Any] = self.__parse_vectorizer() if self.vectorIndexConfig is not None: ret_dict["vectorIndexType"] = self.vectorIndexConfig.vector_index_type().value ret_dict["vectorIndexConfig"] = self.vectorIndexConfig._to_dict() - else: + elif emit_default_vector_index_type: ret_dict["vectorIndexType"] = self.vectorIndexType.value return ret_dict diff --git a/weaviate/collections/classes/config_vectors.py b/weaviate/collections/classes/config_vectors.py index 0fde62fec..ed88c15d8 100644 --- a/weaviate/collections/classes/config_vectors.py +++ b/weaviate/collections/classes/config_vectors.py @@ -87,12 +87,12 @@ class _VectorConfigCreate(_ConfigCreateModel): default=None, alias="vector_index_config" ) - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self, *, emit_default_vector_index_type: bool = True) -> Dict[str, Any]: ret_dict: Dict[str, Any] = self.__parse_vectorizer() if self.vectorIndexConfig is not None: ret_dict["vectorIndexType"] = self.vectorIndexConfig.vector_index_type().value ret_dict["vectorIndexConfig"] = self.vectorIndexConfig._to_dict() - else: + elif emit_default_vector_index_type: ret_dict["vectorIndexType"] = self.vectorIndexType.value return ret_dict diff --git a/weaviate/collections/collections/executor.py b/weaviate/collections/collections/executor.py index 2a733356c..588737600 100644 --- a/weaviate/collections/collections/executor.py +++ b/weaviate/collections/collections/executor.py @@ -253,8 +253,16 @@ def create( f"Invalid collection config create parameters: {e}" ) from e + # Servers >= 1.37.5 apply DEFAULT_VECTOR_INDEX_TYPE to named-vector + # configs that omit `vectorIndexType`; older servers reject the empty + # field, so for them we keep emitting the client-side HNSW default. + emit_default_vector_index_type = not self._connection._weaviate_version.is_at_least( + 1, 37, 5 + ) return self.__create( - config=config._to_dict(), + config=config._to_dict( + emit_default_vector_index_type=emit_default_vector_index_type, + ), data_model_properties=data_model_properties, data_model_references=data_model_references, skip_argument_validation=skip_argument_validation, diff --git a/weaviate/warnings.py b/weaviate/warnings.py index f88eef97f..1c0a1ae0b 100644 --- a/weaviate/warnings.py +++ b/weaviate/warnings.py @@ -251,6 +251,16 @@ def min_occurrences_metric_deprecated() -> None: stacklevel=1, ) + @staticmethod + def async_replication_field_removed_server_side(argument: str) -> None: + warnings.warn( + message=f"""Dep029: The `{argument}` argument in `Configure.Replication.async_config` / `Reconfigure.Replication.async_config` is deprecated. + It was removed from the Weaviate server schema in v1.37.3 and is silently ignored by newer servers. + The argument has no effect against any server >= 1.37.3 and will be removed in a future release.""", + category=DeprecationWarning, + stacklevel=1, + ) + @staticmethod def datetime_insertion_with_no_specified_timezone(date: datetime) -> None: warnings.warn(