11#!/usr/bin/env python
22import re
33from collections .abc import MutableMapping , Iterable
4+ from typing import Any , Dict , FrozenSet , List , Optional , Pattern , Set , Union , TYPE_CHECKING
45from deepdiff .helper import SetOrdered
56import logging
67
78from deepdiff .helper import (
89 strings , numbers , add_to_frozen_set , get_doc , dict_ , RE_COMPILED_TYPE , ipranges
910)
1011
12+ if TYPE_CHECKING :
13+ from typing_extensions import Self
14+
1115logger = logging .getLogger (__name__ )
1216
1317
1418doc = get_doc ('search_doc.rst' )
1519
1620
17- class DeepSearch (dict ):
21+ class DeepSearch (Dict [ str , Union [ Dict [ str , Any ], SetOrdered , List [ str ]]] ):
1822 r"""
1923 **DeepSearch**
2024
@@ -80,52 +84,56 @@ class DeepSearch(dict):
8084
8185 """
8286
83- warning_num = 0
87+ warning_num : int = 0
8488
8589 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 :
97101 if kwargs :
98102 raise ValueError ((
99103 "The following parameter(s) are not valid: %s\n "
100104 "The valid parameters are obj, item, exclude_paths, exclude_types,\n "
101105 "case_sensitive, match_string and verbose_level."
102106 ) % ', ' .join (kwargs .keys ()))
103107
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 (
111115 exclude_types ) # we need tuple for checking isinstance
112- self .verbose_level = verbose_level
116+ self .verbose_level : int = verbose_level
113117 self .update (
114118 matched_paths = self .__set_or_dict (),
115119 matched_values = self .__set_or_dict (),
116120 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
118126 if not strict_checking and (isinstance (item , numbers ) or isinstance (item , ipranges )):
119127 item = str (item )
120128 if self .use_regexp :
121129 try :
122130 item = re .compile (item )
123131 except TypeError as e :
124132 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
126134
127135 # Cases where user wants to match exact string item
128- self .match_string = match_string
136+ self .match_string : bool = match_string
129137
130138 self .__search (obj , item , parents_ids = frozenset ({id (obj )}))
131139
@@ -134,21 +142,25 @@ def __init__(self,
134142 for k in empty_keys :
135143 del self [k ]
136144
137- def __set_or_dict (self ):
145+ def __set_or_dict (self ) -> Union [ Dict [ str , Any ], SetOrdered ] :
138146 return dict_ () if self .verbose_level >= 2 else SetOrdered ()
139147
140- def __report (self , report_key , key , value ) :
148+ def __report (self , report_key : str , key : str , value : Any ) -> None :
141149 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
143153 else :
144- self [report_key ].add (key )
154+ report_set = self [report_key ]
155+ if isinstance (report_set , SetOrdered ):
156+ report_set .add (key )
145157
146158 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 :
152164 """Search objects"""
153165 found = False
154166 if obj == item :
@@ -170,14 +182,16 @@ def __search_obj(self,
170182 obj = {i : getattr (obj , i ) for i in obj .__slots__ }
171183 except AttributeError :
172184 if not found :
173- self ['unprocessed' ].append ("%s" % parent )
185+ unprocessed = self .get ('unprocessed' , [])
186+ if isinstance (unprocessed , list ):
187+ unprocessed .append ("%s" % parent )
174188
175189 return
176190
177191 self .__search_dict (
178192 obj , item , parent , parents_ids , print_as_attribute = True )
179193
180- def __skip_this (self , item , parent ) :
194+ def __skip_this (self , item : Any , parent : str ) -> bool :
181195 skip = False
182196 if parent in self .exclude_paths :
183197 skip = True
@@ -191,11 +205,11 @@ def __skip_this(self, item, parent):
191205 return skip
192206
193207 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 :
199213 """Search dictionaries"""
200214 if print_as_attribute :
201215 parent_text = "%s.%s"
@@ -238,10 +252,10 @@ def __search_dict(self,
238252 parents_ids = parents_ids_added )
239253
240254 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 :
245259 """Search iterables except dictionaries, sets and strings."""
246260 for i , thing in enumerate (obj ):
247261 new_parent = "{}[{}]" .format (parent , i )
@@ -251,7 +265,7 @@ def __search_iterable(self,
251265 if self .case_sensitive or not isinstance (thing , strings ):
252266 thing_cased = thing
253267 else :
254- thing_cased = thing .lower ()
268+ thing_cased = thing .lower () if isinstance ( thing , str ) else thing
255269
256270 if not self .use_regexp and thing_cased == item :
257271 self .__report (
@@ -264,19 +278,19 @@ def __search_iterable(self,
264278 self .__search (thing , item , "%s[%s]" %
265279 (parent , i ), parents_ids_added )
266280
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 :
268282 """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 )
270284
271285 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 ) ):
275289 is_matched = True
276290 if is_matched :
277291 self .__report (report_key = 'matched_values' , key = parent , value = obj )
278292
279- def __search_numbers (self , obj , item , parent ) :
293+ def __search_numbers (self , obj : Any , item : Any , parent : str ) -> None :
280294 if (
281295 item == obj or (
282296 not self .strict_checking and (
@@ -288,11 +302,11 @@ def __search_numbers(self, obj, item, parent):
288302 ):
289303 self .__report (report_key = 'matched_values' , key = parent , value = obj )
290304
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 :
292306 # Checking to see if it has _fields. Which probably means it is a named
293307 # tuple.
294308 try :
295- obj . _asdict
309+ getattr ( obj , ' _asdict' )
296310 # It must be a normal tuple
297311 except AttributeError :
298312 self .__search_iterable (obj , item , parent , parents_ids )
@@ -301,7 +315,7 @@ def __search_tuple(self, obj, item, parent, parents_ids):
301315 self .__search_obj (
302316 obj , item , parent , parents_ids , is_namedtuple = True )
303317
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 :
305319 """The main search method"""
306320 if self .__skip_this (item , parent ):
307321 return
@@ -344,12 +358,12 @@ class grep:
344358 __doc__ = doc
345359
346360 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
351365
352- def __ror__ (self , other ) :
366+ def __ror__ (self , other : Any ) -> "DeepSearch" :
353367 return DeepSearch (obj = other , item = self .item , ** self .kwargs )
354368
355369
0 commit comments