Repository: seperman/deepdiff

Issue:
Support searching with regular expressions
I'd like to use regular expressions to find elements of a dictionary with `grep`.

```python
(Pdb) entity_attributes
{'alone': {'id_': 'alone', 'name': 'Timothy', 'last_name': 'Jackson', 'country': 'Korea', 'rating': 5491}, 'film': {'id_': 'film', 'name': 'David', 'last_name': 'Howell', 'country': 'Moldova', 'rating': 9731}, 'thought': {'id_': 'thought'
, 'name': 'Jason', 'last_name': 'Roberts', 'country': 'Western Sahara', 'rating': 5484}}
(Pdb) entity_attributes | grep('Da')
{'matched_values': ["root['film']['name']"]}
(Pdb) entity_attributes | grep('Da.*')
{}
```

If you give me some guidance on how to do it, I can make a PR

Discussion:

Patch:
diff --git a/deepdiff/search.py b/deepdiff/search.py
index 4226a6a..5233b10 100644
--- a/deepdiff/search.py
+++ b/deepdiff/search.py
@@ -40,6 +40,8 @@ class DeepSearch(dict):
         If True, the value of the object or its children have to exactly match the item.
         If False, the value of the item can be a part of the value of the object or its children
 
+    use_regexp: Boolean, default = False
+
     **Returns**
 
         A DeepSearch object that has the matched paths and matched values.
@@ -83,6 +85,7 @@ class DeepSearch(dict):
                  verbose_level=1,
                  case_sensitive=False,
                  match_string=False,
