Skip to content

Commit

Permalink
Overload boolean operators to work with filters (#1986)
Browse files Browse the repository at this point in the history
Co-authored-by: Håkon V. Treider <[email protected]>
  • Loading branch information
MortGron and haakonvt authored Oct 21, 2024
1 parent c36f442 commit a412717
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Changes are grouped as follows
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [7.63.9] - 2024-10-21
### Added
- Filters can now be combined using `And` and `Or` by using the operators `&` and `|`.
- Filters can now be negated by using the `~` operator (instead of using the `Not` filter)

## [7.63.8] - 2024-10-21
### Fixed
- Data Workflows: workflow external ID and version are now URL encoded to allow characters like `/` when calling `workflows.executions.run`
Expand Down
2 changes: 1 addition & 1 deletion cognite/client/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations

__version__ = "7.63.8"
__version__ = "7.63.9"
__api_subversion__ = "20230101"
27 changes: 27 additions & 0 deletions cognite/client/data_classes/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ def _involved_filter_types(self) -> set[type[Filter]]:
output.update(filter_._involved_filter_types())
return output

def _list_filters_without_nesting(self, other: Filter, operator: type[CompoundFilter]) -> list[Filter]:
filters: list[Filter] = []
filters.extend(self._filters) if isinstance(self, operator) else filters.append(self)
filters.extend(other._filters) if isinstance(other, operator) else filters.append(other)
return filters

def __and__(self, other: Filter) -> And:
return And(*self._list_filters_without_nesting(other, And))

def __or__(self, other: Filter) -> Or:
return Or(*self._list_filters_without_nesting(other, Or))

def __invert__(self) -> Not:
return Not(self)


class UnknownFilter(Filter):
def __init__(self, filter_name: str, filter_body: dict[str, Any]) -> None:
Expand Down Expand Up @@ -322,6 +337,10 @@ class And(CompoundFilter):
>>> flt = And(
... Equals(my_view.as_property_ref("some_property"), 42),
... In(my_view.as_property_ref("another_property"), ["a", "b", "c"]))
Using the "&" operator:
>>> flt = Equals("age", 42) & Equals("name", "Alice")
"""

_filter_name = "and"
Expand Down Expand Up @@ -349,6 +368,10 @@ class Or(CompoundFilter):
>>> flt = Or(
... Equals(my_view.as_property_ref("some_property"), 42),
... In(my_view.as_property_ref("another_property"), ["a", "b", "c"]))
Using the "|" operator:
>>> flt = Equals("name", "Bob") | Equals("name", "Alice")
"""

_filter_name = "or"
Expand All @@ -374,6 +397,10 @@ class Not(CompoundFilter):
>>> is_42 = Equals(my_view.as_property_ref("some_property"), 42)
>>> flt = Not(is_42)
Using the "~" operator:
>>> flt = ~Equals("name", "Bob")
"""

_filter_name = "not"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
name = "cognite-sdk"

version = "7.63.8"
version = "7.63.9"
description = "Cognite Python SDK"
readme = "README.md"
documentation = "https://cognite-sdk-python.readthedocs-hosted.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,31 @@ def dump_filter_test_data() -> Iterator[ParameterSet]:
)
yield pytest.param(prop_list1, expected, id="Prefix filter with list property of objects")
yield pytest.param(prop_list2, expected, id="Prefix filter with list property of dicts")
overloaded_filter = f.Equals(property="name", value="bob") & f.HasData(
containers=[("space", "container")]
) | ~f.Range(property="size", gt=0)
expected = {
"or": [
{
"and": [
{"equals": {"property": ["name"], "value": "bob"}},
{"hasData": [{"type": "container", "space": "space", "externalId": "container"}]},
]
},
{"not": {"range": {"property": ["size"], "gt": 0}}},
]
}
yield pytest.param(overloaded_filter, expected, id="Compound filter with overloaded operators")
nested_overloaded_filter = (f.Equals("a", "b") & f.Equals("c", "d")) & (f.Equals("e", "f") & f.Equals("g", "h"))
expected = {
"and": [
{"equals": {"property": ["a"], "value": "b"}},
{"equals": {"property": ["c"], "value": "d"}},
{"equals": {"property": ["e"], "value": "f"}},
{"equals": {"property": ["g"], "value": "h"}},
]
}
yield pytest.param(nested_overloaded_filter, expected, id="Compound filter with nested overloaded and")


@pytest.mark.parametrize("user_filter, expected", list(dump_filter_test_data()))
Expand Down

0 comments on commit a412717

Please sign in to comment.