Skip to content

Commit

Permalink
In blueetl_core.utils.is_subfilter(), add the strict parameter (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
GianlucaFicarelli authored Apr 11, 2024
1 parent d2954c1 commit 0feee35
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 5 deletions.
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

0 comments on commit 0feee35

Please sign in to comment.