1616"""
1717
1818from functools import total_ordering
19- from typing import Any , Iterator , Iterable , Mapping , Optional , Text , Type
19+ from typing import Any , Iterator , Optional , Type , List , Dict , Iterable
2020
2121from .exceptions import ObjectAlreadyExists
2222from .utils import intersection , OrderedDefaultDict
2323from .enum import DiffSyncActions
2424
25+ # This workaround is used because we are defining a method called `str` in our class definition, which therefore renders
26+ # the builtin `str` type unusable.
27+ StrType = str
28+
2529
2630class Diff :
2731 """Diff Object, designed to store multiple DiffElement object and organize them in a group."""
2832
29- def __init__ (self ):
33+ def __init__ (self ) -> None :
3034 """Initialize a new, empty Diff object."""
31- self .children = OrderedDefaultDict (dict )
35+ self .children = OrderedDefaultDict [ StrType , Dict [ StrType , DiffElement ]] (dict )
3236 """DefaultDict for storing DiffElement objects.
3337
3438 `self.children[group][unique_id] == DiffElement(...)`
3539 """
3640 self .models_processed = 0
3741
38- def __len__ (self ):
42+ def __len__ (self ) -> int :
3943 """Total number of DiffElements stored herein."""
4044 total = 0
4145 for child in self .get_children ():
4246 total += len (child )
4347 return total
4448
45- def complete (self ):
49+ def complete (self ) -> None :
4650 """Method to call when this Diff has been fully populated with data and is "complete".
4751
4852 The default implementation does nothing, but a subclass could use this, for example, to save
4953 the completed Diff to a file or database record.
5054 """
5155
52- def add (self , element : "DiffElement" ):
56+ def add (self , element : "DiffElement" ) -> None :
5357 """Add a new DiffElement to the changeset of this Diff.
5458
5559 Raises:
@@ -61,15 +65,15 @@ def add(self, element: "DiffElement"):
6165
6266 self .children [element .type ][element .name ] = element
6367
64- def groups (self ):
68+ def groups (self ) -> List [ StrType ] :
6569 """Get the list of all group keys in self.children."""
66- return self .children .keys ()
70+ return list ( self .children .keys () )
6771
6872 def has_diffs (self ) -> bool :
6973 """Indicate if at least one of the child elements contains some diff.
7074
7175 Returns:
72- bool: True if at least one child element contains some diff
76+ True if at least one child element contains some diff
7377 """
7478 for group in self .groups ():
7579 for child in self .children [group ].values ():
@@ -96,15 +100,15 @@ def get_children(self) -> Iterator["DiffElement"]:
96100 yield from order_method (self .children [group ])
97101
98102 @classmethod
99- def order_children_default (cls , children : Mapping ) -> Iterator ["DiffElement" ]:
103+ def order_children_default (cls , children : Dict [ StrType , "DiffElement" ] ) -> Iterator ["DiffElement" ]:
100104 """Default method to an Iterator for children.
101105
102106 Since children is already an OrderedDefaultDict, this method is not doing anything special.
103107 """
104108 for child in children .values ():
105109 yield child
106110
107- def summary (self ) -> Mapping [ Text , int ]:
111+ def summary (self ) -> Dict [ StrType , int ]:
108112 """Build a dict summary of this Diff and its child DiffElements."""
109113 summary = {
110114 DiffSyncActions .CREATE : 0 ,
@@ -127,7 +131,7 @@ def summary(self) -> Mapping[Text, int]:
127131 )
128132 return summary
129133
130- def str (self , indent : int = 0 ):
134+ def str (self , indent : int = 0 ) -> StrType :
131135 """Build a detailed string representation of this Diff and its child DiffElements."""
132136 margin = " " * indent
133137 output = []
@@ -144,9 +148,9 @@ def str(self, indent: int = 0):
144148 result = "(no diffs)"
145149 return result
146150
147- def dict (self ) -> Mapping [ Text , Mapping [ Text , Mapping ]]:
151+ def dict (self ) -> Dict [ StrType , Dict [ StrType , Dict ]]:
148152 """Build a dictionary representation of this Diff."""
149- result = OrderedDefaultDict (dict )
153+ result = OrderedDefaultDict [ str , Dict ] (dict )
150154 for child in self .get_children ():
151155 if child .has_diffs (include_children = True ):
152156 result [child .type ][child .name ] = child .dict ()
@@ -159,11 +163,11 @@ class DiffElement: # pylint: disable=too-many-instance-attributes
159163
160164 def __init__ (
161165 self ,
162- obj_type : Text ,
163- name : Text ,
164- keys : Mapping ,
165- source_name : Text = "source" ,
166- dest_name : Text = "dest" ,
166+ obj_type : StrType ,
167+ name : StrType ,
168+ keys : Dict ,
169+ source_name : StrType = "source" ,
170+ dest_name : StrType = "dest" ,
167171 diff_class : Type [Diff ] = Diff ,
168172 ): # pylint: disable=too-many-arguments
169173 """Instantiate a DiffElement.
@@ -177,10 +181,10 @@ def __init__(
177181 dest_name: Name of the destination DiffSync object
178182 diff_class: Diff or subclass thereof to use to calculate the diffs to use for synchronization
179183 """
180- if not isinstance (obj_type , str ):
184+ if not isinstance (obj_type , StrType ):
181185 raise ValueError (f"obj_type must be a string (not { type (obj_type )} )" )
182186
183- if not isinstance (name , str ):
187+ if not isinstance (name , StrType ):
184188 raise ValueError (f"name must be a string (not { type (name )} )" )
185189
186190 self .type = obj_type
@@ -189,18 +193,18 @@ def __init__(
189193 self .source_name = source_name
190194 self .dest_name = dest_name
191195 # Note: *_attrs == None if no target object exists; it'll be an empty dict if it exists but has no _attributes
192- self .source_attrs : Optional [Mapping ] = None
193- self .dest_attrs : Optional [Mapping ] = None
196+ self .source_attrs : Optional [Dict ] = None
197+ self .dest_attrs : Optional [Dict ] = None
194198 self .child_diff = diff_class ()
195199
196- def __lt__ (self , other ) :
200+ def __lt__ (self , other : "DiffElement" ) -> bool :
197201 """Logical ordering of DiffElements.
198202
199203 Other comparison methods (__gt__, __le__, __ge__, etc.) are created by our use of the @total_ordering decorator.
200204 """
201205 return (self .type , self .name ) < (other .type , other .name )
202206
203- def __eq__ (self , other ) :
207+ def __eq__ (self , other : object ) -> bool :
204208 """Logical equality of DiffElements.
205209
206210 Other comparison methods (__gt__, __le__, __ge__, etc.) are created by our use of the @total_ordering decorator.
@@ -216,26 +220,26 @@ def __eq__(self, other):
216220 # TODO also check that self.child_diff == other.child_diff, needs Diff to implement __eq__().
217221 )
218222
219- def __str__ (self ):
223+ def __str__ (self ) -> StrType :
220224 """Basic string representation of a DiffElement."""
221225 return (
222226 f'{ self .type } "{ self .name } " : { self .keys } : '
223227 f"{ self .source_name } → { self .dest_name } : { self .get_attrs_diffs ()} "
224228 )
225229
226- def __len__ (self ):
230+ def __len__ (self ) -> int :
227231 """Total number of DiffElements in this one, including itself."""
228232 total = 1 # self
229233 for child in self .get_children ():
230234 total += len (child )
231235 return total
232236
233237 @property
234- def action (self ) -> Optional [Text ]:
238+ def action (self ) -> Optional [StrType ]:
235239 """Action, if any, that should be taken to remediate the diffs described by this element.
236240
237241 Returns:
238- str: DiffSyncActions ( "create", "update", "delete", or None)
242+ "create", "update", "delete", or None)
239243 """
240244 if self .source_attrs is not None and self .dest_attrs is None :
241245 return DiffSyncActions .CREATE
@@ -251,7 +255,7 @@ def action(self) -> Optional[Text]:
251255 return None
252256
253257 # TODO: separate into set_source_attrs() and set_dest_attrs() methods, or just use direct property access instead?
254- def add_attrs (self , source : Optional [Mapping ] = None , dest : Optional [Mapping ] = None ):
258+ def add_attrs (self , source : Optional [Dict ] = None , dest : Optional [Dict ] = None ) -> None :
255259 """Set additional attributes of a source and/or destination item that may result in diffs."""
256260 # TODO: should source_attrs and dest_attrs be "write-once" properties, or is it OK to overwrite them once set?
257261 if source is not None :
@@ -260,26 +264,26 @@ def add_attrs(self, source: Optional[Mapping] = None, dest: Optional[Mapping] =
260264 if dest is not None :
261265 self .dest_attrs = dest
262266
263- def get_attrs_keys (self ) -> Iterable [Text ]:
267+ def get_attrs_keys (self ) -> Iterable [StrType ]:
264268 """Get the list of shared attrs between source and dest, or the attrs of source or dest if only one is present.
265269
266270 - If source_attrs is not set, return the keys of dest_attrs
267271 - If dest_attrs is not set, return the keys of source_attrs
268272 - If both are defined, return the intersection of both keys
269273 """
270274 if self .source_attrs is not None and self .dest_attrs is not None :
271- return intersection (self .dest_attrs .keys (), self .source_attrs .keys ())
275+ return intersection (list ( self .dest_attrs .keys ()), list ( self .source_attrs .keys () ))
272276 if self .source_attrs is None and self .dest_attrs is not None :
273277 return self .dest_attrs .keys ()
274278 if self .source_attrs is not None and self .dest_attrs is None :
275279 return self .source_attrs .keys ()
276280 return []
277281
278- def get_attrs_diffs (self ) -> Mapping [ Text , Mapping [ Text , Any ]]:
282+ def get_attrs_diffs (self ) -> Dict [ StrType , Dict [ StrType , Any ]]:
279283 """Get the dict of actual attribute diffs between source_attrs and dest_attrs.
280284
281285 Returns:
282- dict: of the form `{"-": {key1: <value>, key2: ...}, "+": {key1: <value>, key2: ...}}`,
286+ Dictionary of the form `{"-": {key1: <value>, key2: ...}, "+": {key1: <value>, key2: ...}}`,
283287 where the `"-"` or `"+"` dicts may be absent.
284288 """
285289 if self .source_attrs is not None and self .dest_attrs is not None :
@@ -301,13 +305,10 @@ def get_attrs_diffs(self) -> Mapping[Text, Mapping[Text, Any]]:
301305 return {"+" : {key : self .source_attrs [key ] for key in self .get_attrs_keys ()}}
302306 return {}
303307
304- def add_child (self , element : "DiffElement" ):
308+ def add_child (self , element : "DiffElement" ) -> None :
305309 """Attach a child object of type DiffElement.
306310
307311 Childs are saved in a Diff object and are organized by type and name.
308-
309- Args:
310- element: DiffElement
311312 """
312313 self .child_diff .add (element )
313314
@@ -336,7 +337,7 @@ def has_diffs(self, include_children: bool = True) -> bool:
336337
337338 return False
338339
339- def summary (self ) -> Mapping [ Text , int ]:
340+ def summary (self ) -> Dict [ StrType , int ]:
340341 """Build a summary of this DiffElement and its children."""
341342 summary = {
342343 DiffSyncActions .CREATE : 0 ,
@@ -353,7 +354,7 @@ def summary(self) -> Mapping[Text, int]:
353354 summary [key ] += child_summary [key ]
354355 return summary
355356
356- def str (self , indent : int = 0 ):
357+ def str (self , indent : int = 0 ) -> StrType :
357358 """Build a detailed string representation of this DiffElement and its children."""
358359 margin = " " * indent
359360 result = f"{ margin } { self .type } : { self .name } "
@@ -377,7 +378,7 @@ def str(self, indent: int = 0):
377378 result += " (no diffs)"
378379 return result
379380
380- def dict (self ) -> Mapping [ Text , Mapping [ Text , Any ]]:
381+ def dict (self ) -> Dict [ StrType , Dict [ StrType , Any ]]:
381382 """Build a dictionary representation of this DiffElement and its children."""
382383 attrs_diffs = self .get_attrs_diffs ()
383384 result = {}
0 commit comments