From 5cb7e1a996782cc71fe67277c9749cddf3ddb927 Mon Sep 17 00:00:00 2001 From: Alex Alexapolsky Date: Thu, 12 Feb 2026 18:20:59 +0100 Subject: [PATCH 1/3] Fix empty list filter raising IndexError instead of WeaviateInvalidInputError --- test/collection/test_filter.py | 30 +++++++++++++++++++++ weaviate/collections/classes/filters.py | 36 +++++++++++++++++++++++++ weaviate/collections/filters.py | 15 ++++++++--- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/test/collection/test_filter.py b/test/collection/test_filter.py index 54c11abc5..65be332b2 100644 --- a/test/collection/test_filter.py +++ b/test/collection/test_filter.py @@ -26,6 +26,36 @@ def test_empty_input_contains_all() -> None: wvc.query.Filter.by_property("test").contains_all([]) +def test_empty_list_equal() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").equal([]) + + +def test_empty_list_not_equal() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").not_equal([]) + + +def test_empty_list_less_than() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").less_than([]) + + +def test_empty_list_less_or_equal() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").less_or_equal([]) + + +def test_empty_list_greater_than() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").greater_than([]) + + +def test_empty_list_greater_or_equal() -> None: + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + wvc.query.Filter.by_property("test").greater_or_equal([]) + + def test_filter_lists() -> None: f1 = wvc.query.Filter.by_property("test").equal("test") f2 = wvc.query.Filter.by_creation_time().greater_or_equal(datetime.datetime.now()) diff --git a/weaviate/collections/classes/filters.py b/weaviate/collections/classes/filters.py index cbeac3aa2..a05d9eab2 100644 --- a/weaviate/collections/classes/filters.py +++ b/weaviate/collections/classes/filters.py @@ -220,18 +220,42 @@ def contains_none(self, val: FilterValuesList) -> _Filters: def equal(self, val: FilterValues) -> _Filters: """Filter on whether the property is equal to the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue(target=self._target_path(), value=val, operator=_Operator.EQUAL) def not_equal(self, val: FilterValues) -> _Filters: """Filter on whether the property is not equal to the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue(target=self._target_path(), value=val, operator=_Operator.NOT_EQUAL) def less_than(self, val: FilterValues) -> _Filters: """Filter on whether the property is less than the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue(target=self._target_path(), value=val, operator=_Operator.LESS_THAN) def less_or_equal(self, val: FilterValues) -> _Filters: """Filter on whether the property is less than or equal to the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue( target=self._target_path(), value=val, @@ -240,6 +264,12 @@ def less_or_equal(self, val: FilterValues) -> _Filters: def greater_than(self, val: FilterValues) -> _Filters: """Filter on whether the property is greater than the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue( target=self._target_path(), value=val, @@ -248,6 +278,12 @@ def greater_than(self, val: FilterValues) -> _Filters: def greater_or_equal(self, val: FilterValues) -> _Filters: """Filter on whether the property is greater than or equal to the given value.""" + if isinstance(val, list) and len(val) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) return _FilterValue( target=self._target_path(), value=val, diff --git a/weaviate/collections/filters.py b/weaviate/collections/filters.py index 41aebf95c..49d847c02 100644 --- a/weaviate/collections/filters.py +++ b/weaviate/collections/filters.py @@ -116,7 +116,9 @@ def __filter_to_text(value: FilterValues) -> Optional[str]: @staticmethod def __filter_to_text_list(value: FilterValues) -> Optional[base_pb2.TextArray]: - if not isinstance(value, list) or not ( + if not isinstance(value, list) or len(value) == 0: + return None + if not ( isinstance(value[0], TIME) or isinstance(value[0], str) or isinstance(value[0], uuid_lib.UUID) @@ -135,14 +137,14 @@ def __filter_to_text_list(value: FilterValues) -> Optional[base_pb2.TextArray]: @staticmethod def __filter_to_bool_list(value: FilterValues) -> Optional[base_pb2.BooleanArray]: - if not isinstance(value, list) or not isinstance(value[0], bool): + if not isinstance(value, list) or len(value) == 0 or not isinstance(value[0], bool): return None return base_pb2.BooleanArray(values=cast(List[bool], value)) @staticmethod def __filter_to_float_list(value: FilterValues) -> Optional[base_pb2.NumberArray]: - if not isinstance(value, list) or not isinstance(value[0], float): + if not isinstance(value, list) or len(value) == 0 or not isinstance(value[0], float): return None return base_pb2.NumberArray(values=cast(List[float], value)) @@ -152,6 +154,7 @@ def __filter_to_int_list(value: FilterValues) -> Optional[base_pb2.IntArray]: # bool is a subclass of int in Python, so the check must ensure it's not a bool if ( not isinstance(value, list) + or len(value) == 0 or not isinstance(value[0], int) or isinstance(value[0], bool) ): @@ -224,6 +227,12 @@ def __parse_filter(value: FilterValues) -> Dict[str, Any]: if isinstance(value, float): return {"valueNumber": value} if isinstance(value, list): + if len(value) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) if isinstance(value[0], str): return {"valueTextArray": value} if isinstance(value[0], uuid_lib.UUID): From 3bcde10bb745642c3ad9445d3dc9d0cc06de7232 Mon Sep 17 00:00:00 2001 From: Dirk Kulawiak Date: Tue, 24 Feb 2026 13:39:35 +0100 Subject: [PATCH 2/3] Add another defensive guard --- test/collection/test_filter.py | 9 +++++++++ weaviate/collections/filters.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/test/collection/test_filter.py b/test/collection/test_filter.py index 65be332b2..a2853cbdc 100644 --- a/test/collection/test_filter.py +++ b/test/collection/test_filter.py @@ -9,8 +9,10 @@ _FilterAnd, _FilterNot, _FilterOr, + _FilterValue, _Operator, ) +from weaviate.collections.filters import _FilterToGRPC from weaviate.proto.v1 import base_pb2 @@ -56,6 +58,13 @@ def test_empty_list_greater_or_equal() -> None: wvc.query.Filter.by_property("test").greater_or_equal([]) +def test_empty_list_grpc_conversion() -> None: + """Ensure the gRPC converter raises WeaviateInvalidInputError for empty lists.""" + fv = _FilterValue(target="test", value=[], operator=_Operator.EQUAL) + with pytest.raises(weaviate.exceptions.WeaviateInvalidInputError): + _FilterToGRPC.convert(fv) + + def test_filter_lists() -> None: f1 = wvc.query.Filter.by_property("test").equal("test") f2 = wvc.query.Filter.by_creation_time().greater_or_equal(datetime.datetime.now()) diff --git a/weaviate/collections/filters.py b/weaviate/collections/filters.py index 49d847c02..1e8285708 100644 --- a/weaviate/collections/filters.py +++ b/weaviate/collections/filters.py @@ -40,6 +40,12 @@ def convert(weav_filter: Optional[_Filters]) -> Optional[base_pb2.Filters]: @staticmethod def __value_filter(weav_filter: _FilterValue) -> base_pb2.Filters: + if isinstance(weav_filter.value, list) and len(weav_filter.value) == 0: + raise WeaviateInvalidInputError( + "Filtering on empty lists is not supported by Weaviate. " + "To filter by property length, use " + "Filter.by_property('prop', length=True).equal(0)" + ) operator = weav_filter.operator._to_grpc() target = _FilterToGRPC.__to_target(weav_filter.target) if isinstance(weav_filter.value, bool): From 460b20b5b02ae7f513a3b6b44903fa51a0454831 Mon Sep 17 00:00:00 2001 From: Dirk Kulawiak Date: Tue, 24 Feb 2026 13:40:51 +0100 Subject: [PATCH 3/3] Fix role names in test --- integration/test_rbac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/test_rbac.py b/integration/test_rbac.py index 93c930672..d98d238a7 100644 --- a/integration/test_rbac.py +++ b/integration/test_rbac.py @@ -389,7 +389,7 @@ ( Permissions.alias(alias="myCAR", collection="*", read=True, delete=True), Role( - name="AlliasRole", + name="AlliasRole3", alias_permissions=[ AliasPermissionOutput( alias="MyCAR", # capitalized the first letter.