1818from collections .abc import Iterable as ABCIterable , Mapping as ABCMapping
1919import enum
2020from inspect import isclass
21- from typing import ClassVar , Iterable , List , Mapping , MutableMapping , Optional , Tuple , Type , Union
21+ from typing import ClassVar , Dict , Iterable , List , Mapping , MutableMapping , Optional , Text , Tuple , Type , Union
2222
2323from pydantic import BaseModel
2424import structlog # type: ignore
@@ -187,28 +187,42 @@ def __repr__(self):
187187 def __str__ (self ):
188188 return self .get_unique_id ()
189189
190- def print_detailed (self , dsync : "Optional[DSync]" = None , indent : int = 0 ):
191- """Print this model and its children."""
190+ def dict (self , ** kwargs ) -> dict :
191+ """Convert this DSyncModel to a dict, excluding the dsync field by default as it is not serializable."""
192+ if "exclude" not in kwargs :
193+ kwargs ["exclude" ] = {"dsync" }
194+ return super ().dict (** kwargs )
195+
196+ def json (self , ** kwargs ) -> str :
197+ """Convert this DSyncModel to a JSON string, excluding the dsync field by default as it is not serializable."""
198+ if "exclude" not in kwargs :
199+ kwargs ["exclude" ] = {"dsync" }
200+ if "exclude_defaults" not in kwargs :
201+ kwargs ["exclude_defaults" ] = True
202+ return super ().json (** kwargs )
203+
204+ def str (self , include_children : bool = True , indent : int = 0 ) -> str :
205+ """Build a detailed string representation of this DSyncModel and optionally its children."""
192206 margin = " " * indent
193- if not dsync :
194- dsync = self .dsync
195- print (f"{ margin } { self .get_type ()} : { self .get_unique_id ()} " )
207+ output = f"{ margin } { self .get_type ()} : { self .get_unique_id ()} : { self .get_attrs ()} "
196208 for modelname , fieldname in self ._children .items ():
197- print ( f" { margin } { modelname } " )
209+ output += f" \n { margin } { fieldname } "
198210 child_ids = getattr (self , fieldname )
199211 if not child_ids :
200- print (f"{ margin } (none)" )
201- for child_id in child_ids :
202- child = None
203- if dsync :
204- child = dsync .get (modelname , child_id )
205- if not child :
206- print (f"{ margin } { child_id } (no details available)" )
207- else :
208- child .print_detailed (dsync , indent + 4 )
212+ output += ": []"
213+ elif not self .dsync or not include_children :
214+ output += f": { child_ids } "
215+ else :
216+ for child_id in child_ids :
217+ child = self .dsync .get (modelname , child_id )
218+ if not child :
219+ output += f"\n { margin } { child_id } (details unavailable)"
220+ else :
221+ output += "\n " + child .str (include_children = include_children , indent = indent + 4 )
222+ return output
209223
210224 @classmethod
211- def create (cls , dsync : "DSync" , ids : dict , attrs : dict ) -> Optional ["DSyncModel" ]:
225+ def create (cls , dsync : "DSync" , ids : Mapping , attrs : Mapping ) -> Optional ["DSyncModel" ]:
212226 """Instantiate this class, along with any platform-specific data creation.
213227
214228 Args:
@@ -225,7 +239,7 @@ def create(cls, dsync: "DSync", ids: dict, attrs: dict) -> Optional["DSyncModel"
225239 """
226240 return cls (** ids , dsync = dsync , ** attrs )
227241
228- def update (self , attrs : dict ) -> Optional ["DSyncModel" ]:
242+ def update (self , attrs : Mapping ) -> Optional ["DSyncModel" ]:
229243 """Update the attributes of this instance, along with any platform-specific data updates.
230244
231245 Args:
@@ -256,7 +270,7 @@ def delete(self) -> Optional["DSyncModel"]:
256270 return self
257271
258272 @classmethod
259- def get_type (cls ) -> str :
273+ def get_type (cls ) -> Text :
260274 """Return the type AKA modelname of the object or the class
261275
262276 Returns:
@@ -265,7 +279,7 @@ def get_type(cls) -> str:
265279 return cls ._modelname
266280
267281 @classmethod
268- def create_unique_id (cls , ** identifiers ) -> str :
282+ def create_unique_id (cls , ** identifiers ) -> Text :
269283 """Construct a unique identifier for this model class.
270284
271285 Args:
@@ -274,19 +288,19 @@ def create_unique_id(cls, **identifiers) -> str:
274288 return "__" .join (str (identifiers [key ]) for key in cls ._identifiers )
275289
276290 @classmethod
277- def get_children_mapping (cls ) -> Mapping [str , str ]:
291+ def get_children_mapping (cls ) -> Mapping [Text , Text ]:
278292 """Get the mapping of types to fieldnames for child models of this model."""
279293 return cls ._children
280294
281- def get_identifiers (self ) -> dict :
295+ def get_identifiers (self ) -> Mapping :
282296 """Get a dict of all identifiers (primary keys) and their values for this object.
283297
284298 Returns:
285299 dict: dictionary containing all primary keys for this device, as defined in _identifiers
286300 """
287301 return self .dict (include = set (self ._identifiers ))
288302
289- def get_attrs (self ) -> dict :
303+ def get_attrs (self ) -> Mapping :
290304 """Get all the non-primary-key attributes or parameters for this object.
291305
292306 Similar to Pydantic's `BaseModel.dict()` method, with the following key differences:
@@ -299,7 +313,7 @@ def get_attrs(self) -> dict:
299313 """
300314 return self .dict (include = set (self ._attributes ))
301315
302- def get_unique_id (self ) -> str :
316+ def get_unique_id (self ) -> Text :
303317 """Get the unique ID of an object.
304318
305319 By default the unique ID is built based on all the primary keys defined in `_identifiers`.
@@ -309,7 +323,7 @@ def get_unique_id(self) -> str:
309323 """
310324 return self .create_unique_id (** self .get_identifiers ())
311325
312- def get_shortname (self ) -> str :
326+ def get_shortname (self ) -> Text :
313327 """Get the (not guaranteed-unique) shortname of an object, if any.
314328
315329 By default the shortname is built based on all the keys defined in `_shortname`.
@@ -427,16 +441,28 @@ def load(self):
427441 """Load all desired data from whatever backend data source into this instance."""
428442 # No-op in this generic class
429443
430- def print_detailed (self , indent : int = 0 ):
431- """Recursively print this DSync and its contained models."""
444+ def dict (self , exclude_defaults : bool = True , ** kwargs ) -> Mapping :
445+ """Represent the DSync contents as a dict, as if it were a Pydantic model."""
446+ data : Dict [str , Dict [str , Dict ]] = {}
447+ for modelname in self ._data :
448+ data [modelname ] = {}
449+ for unique_id , model in self ._data [modelname ].items ():
450+ data [modelname ][unique_id ] = model .dict (exclude_defaults = exclude_defaults , ** kwargs )
451+ return data
452+
453+ def str (self , indent : int = 0 ) -> str :
454+ """Build a detailed string representation of this DSync."""
432455 margin = " " * indent
456+ output = ""
433457 for modelname in self .top_level :
434- print ( f"{ margin } { modelname } " )
458+ output += f"{ margin } { modelname } "
435459 models = self .get_all (modelname )
436460 if not models :
437- print (f"{ margin } (none)" )
438- for model in models :
439- model .print_detailed (self , indent + 2 )
461+ output += ": []"
462+ else :
463+ for model in models :
464+ output += "\n " + model .str (indent = indent + 2 )
465+ return output
440466
441467 # ------------------------------------------------------------------------------
442468 # Synchronization between DSync instances
@@ -505,13 +531,13 @@ def _sync_from_diff_element(
505531 log .debug ("Attempting object creation" )
506532 if obj :
507533 raise ObjectNotCreated (f"Failed to create { object_class .get_type ()} { element .keys } - it exists!" )
508- obj = object_class .create (dsync = self , ids = element .keys , attrs = { key : diffs [key ][ "src" ] for key in diffs } )
534+ obj = object_class .create (dsync = self , ids = element .keys , attrs = diffs ["src" ])
509535 log .info ("Created successfully" , status = "success" )
510536 elif element .action == "update" :
511537 log .debug ("Attempting object update" )
512538 if not obj :
513539 raise ObjectNotUpdated (f"Failed to update { object_class .get_type ()} { element .keys } - not found!" )
514- obj = obj .update (attrs = { key : diffs [key ][ "src" ] for key in diffs } )
540+ obj = obj .update (attrs = diffs ["src" ])
515541 log .info ("Updated successfully" , status = "success" )
516542 elif element .action == "delete" :
517543 log .debug ("Attempting object deletion" )
@@ -579,7 +605,9 @@ def diff_to(self, target: "DSync", diff_class: Type[Diff] = Diff, flags: DSyncFl
579605 # Object Storage Management
580606 # ------------------------------------------------------------------------------
581607
582- def get (self , obj : Union [str , DSyncModel , Type [DSyncModel ]], identifier : Union [str , dict ]) -> Optional [DSyncModel ]:
608+ def get (
609+ self , obj : Union [Text , DSyncModel , Type [DSyncModel ]], identifier : Union [Text , Mapping ]
610+ ) -> Optional [DSyncModel ]:
583611 """Get one object from the data store based on its unique id.
584612
585613 Args:
@@ -589,16 +617,23 @@ def get(self, obj: Union[str, DSyncModel, Type[DSyncModel]], identifier: Union[s
589617 if isinstance (obj , str ):
590618 modelname = obj
591619 if not hasattr (self , obj ):
592- return None
593- object_class = getattr (self , obj )
620+ object_class = None
621+ else :
622+ object_class = getattr (self , obj )
594623 else :
595624 object_class = obj
596625 modelname = obj .get_type ()
597626
598627 if isinstance (identifier , str ):
599628 uid = identifier
600- else :
629+ elif object_class :
601630 uid = object_class .create_unique_id (** identifier )
631+ else :
632+ self ._log .warning (
633+ f"Tried to look up a { modelname } by identifier { identifier } , "
634+ "but don't know how to convert that to a uid string" ,
635+ )
636+ return None
602637
603638 return self ._data [modelname ].get (uid )
604639
@@ -618,7 +653,7 @@ def get_all(self, obj):
618653
619654 return self ._data [modelname ].values ()
620655
621- def get_by_uids (self , uids : List [str ], obj ) -> List [DSyncModel ]:
656+ def get_by_uids (self , uids : List [Text ], obj ) -> List [DSyncModel ]:
622657 """Get multiple objects from the store by their unique IDs/Keys and type.
623658
624659 Args:
0 commit comments