Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 30
Add core engine section in docs and rename example directories#71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
04890dcb2d92db6ef651c93e36b44b76360827cefcf29a4fd7ab6a0081d683854f6b3b6b13497d2bbd5754839fec28f405503a7516e38ea1aa88e886591f73a8e22a1066f74215667148d937ccFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # Global and Model Flags | ||
| These flags offer a powerful way to instruct the core engine how to handle some specific situation without changing the data. One way to think of the flags is to represent them as configuration for the core engine. Currently 2 sets of flags are supported: | ||
| - **global flags**: applicable to all data. | ||
| - **model flags**: applicable to a specific model or to individual instances of a model. | ||
| > *The flags are stored in binary format which allows storing multiple flags in a single variable. See the section below [Working with flags](#working-with-flags) to learn how to manage them.* | ||
| The list of supported flags is expected to grow over time as more use cases are identified. If you think some additional flags should be supported, please reach out via Github to start a discussion. | ||
| ## Global flags | ||
| Global flags can be defined at runtime when calling one of these functions : `diff_to` ,`diff_from`, `sync_to` or `sync_from` | ||
| ```python | ||
| from diffsync.enum import DiffSyncFlags | ||
| flags = DiffSyncFlags.SKIP_UNMATCHED_DST | ||
| diff = nautobot.diff_from(local, flags=flags) | ||
| ``` | ||
| ### Supported Global Flags | ||
| | Name | Description | Binary Value | | ||
| |---|---|---| | ||
| | CONTINUE_ON_FAILURE | Continue synchronizing even if failures are encountered when syncing individual models. | 0b1 | | ||
| | SKIP_UNMATCHED_SRC | Ignore objects that only exist in the source/"from" DiffSync when determining diffs and syncing. If this flag is set, no new objects will be created in the target/"to" DiffSync. | 0b10 | | ||
| | SKIP_UNMATCHED_DST | Ignore objects that only exist in the target/"to" DiffSync when determining diffs and syncing. If this flag is set, no objects will be deleted from the target/"to" DiffSync. | 0b100 | | ||
| | SKIP_UNMATCHED_BOTH | Convenience value combining both SKIP_UNMATCHED_SRC and SKIP_UNMATCHED_DST into a single flag | 0b110 | | ||
| | LOG_UNCHANGED_RECORDS | If this flag is set, a log message will be generated during synchronization for each model, even unchanged ones. | 0b1000 | | ||
| ## Model flags | ||
| Model flags are stored in the attribute `model_flags` of each model and are usually set when the data is being loaded into the adapter. | ||
| ```python | ||
| from diffsync import DiffSync | ||
| from diffsync.enum import DiffSyncModelFlags | ||
| from model import MyDeviceModel | ||
| class MyAdapter(DiffSync): | ||
| device = MyDeviceModel | ||
| def load(self, data): | ||
| """Load all devices into the adapter and add the flag IGNORE to all firewall devices.""" | ||
| for device in data.get("devices"): | ||
| obj = self.device(name=device["name"]) | ||
| if "firewall" in device["name"]: | ||
| obj.model_flags = DiffSyncModelFlags.IGNORE | ||
| self.add(obj) | ||
| ``` | ||
| ### Supported Model Flags | ||
| | 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 | | ||
| ## Working with flags | ||
| Flags are stored in binary format. In binary format, each bit of a variable represents 1 flag which allow us to have up to many flags stored in a single variable. Using binary flags provides more flexibility to add support for more flags in the future without redefining the current interfaces and the current DiffSync API. | ||
| ### Enable a flag (Bitwise OR) | ||
| Enabling a flag is possible with the bitwise OR operator `|=`. It's important to use the bitwise operator OR when enabling a flags to ensure that the value of other flags remains unchanged. | ||
| ```python | ||
| >>> from diffsync.enum import DiffSyncFlags | ||
| >>> flags = DiffSyncFlags.CONTINUE_ON_FAILURE | ||
| >>> flags | ||
| <DiffSyncFlags.CONTINUE_ON_FAILURE: 1> | ||
| >>> bin(flags.value) | ||
| '0b1' | ||
| >>> flags |= DiffSyncFlags.SKIP_UNMATCHED_DST | ||
| >>> flags | ||
| <DiffSyncFlags.SKIP_UNMATCHED_DST|CONTINUE_ON_FAILURE: 5> | ||
| >>> bin(flags.value) | ||
| '0b101' | ||
| ``` | ||
| ### Checking the value of a specific flag (bitwise AND) | ||
| Validating if a flag is enabled is possible with the bitwise operator AND: `&`. The AND operator will return 0 if the flag is not set and the binary value of the flag if it's enabled. To convert the result of the test into a proper conditional it's possible to wrap the bitwise AND operator into a `bool` function. | ||
| ```python | ||
| >>> from diffsync.enum import DiffSyncFlags | ||
| >>> flags = DiffSyncFlags.NONE | ||
| >>> bool(flags & DiffSyncFlags.CONTINUE_ON_FAILURE) | ||
| False | ||
| >>> flags |= DiffSyncFlags.CONTINUE_ON_FAILURE | ||
| >>> bool(flags & DiffSyncFlags.CONTINUE_ON_FAILURE) | ||
| True | ||
| ``` | ||
| ### Disable a flag (bitwise NOT) | ||
| After a flag has been enabled, it's possible to disable it with a bitwise AND NOT operator : `&= ~` | ||
| ```python | ||
| >>> from diffsync.enum import DiffSyncFlags | ||
| >>> flags = DiffSyncFlags.NONE | ||
| # Setting the flags SKIP_UNMATCHED_DST and CONTINUE_ON_FAILURE | ||
| >>> flags |= DiffSyncFlags.SKIP_UNMATCHED_DST | DiffSyncFlags.CONTINUE_ON_FAILURE | ||
| >>> flags | ||
| <DiffSyncFlags.SKIP_UNMATCHED_DST|CONTINUE_ON_FAILURE: 5> | ||
| >>> bool(flags & DiffSyncFlags.SKIP_UNMATCHED_DST) | ||
| True | ||
| # Unsetting the flag SKIP_UNMATCHED_DST; CONTINUE_ON_FAILURE remains set | ||
| >>> flags &= ~DiffSyncFlags.SKIP_UNMATCHED_DST | ||
| >>> flags | ||
| <DiffSyncFlags.CONTINUE_ON_FAILURE: 1> | ||
| >>> bool(flags & DiffSyncFlags.SKIP_UNMATCHED_DST) | ||
| False | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Custom Diff Class | ||
| When performing a diff or a sync operation, a diff object is generated. A diff object is itself composed of DiffElement objects representing the different elements of the original datasets with their differences. | ||
| The diff object helps to access all the DiffElements. It's possible to provide your own Diff class in order to customize some of its capabilities the main one being the order in which the elements are processed. | ||
| ## Using your own Diff class | ||
| To use your own diff class, you need to provide it at runtime when calling one of these functions : `diff_to`, `diff_from`, `sync_to` or `sync_from`. | ||
| ```python | ||
| >>> from diffsync.enum import DiffSyncFlags | ||
| >>> from diff import AlphabeticalOrderDiff | ||
| >>> diff = remote_adapter.diff_from(local_adapter, diff_class=AlphabeticalOrderDiff) | ||
| >>> type(diff) | ||
| <class 'AlphabeticalOrderDiff'> | ||
| ``` | ||
| ## Change the order in which the element are being processed | ||
| By default, all objects of the same type will be stored in a dictionary and as such the order in which they will be processed during a diff or a sync operation is not guaranteed (although in most cases, it will match the order in which they were initially loaded and added to the adapter). When the order in which a given group of object should be processed is important, it's possible to define your own ordering inside a custom Diff class. | ||
| When iterating over a list of objects, either at the top level or as a group of children of a given object, the core engine is looking for a function named after the type of the object `order_children_<type>` and if none is found it will rely on the default function `order_children_default`. Either function need to be present and need to return an Iterator of DiffElement. | ||
| In the example below, by default all devices will be sorted per type of CRUD operations (`order_children_device`) while all other objects will be sorted alphabetically (`order_children_default`) | ||
| ```python | ||
| class MixedOrderingDiff(Diff): | ||
| """Alternate diff class to list children in alphabetical order, except devices to be ordered by CRUD action.""" | ||
| @classmethod | ||
| def order_children_default(cls, children): | ||
| """Simple diff to return all children in alphabetical order.""" | ||
| for child_name, child in sorted(children.items()): | ||
| yield children[child_name] | ||
| @classmethod | ||
| def order_children_device(cls, children): | ||
| """Return a list of device sorted by CRUD action and alphabetically.""" | ||
| children_by_type = defaultdict(list) | ||
| # Organize the children's name by action create, update or delete | ||
| for child_name, child in children.items(): | ||
| action = child.action or "skip" | ||
| children_by_type[action].append(child_name) | ||
| # Create a global list, organized per action | ||
| sorted_children = sorted(children_by_type["create"]) | ||
| sorted_children += sorted(children_by_type["update"]) | ||
| sorted_children += sorted(children_by_type["delete"]) | ||
| sorted_children += sorted(children_by_type["skip"]) | ||
| for name in sorted_children: | ||
| yield children[name] | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| Core Engine | ||
| =========== | ||
| The core engine of DiffSync is meant to be transparent for most users but in some cases it's important to have the ability to change its behavior to adjust to some specific use cases. For these use cases, there are several ways to customize its behavior: | ||
| - Global and Model Flags | ||
| - Diff class | ||
| .. mdinclude:: 01-flags.md | ||
| .. mdinclude:: 02-customize-diff-class.md | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2,8 +2,8 @@ | ||
| Examples | ||
| ############ | ||
| .. toctree:: | ||
| :maxdepth: 2 | ||
| For each example, the complete source code is `available in Github <https://github.com/networktocode/diffsync/tree/main/examples>`_ in the `examples` directory | ||
| 01-multiple-data-sources | ||
| 02-callback-function | ||
| .. mdinclude:: ../../../examples/01-multiple-data-sources/README.md | ||
| .. mdinclude:: ../../../examples/02-callback-function/README.md | ||
| .. mdinclude:: ../../../examples/03-remote-system/README.md | ||
glennmatthews marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a huge fan of mixing-and-matching Markdown and ReST in the same docs. Which one is our standard for newly created documentation files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have it clearly defined yet, I'll open a new issue to track that