+                 use_regexp=False,
                  **kwargs):
         if kwargs:
             raise ValueError((
@@ -104,6 +107,9 @@ class DeepSearch(dict):
             matched_paths=self.__set_or_dict(),
             matched_values=self.__set_or_dict(),
             unprocessed=[])
+        self.use_regexp = use_regexp
+        if self.use_regexp:
+            self.search_regexp = re.compile(item)
 
         # Cases where user wants to match exact string item
         self.match_string = match_string
@@ -135,7 +141,7 @@ class DeepSearch(dict):
         if obj == item:
             found = True
             # We report the match but also continue inside the match to see if there are
-            # furthur matches inside the `looped` object.
+            # further matches inside the `looped` object.
             self.__report(report_key='matched_values', key=parent, value=obj)
 
         try:
@@ -205,7 +211,8 @@ class DeepSearch(dict):
 
             str_item = str(item)
             if (self.match_string and str_item == new_parent_cased) or\
-               (not self.match_string and str_item in new_parent_cased):
+               (not self.match_string and str_item in new_parent_cased) or\
+               (self.use_regexp and self.search_regexp.search(new_parent_cased)):
                 self.__report(
                     report_key='matched_paths',
                     key=new_parent,
@@ -233,7 +240,9 @@ class DeepSearch(dict):
             else:
                 thing_cased = thing.lower()
 
-            if thing_cased == item:
+            if thing_cased == item or \
+                    (isinstance(thing_cased, str) and isinstance(item, str) and \
+                    self.use_regexp and self.search_regexp.search(thing_cased)):
                 self.__report(
                     report_key='matched_values', key=new_parent, value=thing)
             else:
@@ -248,7 +257,9 @@ class DeepSearch(dict):
         """Compare strings"""
         obj_text = obj if self.case_sensitive else obj.lower()
 
-        if (self.match_string and item == obj_text) or (not self.match_string and item in obj_text):
+        if (self.match_string and item == obj_text) or \
+            (not self.match_string and item in obj_text) or \
+            (self.use_regexp and self.search_regexp.search(obj_text)):
             self.__report(report_key='matched_values', key=parent, value=obj)
 
     def __search_numbers(self, obj, item, parent):
diff --git a/requirements-cli.txt b/requirements-cli.txt
index 361b58b..16e2df7 100644
--- a/requirements-cli.txt
+++ b/requirements-cli.txt
@@ -1,4 +1,4 @@
 click==7.1.2
-pyyaml==5.4
+pyyaml==5.3.1
 toml==0.10.2
 clevercsv==0.6.6


Test patch:
diff --git a/tests/test_search.py b/tests/test_search.py
index 247e648..51150bb 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -336,6 +336,89 @@ class TestDeepSearch:
         result = {'matched_values': {'root.a'}}
         assert DeepSearch(obj, item, verbose_level=1) == result
 
+    def test_dont_use_regex_by_default(self):
+        obj = "long string somewhere"
+        item = "some.*"
+        result = {}
+        assert DeepSearch(obj, item, verbose_level=1) == result
+
+    def test_regex_in_string(self):
+        obj = "long string somewhere"
+        item = "some.*"
+        result = {"matched_values": {"root"}}
+        assert DeepSearch(obj, item, verbose_level=1, use_regexp=True) == result
+
+    def test_regex_in_string_in_tuple(self):
+        obj = ("long", "string", 0, "somewhere")
+        item = "some.*"
+        result = {"matched_values": {"root[3]"}}
+        assert DeepSearch(obj, item, verbose_level=1, use_regexp=True) == result
+
+    def test_regex_in_string_in_list(self):
+        obj = ["long", "string", 0, "somewhere"]
+        item = "some.*"
+        result = {"matched_values": {"root[3]"}}
+        assert DeepSearch(obj, item, verbose_level=1, use_regexp=True) == result
+
+    def test_regex_in_string_in_dictionary(self):
+        obj = {"long": "somewhere", "string": 2, 0: 0, "somewhere": "around"}
+        result = {
+            "matched_paths": {"root['somewhere']"},
+            "matched_values": {"root['long']"},
+        }
+        item = "some.*"
+        ds = DeepSearch(obj, item, verbose_level=1, use_regexp=True)
+        assert ds == result
+
+    def test_regex_in_string_in_dictionary_in_list_verbose(self):
+        obj = [
+            "something somewhere",
+            {"long": "somewhere", "string": 2, 0: 0, "somewhere": "around"},
+        ]
+        result = {
+            "matched_paths": {"root[1]['somewhere']": "around"},
+            "matched_values": {
+                "root[1]['long']": "somewhere",
+                "root[0]": "something somewhere",
+            },
+        }
+        item = "some.*"
+        ds = DeepSearch(obj, item, verbose_level=2, use_regexp=True)
+        assert ds == result
+
+    def test_regex_in_custom_object(self):
+        obj = CustomClass("here, something", "somewhere")
+        result = {"matched_values": {"root.b"}}
+        item = "somew.*"
+        ds = DeepSearch(obj, item, verbose_level=1, use_regexp=True)
+        assert ds == result
+
+    def test_regex_in_custom_object_in_dictionary_verbose(self):
+        obj = {1: CustomClass("here, something", "somewhere out there")}
+        result = {"matched_values": {"root[1].b": "somewhere out there"}}
+        item = "somew.*"
+        ds = DeepSearch(obj, item, verbose_level=2, use_regexp=True)
+        assert ds == result
+
+    def test_regex_in_named_tuples_verbose(self):
+        from collections import namedtuple
+
+        Point = namedtuple("Point", ["x", "somewhere_good"])
+        obj = Point(x="my keys are somewhere", somewhere_good=22)
+        item = "some.*"
+        ds = DeepSearch(obj, item, verbose_level=2, use_regexp=True)
+        result = {
+            "matched_values": {"root.x": "my keys are somewhere"},
+            "matched_paths": {"root.somewhere_good": 22},
+        }
+        assert ds == result
+
+    def test_regex_in_string_in_set_verbose(self):
+        obj = {"long", "string", 0, "somewhere"}
+        # result = {"matched_values": {'root[3]': "somewhere"}}
+        item = "some.*"
+        ds = DeepSearch(obj, item, verbose_level=2, use_regexp=True)
+        assert list(ds["matched_values"].values())[0] == "somewhere"
 
 class TestGrep: