55# You might need to run it many times since dictionaries come in different orders
66# every time you run the docstrings.
77# However the docstring expects it in a specific order in order to pass!
8+ import pytz
89import difflib
910import logging
1011import types
1112import datetime
1213from enum import Enum
1314from copy import deepcopy
1415from math import isclose as is_close
15- from typing import List , Dict , Callable , Union , Any , Pattern , Tuple , Optional
16+ from typing import List , Dict , Callable , Union , Any , Pattern , Tuple , Optional , Set , FrozenSet
1617from collections .abc import Mapping , Iterable , Sequence
1718from collections import defaultdict
1819from inspect import getmembers
@@ -110,6 +111,8 @@ def _report_progress(_stats, progress_logger, duration):
110111 'ignore_private_variables' ,
111112 'encodings' ,
112113 'ignore_encoding_errors' ,
114+ 'default_timezone' ,
115+ 'custom_operators' ,
113116)
114117
115118
@@ -128,10 +131,11 @@ def __init__(self,
128131 custom_operators : Optional [List [Any ]] = None ,
129132 cutoff_distance_for_pairs : float = CUTOFF_DISTANCE_FOR_PAIRS_DEFAULT ,
130133 cutoff_intersection_for_pairs : float = CUTOFF_INTERSECTION_FOR_PAIRS_DEFAULT ,
134+ default_timezone :Union [datetime .timezone , datetime .timezone , pytz .tzinfo .BaseTzInfo ]= datetime .timezone .utc ,
131135 encodings : Optional [List [str ]]= None ,
132136 exclude_obj_callback : Optional [Callable ]= None ,
133137 exclude_obj_callback_strict : Optional [Callable ]= None ,
134- exclude_paths : Union [str , List [str ], None ]= None ,
138+ exclude_paths : Union [str , List [str ], Set [ str ], FrozenSet [ str ], None ]= None ,
135139 exclude_regex_paths : Union [str , List [str ], Pattern [str ], List [Pattern [str ]], None ]= None ,
136140 exclude_types : Optional [List [Any ]]= None ,
137141 get_deep_distance : bool = False ,
@@ -154,6 +158,8 @@ def __init__(self,
154158 include_paths : Union [str , List [str ], None ]= None ,
155159 iterable_compare_func : Optional [Callable ]= None ,
156160 log_frequency_in_sec : int = 0 ,
161+ log_scale_similarity_threshold : float = 0.1 ,
162+ log_stacktrace : bool = False ,
157163 math_epsilon : Optional [float ]= None ,
158164 max_diffs : Optional [int ]= None ,
159165 max_passes : int = 10000000 ,
@@ -162,11 +168,10 @@ def __init__(self,
162168 progress_logger : Callable = logger .info ,
163169 report_repetition : bool = False ,
164170 significant_digits : Optional [int ]= None ,
165- use_log_scale : bool = False ,
166- log_scale_similarity_threshold : float = 0.1 ,
167171 threshold_to_diff_deeper : float = 0.33 ,
168172 truncate_datetime : Optional [str ]= None ,
169173 use_enum_value : bool = False ,
174+ use_log_scale : bool = False ,
170175 verbose_level : int = 1 ,
171176 view : str = TEXT_VIEW ,
172177 zip_ordered_iterables : bool = False ,
@@ -183,8 +188,8 @@ def __init__(self,
183188 "ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, "
184189 "view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, "
185190 "cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, "
186- "cache_tuning_sample_size, get_deep_distance, group_by, group_by_sort_key, cache_purge_level, "
187- "math_epsilon, iterable_compare_func, use_enum_value, _original_type, threshold_to_diff_deeper, "
191+ "cache_tuning_sample_size, get_deep_distance, group_by, group_by_sort_key, cache_purge_level, log_stacktrace, "
192+ "math_epsilon, iterable_compare_func, use_enum_value, _original_type, threshold_to_diff_deeper, default_timezone "
188193 "ignore_order_func, custom_operators, encodings, ignore_encoding_errors, use_log_scale, log_scale_similarity_threshold "
189194 "_parameters and _shared_parameters." ) % ', ' .join (kwargs .keys ()))
190195
@@ -205,6 +210,8 @@ def __init__(self,
205210 self .use_enum_value = use_enum_value
206211 self .log_scale_similarity_threshold = log_scale_similarity_threshold
207212 self .use_log_scale = use_log_scale
213+ self .default_timezone = default_timezone
214+ self .log_stacktrace = log_stacktrace
208215 self .threshold_to_diff_deeper = threshold_to_diff_deeper
209216 self .ignore_string_type_changes = ignore_string_type_changes
210217 self .ignore_type_in_groups = self .get_ignore_types_in_groups (
@@ -272,6 +279,10 @@ def _group_by_sort_key(x):
272279 self .cache_size = cache_size
273280 _parameters = self .__dict__ .copy ()
274281 _parameters ['group_by' ] = None # overwriting since these parameters will be passed on to other passes.
282+ if log_stacktrace :
283+ self .log_err = logger .exception
284+ else :
285+ self .log_err = logger .error
275286
276287 # Non-Root
277288 if _shared_parameters :
@@ -732,7 +743,7 @@ def _compare_in_order(
732743 self , level ,
733744 t1_from_index = None , t1_to_index = None ,
734745 t2_from_index = None , t2_to_index = None
735- ):
746+ ) -> List [ Tuple [ Tuple [ int , int ], Tuple [ Any , Any ]]] :
736747 """
737748 Default compare if `iterable_compare_func` is not provided.
738749 This will compare in sequence order.
@@ -752,7 +763,7 @@ def _get_matching_pairs(
752763 self , level ,
753764 t1_from_index = None , t1_to_index = None ,
754765 t2_from_index = None , t2_to_index = None
755- ):
766+ ) -> List [ Tuple [ Tuple [ int , int ], Tuple [ Any , Any ]]] :
756767 """
757768 Given a level get matching pairs. This returns list of two tuples in the form:
758769 [
@@ -1084,44 +1095,43 @@ def _create_hashtable(self, level, t):
10841095 # It only includes the ones needed when comparing iterables.
10851096 # The self.hashes dictionary gets shared between different runs of DeepHash
10861097 # So that any object that is already calculated to have a hash is not re-calculated.
1087- deep_hash = DeepHash (item ,
1088- hashes = self .hashes ,
1089- parent = parent ,
1090- apply_hash = True ,
1091- ** self .deephash_parameters ,
1092- )
1098+ deep_hash = DeepHash (
1099+ item ,
1100+ hashes = self .hashes ,
1101+ parent = parent ,
1102+ apply_hash = True ,
1103+ ** self .deephash_parameters ,
1104+ )
10931105 except UnicodeDecodeError as err :
10941106 err .reason = f"Can not produce a hash for { level .path ()} : { err .reason } "
10951107 raise
1096- except Exception as e : # pragma: no cover
1097- logger .error ("Can not produce a hash for %s."
1098- "Not counting this object.\n %s" %
1099- (level .path (), e ))
1108+ except NotImplementedError :
1109+ raise
1110+ # except Exception as e: # pragma: no cover
1111+ # logger.error("Can not produce a hash for %s."
1112+ # "Not counting this object.\n %s" %
1113+ # (level.path(), e))
11001114 else :
11011115 try :
11021116 item_hash = deep_hash [item ]
11031117 except KeyError :
11041118 pass
11051119 else :
11061120 if item_hash is unprocessed : # pragma: no cover
1107- logger . warning ("Item %s was not processed while hashing "
1121+ self . log_err ("Item %s was not processed while hashing "
11081122 "thus not counting this object." %
11091123 level .path ())
11101124 else :
11111125 self ._add_hash (hashes = local_hashes , item_hash = item_hash , item = item , i = i )
11121126
11131127 # Also we hash the iterables themselves too so that we can later create cache keys from those hashes.
1114- try :
1115- DeepHash (
1116- obj ,
1117- hashes = self .hashes ,
1118- parent = level .path (),
1119- apply_hash = True ,
1120- ** self .deephash_parameters ,
1121- )
1122- except Exception as e : # pragma: no cover
1123- logger .error ("Can not produce a hash for iterable %s. %s" %
1124- (level .path (), e ))
1128+ DeepHash (
1129+ obj ,
1130+ hashes = self .hashes ,
1131+ parent = level .path (),
1132+ apply_hash = True ,
1133+ ** self .deephash_parameters ,
1134+ )
11251135 return local_hashes
11261136
11271137 @staticmethod
@@ -1490,17 +1500,17 @@ def _diff_numbers(self, level, local_tree=None, report_type_change=True):
14901500
14911501 def _diff_datetime (self , level , local_tree = None ):
14921502 """Diff DateTimes"""
1493- level .t1 = datetime_normalize (self .truncate_datetime , level .t1 )
1494- level .t2 = datetime_normalize (self .truncate_datetime , level .t2 )
1503+ level .t1 = datetime_normalize (self .truncate_datetime , level .t1 , default_timezone = self . default_timezone )
1504+ level .t2 = datetime_normalize (self .truncate_datetime , level .t2 , default_timezone = self . default_timezone )
14951505
14961506 if level .t1 != level .t2 :
14971507 self ._report_result ('values_changed' , level , local_tree = local_tree )
14981508
14991509 def _diff_time (self , level , local_tree = None ):
15001510 """Diff DateTimes"""
15011511 if self .truncate_datetime :
1502- level .t1 = datetime_normalize (self .truncate_datetime , level .t1 )
1503- level .t2 = datetime_normalize (self .truncate_datetime , level .t2 )
1512+ level .t1 = datetime_normalize (self .truncate_datetime , level .t1 , default_timezone = self . default_timezone )
1513+ level .t2 = datetime_normalize (self .truncate_datetime , level .t2 , default_timezone = self . default_timezone )
15041514
15051515 if level .t1 != level .t2 :
15061516 self ._report_result ('values_changed' , level , local_tree = local_tree )
0 commit comments