Skip to content

Commit aa94735

Browse files
committed
Add optional callback for sync progress
1 parent 7650ecd commit aa94735

File tree

3 files changed

+55
-7
lines changed

3 files changed

+55
-7
lines changed

diffsync/__init__.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -455,29 +455,45 @@ def str(self, indent: int = 0) -> str:
455455
# Synchronization between DiffSync instances
456456
# ------------------------------------------------------------------------------
457457

458-
def sync_from(self, source: "DiffSync", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE):
458+
def sync_from(
459+
self,
460+
source: "DiffSync",
461+
diff_class: Type[Diff] = Diff,
462+
flags: DiffSyncFlags = DiffSyncFlags.NONE,
463+
callback: Optional[Callable[[int, int], None]] = None,
464+
):
459465
"""Synchronize data from the given source DiffSync object into the current DiffSync object.
460466
461467
Args:
462468
source (DiffSync): object to sync data from into this one
463469
diff_class (class): Diff or subclass thereof to use to calculate the diffs to use for synchronization
464470
flags (DiffSyncFlags): Flags influencing the behavior of this sync.
471+
callback (function): Function with parameters (current, total), to be called at intervals as the
472+
calculation of the diff proceeds.
465473
"""
466474
diff = self.diff_from(source, diff_class=diff_class, flags=flags)
467-
syncer = DiffSyncSyncer(diff=diff, src_diffsync=source, dst_diffsync=self, flags=flags)
475+
syncer = DiffSyncSyncer(diff=diff, src_diffsync=source, dst_diffsync=self, flags=flags, callback=callback)
468476
result = syncer.perform_sync()
469477
if result:
470478
self.sync_complete(source, diff, flags, syncer.base_logger)
471479

472-
def sync_to(self, target: "DiffSync", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE):
480+
def sync_to(
481+
self,
482+
target: "DiffSync",
483+
diff_class: Type[Diff] = Diff,
484+
flags: DiffSyncFlags = DiffSyncFlags.NONE,
485+
callback: Optional[Callable[[int, int], None]] = None,
486+
):
473487
"""Synchronize data from the current DiffSync object into the given target DiffSync object.
474488
475489
Args:
476490
target (DiffSync): object to sync data into from this one.
477491
diff_class (class): Diff or subclass thereof to use to calculate the diffs to use for synchronization
478492
flags (DiffSyncFlags): Flags influencing the behavior of this sync.
493+
callback (function): Function with parameters (current, total), to be called at intervals as the
494+
calculation of the diff proceeds.
479495
"""
480-
target.sync_from(self, diff_class=diff_class, flags=flags)
496+
target.sync_from(self, diff_class=diff_class, flags=flags, callback=callback)
481497

482498
def sync_complete(
483499
self,

diffsync/diff.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def __init__(self):
3333
`self.children[group][unique_id] == DiffElement(...)`
3434
"""
3535

36+
def __len__(self):
37+
"""Total number of DiffElements stored herein."""
38+
total = 0
39+
for child in self.get_children():
40+
total += len(child)
41+
return total
42+
3643
def complete(self):
3744
"""Method to call when this Diff has been fully populated with data and is "complete".
3845
@@ -205,6 +212,13 @@ def __str__(self):
205212
f"{self.source_name}{self.dest_name} : {self.get_attrs_diffs()}"
206213
)
207214

215+
def __len__(self):
216+
"""Total number of DiffElements in this one, including itself."""
217+
total = 1 # self
218+
for child in self.get_children():
219+
total += len(child)
220+
return total
221+
208222
@property
209223
def action(self) -> Optional[Text]:
210224
"""Action, if any, that should be taken to remediate the diffs described by this element.

diffsync/helpers.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,19 +245,28 @@ def diff_child_objects(
245245
return diff_element
246246

247247

248-
class DiffSyncSyncer:
248+
class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes
249249
"""Helper class implementing data synchronization logic for DiffSync.
250250
251251
Independent from DiffSync and DiffSyncModel as those classes are purely data objects, while this stores some state.
252252
"""
253253

254-
def __init__(
255-
self, diff: Diff, src_diffsync: "DiffSync", dst_diffsync: "DiffSync", flags: DiffSyncFlags,
254+
def __init__( # pylint: disable=too-many-arguments
255+
self,
256+
diff: Diff,
257+
src_diffsync: "DiffSync",
258+
dst_diffsync: "DiffSync",
259+
flags: DiffSyncFlags,
260+
callback: Optional[Callable[[int, int], None]] = None,
256261
):
257262
"""Create a DiffSyncSyncer instance, ready to call `perform_sync()` against."""
258263
self.diff = diff
259264
self.dst_diffsync = dst_diffsync
260265
self.flags = flags
266+
self.callback = callback
267+
268+
self.elements_processed = 0
269+
self.total_elements = len(diff)
261270

262271
self.base_logger = structlog.get_logger().new(src=src_diffsync, dst=dst_diffsync, flags=flags)
263272

@@ -266,6 +275,13 @@ def __init__(
266275
self.model_class: Type["DiffSyncModel"]
267276
self.action: Optional[str] = None
268277

278+
def incr_elements_processed(self, delta: int = 1):
279+
"""Increment self.elements_processed, then call self.callback if present."""
280+
if delta:
281+
self.elements_processed += delta
282+
if self.callback:
283+
self.callback(self.elements_processed, self.total_elements)
284+
269285
def perform_sync(self) -> bool:
270286
"""Perform data synchronization based on the provided diff.
271287
@@ -327,6 +343,8 @@ def sync_diff_element(self, element: DiffElement, parent_model: "DiffSyncModel"
327343
return changed
328344
self.dst_diffsync.remove(model)
329345

346+
self.incr_elements_processed()
347+
330348
for child in element.get_children():
331349
changed |= self.sync_diff_element(child, parent_model=model)
332350

0 commit comments

Comments
 (0)