5
5
# You might need to run it many times since dictionaries come in different orders
6
6
# every time you run the docstrings.
7
7
# However the docstring expects it in a specific order in order to pass!
8
+ import pytz
8
9
import difflib
9
10
import logging
10
11
import types
11
12
import datetime
12
13
from enum import Enum
13
14
from copy import deepcopy
14
15
from 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
16
17
from collections .abc import Mapping , Iterable , Sequence
17
18
from collections import defaultdict
18
19
from inspect import getmembers
@@ -110,6 +111,8 @@ def _report_progress(_stats, progress_logger, duration):
110
111
'ignore_private_variables' ,
111
112
'encodings' ,
112
113
'ignore_encoding_errors' ,
114
+ 'default_timezone' ,
115
+ 'custom_operators' ,
113
116
)
114
117
115
118
@@ -128,10 +131,11 @@ def __init__(self,
128
131
custom_operators : Optional [List [Any ]] = None ,
129
132
cutoff_distance_for_pairs : float = CUTOFF_DISTANCE_FOR_PAIRS_DEFAULT ,
130
133
cutoff_intersection_for_pairs : float = CUTOFF_INTERSECTION_FOR_PAIRS_DEFAULT ,
134
+ default_timezone :Union [datetime .timezone , datetime .timezone , pytz .tzinfo .BaseTzInfo ]= datetime .timezone .utc ,
131
135
encodings : Optional [List [str ]]= None ,
132
136
exclude_obj_callback : Optional [Callable ]= None ,
133
137
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 ,
135
139
exclude_regex_paths : Union [str , List [str ], Pattern [str ], List [Pattern [str ]], None ]= None ,
136
140
exclude_types : Optional [List [Any ]]= None ,
137
141
get_deep_distance : bool = False ,
@@ -154,6 +158,8 @@ def __init__(self,
154
158
include_paths : Union [str , List [str ], None ]= None ,
155
159
iterable_compare_func : Optional [Callable ]= None ,
156
160
log_frequency_in_sec : int = 0 ,
161
+ log_scale_similarity_threshold : float = 0.1 ,
162
+ log_stacktrace : bool = False ,
157
163
math_epsilon : Optional [float ]= None ,
158
164
max_diffs : Optional [int ]= None ,
159
165
max_passes : int = 10000000 ,
@@ -162,11 +168,10 @@ def __init__(self,
162
168
progress_logger : Callable = logger .info ,
163
169
report_repetition : bool = False ,
164
170
significant_digits : Optional [int ]= None ,
165
- use_log_scale : bool = False ,
166
- log_scale_similarity_threshold : float = 0.1 ,
167
171
threshold_to_diff_deeper : float = 0.33 ,
168
172
truncate_datetime : Optional [str ]= None ,
169
173
use_enum_value : bool = False ,
174
+ use_log_scale : bool = False ,
170
175
verbose_level : int = 1 ,
171
176
view : str = TEXT_VIEW ,
172
177
zip_ordered_iterables : bool = False ,
@@ -183,8 +188,8 @@ def __init__(self,
183
188
"ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, "
184
189
"view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, "
185
190
"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 "
188
193
"ignore_order_func, custom_operators, encodings, ignore_encoding_errors, use_log_scale, log_scale_similarity_threshold "
189
194
"_parameters and _shared_parameters." ) % ', ' .join (kwargs .keys ()))
190
195
@@ -205,6 +210,8 @@ def __init__(self,
205
210
self .use_enum_value = use_enum_value
206
211
self .log_scale_similarity_threshold = log_scale_similarity_threshold
207
212
self .use_log_scale = use_log_scale
213
+ self .default_timezone = default_timezone
214
+ self .log_stacktrace = log_stacktrace
208
215
self .threshold_to_diff_deeper = threshold_to_diff_deeper
209
216
self .ignore_string_type_changes = ignore_string_type_changes
210
217
self .ignore_type_in_groups = self .get_ignore_types_in_groups (
@@ -272,6 +279,10 @@ def _group_by_sort_key(x):
272
279
self .cache_size = cache_size
273
280
_parameters = self .__dict__ .copy ()
274
281
_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
275
286
276
287
# Non-Root
277
288
if _shared_parameters :
@@ -732,7 +743,7 @@ def _compare_in_order(
732
743
self , level ,
733
744
t1_from_index = None , t1_to_index = None ,
734
745
t2_from_index = None , t2_to_index = None
735
- ):
746
+ ) -> List [ Tuple [ Tuple [ int , int ], Tuple [ Any , Any ]]] :
736
747
"""
737
748
Default compare if `iterable_compare_func` is not provided.
738
749
This will compare in sequence order.
@@ -752,7 +763,7 @@ def _get_matching_pairs(
752
763
self , level ,
753
764
t1_from_index = None , t1_to_index = None ,
754
765
t2_from_index = None , t2_to_index = None
755
- ):
766
+ ) -> List [ Tuple [ Tuple [ int , int ], Tuple [ Any , Any ]]] :
756
767
"""
757
768
Given a level get matching pairs. This returns list of two tuples in the form:
758
769
[
@@ -1084,44 +1095,43 @@ def _create_hashtable(self, level, t):
1084
1095
# It only includes the ones needed when comparing iterables.
1085
1096
# The self.hashes dictionary gets shared between different runs of DeepHash
1086
1097
# 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
+ )
1093
1105
except UnicodeDecodeError as err :
1094
1106
err .reason = f"Can not produce a hash for { level .path ()} : { err .reason } "
1095
1107
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))
1100
1114
else :
1101
1115
try :
1102
1116
item_hash = deep_hash [item ]
1103
1117
except KeyError :
1104
1118
pass
1105
1119
else :
1106
1120
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 "
1108
1122
"thus not counting this object." %
1109
1123
level .path ())
1110
1124
else :
1111
1125
self ._add_hash (hashes = local_hashes , item_hash = item_hash , item = item , i = i )
1112
1126
1113
1127
# 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
+ )
1125
1135
return local_hashes
1126
1136
1127
1137
@staticmethod
@@ -1490,17 +1500,17 @@ def _diff_numbers(self, level, local_tree=None, report_type_change=True):
1490
1500
1491
1501
def _diff_datetime (self , level , local_tree = None ):
1492
1502
"""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 )
1495
1505
1496
1506
if level .t1 != level .t2 :
1497
1507
self ._report_result ('values_changed' , level , local_tree = local_tree )
1498
1508
1499
1509
def _diff_time (self , level , local_tree = None ):
1500
1510
"""Diff DateTimes"""
1501
1511
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 )
1504
1514
1505
1515
if level .t1 != level .t2 :
1506
1516
self ._report_result ('values_changed' , level , local_tree = local_tree )
0 commit comments