2020from pydantic import BaseModel , PrivateAttr
2121import structlog # type: ignore
2222
23- from .diff import Diff
24- from .enum import DiffSyncModelFlags , DiffSyncFlags , DiffSyncStatus
25- from .exceptions import DiffClassMismatch , ObjectAlreadyExists , ObjectStoreWrongType , ObjectNotFound
26- from .helpers import DiffSyncDiffer , DiffSyncSyncer
27- from .store import BaseStore
28- from .store .local import LocalStore
23+ from diffsync .diff import Diff
24+ from diffsync .enum import DiffSyncModelFlags , DiffSyncFlags , DiffSyncStatus
25+ from diffsync .exceptions import DiffClassMismatch , ObjectAlreadyExists , ObjectStoreWrongType , ObjectNotFound
26+ from diffsync .helpers import DiffSyncDiffer , DiffSyncSyncer
27+ from diffsync .store import BaseStore
28+ from diffsync .store .local import LocalStore
29+ from diffsync .utils import get_path , set_key , tree_string
2930
3031
3132class DiffSyncModel (BaseModel ):
@@ -396,7 +397,7 @@ def remove_child(self, child: "DiffSyncModel"):
396397 childs .remove (child .get_unique_id ())
397398
398399
399- class DiffSync :
400+ class DiffSync : # pylint: disable=too-many-public-methods
400401 """Class for storing a group of DiffSyncModel instances and diffing/synchronizing to another DiffSync instance."""
401402
402403 # In any subclass, you would add mapping of names to specific model classes here:
@@ -460,6 +461,26 @@ def __len__(self):
460461 """Total number of elements stored."""
461462 return self .store .count ()
462463
464+ @classmethod
465+ def _get_initial_value_order (cls ) -> List [str ]:
466+ """Get the initial value order of diffsync object.
467+
468+ Returns:
469+ List of model-referencing attribute names in the order they are initially processed.
470+ """
471+ if hasattr (cls , "top_level" ) and isinstance (getattr (cls , "top_level" ), list ):
472+ value_order = cls .top_level .copy ()
473+ else :
474+ value_order = []
475+
476+ for item in dir (cls ):
477+ _method = getattr (cls , item )
478+ if item in value_order :
479+ continue
480+ if isclass (_method ) and issubclass (_method , DiffSyncModel ):
481+ value_order .append (item )
482+ return value_order
483+
463484 def load (self ):
464485 """Load all desired data from whatever backend data source into this instance."""
465486 # No-op in this generic class
@@ -489,6 +510,18 @@ def str(self, indent: int = 0) -> str:
489510 output += "\n " + model .str (indent = indent + 2 )
490511 return output
491512
513+ def load_from_dict (self , data : Dict ):
514+ """The reverse of `dict` method, taking a dictionary and loading into the inventory.
515+
516+ Args:
517+ data: Dictionary in the format that `dict` would export as
518+ """
519+ value_order = self ._get_initial_value_order ()
520+ for field_name in value_order :
521+ model_class = getattr (self , field_name )
522+ for values in data .get (field_name , {}).values ():
523+ self .add (model_class (** values ))
524+
492525 # ------------------------------------------------------------------------------
493526 # Synchronization between DiffSync instances
494527 # ------------------------------------------------------------------------------
@@ -625,7 +658,6 @@ def get_all_model_names(self):
625658 def get (
626659 self , obj : Union [Text , DiffSyncModel , Type [DiffSyncModel ]], identifier : Union [Text , Mapping ]
627660 ) -> DiffSyncModel :
628-
629661 """Get one object from the data store based on its unique id.
630662
631663 Args:
@@ -638,6 +670,26 @@ def get(
638670 """
639671 return self .store .get (model = obj , identifier = identifier )
640672
673+ def get_or_none (
674+ self , obj : Union [Text , DiffSyncModel , Type [DiffSyncModel ]], identifier : Union [Text , Mapping ]
675+ ) -> Optional [DiffSyncModel ]:
676+ """Get one object from the data store based on its unique id or get a None
677+
678+ Args:
679+ obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve
680+ identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values
681+
682+ Raises:
683+ ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class)
684+
685+ Returns:
686+ DiffSyncModel matching provided criteria
687+ """
688+ try :
689+ return self .get (obj , identifier )
690+ except ObjectNotFound :
691+ return None
692+
641693 def get_all (self , obj : Union [Text , DiffSyncModel , Type [DiffSyncModel ]]) -> List [DiffSyncModel ]:
642694 """Get all objects of a given type.
643695
@@ -663,6 +715,32 @@ def get_by_uids(
663715 """
664716 return self .store .get_by_uids (uids = uids , model = obj )
665717
718+ @classmethod
719+ def get_tree_traversal (cls , as_dict : bool = False ) -> Union [Text , Mapping ]:
720+ """Get a string describing the tree traversal for the diffsync object.
721+
722+ Args:
723+ as_dict: Whether or not to return as a dictionary
724+
725+ Returns:
726+ A string or dictionary representation of tree
727+ """
728+ value_order = cls ._get_initial_value_order ()
729+ output_dict : Dict = {}
730+ for key in value_order :
731+ model_obj = getattr (cls , key )
732+ if not get_path (output_dict , key ):
733+ set_key (output_dict , [key ])
734+ if hasattr (model_obj , "_children" ):
735+ children = getattr (model_obj , "_children" )
736+ for child_key in list (children .keys ()):
737+ path = get_path (output_dict , key ) or [key ]
738+ path .append (child_key )
739+ set_key (output_dict , path )
740+ if as_dict :
741+ return output_dict
742+ return tree_string (output_dict , cls .__name__ )
743+
666744 def add (self , obj : DiffSyncModel ):
667745 """Add a DiffSyncModel object to the store.
668746
0 commit comments