Skip to content

Commit 5ae448b

Browse files
committed
Adding grep
1 parent cd1387c commit 5ae448b

File tree

4 files changed

+110
-15
lines changed

4 files changed

+110
-15
lines changed

deepdiff/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s')
55

66
from .diff import DeepDiff
7-
from .search import DeepSearch
7+
from .search import DeepSearch, grep
88
from .contenthash import DeepHash
99
from .helper import py3

deepdiff/helper.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,22 @@ class Verbose(object):
113113
Global verbose level
114114
"""
115115
level = 1
116+
117+
118+
if py3:
119+
class filtered(filter):
120+
121+
@property
122+
def listed(self):
123+
self._listed = getattr(self, "_listed", list(self))
124+
return self._listed
125+
126+
def __str__(self):
127+
return str(self.listed)
128+
129+
__repr__ = __str__
130+
131+
def __eq__(self, other):
132+
return self.listed == other
133+
else:
134+
filtered = filter

deepdiff/search.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections import MutableMapping
1111
import logging
1212

13-
from deepdiff.helper import py3, strings, numbers, items
13+
from deepdiff.helper import py3, strings, numbers, items, filtered
1414

1515
logger = logging.getLogger(__name__)
1616

@@ -77,6 +77,7 @@ def __init__(self,
7777
exclude_paths=set(),
7878
exclude_types=set(),
7979
verbose_level=1,
80+
case_sensitive=False,
8081
**kwargs):
8182
if kwargs:
8283
raise ValueError((
@@ -85,7 +86,8 @@ def __init__(self,
8586
) % ', '.join(kwargs.keys()))
8687

8788
self.obj = obj
88-
self.item = item
89+
self.case_sensitive = case_sensitive if isinstance(item, strings) else True
90+
item = item if self.case_sensitive else item.lower()
8991
self.exclude_paths = set(exclude_paths)
9092
self.exclude_types = set(exclude_types)
9193
self.exclude_types_tuple = tuple(
@@ -180,8 +182,9 @@ def __search_dict(self,
180182
parents_ids_added = self.__add_to_frozen_set(parents_ids, item_id)
181183

182184
new_parent = parent_text % (parent, item_key_str)
185+
new_parent_cased = new_parent if self.case_sensitive else new_parent.lower()
183186

184-
if str(item) in new_parent:
187+
if str(item) in new_parent_cased:
185188
self.__report(
186189
report_key='matched_paths',
187190
key=new_parent,
@@ -200,25 +203,31 @@ def __search_iterable(self,
200203
parents_ids=frozenset({})):
201204
"""Search iterables except dictionaries, sets and strings."""
202205

203-
for i, x in enumerate(obj):
206+
for i, thing in enumerate(obj):
204207
new_parent = "%s[%s]" % (parent, i)
205-
if self.__skip_this(x, parent=new_parent):
208+
if self.__skip_this(thing, parent=new_parent):
206209
continue
207-
if x == item:
210+
211+
if self.case_sensitive or not isinstance(thing, strings):
212+
thing_cased = thing
213+
else:
214+
thing_cased = thing.lower()
215+
216+
if thing_cased == item:
208217
self.__report(
209-
report_key='matched_values', key=new_parent, value=x)
218+
report_key='matched_values', key=new_parent, value=thing)
210219
else:
211-
item_id = id(x)
220+
item_id = id(thing)
212221
if parents_ids and item_id in parents_ids:
213222
continue
214-
parents_ids_added = self.__add_to_frozen_set(parents_ids,
215-
item_id)
216-
self.__search(x, item, "%s[%s]" %
223+
parents_ids_added = self.__add_to_frozen_set(parents_ids, item_id)
224+
self.__search(thing, item, "%s[%s]" %
217225
(parent, i), parents_ids_added)
218226

219227
def __search_str(self, obj, item, parent):
220228
"""Compare strings"""
221-
if item in obj:
229+
obj_text = obj if self.case_sensitive else obj.lower()
230+
if item in obj_text:
222231
self.__report(report_key='matched_values', key=parent, value=obj)
223232

224233
def __search_numbers(self, obj, item, parent):
@@ -275,6 +284,21 @@ def __search(self, obj, item, parent="root", parents_ids=frozenset({})):
275284
self.__search_obj(obj, item, parent, parents_ids)
276285

277286

287+
class grep(object):
288+
"""
289+
Grep!
290+
"""
291+
292+
def __init__(self,
293+
item,
294+
**kwargs):
295+
self.item = item
296+
self.kwargs = kwargs
297+
298+
def __ror__(self, other):
299+
return DeepSearch(obj=other, item=self.item, **self.kwargs)
300+
301+
278302
if __name__ == "__main__": # pragma: no cover
279303
if not py3:
280304
sys.exit("Please run with Python 3 to verify the doc strings.")

tests/test_search.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
nosetests --with-coverage --cover-package=deepdiff
1212
1313
To run a specific test, run this from the root of repo:
14-
nosetests .\tests\search_tests.py:DeepSearchTestCase.test_string_in_root
14+
nosetests tests/test_search.py:DeepSearchTestCase.test_case_insensitive_of_str_in_list
1515
"""
1616
import unittest
17-
from deepdiff import DeepSearch
17+
from deepdiff import DeepSearch, grep
1818
import logging
1919
logging.disable(logging.CRITICAL)
2020

