diff --git a/deepdiff/search.py b/deepdiff/search.py index 9b1b11a1..ff37c164 100644 --- a/deepdiff/search.py +++ b/deepdiff/search.py @@ -106,7 +106,7 @@ def __init__(self, self.obj: Any = obj self.case_sensitive: bool = case_sensitive if isinstance(item, strings) else True - item = item if self.case_sensitive else (item.lower() if isinstance(item, str) else item) + item = item if self.case_sensitive or use_regexp else (item.lower() if isinstance(item, str) else item) _exclude_exact, self.exclude_glob_paths = separate_wildcard_and_exact_paths(set(exclude_paths) if exclude_paths else None) self.exclude_paths: SetOrdered = SetOrdered(_exclude_exact) if _exclude_exact else SetOrdered() self.exclude_regex_paths: List[Pattern[str]] = [re.compile(exclude_regex_path) for exclude_regex_path in exclude_regex_paths] @@ -127,7 +127,7 @@ def __init__(self, item = str(item) if self.use_regexp: try: - item = re.compile(item) + item = re.compile(item, flags=0 if self.case_sensitive else re.IGNORECASE) except TypeError as e: raise TypeError(f"The passed item of {item} is not usable for regex: {e}") from None self.strict_checking: bool = strict_checking @@ -236,7 +236,7 @@ def __search_dict(self, parents_ids_added = add_to_frozen_set(parents_ids, item_id) new_parent = parent_text % (parent, item_key_str) - new_parent_cased = new_parent if self.case_sensitive else new_parent.lower() + new_parent_cased = new_parent if self.case_sensitive or self.use_regexp else new_parent.lower() str_item = str(item) if (self.match_string and str_item == new_parent_cased) or\ @@ -282,7 +282,7 @@ def __search_iterable(self, def __search_str(self, obj: Union[str, bytes, memoryview], item: Union[str, bytes, memoryview, Pattern[str]], parent: str) -> None: """Compare strings""" - obj_text = obj if self.case_sensitive else (obj.lower() if isinstance(obj, str) else obj) + obj_text = obj if self.case_sensitive or self.use_regexp else (obj.lower() if isinstance(obj, str) else obj) is_matched = False if self.use_regexp and isinstance(item, type(re.compile(''))): diff --git a/tests/test_search.py b/tests/test_search.py index 3984349a..f9cd65f1 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -369,6 +369,34 @@ def test_regex_in_string(self): result = {"matched_values": {"root"}} assert DeepSearch(obj, item, verbose_level=1, use_regexp=True) == result + @pytest.mark.parametrize('case_sensitive', [False, True]) + @pytest.mark.parametrize('pattern, matching, nonmatching', [ + (r'\D+', 'ABC', '123'), + (r'\S+', 'ABC', ' '), + (r'\W+', '!', 'ABC'), + (r'\Boo\B', 'book', 'oo'), + (r'\N{LATIN CAPITAL LETTER A}', 'A', 'b'), + (r'(?-i:ABC)', 'ABC', 'abc'), + ]) + def test_regex_preserves_pattern_syntax(self, pattern, matching, nonmatching, case_sensitive): + result = DeepSearch([matching, nonmatching], pattern, use_regexp=True, case_sensitive=case_sensitive) + assert result == {'matched_values': {'root[0]'}} + + @pytest.mark.parametrize('pattern, obj, expected_path', [ + (r"\['\D+'\]$", {'ABC': 1, '123': 2}, "root['ABC']"), + (r"\['(?-i:ABC)'\]$", {'ABC': 1, 'abc': 2}, "root['ABC']"), + ]) + def test_regex_preserves_path_syntax(self, pattern, obj, expected_path): + assert DeepSearch(obj, pattern, use_regexp=True) == {'matched_paths': {expected_path}} + + @pytest.mark.parametrize('case_sensitive, expected_paths', [ + (False, {'root[0]', 'root[1]'}), + (True, {'root[0]'}), + ]) + def test_regex_case_sensitivity(self, case_sensitive, expected_paths): + result = DeepSearch(['ABC', 'abc', '123'], '[A-Z]+', use_regexp=True, case_sensitive=case_sensitive) + assert result == {'matched_values': expected_paths} + def test_regex_does_not_match_the_regex_string_itself(self): obj = ["We like python", "but not (?:p|t)ython"] item = "(?:p|t)ython"