Skip to content

Commit

Permalink
Merge pull request #220 from networktocode/lk-natural-deletion-order
Browse files Browse the repository at this point in the history
Implement DiffSyncModelFlags.NATURAL_DELETION_ORDER.
  • Loading branch information
Kircheneer authored May 3, 2023
2 parents 34c210c + 3696421 commit 2967c2e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 15 deletions.
7 changes: 7 additions & 0 deletions diffsync/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class DiffSyncModelFlags(enum.Flag):
If this flag is set, the model will not be deleted from the target/"to" DiffSync.
"""

NATURAL_DELETION_ORDER = 0b10000
"""When deleting, delete children before instances of this this element.
If this flag is set, the models children will be deleted from the target/"to" DiffSync before the models instances
themselves.
"""

SKIP_UNMATCHED_BOTH = SKIP_UNMATCHED_SRC | SKIP_UNMATCHED_DST


Expand Down
26 changes: 17 additions & 9 deletions diffsync/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,7 @@ def sync_diff_element(self, element: DiffElement, parent_model: Optional["DiffSy
attrs = diffs.get("+", {})

# Retrieve Source Object to get its flags
src_model: Optional["DiffSyncModel"]
try:
src_model = self.src_diffsync.get(self.model_class, ids)
except ObjectNotFound:
src_model = None
src_model = self.src_diffsync.get_or_none(self.model_class, ids)

# Retrieve Dest (and primary) Object
dst_model: Optional["DiffSyncModel"]
Expand All @@ -364,31 +360,43 @@ def sync_diff_element(self, element: DiffElement, parent_model: Optional["DiffSy
except ObjectNotFound:
dst_model = None

natural_deletion_order = False
skip_children = False
# Set up flag booleans
if dst_model:
natural_deletion_order = bool(dst_model.model_flags & DiffSyncModelFlags.NATURAL_DELETION_ORDER)
skip_children = bool(dst_model.model_flags & DiffSyncModelFlags.SKIP_CHILDREN_ON_DELETE)

changed = False
if natural_deletion_order and self.action == DiffSyncActions.DELETE and not skip_children:
for child in element.get_children():
changed |= self.sync_diff_element(child, parent_model=dst_model)

changed, modified_model = self.sync_model(src_model=src_model, dst_model=dst_model, ids=ids, attrs=attrs)
dst_model = modified_model or dst_model

if not modified_model or not dst_model:
self.logger.warning("No object resulted from sync, will not process child objects.")
return changed

if self.action == DiffSyncActions.CREATE: # type: ignore
if self.action == DiffSyncActions.CREATE:
if parent_model:
parent_model.add_child(dst_model)
self.dst_diffsync.add(dst_model)
elif self.action == DiffSyncActions.DELETE:
if parent_model:
parent_model.remove_child(dst_model)

skip_children = bool(dst_model.model_flags & DiffSyncModelFlags.SKIP_CHILDREN_ON_DELETE)
self.dst_diffsync.remove(dst_model, remove_children=skip_children)

if skip_children:
return changed

self.incr_elements_processed()

for child in element.get_children():
changed |= self.sync_diff_element(child, parent_model=dst_model)
if not natural_deletion_order:
for child in element.get_children():
changed |= self.sync_diff_element(child, parent_model=dst_model)

return changed

Expand Down
13 changes: 7 additions & 6 deletions docs/source/core_engine/01-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ class MyAdapter(DiffSync):

### Supported Model Flags

| Name | Description | Binary Value |
|---|---|---|
| Name | Description | Binary Value |
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
| IGNORE | Do not render diffs containing this model; do not make any changes to this model when synchronizing. Can be used to indicate a model instance that exists but should not be changed by DiffSync. | 0b1 |
| SKIP_CHILDREN_ON_DELETE | When deleting this model, do not recursively delete its children. Can be used for the case where deletion of a model results in the automatic deletion of all its children. | 0b10 |
| SKIP_UNMATCHED_SRC | Ignore the model if it only exists in the source/"from" DiffSync when determining diffs and syncing. If this flag is set, no new model will be created in the target/"to" DiffSync. | 0b100 |
| SKIP_UNMATCHED_DST | Ignore the model if it only exists in the target/"to" DiffSync when determining diffs and syncing. If this flag is set, the model will not be deleted from the target/"to" DiffSync. | 0b1000 |
| SKIP_UNMATCHED_BOTH | Convenience value combining both SKIP_UNMATCHED_SRC and SKIP_UNMATCHED_DST into a single flag | 0b1100 |
| SKIP_CHILDREN_ON_DELETE | When deleting this model, do not recursively delete its children. Can be used for the case where deletion of a model results in the automatic deletion of all its children. | 0b10 |
| SKIP_UNMATCHED_SRC | Ignore the model if it only exists in the source/"from" DiffSync when determining diffs and syncing. If this flag is set, no new model will be created in the target/"to" DiffSync. | 0b100 |
| SKIP_UNMATCHED_DST | Ignore the model if it only exists in the target/"to" DiffSync when determining diffs and syncing. If this flag is set, the model will not be deleted from the target/"to" DiffSync. | 0b1000 |
| SKIP_UNMATCHED_BOTH | Convenience value combining both SKIP_UNMATCHED_SRC and SKIP_UNMATCHED_DST into a single flag | 0b1100 |
| NATURAL_DELETION_ORDER | When deleting, delete children before instances of this model. | 0b10000 |

## Working with flags

Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_diffsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ class NoDeleteInterfaceDiffSync(BackendA):
extra_models.load()
extra_device = extra_models.device(name="nyc-spine3", site_name="nyc", role="spine")
extra_device.model_flags |= DiffSyncModelFlags.SKIP_CHILDREN_ON_DELETE
extra_device.model_flags |= DiffSyncModelFlags.NATURAL_DELETION_ORDER
extra_models.get(extra_models.site, "nyc").add_child(extra_device)
extra_models.add(extra_device)
extra_interface = extra_models.interface(name="eth0", device_name="nyc-spine3")
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/test_diffsync_model_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from typing import List

import pytest

from diffsync import DiffSync, DiffSyncModel
from diffsync.enum import DiffSyncModelFlags
from diffsync.exceptions import ObjectNotFound

Expand Down Expand Up @@ -111,3 +113,52 @@ def test_diffsync_diff_with_ignore_flag_on_target_models(backend_a, backend_a_mi
diff = backend_a.diff_from(backend_a_minus_some_models)
print(diff.str()) # for debugging of any failure
assert not diff.has_diffs()


def test_diffsync_diff_with_natural_deletion_order():
# This list will contain the order in which the delete methods were called
call_order = []

class TestModelChild(DiffSyncModel): # pylint: disable=missing-class-docstring
_modelname = "child"
_identifiers = ("name",)

name: str

def delete(self):
call_order.append(self.name)
return super().delete()

class TestModelParent(DiffSyncModel): # pylint: disable=missing-class-docstring
_modelname = "parent"
_identifiers = ("name",)
_children = {"child": "children"}

name: str
children: List[TestModelChild] = []

def delete(self):
call_order.append(self.name)
return super().delete()

class TestBackend(DiffSync): # pylint: disable=missing-class-docstring
top_level = ["parent"]

parent = TestModelParent
child = TestModelChild

def load(self):
parent = self.parent(name="Test-Parent")
parent.model_flags |= DiffSyncModelFlags.NATURAL_DELETION_ORDER
self.add(parent)
child = self.child(name="Test-Child")
parent.add_child(child)
self.add(child)

source = TestBackend()
source.load()
destination = TestBackend()
destination.load()
source.remove(source.get("parent", {"name": "Test-Parent"}), remove_children=True)
source.sync_to(destination)
assert call_order == ["Test-Child", "Test-Parent"]

0 comments on commit 2967c2e

Please sign in to comment.