diff --git a/src/agent_toolkit/forestadmin/agent_toolkit/resources/collections/filter.py b/src/agent_toolkit/forestadmin/agent_toolkit/resources/collections/filter.py index 633ff1c6d..76d828b53 100644 --- a/src/agent_toolkit/forestadmin/agent_toolkit/resources/collections/filter.py +++ b/src/agent_toolkit/forestadmin/agent_toolkit/resources/collections/filter.py @@ -288,7 +288,7 @@ def _get_expected_type_for_condition( if operator in operators_expecting_number: return PrimitiveType.NUMBER - if operator == Operator.IN: + if operator in [Operator.IN, Operator.NOT_IN]: return [cast(PrimitiveType, field_schema["column_type"])] # type:ignore return cast(PrimitiveType, field_schema["column_type"]) diff --git a/src/agent_toolkit/forestadmin/agent_toolkit/utils/forest_schema/filterable.py b/src/agent_toolkit/forestadmin/agent_toolkit/utils/forest_schema/filterable.py index 03f8f588d..b5288b2b6 100644 --- a/src/agent_toolkit/forestadmin/agent_toolkit/utils/forest_schema/filterable.py +++ b/src/agent_toolkit/forestadmin/agent_toolkit/utils/forest_schema/filterable.py @@ -29,8 +29,14 @@ class FrontendFilterableUtils: OPERATOR_BY_TYPES = { PrimitiveType.BOOLEAN: BASE_OPERATORS, - PrimitiveType.ENUM: [*BASE_OPERATORS, Operator.IN], - PrimitiveType.NUMBER: [*BASE_OPERATORS, Operator.IN, Operator.GREATER_THAN, Operator.LESS_THAN], + PrimitiveType.ENUM: [*BASE_OPERATORS, Operator.IN, Operator.NOT_IN], + PrimitiveType.NUMBER: [ + *BASE_OPERATORS, + Operator.IN, + Operator.GREATER_THAN, + Operator.LESS_THAN, + Operator.NOT_IN, + ], PrimitiveType.STRING: [ *BASE_OPERATORS, Operator.IN, @@ -39,7 +45,7 @@ class FrontendFilterableUtils: Operator.CONTAINS, Operator.NOT_CONTAINS, ], - PrimitiveType.UUID: BASE_OPERATORS, + PrimitiveType.UUID: [*BASE_OPERATORS, Operator.IN, Operator.NOT_IN], PrimitiveType.DATE: [*BASE_OPERATORS, *DATE_OPERATORS], PrimitiveType.DATE_ONLY: [*BASE_OPERATORS, *DATE_OPERATORS], PrimitiveType.TIME_ONLY: [*BASE_OPERATORS, *DATE_OPERATORS], diff --git a/src/agent_toolkit/tests/resources/collections/test_filter.py b/src/agent_toolkit/tests/resources/collections/test_filter.py index 105871882..54cb1aef3 100644 --- a/src/agent_toolkit/tests/resources/collections/test_filter.py +++ b/src/agent_toolkit/tests/resources/collections/test_filter.py @@ -24,11 +24,11 @@ def setUpClass(cls) -> None: column_type=PrimitiveType.NUMBER, is_primary_key=True, type=FieldType.COLUMN, - filter_operators=set([Operator.IN, Operator.EQUAL]), + filter_operators=set([Operator.IN, Operator.EQUAL, Operator.NOT_IN]), ), "title": Column( column_type=PrimitiveType.STRING, - filter_operators=[Operator.IN, Operator.EQUAL], + filter_operators=[Operator.IN, Operator.EQUAL, Operator.NOT_IN], type=FieldType.COLUMN, ), "author": ManyToOne( @@ -83,6 +83,21 @@ def test_parse_condition_tree_should_parse_array_when_IN_operator_str(self): ) condition_tree = parse_condition_tree(request) self.assertEqual(condition_tree.value, ["Foundation", "Harry Potter"]) + self.assertEqual(condition_tree.operator, Operator.IN) + + def test_parse_condition_tree_should_parse_array_when_NOT_IN_operator_str(self): + request = RequestCollection( + method=RequestMethod.GET, + body=None, + query={ + "filters": '{"field":"title","operator":"not_in","value":"Foundation,Harry Potter"}', + "collection_name": "Book", + }, + collection=self.collection_book, + ) + condition_tree = parse_condition_tree(request) + self.assertEqual(condition_tree.value, ["Foundation", "Harry Potter"]) + self.assertEqual(condition_tree.operator, Operator.NOT_IN) def test_parse_condition_tree_should_parse_array_when_IN_operator_int(self): request = RequestCollection( @@ -96,6 +111,21 @@ def test_parse_condition_tree_should_parse_array_when_IN_operator_int(self): ) condition_tree = parse_condition_tree(request) self.assertEqual(condition_tree.value, [1, 2]) + self.assertEqual(condition_tree.operator, Operator.IN) + + def test_parse_condition_tree_should_parse_array_when_NOT_IN_operator_int(self): + request = RequestCollection( + method=RequestMethod.GET, + body=None, + query={ + "filters": '{"field":"id","operator":"not_in","value": [1,2]}', + "collection_name": "Book", + }, + collection=self.collection_book, + ) + condition_tree = parse_condition_tree(request) + self.assertEqual(condition_tree.value, [1, 2]) + self.assertEqual(condition_tree.operator, Operator.NOT_IN) def test_parse_condition_tree_should_parse_complex_condition_tree(self): request = RequestCollection( diff --git a/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/empty/collection.py b/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/empty/collection.py index f67d15cf0..7060f2123 100644 --- a/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/empty/collection.py +++ b/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/empty/collection.py @@ -86,6 +86,11 @@ def _and_returns_empty_set(self, conditions: List[ConditionTree]) -> bool: elif leaf.field in values_by_field and leaf.operator == Operator.IN: values_by_field[leaf.field] = [value for value in values_by_field[leaf.field] if value in leaf.value] + elif leaf.field in values_by_field and leaf.operator == Operator.NOT_IN: + values_by_field[leaf.field] = [ + value for value in values_by_field[leaf.field] if value not in leaf.value + ] + for value in values_by_field.values(): if len(value) == 0: return True