1
1
#!/usr/bin/env python
2
2
import re
3
3
from collections .abc import MutableMapping , Iterable
4
+ from typing import Any , Dict , FrozenSet , List , Optional , Pattern , Set , Union , TYPE_CHECKING
4
5
from deepdiff .helper import SetOrdered
5
6
import logging
6
7
7
8
from deepdiff .helper import (
8
9
strings , numbers , add_to_frozen_set , get_doc , dict_ , RE_COMPILED_TYPE , ipranges
9
10
)
10
11
12
+ if TYPE_CHECKING :
13
+ from typing_extensions import Self
14
+
11
15
logger = logging .getLogger (__name__ )
12
16
13
17
14
18
doc = get_doc ('search_doc.rst' )
15
19
16
20
17
- class DeepSearch (dict ):
21
+ class DeepSearch (Dict [ str , Union [ Dict [ str , Any ], SetOrdered , List [ str ]]] ):
18
22
r"""
19
23
**DeepSearch**
20
24
@@ -80,52 +84,56 @@ class DeepSearch(dict):
80
84
81
85
"""
82
86
83
- warning_num = 0
87
+ warning_num : int = 0
84
88
85
89
def __init__ (self ,
86
- obj ,
87
- item ,
88
- exclude_paths = SetOrdered (),
89
- exclude_regex_paths = SetOrdered (),
90
- exclude_types = SetOrdered (),
91
- verbose_level = 1 ,
92
- case_sensitive = False ,
93
- match_string = False ,
94
- use_regexp = False ,
95
- strict_checking = True ,
96
- ** kwargs ) :
90
+ obj : Any ,
91
+ item : Any ,
92
+ exclude_paths : Union [ SetOrdered , Set [ str ], List [ str ]] = SetOrdered (),
93
+ exclude_regex_paths : Union [ SetOrdered , Set [ Union [ str , Pattern [ str ]]], List [ Union [ str , Pattern [ str ]]]] = SetOrdered (),
94
+ exclude_types : Union [ SetOrdered , Set [ type ], List [ type ]] = SetOrdered (),
95
+ verbose_level : int = 1 ,
96
+ case_sensitive : bool = False ,
97
+ match_string : bool = False ,
98
+ use_regexp : bool = False ,
99
+ strict_checking : bool = True ,
100
+ ** kwargs : Any ) -> None :
97
101
if kwargs :
98
102
raise ValueError ((
99
103
"The following parameter(s) are not valid: %s\n "
100
104
"The valid parameters are obj, item, exclude_paths, exclude_types,\n "
101
105
"case_sensitive, match_string and verbose_level."
102
106
) % ', ' .join (kwargs .keys ()))
103
107
104
- self .obj = obj
105
- self .case_sensitive = case_sensitive if isinstance (item , strings ) else True
106
- item = item if self .case_sensitive else item .lower ()
107
- self .exclude_paths = SetOrdered (exclude_paths )
108
- self .exclude_regex_paths = [re .compile (exclude_regex_path ) for exclude_regex_path in exclude_regex_paths ]
109
- self .exclude_types = SetOrdered (exclude_types )
110
- self .exclude_types_tuple = tuple (
108
+ self .obj : Any = obj
109
+ self .case_sensitive : bool = case_sensitive if isinstance (item , strings ) else True
110
+ item = item if self .case_sensitive else ( item .lower () if isinstance ( item , str ) else item )
111
+ self .exclude_paths : SetOrdered = SetOrdered (exclude_paths )
112
+ self .exclude_regex_paths : List [ Pattern [ str ]] = [re .compile (exclude_regex_path ) for exclude_regex_path in exclude_regex_paths ]
113
+ self .exclude_types : SetOrdered = SetOrdered (exclude_types )
114
+ self .exclude_types_tuple : tuple [ type , ...] = tuple (
111
115
exclude_types ) # we need tuple for checking isinstance
112
- self .verbose_level = verbose_level
116
+ self .verbose_level : int = verbose_level
113
117
self .update (
114
118
matched_paths = self .__set_or_dict (),
115
119
matched_values = self .__set_or_dict (),
116
120
unprocessed = [])
117
- self .use_regexp = use_regexp
121
+ # Type narrowing for mypy/pyright
122
+ self .matched_paths : Union [Dict [str , Any ], SetOrdered ]
123
+ self .matched_values : Union [Dict [str , Any ], SetOrdered ]
124
+ self .unprocessed : List [str ]
125
+ self .use_regexp : bool = use_regexp
118
126
if not strict_checking and (isinstance (item , numbers ) or isinstance (item , ipranges )):
119
127
item = str (item )
120
128
if self .use_regexp :
121
129
try :
122
130
item = re .compile (item )
123
131
except TypeError as e :
124
132
raise TypeError (f"The passed item of { item } is not usable for regex: { e } " ) from None
125
- self .strict_checking = strict_checking
133
+ self .strict_checking : bool = strict_checking
126
134
127
135
# Cases where user wants to match exact string item
128
- self .match_string = match_string
136
+ self .match_string : bool = match_string
129
137
130
138
self .__search (obj , item , parents_ids = frozenset ({id (obj )}))
131
139
@@ -134,21 +142,25 @@ def __init__(self,
134
142
for k in empty_keys :
135
143
del self [k ]
136
144
137
- def __set_or_dict (self ):
145
+ def __set_or_dict (self ) -> Union [ Dict [ str , Any ], SetOrdered ] :
138
146
return dict_ () if self .verbose_level >= 2 else SetOrdered ()
139
147
140
- def __report (self , report_key , key , value ) :
148
+ def __report (self , report_key : str , key : str , value : Any ) -> None :
141
149
if self .verbose_level >= 2 :
142
- self [report_key ][key ] = value
150
+ report_dict = self [report_key ]
151
+ if isinstance (report_dict , dict ):
152
+ report_dict [key ] = value
143
153
else :
144
- self [report_key ].add (key )
154
+ report_set = self [report_key ]
155
+ if isinstance (report_set , SetOrdered ):
156
+ report_set .add (key )
145
157
146
158
def __search_obj (self ,
147
- obj ,
148
- item ,
149
- parent ,
150
- parents_ids = frozenset (),
151
- is_namedtuple = False ):
159
+ obj : Any ,
160
+ item : Any ,
161
+ parent : str ,
162
+ parents_ids : FrozenSet [ int ] = frozenset (),
163
+ is_namedtuple : bool = False ) -> None :
152
164
"""Search objects"""
153
165
found = False
154
166
if obj == item :
@@ -170,14 +182,16 @@ def __search_obj(self,
170
182
obj = {i : getattr (obj , i ) for i in obj .__slots__ }
171
183
except AttributeError :
172
184
if not found :
173
- self ['unprocessed' ].append ("%s" % parent )
185
+ unprocessed = self .get ('unprocessed' , [])
186
+ if isinstance (unprocessed , list ):
187
+ unprocessed .append ("%s" % parent )
174
188
175
189
return
176
190
177
191
self .__search_dict (
178
192
obj , item , parent , parents_ids , print_as_attribute = True )
179
193
180
- def __skip_this (self , item , parent ) :
194
+ def __skip_this (self , item : Any , parent : str ) -> bool :
181
195
skip = False
182
196
if parent in self .exclude_paths :
183
197
skip = True
@@ -191,11 +205,11 @@ def __skip_this(self, item, parent):
191
205
return skip
192
206
193
207
def __search_dict (self ,
194
- obj ,
195
- item ,
196
- parent ,
197
- parents_ids = frozenset (),
198
- print_as_attribute = False ):
208
+ obj : Union [ Dict [ Any , Any ], MutableMapping [ Any , Any ]] ,
209
+ item : Any ,
210
+ parent : str ,
211
+ parents_ids : FrozenSet [ int ] = frozenset (),
212
+ print_as_attribute : bool = False ) -> None :
199
213
"""Search dictionaries"""
200
214
if print_as_attribute :
201
215
parent_text = "%s.%s"
@@ -238,10 +252,10 @@ def __search_dict(self,
238
252
parents_ids = parents_ids_added )
239
253
240
254
def __search_iterable (self ,
241
- obj ,
242
- item ,
243
- parent = "root" ,
244
- parents_ids = frozenset ()):
255
+ obj : Iterable [ Any ] ,
256
+ item : Any ,
257
+ parent : str = "root" ,
258
+ parents_ids : FrozenSet [ int ] = frozenset ()) -> None :
245
259
"""Search iterables except dictionaries, sets and strings."""
246
260
for i , thing in enumerate (obj ):
247
261
new_parent = "{}[{}]" .format (parent , i )
@@ -251,7 +265,7 @@ def __search_iterable(self,
251
265
if self .case_sensitive or not isinstance (thing , strings ):
252
266
thing_cased = thing
253
267
else :
254
- thing_cased = thing .lower ()
268
+ thing_cased = thing .lower () if isinstance ( thing , str ) else thing
255
269
256
270
if not self .use_regexp and thing_cased == item :
257
271
self .__report (
@@ -264,19 +278,19 @@ def __search_iterable(self,
264
278
self .__search (thing , item , "%s[%s]" %
265
279
(parent , i ), parents_ids_added )
266
280
267
- def __search_str (self , obj , item , parent ) :
281
+ def __search_str (self , obj : Union [ str , bytes , memoryview ], item : Union [ str , bytes , memoryview , Pattern [ str ]], parent : str ) -> None :
268
282
"""Compare strings"""
269
- obj_text = obj if self .case_sensitive else obj .lower ()
283
+ obj_text = obj if self .case_sensitive else ( obj .lower () if isinstance ( obj , str ) else obj )
270
284
271
285
is_matched = False
272
- if self .use_regexp :
273
- is_matched = item .search (obj_text )
274
- elif (self .match_string and item == obj_text ) or (not self .match_string and item in obj_text ):
286
+ if self .use_regexp and isinstance ( item , type ( re . compile ( '' ))) :
287
+ is_matched = bool ( item .search (str ( obj_text )) )
288
+ elif (self .match_string and str ( item ) == str ( obj_text )) or (not self .match_string and str ( item ) in str ( obj_text ) ):
275
289
is_matched = True
276
290
if is_matched :
277
291
self .__report (report_key = 'matched_values' , key = parent , value = obj )
278
292
279
- def __search_numbers (self , obj , item , parent ) :
293
+ def __search_numbers (self , obj : Any , item : Any , parent : str ) -> None :
280
294
if (
281
295
item == obj or (
282
296
not self .strict_checking and (
@@ -288,11 +302,11 @@ def __search_numbers(self, obj, item, parent):
288
302
):
289
303
self .__report (report_key = 'matched_values' , key = parent , value = obj )
290
304
291
- def __search_tuple (self , obj , item , parent , parents_ids ) :
305
+ def __search_tuple (self , obj : tuple [ Any , ...], item : Any , parent : str , parents_ids : FrozenSet [ int ]) -> None :
292
306
# Checking to see if it has _fields. Which probably means it is a named
293
307
# tuple.
294
308
try :
295
- obj . _asdict
309
+ getattr ( obj , ' _asdict' )
296
310
# It must be a normal tuple
297
311
except AttributeError :
298
312
self .__search_iterable (obj , item , parent , parents_ids )
@@ -301,7 +315,7 @@ def __search_tuple(self, obj, item, parent, parents_ids):
301
315
self .__search_obj (
302
316
obj , item , parent , parents_ids , is_namedtuple = True )
303
317
304
- def __search (self , obj , item , parent = "root" , parents_ids = frozenset ()):
318
+ def __search (self , obj : Any , item : Any , parent : str = "root" , parents_ids : FrozenSet [ int ] = frozenset ()) -> None :
305
319
"""The main search method"""
306
320
if self .__skip_this (item , parent ):
307
321
return
@@ -344,12 +358,12 @@ class grep:
344
358
__doc__ = doc
345
359
346
360
def __init__ (self ,
347
- item ,
348
- ** kwargs ) :
349
- self .item = item
350
- self .kwargs = kwargs
361
+ item : Any ,
362
+ ** kwargs : Any ) -> None :
363
+ self .item : Any = item
364
+ self .kwargs : Dict [ str , Any ] = kwargs
351
365
352
- def __ror__ (self , other ) :
366
+ def __ror__ (self , other : Any ) -> "DeepSearch" :
353
367
return DeepSearch (obj = other , item = self .item , ** self .kwargs )
354
368
355
369
0 commit comments