11import logging
22from collections .abc import Mapping
33from copy import copy
4+ from typing import Any , Dict , List , Optional , Set , Union , Literal , Type , TYPE_CHECKING
45from deepdiff .helper import (
56 RemapDict , strings , notpresent , get_type , numpy_numbers , np , literal_eval_extended ,
67 dict_ , SetOrdered )
78from deepdiff .path import stringify_element
89
10+ if TYPE_CHECKING :
11+ from deepdiff .diff import DeepDiff
12+
913logger = logging .getLogger (__name__ )
1014
11- FORCE_DEFAULT = 'fake'
12- UP_DOWN = {'up' : 'down' , 'down' : 'up' }
15+ FORCE_DEFAULT : Literal [ 'fake' ] = 'fake'
16+ UP_DOWN : Dict [ str , str ] = {'up' : 'down' , 'down' : 'up' }
1317
14- REPORT_KEYS = {
18+ REPORT_KEYS : Set [ str ] = {
1519 "type_changes" ,
1620 "dictionary_item_added" ,
1721 "dictionary_item_removed" ,
2731 "repetition_change" ,
2832}
2933
30- CUSTOM_FIELD = "__internal:custom:extra_info"
34+ CUSTOM_FIELD : str = "__internal:custom:extra_info"
3135
3236
3337class DoesNotExist (Exception ):
@@ -36,7 +40,7 @@ class DoesNotExist(Exception):
3640
3741class ResultDict (RemapDict ):
3842
39- def remove_empty_keys (self ):
43+ def remove_empty_keys (self ) -> None :
4044 """
4145 Remove empty keys from this object. Should always be called after the result is final.
4246 :return:
@@ -48,11 +52,11 @@ def remove_empty_keys(self):
4852
4953
5054class TreeResult (ResultDict ):
51- def __init__ (self ):
55+ def __init__ (self ) -> None :
5256 for key in REPORT_KEYS :
5357 self [key ] = SetOrdered ()
5458
55- def mutual_add_removes_to_become_value_changes (self ):
59+ def mutual_add_removes_to_become_value_changes (self ) -> None :
5660 """
5761 There might be the same paths reported in the results as removed and added.
5862 In such cases they should be reported as value_changes.
@@ -84,12 +88,16 @@ def mutual_add_removes_to_become_value_changes(self):
8488 if 'iterable_item_added' in self and not iterable_item_added :
8589 del self ['iterable_item_added' ]
8690
87- def __getitem__ (self , item ) :
91+ def __getitem__ (self , item : str ) -> SetOrdered :
8892 if item not in self :
8993 self [item ] = SetOrdered ()
90- return self .get (item )
94+ result = self .get (item )
95+ if result is None :
96+ result = SetOrdered ()
97+ self [item ] = result
98+ return result
9199
92- def __len__ (self ):
100+ def __len__ (self ) -> int :
93101 length = 0
94102 for value in self .values ():
95103 if isinstance (value , SetOrdered ):
@@ -100,9 +108,9 @@ def __len__(self):
100108
101109
102110class TextResult (ResultDict ):
103- ADD_QUOTES_TO_STRINGS = True
111+ ADD_QUOTES_TO_STRINGS : bool = True
104112
105- def __init__ (self , tree_results = None , verbose_level = 1 ) :
113+ def __init__ (self , tree_results : Optional [ 'TreeResult' ] = None , verbose_level : int = 1 ) -> None :
106114 self .verbose_level = verbose_level
107115 # TODO: centralize keys
108116 self .update ({
@@ -124,10 +132,10 @@ def __init__(self, tree_results=None, verbose_level=1):
124132 if tree_results :
125133 self ._from_tree_results (tree_results )
126134
127- def __set_or_dict (self ):
135+ def __set_or_dict (self ) -> Union [ Dict [ str , Any ], SetOrdered ] :
128136 return {} if self .verbose_level >= 2 else SetOrdered ()
129137
130- def _from_tree_results (self , tree ) :
138+ def _from_tree_results (self , tree : 'TreeResult' ) -> None :
131139 """
132140 Populate this object by parsing an existing reference-style result dictionary.
133141 :param tree: A TreeResult
@@ -149,7 +157,7 @@ def _from_tree_results(self, tree):
149157 self ._from_tree_deep_distance (tree )
150158 self ._from_tree_custom_results (tree )
151159
152- def _from_tree_default (self , tree , report_type , ignore_if_in_iterable_opcodes = False ):
160+ def _from_tree_default (self , tree : 'TreeResult' , report_type : str , ignore_if_in_iterable_opcodes : bool = False ) -> None :
153161 if report_type in tree :
154162
155163 for change in tree [report_type ]: # report each change
@@ -291,9 +299,9 @@ def _from_tree_custom_results(self, tree):
291299
292300
293301class DeltaResult (TextResult ):
294- ADD_QUOTES_TO_STRINGS = False
302+ ADD_QUOTES_TO_STRINGS : bool = False
295303
296- def __init__ (self , tree_results = None , ignore_order = None , always_include_values = False , _iterable_opcodes = None ):
304+ def __init__ (self , tree_results : Optional [ 'TreeResult' ] = None , ignore_order : Optional [ bool ] = None , always_include_values : bool = False , _iterable_opcodes : Optional [ Dict [ str , Any ]] = None ) -> None :
297305 self .ignore_order = ignore_order
298306 self .always_include_values = always_include_values
299307
@@ -517,15 +525,15 @@ class DiffLevel:
517525 """
518526
519527 def __init__ (self ,
520- t1 ,
521- t2 ,
522- down = None ,
523- up = None ,
524- report_type = None ,
525- child_rel1 = None ,
526- child_rel2 = None ,
527- additional = None ,
528- verbose_level = 1 ) :
528+ t1 : Any ,
529+ t2 : Any ,
530+ down : Optional [ 'DiffLevel' ] = None ,
531+ up : Optional [ 'DiffLevel' ] = None ,
532+ report_type : Optional [ str ] = None ,
533+ child_rel1 : Optional [ 'ChildRelationship' ] = None ,
534+ child_rel2 : Optional [ 'ChildRelationship' ] = None ,
535+ additional : Optional [ Dict [ str , Any ]] = None ,
536+ verbose_level : int = 1 ) -> None :
529537 """
530538 :param child_rel1: Either:
531539 - An existing ChildRelationship object describing the "down" relationship for t1; or
@@ -581,7 +589,7 @@ def __init__(self,
581589
582590 self .verbose_level = verbose_level
583591
584- def __repr__ (self ):
592+ def __repr__ (self ) -> str :
585593 if self .verbose_level :
586594 from deepdiff .summarize import summarize
587595
@@ -596,7 +604,7 @@ def __repr__(self):
596604 result = "<{}>" .format (self .path ())
597605 return result
598606
599- def __setattr__ (self , key , value ) :
607+ def __setattr__ (self , key : str , value : Any ) -> None :
600608 # Setting up or down, will set the opposite link in this linked list.
601609 if key in UP_DOWN and value is not None :
602610 self .__dict__ [key ] = value
@@ -605,15 +613,15 @@ def __setattr__(self, key, value):
605613 else :
606614 self .__dict__ [key ] = value
607615
608- def __iter__ (self ):
616+ def __iter__ (self ) -> Any :
609617 yield self .t1
610618 yield self .t2
611619
612620 @property
613- def repetition (self ):
621+ def repetition (self ) -> Dict [ str , Any ] :
614622 return self .additional ['repetition' ]
615623
616- def auto_generate_child_rel (self , klass , param , param2 = None ):
624+ def auto_generate_child_rel (self , klass : Type [ 'ChildRelationship' ] , param : Any , param2 : Optional [ Any ] = None ) -> None :
617625 """
618626 Auto-populate self.child_rel1 and self.child_rel2.
619627 This requires self.down to be another valid DiffLevel object.
@@ -630,7 +638,7 @@ def auto_generate_child_rel(self, klass, param, param2=None):
630638 klass = klass , parent = self .t2 , child = self .down .t2 , param = param if param2 is None else param2 ) # type: ignore
631639
632640 @property
633- def all_up (self ):
641+ def all_up (self ) -> 'DiffLevel' :
634642 """
635643 Get the root object of this comparison.
636644 (This is a convenient wrapper for following the up attribute as often as you can.)
@@ -642,7 +650,7 @@ def all_up(self):
642650 return level
643651
644652 @property
645- def all_down (self ):
653+ def all_down (self ) -> 'DiffLevel' :
646654 """
647655 Get the leaf object of this comparison.
648656 (This is a convenient wrapper for following the down attribute as often as you can.)
@@ -654,10 +662,10 @@ def all_down(self):
654662 return level
655663
656664 @staticmethod
657- def _format_result (root , result ) :
665+ def _format_result (root : str , result : Optional [ str ]) -> Optional [ str ] :
658666 return None if result is None else "{}{}" .format (root , result )
659667
660- def get_root_key (self , use_t2 = False ):
668+ def get_root_key (self , use_t2 : bool = False ) -> Any :
661669 """
662670 Get the path's root key value for this change
663671
@@ -674,7 +682,7 @@ def get_root_key(self, use_t2=False):
674682 return next_rel .param
675683 return notpresent
676684
677- def path (self , root = "root" , force = None , get_parent_too = False , use_t2 = False , output_format = 'str' , reporting_move = False ):
685+ def path (self , root : str = "root" , force : Optional [ str ] = None , get_parent_too : bool = False , use_t2 : bool = False , output_format : Literal [ 'str' , 'list' ] = 'str' , reporting_move : bool = False ) -> Any :
678686 """
679687 A python syntax string describing how to descend to this level, assuming the top level object is called root.
680688 Returns None if the path is not representable as a string.
@@ -765,18 +773,18 @@ def path(self, root="root", force=None, get_parent_too=False, use_t2=False, outp
765773 output = (self ._format_result (root , parent ), param , self ._format_result (root , result )) # type: ignore
766774 else :
767775 self ._path [cache_key ] = result
768- output = self ._format_result (root , result )
776+ output = self ._format_result (root , result ) if isinstance ( result , ( str , type ( None ))) else None
769777 else :
770778 output = result
771779 return output
772780
773781 def create_deeper (self ,
774- new_t1 ,
775- new_t2 ,
776- child_relationship_class ,
777- child_relationship_param = None ,
778- child_relationship_param2 = None ,
779- report_type = None ):
782+ new_t1 : Any ,
783+ new_t2 : Any ,
784+ child_relationship_class : Type [ 'ChildRelationship' ] ,
785+ child_relationship_param : Optional [ Any ] = None ,
786+ child_relationship_param2 : Optional [ Any ] = None ,
787+ report_type : Optional [ str ] = None ) -> 'DiffLevel' :
780788 """
781789 Start a new comparison level and correctly link it to this one.
782790 :rtype: DiffLevel
@@ -791,12 +799,12 @@ def create_deeper(self,
791799 return result
792800
793801 def branch_deeper (self ,
794- new_t1 ,
795- new_t2 ,
796- child_relationship_class ,
797- child_relationship_param = None ,
798- child_relationship_param2 = None ,
799- report_type = None ):
802+ new_t1 : Any ,
803+ new_t2 : Any ,
804+ child_relationship_class : Type [ 'ChildRelationship' ] ,
805+ child_relationship_param : Optional [ Any ] = None ,
806+ child_relationship_param2 : Optional [ Any ] = None ,
807+ report_type : Optional [ str ] = None ) -> 'DiffLevel' :
800808 """
801809 Branch this comparison: Do not touch this comparison line, but create a new one with exactly the same content,
802810 just one level deeper.
@@ -807,7 +815,7 @@ def branch_deeper(self,
807815 return branch .create_deeper (new_t1 , new_t2 , child_relationship_class ,
808816 child_relationship_param , child_relationship_param2 , report_type )
809817
810- def copy (self ):
818+ def copy (self ) -> 'DiffLevel' :
811819 """
812820 Get a deep copy of this comparision line.
813821 :return: The leaf ("downmost") object of the copy.
@@ -850,20 +858,20 @@ class ChildRelationship:
850858
851859 # Format to a be used for representing param.
852860 # E.g. for a dict, this turns a formatted param param "42" into "[42]".
853- param_repr_format = None
861+ param_repr_format : Optional [ str ] = None
854862
855863 # This is a hook allowing subclasses to manipulate param strings.
856864 # :param string: Input string
857865 # :return: Manipulated string, as appropriate in this context.
858- quote_str = None
866+ quote_str : Optional [ str ] = None
859867
860868 @staticmethod
861- def create (klass , parent , child , param = None ):
869+ def create (klass : Type [ 'ChildRelationship' ] , parent : Any , child : Any , param : Optional [ Any ] = None ) -> 'ChildRelationship' :
862870 if not issubclass (klass , ChildRelationship ):
863871 raise TypeError
864872 return klass (parent , child , param )
865873
866- def __init__ (self , parent , child , param = None ):
874+ def __init__ (self , parent : Any , child : Any , param : Optional [ Any ] = None ) -> None :
867875 # The parent object of this relationship, e.g. a dict
868876 self .parent = parent
869877
@@ -873,7 +881,7 @@ def __init__(self, parent, child, param=None):
873881 # A subclass-dependent parameter describing how to get from parent to child, e.g. the key in a dict
874882 self .param = param
875883
876- def __repr__ (self ):
884+ def __repr__ (self ) -> str :
877885 from deepdiff .summarize import summarize
878886
879887 name = "<{} parent:{}, child:{}, param:{}>"
@@ -882,7 +890,7 @@ def __repr__(self):
882890 param = summarize (self .param , max_length = 15 )
883891 return name .format (self .__class__ .__name__ , parent , child , param )
884892
885- def get_param_repr (self , force = None ):
893+ def get_param_repr (self , force : Optional [ str ] = None ) -> Optional [ str ] :
886894 """
887895 Returns a formatted param python parsable string describing this relationship,
888896 or None if the relationship is not representable as a string.
@@ -899,7 +907,7 @@ def get_param_repr(self, force=None):
899907 """
900908 return self .stringify_param (force )
901909
902- def stringify_param (self , force = None ):
910+ def stringify_param (self , force : Optional [ str ] = None ) -> Optional [ str ] :
903911 """
904912 Convert param to a string. Return None if there is no string representation.
905913 This is called by get_param_repr()
@@ -946,13 +954,13 @@ def stringify_param(self, force=None):
946954
947955
948956class DictRelationship (ChildRelationship ):
949- param_repr_format = "[{}]"
950- quote_str = "'{}'"
957+ param_repr_format : Optional [ str ] = "[{}]"
958+ quote_str : Optional [ str ] = "'{}'"
951959
952960
953961class NumpyArrayRelationship (ChildRelationship ):
954- param_repr_format = "[{}]"
955- quote_str = None
962+ param_repr_format : Optional [ str ] = "[{}]"
963+ quote_str : Optional [ str ] = None
956964
957965
958966class SubscriptableIterableRelationship (DictRelationship ):
@@ -970,9 +978,9 @@ class SetRelationship(InaccessibleRelationship):
970978
971979class NonSubscriptableIterableRelationship (InaccessibleRelationship ):
972980
973- param_repr_format = "[{}]"
981+ param_repr_format : Optional [ str ] = "[{}]"
974982
975- def get_param_repr (self , force = None ):
983+ def get_param_repr (self , force : Optional [ str ] = None ) -> Optional [ str ] :
976984 if force == 'yes' :
977985 result = "(unrepresentable)"
978986 elif force == 'fake' and self .param :
@@ -984,4 +992,4 @@ def get_param_repr(self, force=None):
984992
985993
986994class AttributeRelationship (ChildRelationship ):
987- param_repr_format = ".{}"
995+ param_repr_format : Optional [ str ] = ".{}"
0 commit comments