@@ -98,6 +98,23 @@ def test_string_in_dictionary(self):
9898
ds = DeepSearch(obj, item, verbose_level=1)
9999
self.assertEqual(ds, result)
100100

101+
def test_string_in_dictionary_case_insensitive(self):
102+
obj = {"long": "Somewhere over there!", "string": 2, 0: 0, "SOMEWHERE": "around"}
103+
result = {
104+
'matched_paths': {"root['SOMEWHERE']"},
105+
'matched_values': {"root['long']"}
106+
}
107+
ds = DeepSearch(obj, item, verbose_level=1, case_sensitive=False)
108+
self.assertEqual(ds, result)
109+
110+
def test_string_in_dictionary_key_case_insensitive_partial(self):
111+
obj = {"SOMEWHERE here": "around"}
112+
result = {
113+
'matched_paths': {"root['SOMEWHERE here']"}
114+
}
115+
ds = DeepSearch(obj, item, verbose_level=1, case_sensitive=False)
116+
self.assertEqual(ds, result)
117+
101118
def test_string_in_dictionary_verbose(self):
102119
obj = {"long": "somewhere", "string": 2, 0: 0, "somewhere": "around"}
103120
result = {
@@ -244,3 +261,38 @@ def __str__(self):
244261
self.assertEqual(ds, result)
245262
ds = DeepSearch(obj, item, verbose_level=2)
246263
self.assertEqual(ds, result)
264+
265+
def test_case_insensitive_of_str_in_list(self):
266+
obj = ["a", "bb", "BBC", "aBbB"]
267+
item = "BB"
268+
result = {"matched_values": {'root[1]', 'root[2]', 'root[3]'}}
269+
self.assertEqual(DeepSearch(obj, item, verbose_level=1, case_sensitive=False), result)
270+
271+
def test_case_sensitive_of_str_in_list(self):
272+
obj = ["a", "bb", "BBC", "aBbB"]
273+
item = "BB"
274+
result = {"matched_values": {'root[2]'}}
275+
self.assertEqual(DeepSearch(obj, item, verbose_level=1, case_sensitive=True), result)
276+
277+
def test_case_sensitive_of_str_in_one_liner(self):
278+
obj = "Hello, what's up?"
279+
item = "WHAT"
280+
result = {}
281+
self.assertEqual(DeepSearch(obj, item, verbose_level=1, case_sensitive=True), result)
282+
283+
def test_case_insensitive_of_str_in_one_liner(self):
284+
obj = "Hello, what's up?"
285+
item = "WHAT"
286+
result = {'matched_values': {'root'}}
287+
self.assertEqual(DeepSearch(obj, item, verbose_level=1, case_sensitive=False), result)
288+
289+
290+
class GrepTestCase(unittest.TestCase):
291+
292+
def test_grep_dict(self):
293+
obj = {
294+
"for life": "vegan",
295+
"ingredients": ["no meat", "no eggs", "no dairy", "somewhere"]
296+
}
297+
ds = obj | grep(item)
298+
self.assertEqual(ds, {'matched_values': {"root['ingredients'][3]"}})

0 commit comments

Comments
 (0)