Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In blueetl_core.utils.is_subfilter(), add the strict parameter #12

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

Version 0.2.3
-------------

Improvements
~~~~~~~~~~~~

- In ``blueetl_core.utils.is_subfilter()``, add the ``strict`` parameter.

Version 0.2.2
-------------

Expand Down
25 changes: 21 additions & 4 deletions src/blueetl_core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,32 @@ def compare(obj: Union[pd.Series, pd.Index], value: Any) -> np.ndarray:
return np.asarray(obj == value)


def is_subfilter(left: dict, right: dict) -> bool:
def is_subfilter(left: dict, right: dict, strict: bool = False) -> bool:
"""Return True if ``left`` is a subfilter of ``right``, False otherwise.

``left`` is a subfilter of ``right`` if it's equal or more specific.
Args:
left: left filter dict.
right: right filter dict.
strict: if False, ``left`` is a subfilter of ``right`` if it's equal or more specific;
if True, ``left`` is a subfilter of ``right`` only if it's more specific.

Examples:
>>> print(is_subfilter({}, {}))
True
>>> print(is_subfilter({}, {}, strict=True))
False
>>> print(is_subfilter({}, {"key": 1}))
False
>>> print(is_subfilter({"key": 1}, {}))
True
>>> print(is_subfilter({"key": 1}, {"key": 1}))
True
>>> print(is_subfilter({"key": 1}, {"key": 1}, strict=True))
False
>>> print(is_subfilter({"key": 1}, {"key": [1]}))
True
>>> print(is_subfilter({"key": 1}, {"key": [1]}, strict=True))
False
>>> print(is_subfilter({"key": 1}, {"key": [1, 2]}))
True
>>> print(is_subfilter({"key": 1}, {"key": {"isin": [1, 2]}}))
Expand Down Expand Up @@ -178,7 +190,7 @@ def _to_dict(obj) -> dict:
return {"isin": [obj]}

def _is_subdict(d1: dict, d2: dict) -> bool:
"""Return True if d1 is a subdict of d2."""
"""Return True if d1 is a subdict of d2, or d1 and d2 are equal."""
# mapping operator -> operation
operators = {
"ne": operator.eq,
Expand All @@ -201,14 +213,19 @@ def _is_subdict(d1: dict, d2: dict) -> bool:
L.debug("unmatched keys: %s", sorted(unmatched_keys))
return len(unmatched_keys) == 0

# keys present in left, but missing or different in right
difference = set(left)
for key in right:
if key not in left:
return False
dict_left = _to_dict(left[key])
dict_right = _to_dict(right[key])
if strict and dict_left == dict_right:
difference.remove(key)
continue
if not _is_subdict(dict_left, dict_right):
return False
return True
return not strict or len(difference) > 0


def smart_concat(iterable, *, keys=None, copy=False, skip_empty=True, **kwargs):
Expand Down
45 changes: 44 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def test_compare_empty_filter():
({}, {"key": 1}, False),
({"key": 1}, {}, True),
({"key": 1}, {"key": 1}, True),
({"key": 1}, {"key": [1]}, True),
({"key": 1}, {"key": [1, 2]}, True),
({"key": 1}, {"key": {"isin": [1, 2]}}, True),
({"key": 1}, {"key": 2}, False),
Expand Down Expand Up @@ -155,10 +156,52 @@ def test_compare_empty_filter():
],
)
def test_is_subfilter(left, right, expected):
result = test_module.is_subfilter(left, right)
result = test_module.is_subfilter(left, right, strict=False)
assert result == expected


@pytest.mark.parametrize(
"left, right, expected",
[
({}, {}, False),
({}, {"key": 1}, False),
({"key": 1}, {}, True),
({"key": 1}, {"key": 1}, False),
({"key": 1}, {"key": [1]}, False),
({"key": 1}, {"key": [1, 2]}, True),
({"key": 1}, {"key": {"isin": [1, 2]}}, True),
({"key": 1}, {"key": 2}, False),
({"key": 1}, {"key": [2, 3]}, False),
({"key": 1}, {"key": {"isin": [2, 3]}}, False),
({"key1": 1, "key2": 2}, {"key1": 1}, True),
({"key1": 1}, {"key1": 1, "key2": 2}, False),
({"key": {"isin": [1, 2]}}, {"key": 1}, False),
({"key": {"ne": 3}}, {"key": {"ne": 3}}, False),
({"key": {"ne": 3}}, {"key": {"ne": 4}}, False),
({"key": {"gt": 3}}, {"key": {"gt": 2}}, True),
({"key": {"gt": 3}}, {"key": {"gt": 3}}, False),
({"key": {"gt": 3}}, {"key": {"gt": 4}}, False),
({"key": {"ge": 3}}, {"key": {"ge": 2}}, True),
({"key": {"ge": 3}}, {"key": {"ge": 3}}, False),
({"key": {"ge": 3}}, {"key": {"ge": 4}}, False),
({"key": {"lt": 3}}, {"key": {"lt": 2}}, False),
({"key": {"lt": 3}}, {"key": {"lt": 3}}, False),
({"key": {"lt": 3}}, {"key": {"lt": 4}}, True),
({"key": {"le": 3}}, {"key": {"le": 2}}, False),
({"key": {"le": 3}}, {"key": {"le": 3}}, False),
({"key": {"le": 3}}, {"key": {"le": 4}}, True),
({"key": {"le": 3, "ge": 1}}, {"key": {"le": 4}}, True),
({"key": {"le": 3, "ge": 1}}, {"key": {"le": 4, "ge": 0}}, True),
({"key": 1}, {"key": {"eq": 1}}, False),
({"key": 1}, {"key": {"eq": 1, "isin": [1, 2]}}, False),
({"key": 1}, {"key": {"eq": 1, "isin": [2, 3]}}, False),
],
)
def test_is_subfilter_strict(left, right, expected):
result = test_module.is_subfilter(left, right, strict=True)
assert result is expected


def test_smart_concat_series(series1):
obj1 = series1.copy() + 1
obj2 = series1.copy() + 2
Expand Down